User Timing API: Custom Marks in JavaScript

The User Timing API is a standardized browser interface that enables developers to measure custom performance metrics within their web applications with sub-millisecond precision. By placing programmatic timestamps and duration calculations directly into your code, this API integrates custom tracking data into the browser’s performance timeline. This article explains what the User Timing API is, how it works, and how to inject custom marks and measures into browser JavaScript profiles for deep performance analysis.

What is the User Timing API?

The User Timing API is part of the W3C High Resolution Time specification. It provides a standardized mechanism to record timestamps and calculate durations using high-resolution time (DOMHighResTimeStamp). Unlike Date.now(), which is limited to millisecond accuracy and subject to system clock skews, the User Timing API relies on performance.now(), which measures time relative to the navigation start with microsecond precision.

The API exposes two primary primitives through the global performance object:

  1. Marks (PerformanceMark): Named, instantaneous timestamps on the performance timeline.
  2. Measures (PerformanceMeasure): Named duration intervals calculated between two marks, or between a mark and an arbitrary point in time.

How the User Timing API Injects Marks into Profiles

When you call User Timing methods, the browser creates performance entries and stores them in the browser’s Performance Timeline buffer. Developer tools (such as Chrome DevTools, Edge DevTools, and Firefox Profiler) automatically hook into this buffer.

During CPU profiling or performance recording sessions, the browser maps these entries directly onto the flame chart in a dedicated Timings track. This overlays your custom application events directly above network requests, rendering frames, and JavaScript execution stacks.

1. Creating Custom Marks

To record a specific event or point in time, use performance.mark(). You pass a unique string identifier as the mark name:

// Mark the start of a critical operation
performance.mark('fetchData_start');

await fetchLargeDataset();

// Mark the end of the operation
performance.mark('fetchData_end');

When DevTools records this code execution, fetchData_start and fetchData_end appear as individual flags along the recording’s timeline.

2. Measuring Durations Between Marks

To calculate the elapsed time between two points, use performance.measure(). This method takes a custom measure name, a start mark, and an end mark:

// Create a measure spanning the two marks
performance.measure('fetchData_duration', 'fetchData_start', 'fetchData_end');

The resulting measure appears as an interactive colored bar on the DevTools “Timings” track. Clicking the bar reveals exact duration statistics and helps you correlate the time spent in JavaScript functions with layout or paint operations happening concurrently.

3. Adding Custom Metadata to Marks and Measures

Modern implementations of the User Timing Level 3 specification allow you to pass custom metadata using a details object:

performance.mark('renderComponent', {
  detail: { componentName: 'UserProfile', itemsCount: 42 }
});

performance.measure('renderComponent_duration', {
  start: 'renderComponent_start',
  end: 'renderComponent_end',
  detail: { renderType: 'initial' }
});

This metadata is accessible programmatically via performance.getEntriesByName() and within supporting browser profiling tools, providing contextual debugging information directly inside the trace.

Retrieving and Cleaning Up Timing Data

User timing entries can be queried programmatically to send performance data to analytics backends:

// Retrieve all marks
const marks = performance.getEntriesByType('mark');

// Retrieve a specific measure
const measure = performance.getEntriesByName('fetchData_duration')[0];
console.log(`Execution time: ${measure.duration}ms`);

To avoid memory leaks in long-running single-page applications, clear entries from the buffer when they are no longer needed:

performance.clearMarks('fetchData_start');
performance.clearMeasures('fetchData_duration');

Benefits Over Standard Logging