2017-08-08 00:53:24 +02:00
|
|
|
# Performance Timing API
|
2017-11-04 09:08:46 +01:00
|
|
|
|
|
|
|
<!--introduced_in=v8.5.0-->
|
2017-08-08 00:53:24 +02:00
|
|
|
|
2017-08-24 02:46:02 +02:00
|
|
|
> Stability: 1 - Experimental
|
|
|
|
|
2017-08-08 00:53:24 +02:00
|
|
|
The Performance Timing API provides an implementation of the
|
|
|
|
[W3C Performance Timeline][] specification. The purpose of the API
|
|
|
|
is to support collection of high resolution performance metrics.
|
|
|
|
This is the same Performance API as implemented in modern Web browsers.
|
|
|
|
|
|
|
|
```js
|
2018-03-23 19:23:22 +01:00
|
|
|
const { PerformanceObserver, performance } = require('perf_hooks');
|
|
|
|
|
|
|
|
const obs = new PerformanceObserver((items) => {
|
|
|
|
console.log(items.getEntries()[0].duration);
|
|
|
|
performance.clearMarks();
|
|
|
|
});
|
|
|
|
obs.observe({ entryTypes: ['measure'] });
|
|
|
|
|
2017-08-08 00:53:24 +02:00
|
|
|
performance.mark('A');
|
|
|
|
doSomeLongRunningProcess(() => {
|
|
|
|
performance.mark('B');
|
|
|
|
performance.measure('A to B', 'A', 'B');
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
## Class: Performance
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
### performance.clearMarks([name])
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* `name` {string}
|
|
|
|
|
|
|
|
If `name` is not provided, removes all `PerformanceMark` objects from the
|
|
|
|
Performance Timeline. If `name` is provided, removes only the named mark.
|
|
|
|
|
|
|
|
### performance.mark([name])
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* `name` {string}
|
|
|
|
|
|
|
|
Creates a new `PerformanceMark` entry in the Performance Timeline. A
|
|
|
|
`PerformanceMark` is a subclass of `PerformanceEntry` whose
|
|
|
|
`performanceEntry.entryType` is always `'mark'`, and whose
|
|
|
|
`performanceEntry.duration` is always `0`. Performance marks are used
|
|
|
|
to mark specific significant moments in the Performance Timeline.
|
|
|
|
|
|
|
|
### performance.measure(name, startMark, endMark)
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* `name` {string}
|
|
|
|
* `startMark` {string}
|
|
|
|
* `endMark` {string}
|
|
|
|
|
|
|
|
Creates a new `PerformanceMeasure` entry in the Performance Timeline. A
|
|
|
|
`PerformanceMeasure` is a subclass of `PerformanceEntry` whose
|
|
|
|
`performanceEntry.entryType` is always `'measure'`, and whose
|
|
|
|
`performanceEntry.duration` measures the number of milliseconds elapsed since
|
|
|
|
`startMark` and `endMark`.
|
|
|
|
|
|
|
|
The `startMark` argument may identify any *existing* `PerformanceMark` in the
|
2018-01-01 23:50:02 +01:00
|
|
|
Performance Timeline, or *may* identify any of the timestamp properties
|
2017-08-08 00:53:24 +02:00
|
|
|
provided by the `PerformanceNodeTiming` class. If the named `startMark` does
|
|
|
|
not exist, then `startMark` is set to [`timeOrigin`][] by default.
|
|
|
|
|
|
|
|
The `endMark` argument must identify any *existing* `PerformanceMark` in the
|
2018-01-01 23:50:02 +01:00
|
|
|
Performance Timeline or any of the timestamp properties provided by the
|
2017-08-08 00:53:24 +02:00
|
|
|
`PerformanceNodeTiming` class. If the named `endMark` does not exist, an
|
|
|
|
error will be thrown.
|
|
|
|
|
|
|
|
### performance.nodeTiming
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {PerformanceNodeTiming}
|
|
|
|
|
|
|
|
An instance of the `PerformanceNodeTiming` class that provides performance
|
|
|
|
metrics for specific Node.js operational milestones.
|
|
|
|
|
|
|
|
### performance.now()
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* Returns: {number}
|
|
|
|
|
2018-02-25 23:26:22 +01:00
|
|
|
Returns the current high resolution millisecond timestamp, where 0 represents
|
|
|
|
the start of the current `node` process.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
### performance.timeOrigin
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
2018-02-25 23:26:22 +01:00
|
|
|
The [`timeOrigin`][] specifies the high resolution millisecond timestamp at
|
|
|
|
which the current `node` process began, measured in Unix time.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
### performance.timerify(fn)
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* `fn` {Function}
|
|
|
|
|
|
|
|
Wraps a function within a new function that measures the running time of the
|
|
|
|
wrapped function. A `PerformanceObserver` must be subscribed to the `'function'`
|
|
|
|
event type in order for the timing details to be accessed.
|
|
|
|
|
|
|
|
```js
|
|
|
|
const {
|
|
|
|
performance,
|
|
|
|
PerformanceObserver
|
|
|
|
} = require('perf_hooks');
|
|
|
|
|
|
|
|
function someFunction() {
|
|
|
|
console.log('hello world');
|
|
|
|
}
|
|
|
|
|
|
|
|
const wrapped = performance.timerify(someFunction);
|
|
|
|
|
|
|
|
const obs = new PerformanceObserver((list) => {
|
|
|
|
console.log(list.getEntries()[0].duration);
|
|
|
|
obs.disconnect();
|
|
|
|
});
|
2017-09-14 10:36:49 +02:00
|
|
|
obs.observe({ entryTypes: ['function'] });
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
// A performance timeline entry will be created
|
|
|
|
wrapped();
|
|
|
|
```
|
|
|
|
|
|
|
|
## Class: PerformanceEntry
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
### performanceEntry.duration
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The total number of milliseconds elapsed for this entry. This value will not
|
|
|
|
be meaningful for all Performance Entry types.
|
|
|
|
|
|
|
|
### performanceEntry.name
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {string}
|
|
|
|
|
|
|
|
The name of the performance entry.
|
|
|
|
|
|
|
|
### performanceEntry.startTime
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The high resolution millisecond timestamp marking the starting time of the
|
|
|
|
Performance Entry.
|
|
|
|
|
|
|
|
### performanceEntry.entryType
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {string}
|
|
|
|
|
2018-02-14 20:06:32 +01:00
|
|
|
The type of the performance entry. Currently it may be one of: `'node'`,
|
2018-03-25 03:48:51 +02:00
|
|
|
`'mark'`, `'measure'`, `'gc'`, `'function'`, or `'http2'`.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
### performanceEntry.kind
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
When `performanceEntry.entryType` is equal to `'gc'`, the `performance.kind`
|
|
|
|
property identifies the type of garbage collection operation that occurred.
|
|
|
|
The value may be one of:
|
|
|
|
|
|
|
|
* `perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR`
|
|
|
|
* `perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR`
|
|
|
|
* `perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL`
|
|
|
|
* `perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB`
|
|
|
|
|
|
|
|
## Class: PerformanceNodeTiming extends PerformanceEntry
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
Provides timing details for Node.js itself.
|
|
|
|
|
|
|
|
### performanceNodeTiming.bootstrapComplete
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The high resolution millisecond timestamp at which the Node.js process
|
2018-02-25 23:26:22 +01:00
|
|
|
completed bootstrapping. If bootstrapping has not yet finished, the property
|
|
|
|
has the value of -1.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
### performanceNodeTiming.loopExit
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The high resolution millisecond timestamp at which the Node.js event loop
|
2018-02-25 23:26:22 +01:00
|
|
|
exited. If the event loop has not yet exited, the property has the value of -1.
|
|
|
|
It can only have a value of not -1 in a handler of the [`'exit'`][] event.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
### performanceNodeTiming.loopStart
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The high resolution millisecond timestamp at which the Node.js event loop
|
2018-02-25 23:26:22 +01:00
|
|
|
started. If the event loop has not yet started (e.g., in the first tick of the
|
|
|
|
main script), the property has the value of -1.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
### performanceNodeTiming.nodeStart
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The high resolution millisecond timestamp at which the Node.js process was
|
|
|
|
initialized.
|
|
|
|
|
|
|
|
### performanceNodeTiming.v8Start
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
|
|
|
* {number}
|
|
|
|
|
|
|
|
The high resolution millisecond timestamp at which the V8 platform was
|
|
|
|
initialized.
|
|
|
|
|
2018-04-14 13:38:02 +02:00
|
|
|
## Class: PerformanceObserver
|
|
|
|
|
|
|
|
### new PerformanceObserver(callback)
|
2017-08-08 00:53:24 +02:00
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
|
2018-04-14 13:38:02 +02:00
|
|
|
* `callback` {Function}
|
|
|
|
* `list` {PerformanceObserverEntryList}
|
|
|
|
* `observer` {PerformanceObserver}
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
`PerformanceObserver` objects provide notifications when new
|
|
|
|
`PerformanceEntry` instances have been added to the Performance Timeline.
|
|
|
|
|
|
|
|
```js
|
|
|
|
const {
|
|
|
|
performance,
|
|
|
|
PerformanceObserver
|
|
|
|
} = require('perf_hooks');
|
|
|
|
|
|
|
|
const obs = new PerformanceObserver((list, observer) => {
|
|
|
|
console.log(list.getEntries());
|
|
|
|
observer.disconnect();
|
|
|
|
});
|
|
|
|
obs.observe({ entryTypes: ['mark'], buffered: true });
|
|
|
|
|
|
|
|
performance.mark('test');
|
|
|
|
```
|
|
|
|
Because `PerformanceObserver` instances introduce their own additional
|
|
|
|
performance overhead, instances should not be left subscribed to notifications
|
|
|
|
indefinitely. Users should disconnect observers as soon as they are no
|
|
|
|
longer needed.
|
|
|
|
|
2018-04-14 13:38:02 +02:00
|
|
|
The `callback` is invoked when a `PerformanceObserver` is
|
2017-08-08 00:53:24 +02:00
|
|
|
notified about new `PerformanceEntry` instances. The callback receives a
|
|
|
|
`PerformanceObserverEntryList` instance and a reference to the
|
|
|
|
`PerformanceObserver`.
|
|
|
|
|
|
|
|
### performanceObserver.disconnect()
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
Disconnects the `PerformanceObserver` instance from all notifications.
|
|
|
|
|
|
|
|
### performanceObserver.observe(options)
|
|
|
|
<!-- YAML
|
2017-09-10 04:58:50 +02:00
|
|
|
added: v8.5.0
|
2017-08-08 00:53:24 +02:00
|
|
|
-->
|
|
|
|
* `options` {Object}
|
2018-04-09 14:25:04 +02:00
|
|
|
* `entryTypes` {string[]} An array of strings identifying the types of
|
2017-08-08 00:53:24 +02:00
|
|
|
`PerformanceEntry` instances the observer is interested in. If not
|
|
|
|
provided an error will be thrown.
|
|
|
|
* `buffered` {boolean} If true, the notification callback will be
|
|
|
|
called using `setImmediate()` and multiple `PerformanceEntry` instance
|
|
|
|
notifications will be buffered internally. If `false`, notifications will
|
2018-04-02 03:44:32 +02:00
|
|
|
be immediate and synchronous. **Default:** `false`.
|
2017-08-08 00:53:24 +02:00
|
|
|
|
|
|
|
Subscribes the `PerformanceObserver` instance to notifications of new
|
|
|
|
`PerformanceEntry` instances identified by `options.entryTypes`.
|
|
|
|
|
|
|
|
When `options.buffered` is `false`, the `callback` will be invoked once for
|
|
|
|
every `PerformanceEntry` instance:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const {
|
|
|
|
performance,
|
|
|
|
PerformanceObserver
|
|
|
|
} = require('perf_hooks');
|
|
|
|
|
|
|
|
const obs = new PerformanceObserver((list, observer) => {
|
|
|
|
// called three times synchronously. list contains one item
|
|
|
|
});
|
|
|
|
obs.observe({ entryTypes: ['mark'] });
|
|
|
|
|
|
|
|
for (let n = 0; n < 3; n++)
|
|
|
|
performance.mark(`test${n}`);
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
const {
|
|
|
|
performance,
|
|
|
|
PerformanceObserver
|
|
|
|
} = require('perf_hooks');
|
|
|
|
|
|
|
|
const obs = new PerformanceObserver((list, observer) => {
|
|
|
|
// called once. list contains three items
|
|
|
|
});
|
|
|
|
obs.observe({ entryTypes: ['mark'], buffered: true });
|
|
|
|
|
|
|
|
for (let n = 0; n < 3; n++)
|
|
|
|
performance.mark(`test${n}`);
|
|
|
|
```
|
|
|
|
|
2018-04-14 13:38:02 +02:00
|
|
|
## Class: PerformanceObserverEntryList
|
|
|
|
<!-- YAML
|
|
|
|
added: v8.5.0
|
|
|
|
-->
|
|
|
|
|
|
|
|
The `PerformanceObserverEntryList` class is used to provide access to the
|
|
|
|
`PerformanceEntry` instances passed to a `PerformanceObserver`.
|
|
|
|
|
|
|
|
### performanceObserverEntryList.getEntries()
|
|
|
|
<!-- YAML
|
|
|
|
added: v8.5.0
|
|
|
|
-->
|
|
|
|
|
|
|
|
* Returns: {PerformanceEntry[]}
|
|
|
|
|
|
|
|
Returns a list of `PerformanceEntry` objects in chronological order
|
|
|
|
with respect to `performanceEntry.startTime`.
|
|
|
|
|
|
|
|
### performanceObserverEntryList.getEntriesByName(name[, type])
|
|
|
|
<!-- YAML
|
|
|
|
added: v8.5.0
|
|
|
|
-->
|
|
|
|
|
|
|
|
* `name` {string}
|
|
|
|
* `type` {string}
|
|
|
|
* Returns: {PerformanceEntry[]}
|
|
|
|
|
|
|
|
Returns a list of `PerformanceEntry` objects in chronological order
|
|
|
|
with respect to `performanceEntry.startTime` whose `performanceEntry.name` is
|
|
|
|
equal to `name`, and optionally, whose `performanceEntry.entryType` is equal to
|
|
|
|
`type`.
|
|
|
|
|
|
|
|
### performanceObserverEntryList.getEntriesByType(type)
|
|
|
|
<!-- YAML
|
|
|
|
added: v8.5.0
|
|
|
|
-->
|
|
|
|
|
|
|
|
* `type` {string}
|
|
|
|
* Returns: {PerformanceEntry[]}
|
|
|
|
|
|
|
|
Returns a list of `PerformanceEntry` objects in chronological order
|
|
|
|
with respect to `performanceEntry.startTime` whose `performanceEntry.entryType`
|
|
|
|
is equal to `type`.
|
|
|
|
|
2017-08-08 00:53:24 +02:00
|
|
|
## Examples
|
|
|
|
|
|
|
|
### Measuring the duration of async operations
|
|
|
|
|
|
|
|
The following example uses the [Async Hooks][] and Performance APIs to measure
|
|
|
|
the actual duration of a Timeout operation (including the amount of time it
|
|
|
|
to execute the callback).
|
|
|
|
|
|
|
|
```js
|
|
|
|
'use strict';
|
|
|
|
const async_hooks = require('async_hooks');
|
|
|
|
const {
|
|
|
|
performance,
|
|
|
|
PerformanceObserver
|
|
|
|
} = require('perf_hooks');
|
|
|
|
|
|
|
|
const set = new Set();
|
|
|
|
const hook = async_hooks.createHook({
|
|
|
|
init(id, type) {
|
|
|
|
if (type === 'Timeout') {
|
|
|
|
performance.mark(`Timeout-${id}-Init`);
|
|
|
|
set.add(id);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
destroy(id) {
|
|
|
|
if (set.has(id)) {
|
|
|
|
set.delete(id);
|
|
|
|
performance.mark(`Timeout-${id}-Destroy`);
|
|
|
|
performance.measure(`Timeout-${id}`,
|
|
|
|
`Timeout-${id}-Init`,
|
|
|
|
`Timeout-${id}-Destroy`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
hook.enable();
|
|
|
|
|
|
|
|
const obs = new PerformanceObserver((list, observer) => {
|
|
|
|
console.log(list.getEntries()[0]);
|
|
|
|
performance.clearMarks();
|
|
|
|
observer.disconnect();
|
|
|
|
});
|
|
|
|
obs.observe({ entryTypes: ['measure'], buffered: true });
|
|
|
|
|
|
|
|
setTimeout(() => {}, 1000);
|
|
|
|
```
|
|
|
|
|
|
|
|
### Measuring how long it takes to load dependencies
|
|
|
|
|
|
|
|
The following example measures the duration of `require()` operations to load
|
|
|
|
dependencies:
|
|
|
|
|
|
|
|
<!-- eslint-disable no-global-assign -->
|
|
|
|
```js
|
|
|
|
'use strict';
|
|
|
|
const {
|
|
|
|
performance,
|
|
|
|
PerformanceObserver
|
|
|
|
} = require('perf_hooks');
|
|
|
|
const mod = require('module');
|
|
|
|
|
|
|
|
// Monkey patch the require function
|
|
|
|
mod.Module.prototype.require =
|
|
|
|
performance.timerify(mod.Module.prototype.require);
|
|
|
|
require = performance.timerify(require);
|
|
|
|
|
|
|
|
// Activate the observer
|
|
|
|
const obs = new PerformanceObserver((list) => {
|
|
|
|
const entries = list.getEntries();
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
console.log(`require('${entry[0]}')`, entry.duration);
|
|
|
|
});
|
|
|
|
obs.disconnect();
|
|
|
|
});
|
|
|
|
obs.observe({ entryTypes: ['function'], buffered: true });
|
|
|
|
|
|
|
|
require('some-module');
|
|
|
|
```
|
|
|
|
|
2018-02-25 23:26:22 +01:00
|
|
|
[`'exit'`]: process.html#process_event_exit
|
2017-08-08 00:53:24 +02:00
|
|
|
[`timeOrigin`]: https://w3c.github.io/hr-time/#dom-performance-timeorigin
|
2017-09-14 10:36:49 +02:00
|
|
|
[Async Hooks]: async_hooks.html
|
2017-08-08 00:53:24 +02:00
|
|
|
[W3C Performance Timeline]: https://w3c.github.io/performance-timeline/
|