page-timing

⏱ Collect and measure browser performance metrics

Usage no npm install needed!

<script type="module">
  import pageTiming from 'https://cdn.skypack.dev/page-timing';
</script>

README

page-timing

⏱ Collect and measure browser performance metrics

All metrics are converted to snake_case

import { navigation, paint } from 'page-timing';

(async () => {
    const results = await Promise.all([
        paint(),
        navigation()
    ]);

    const metrics = Object.assign(...results);

    fetch('/browser-performance-metrics', {
        method: 'POST',
        body: JSON.stringify({
            page_name: 'my page',
            metrics
        }),
    });
})();

API endpoints

Metrics

| Name | Meaning | Group | Type | - | - | - | - | navigation_start | Termination of previous document upon navigating | navigation | number | unload_event_start | Previous document unload | navigation | number | unload_event_end | | navigation | number | redirect_count | Numbers of redirects while requesting this page | navigation | number | redirect_start | Redirect from previous document | navigation | number | redirect_end | | navigation | number | fetch_start | Ready to fetch the document | navigation | number | domain_lookup_start | | navigation | number | domain_lookup_end | | navigation | number | duration | Difference between responseEnd and startTime | navigation | number | connect_start | Sent request to open a connection | navigation | number | connect_end | | navigation | number | secure_connection_start | Secure connection handshake | navigation | number | request_start | Request the document | navigation | number | response_start | Received the first byte of the response | navigation | number | response_end | Received the last byte of the response | navigation | number | dom_loading | Parser started work | navigation | number | dom_interactive | Parser finished work on main document. Changed document readyState to "interactive" | navigation | number | dom_content_loaded_event_start | Executed required scripts after parsing the document | navigation | number | dom_content_loaded_event_end | | navigation | number | dom_complete | Changed document readyState to "complete" | navigation | number | load_event_start | All assets are loaded. Document fires "load" event | navigation | number | load_event_end | Document finished executing "load" event listeners | navigation | number | transfer_size | Size (octets) of response headers and payload body | navigation | number | encoded_body_size | Size (octets) of payload body | navigation | number | decoded_body_size | Size (octets) of message body | navigation | number | worker_start | Time until service worker ran | navigation | number | first_paint | User agent first rendered after navigation | paint | number | first_contentful_paint | Document contains at least one element that is paintable and contentful † | paint | number | first_image_paint | TBD | paint | number | final_asset_javascript_count | Total number of Javascript resources | assets | number | final_asset_javascript_load | Loading time spent on Javascript resources | assets | number | final_asset_javascript_size | Total size of Javascript resources | assets | number | final_asset_stylesheets_count | Total number of CSS resources | assets | number | final_asset_stylesheets_load | Loading time spent on CSS resources | assets | number | final_asset_stylesheets_size | Total size of CSS resources | assets | number | final_asset_images_count | Total number of image resources | assets | number | final_asset_images_load | Loading time spent on image resources | assets | number | final_asset_images_size | Total size of image resources | assets | number | final_asset_other_count | Total number of other resources | assets | number | final_asset_other_load | Loading time spent on other resources | assets | number | final_asset_other_size | Total size of other resources | assets | number | connection_type | bluetooth, cellular, ethernet, none, wifi, wimax, other, unknown | connection | string | effective_bandwidth | Mbps | connection | number | effective_connection_type | slow-2g, 2g, 3g, 4g | connection | string | effective_max_bandwidth | Mbps | connection | number | reduced_data_usage | Vendor's "Data Saver" feature enables | connection | boolean | round_trip_time | Estimated effective round-trip in ms | connection | number | navigation_type | navigate, reload, back_forward, prerender | connection | string | js_heap_size_limit | Maximum bytes available for JS heap | memory | number | total_js_heap_size | Total allocated bytes for JS heap | memory | number | used_js_heap_size | Currently active bytes of JS heap | memory | number | window_inner_height | Height of the window's layout viewport | display | number | window_inner_width | Width of the window's layout viewport | display | number | screen_color_depth | Color depth of the screen | display | number | screen_pixel_depth | Bit depth of the screen | display | number | screen_orientation_type | landscape-primary, landscape-secondary, portrait-primary, portrait-secondary | display | string | final_dom_node_count | Total number of nodes under the document object | dom | number | final_dom_nest_depth | Highest nesting depth of DOM element under the document | dom | number | final_html_size | Character count of the HTML document | dom | number | page_time_elapsed | milliseconds elapsed since the time origin | elapsed | number

contentful element: A visible element which contains non empty text, media content or input.

More functions

fps

Measure page frame rate at a certain point in time

import { fps } from 'page-timing';

const frames_per_second = await fps();
console.log({ frames_per_second });

Increase sample rate by checking more than one second. (Result is still in frames per second)

const frames_per_second = await fps({ sample: 5 });
console.log({ frames_per_second });

measure

Wrap a function and measure it's execution time in milliseconds into a performance measure entry.

import { measure } from 'page-timing';

async function myFunction(
    await wait(50);
    doSomethingElse();
}

await measure(myFunction, 'my-function');

// Example: Convert entries to a named array
Object.assign(
    ...performance.getEntriesByType('measure').map(
        ({ name, duration }) => ({[name]: duration})
    )
);
// {my-function: 53.35999990347773}

// Example: Retrieve a specific entry
const { duration } = performance.getEntriesByName('my-function');
// 53.35999990347773

Illustration of navigation events

Bonus

A simple example to add web vitals and TTI

npm i page-timing web-vitals tti-polyfill
import { all, connection } from 'page-timing';
import { getLCP, getFID, getCLS } from 'web-vitals';
import TTI from 'tti-polyfill';

(async () => {
    const connectionInfo = await connection();

    // Send metrics from browser performance API
    send(await all());

    // Send web vitals to the same endpoint
    [
        [getLCP, 'largest_contentful_paint'],
        [getFID, 'first_input_delay'],
        [getCLS, 'cumulative_layout_shift'],
    ].forEach(
        ([ fn, name ]) => fn(
            ({ value }) => send({
                [name]: value,
                ...connectionInfo // Some connection info
            })
        )
    );

    TTI.getFirstConsistentlyInteractive().then(
        (time_to_interactive) => send({
            time_to_interactive,
            ...connectionInfo // Some connection info
        })
    ).catch(() => null)
})();

const send = metrics => fetch('/browser-performance-metrics', {
  method: 'POST',
  body: JSON.stringify({ page_name: 'my page', metrics }),
});