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:
- Marks (
PerformanceMark): Named, instantaneous timestamps on the performance timeline. - 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
- Zero Profiler Distortion: Unlike
console.log()orconsole.time(), which can introduce execution overhead and affect the flame graph, User Timing methods have minimal performance impact. - Unified Visual Context: Custom marks are placed alongside browser lifecycle events (such as DOMContentLoaded, First Contentful Paint, and Garbage Collection), enabling precise root-cause analysis of performance bottlenecks.
- Standardized Automation: Entries can be collected
automatically in Real User Monitoring (RUM) pipelines using
PerformanceObserverinstances.