This specification extends the High Resolution Time specification [[HR-TIME-3]] by providing methods to store and retrieve high resolution performance metric data.
This Performance Timeline specification replaces the first version of [[PERFORMANCE-TIMELINE]] and includes:
Accurately measuring performance characteristics of web applications is an important aspect of making web applications faster. This specification defines the necessary Performance Timeline primitives that enable web developers to access, instrument, and retrieve various performance metrics from the full lifecycle of a web application.
[[NAVIGATION-TIMING-2]], [[RESOURCE-TIMING-2]], and [[USER-TIMING-2]] are examples of specifications that define timing information related to the navigation of the document, resources on the page, and developer scripts, respectively. Together these and other performance interfaces define performance metrics that describe the Performance Timeline of a web application. For example, the following script shows how a developer can access the Performance Timeline to obtain performance metrics related to the navigation of the document, resources on the page, and developer scripts:
<!doctype html> <html> <head></head> <body onload="init()"> <img id="image0" src="https://www.w3.org/Icons/w3c_main.png" /> <script> function init() { // see [[USER-TIMING-2]] performance.mark("startWork"); doWork(); // Some developer code performance.mark("endWork"); measurePerf(); } function measurePerf() { performance .getEntries() .map(entry => JSON.stringify(entry, null, 2)) .forEach(json => console.log(json)); } </script> </body> </html>
Alternatively, the developer can observe the Performance Timeline and be notified of new performance metrics and, optionally, previously buffered performance metrics of specified type, via the PerformanceObserver interface.
The PerformanceObserver interface was added and is designed to address limitations of the buffer-based approach shown in the first example. By using the PerformanceObserver interface, the application can:
The developer is encouraged to use PerformanceObserver where possible. Further, new performance API's and metrics may only be available through the PerformanceObserver interface. The observer works by specifying a callback in the constructor and specifying the performance entries it's interested in via the observe() method. The user agent chooses when to execute the callback, which receives performance entries that have been queued.
There are special considerations regarding initial page load when using the PerformanceObserver interface: a registration must be active to receive events but the registration script may not be available or may not be desired in the critical path. To address this, user agents buffer some number of events while the page is being constructed, and these buffered events can be accessed via the buffered flag when registering the observer. When this flag is set, the user agent retrieves and dispatches events that it has buffered, for the specified entry type, and delivers them in the first callback after the observe() call occurs.
The number of buffered events is determined by the specification that defines the metric and buffering is intended to used for first-N events only; buffering is not unbounded or continuous.
<!doctype html> <html> <head></head> <body> <img id="image0" src="https://www.w3.org/Icons/w3c_main.png" /> <script> // Know when the entry types we would like to use are not supported. function detectSupport(entryTypes) { for (const entryType of entryTypes) { if (!PerformanceObserver.supportedEntryTypes.includes(entryType)) { // Indicate to client-side analytics that |entryType| is not supported. } } } detectSupport(["resource", "mark", "measure"]); const userTimingObserver = new PerformanceObserver(list => { list .getEntries() // Get the values we are interested in .map(({ name, entryType, startTime, duration }) => { const obj = { "Duration": duration, "Entry Type": entryType, "Name": name, "Start Time": startTime, }; return JSON.stringify(obj, null, 2); }) // Display them to the console. .forEach(console.log); // Disconnect after processing the events. userTimingObserver.disconnect(); }); // Subscribe to new events for User-Timing. userTimingObserver.observe({entryTypes: ["mark", "measure"]}); const resourceObserver = new PerformanceObserver(list => { list .getEntries() // Get the values we are interested in .map(({ name, startTime, fetchStart, responseStart, responseEnd }) => { const obj = { "Name": name, "Start Time": startTime, "Fetch Start": fetchStart, "Response Start": responseStart, "Response End": responseEnd, }; return JSON.stringify(obj, null, 2); }) // Display them to the console. .forEach(console.log); // Disconnect after processing the events. resourceObserver.disconnect(); }); // Retrieve buffered events and subscribe to newer events for Resource Timing. resourceObserver.observe({type: "resource", buffered: true}); </script> </body> </html>
Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant).
Each global object has:
DOMString
, representing
the entry type to which the buffer belongs. The map's value is the
following tuple:
boolean
availableFromTimeline,
initialized to the registry
value for this entry type.
Each Document has:
In order to get the relevant performance entry tuple, given entryType and globalObject as input, run the following steps:
This extends the {{Performance}} interface from [[HR-TIME-3]] and hosts performance related attributes and methods used to retrieve the performance metric data from the Performance Timeline.
partial interface Performance { PerformanceEntryList getEntries (); PerformanceEntryList getEntriesByType (DOMString type); PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type); }; typedef sequence<PerformanceEntry> PerformanceEntryList;
The PerformanceEntryList represents a sequence of PerformanceEntry, providing developers with all the convenience methods found on JavaScript arrays.
Returns a PerformanceEntryList object returned by the
filter buffer map by name and type algorithm with
name and type set to null
.
Returns a PerformanceEntryList object returned by filter
buffer map by name and type algorithm with name set to
null
, and type set to the method's input
type
parameter.
Returns a PerformanceEntryList object returned by filter
buffer map by name and type algorithm with name set to
the method input name
parameter, and type set
to null
if optional `entryType` is omitted, or set to the
method's input type
parameter otherwise.
The PerformanceEntry interface hosts the performance data of various metrics.
[Exposed=(Window,Worker)] interface PerformanceEntry { readonly attribute unsigned long long id; readonly attribute DOMString name; readonly attribute DOMString entryType; readonly attribute DOMHighResTimeStamp startTime; readonly attribute DOMHighResTimeStamp duration; readonly attribute unsigned long long navigationId; [Default] object toJSON(); };
All `entryType` values are defined in the
relevantregistry.
Examples include: "mark"
and "measure"
[[USER-TIMING-2]], "navigation"
[[NAVIGATION-TIMING-2]],
and "resource"
[[RESOURCE-TIMING-2]].
When toJSON is called, run [[WebIDL]]'s default toJSON steps.
A PerformanceEntry has a {{DOMHighResTimeStamp}} end time, initially 0.
To initialize a PerformanceEntry entry given a {{DOMHighResTimeStamp}} startTime,
a DOMString
entryType, a DOMString
name, and an optional {{DOMHighResTimeStamp}} endTime (default 0
):
The PerformanceObserver interface can be used to observe the Performance Timeline to be notified of new performance metrics as they are recorded, and optionally buffered performance metrics.
Each PerformanceObserver has these associated concepts:
DOMString
observer type which is initially
"undefined"
.The `PerformanceObserver(callback)` constructor must create a new PerformanceObserver object with its observer callback set to callback and then return it.
A registered performance observer is a struct consisting of an observer member (a PerformanceObserver object) and an options list member (a list of PerformanceObserverInit dictionaries).
callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries, PerformanceObserver observer, optional PerformanceObserverCallbackOptions options = {}); [Exposed=(Window,Worker)] interface PerformanceObserver { constructor(PerformanceObserverCallback callback); undefined observe (optional PerformanceObserverInit options = {}); undefined disconnect (); PerformanceEntryList takeRecords(); [SameObject] static readonly attribute FrozenArray<DOMString> supportedEntryTypes; };
To keep the performance overhead to minimum the application ought to only subscribe to event types that it is interested in, and disconnect the observer once it no longer needs to observe the performance data. Filtering by name is not supported, as it would implicitly require a subscription for all event types — this is possible, but discouraged, as it will generate a significant volume of events.
dictionary PerformanceObserverCallbackOptions { unsigned long long droppedEntriesCount; };
The observe() method instructs the user agent to register the observer and must run these steps:
"undefined"
:
"multiple"
.
"single"
.
"single"
and options's entryTypes
member is present, then [=exception/throw=] an
{{"InvalidModificationError"}}.
"multiple"
and options's type member
is present, then [=exception/throw=] an
{{"InvalidModificationError"}}.
"multiple"
, run the following steps:
"single"
.
For each entry in tuple's performance entry buffer:
A PerformanceObserver object needs to always call observe() with options's entryTypes set OR always call observe() with options's type set. If one PerformanceObserver calls observe() with entryTypes and also calls observe with type, then an exception is thrown. This is meant to avoid confusion with how calls would stack. When using entryTypes, no other parameters in PerformanceObserverInit can be used. In addition, multiple observe() calls will override for backwards compatibility and because a single call should suffice in this case. On the other hand, when using type, calls will stack because a single call can only specify one type. Calling observe() with a repeated type will also override.
dictionary PerformanceObserverInit { sequence<DOMString> entryTypes; DOMString type; boolean buffered; };
[Exposed=(Window,Worker)] interface PerformanceObserverEntryList { PerformanceEntryList getEntries(); PerformanceEntryList getEntriesByType (DOMString type); PerformanceEntryList getEntriesByName (DOMString name, optional DOMString type); };
Each {{PerformanceObserverEntryList}} object has an associated entry list, which consists of a {{PerformanceEntryList}} and is initialized upon construction.
Returns a PerformanceEntryList object returned by filter
buffer by name and type algorithm with this's entry list,
name and type set to null
.
Returns a PerformanceEntryList object returned by filter
buffer by name and type algorithm with this's entry list,
name set to null
, and type set to the
method's input type
parameter.
Returns a PerformanceEntryList object returned by filter
buffer by name and type algorithm with this's entry list,
name set to the method input name
parameter, and
type set to null
if optional `entryType` is omitted,
or set to the method's input type
parameter otherwise.
The takeRecords() method must return a copy of this's observer buffer, and also empty this's observer buffer.
The disconnect() method must do the following:
Each global object has an associated frozen array of supported entry types, which is initialized to the FrozenArray created from the sequence of strings among the registry that are supported for the global object, in alphabetical order.
When supportedEntryTypes's attribute getter is called, run the following steps:
This attribute allows web developers to easily know which entry types are supported by the user agent.
PerformanceEntry
To queue a PerformanceEntry (newEntry), run these steps:
If regObs's options list contains a PerformanceObserverInit options whose entryTypes member includes entryType or whose type member equals to entryType:
PerformanceEntry
To queue a navigation PerformanceEntry (newEntry), run these steps:
When asked to queue the PerformanceObserver task, given relevantGlobal as input, run the following steps:
The performance timeline task queue is a low priority queue that, if possible, should be processed by the user agent during idle periods to minimize impact of performance monitoring code.
When asked to run the filter buffer map by name and type algorithm with optional name and type, run the following steps:
When asked to run the filter buffer by name and type algorithm, with buffer, name, and type as inputs, run the following steps:
entryType
attribute, continue to next entry.
name
attribute, continue to next entry.
To determine if a performance entry buffer is full, with tuple as input, run the following steps:
When asked to generate an id for a PerformanceEntry entry, run the following steps:
A user agent may choose to increase the last performance entry idit by a small random integer every time. A user agent must not pick a single global random integer and increase the last performance entry id of all global objects by that amount because this could introduce cross origin leaks.
The last performance entry id has an initial random value, and is increased by a small number chosen by the user agent instead of 1 to discourage developers from considering it as a counter of the number of entries that have been generated in the web application.
This specification extends the {{Performance}} interface defined by [[HR-TIME-3]] and provides methods to queue and retrieve entries from the performance timeline. Please refer to [[HR-TIME-3]] for privacy considerations of exposing high-resoluting timing information. Each new specification introducing new performance entries should have its own privacy considerations as well.
The last performance entry id is deliberately initialized to a random value, and is incremented by another small value every time a new {{PerformanceEntry}} is queued. User agents may choose to use a consistent increment for all users, or may pick a different increment for each global object, or may choose a new random increment for each {{PerformanceEntry}}. However, in order to prevent cross-origin leaks, and ensure that this does not enable fingerprinting, user agents must not just pick a unique random integer, and use it as a consistent increment for all {{PerformanceEntry}} objects across all global objects.
This specification extends the {{Performance}} interface defined by [[HR-TIME-3]] and provides methods to queue and retrieve entries from the performance timeline. Please refer to [[HR-TIME-3]] for security considerations of exposing high-resoluting timing information. Each new specification introducing new performance entries should have its own security considerations as well.
The [[INFRA]] specification defines the following: key, getting the value of an entry.
Thanks to Arvind Jain, Boris Zbarsky, Jatinder Mann, Nat Duca, Philippe Le Hegaret, Ryosuke Niwa, Shubhie Panicker, Todd Reifsteck, Yoav Weiss, and Zhiheng Wang, for their contributions to this work.