⏱ 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
}),
});
})();
- navigation: Navigation Timing Illustration 🎨
- paint: Paint Timing
- assets: Information about page resources when this function is called
- connection: Network Information
- memory: Memory API information
- display: Screen and document information
- dom: Calculated metrics from the document object
- elapsed: Time when the measurements were taken
- all: A compound object containing all of the above
- measure: A helper function: Add measure entries to navigation timing API
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.
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 });
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
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 }),
});