mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
perf_hooks: add PerformanceResourceTiming
perf_hooks: create clearResourceTimings perf_hooks: add resourcetiming test parallel perf_hooks: add markResourceTiming perf_hooks: fix observable when using resource perf_hooks: fix observable when using resource perf_hooks: add class comments perf_hooks: add PerformanceResourceTiming perf_hooks: create clearResourceTimings perf_hooks: add resourcetiming test parallel perf_hooks: add markResourceTiming perf_hooks: fix observable when using resource perf_hooks: fix observable when using resource perf_hooks: add class comments perf_hooks: add Resource Timing documentation benchmark: measure resource timing module perf_hooks: add check avoiding new PerformanceResourceTiming perf_hooks: adjust doc PR-URL: https://github.com/nodejs/node/pull/42725 Fixes: https://github.com/nodejs/undici/issues/952 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
77
benchmark/perf_hooks/resourcetiming.js
Normal file
77
benchmark/perf_hooks/resourcetiming.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common.js');
|
||||
|
||||
const {
|
||||
PerformanceObserver,
|
||||
performance,
|
||||
} = require('perf_hooks');
|
||||
|
||||
function createTimingInfo({
|
||||
startTime = 0,
|
||||
redirectStartTime = 0,
|
||||
redirectEndTime = 0,
|
||||
postRedirectStartTime = 0,
|
||||
finalServiceWorkerStartTime = 0,
|
||||
finalNetworkRequestStartTime = 0,
|
||||
finalNetworkResponseStartTime = 0,
|
||||
endTime = 0,
|
||||
encodedBodySize = 0,
|
||||
decodedBodySize = 0,
|
||||
finalConnectionTimingInfo = null
|
||||
}) {
|
||||
if (finalConnectionTimingInfo !== null) {
|
||||
finalConnectionTimingInfo.domainLookupStartTime =
|
||||
finalConnectionTimingInfo.domainLookupStartTime || 0;
|
||||
finalConnectionTimingInfo.domainLookupEndTime =
|
||||
finalConnectionTimingInfo.domainLookupEndTime || 0;
|
||||
finalConnectionTimingInfo.connectionStartTime =
|
||||
finalConnectionTimingInfo.connectionStartTime || 0;
|
||||
finalConnectionTimingInfo.connectionEndTime =
|
||||
finalConnectionTimingInfo.connectionEndTime || 0;
|
||||
finalConnectionTimingInfo.secureConnectionStartTime =
|
||||
finalConnectionTimingInfo.secureConnectionStartTime || 0;
|
||||
finalConnectionTimingInfo.ALPNNegotiatedProtocol =
|
||||
finalConnectionTimingInfo.ALPNNegotiatedProtocol || [];
|
||||
}
|
||||
return {
|
||||
startTime,
|
||||
redirectStartTime,
|
||||
redirectEndTime,
|
||||
postRedirectStartTime,
|
||||
finalServiceWorkerStartTime,
|
||||
finalNetworkRequestStartTime,
|
||||
finalNetworkResponseStartTime,
|
||||
endTime,
|
||||
encodedBodySize,
|
||||
decodedBodySize,
|
||||
finalConnectionTimingInfo,
|
||||
};
|
||||
}
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [1e5],
|
||||
observe: ['resource'],
|
||||
});
|
||||
|
||||
function test() {
|
||||
const timingInfo = createTimingInfo({ finalConnectionTimingInfo: {} });
|
||||
performance.markResourceTiming(
|
||||
timingInfo,
|
||||
'http://localhost:8080',
|
||||
'fetch',
|
||||
{},
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
function main({ n, observe }) {
|
||||
const obs = new PerformanceObserver(() => {
|
||||
bench.end(n);
|
||||
});
|
||||
obs.observe({ entryTypes: [observe], buffered: true });
|
||||
|
||||
bench.start();
|
||||
for (let i = 0; i < 1e5; i++)
|
||||
test();
|
||||
}
|
||||
@@ -15,6 +15,7 @@ Node.js supports the following [Web Performance APIs][]:
|
||||
* [High Resolution Time][]
|
||||
* [Performance Timeline][]
|
||||
* [User Timing][]
|
||||
* [Resource Timing][]
|
||||
|
||||
```js
|
||||
const { PerformanceObserver, performance } = require('node:perf_hooks');
|
||||
@@ -66,6 +67,17 @@ added: v16.7.0
|
||||
If `name` is not provided, removes all `PerformanceMeasure` objects from the
|
||||
Performance Timeline. If `name` is provided, removes only the named mark.
|
||||
|
||||
### `performance.clearResourceTimings([name])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `name` {string}
|
||||
|
||||
If `name` is not provided, removes all `PerformanceResourceTiming` objects from
|
||||
the Resource Timeline. If `name` is provided, removes only the named resource.
|
||||
|
||||
### `performance.eventLoopUtilization([utilization1[, utilization2]])`
|
||||
|
||||
<!-- YAML
|
||||
@@ -198,6 +210,33 @@ and can be queried with `performance.getEntries`,
|
||||
observation is performed, the entries should be cleared from the global
|
||||
Performance Timeline manually with `performance.clearMarks`.
|
||||
|
||||
### \`performance.markResourceTiming(timingInfo, requestedUrl, initiatorType,
|
||||
|
||||
global, cacheMode)\`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `timingInfo` {Object} [Fetch Timing Info][]
|
||||
* `requestedUrl` {string} The resource url
|
||||
* `initiatorType` {string} The initiator name, e.g: 'fetch'
|
||||
* `global` {Object}
|
||||
* `cacheMode` {string} The cache mode must be an empty string ('') or 'local'
|
||||
|
||||
_This property is an extension by Node.js. It is not available in Web browsers._
|
||||
|
||||
Creates a new `PerformanceResourceTiming` entry in the Resource Timeline. A
|
||||
`PerformanceResourceTiming` is a subclass of `PerformanceEntry` whose
|
||||
`performanceEntry.entryType` is always `'resource'`. Performance resources
|
||||
are used to mark moments in the Resource Timeline.
|
||||
|
||||
The created `PerformanceMark` entry is put in the global Resource Timeline
|
||||
and can be queried with `performance.getEntries`,
|
||||
`performance.getEntriesByName`, and `performance.getEntriesByType`. When the
|
||||
observation is performed, the entries should be cleared from the global
|
||||
Performance Timeline manually with `performance.clearResourceTimings`.
|
||||
|
||||
### `performance.measure(name[, startMarkOrOptions[, endMark]])`
|
||||
|
||||
<!-- YAML
|
||||
@@ -653,6 +692,188 @@ added: v8.5.0
|
||||
The high resolution millisecond timestamp at which the V8 platform was
|
||||
initialized.
|
||||
|
||||
## Class: `PerformanceResourceTiming`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Extends: {PerformanceEntry}
|
||||
|
||||
Provides detailed network timing data regarding the loading of an application's
|
||||
resources.
|
||||
|
||||
The constructor of this class is not exposed to users directly.
|
||||
|
||||
### `performanceResourceTiming.workerStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp at immediately before dispatching
|
||||
the `fetch` request. If the resource is not intercepted by a worker the property
|
||||
will always return 0.
|
||||
|
||||
### `performanceResourceTiming.redirectStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp that represents the start time
|
||||
of the fetch which initiates the redirect.
|
||||
|
||||
### `performanceResourceTiming.redirectEnd`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp that will be created immediately after
|
||||
receiving the last byte of the response of the last redirect.
|
||||
|
||||
### `performanceResourceTiming.fetchStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp immediately before the Node.js starts
|
||||
to fetch the resource.
|
||||
|
||||
### `performanceResourceTiming.domainLookupStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp immediately before the Node.js starts
|
||||
the domain name lookup for the resource.
|
||||
|
||||
### `performanceResourceTiming.domainLookupEnd`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp representing the time immediately
|
||||
after the Node.js finished the domain name lookup for the resource.
|
||||
|
||||
### `performanceResourceTiming.connectStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp representing the time immediately
|
||||
before Node.js starts to establish the connection to the server to retrieve
|
||||
the resource.
|
||||
|
||||
### `performanceResourceTiming.connectEnd`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp representing the time immediately
|
||||
after Node.js finishes establishing the connection to the server to retrieve
|
||||
the resource.
|
||||
|
||||
### `performanceResourceTiming.secureConnectionStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp representing the time immediately
|
||||
before Node.js starts the handshake process to secure the current connection.
|
||||
|
||||
### `performanceResourceTiming.requestStart`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp representing the time immediately
|
||||
before Node.js receives the first byte of the response from the server.
|
||||
|
||||
### `performanceResourceTiming.responseEnd`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
The high resolution millisecond timestamp representing the time immediately
|
||||
after Node.js receives the last byte of the resource or immediately before
|
||||
the transport connection is closed, whichever comes first.
|
||||
|
||||
### `performanceResourceTiming.transferSize`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
A number representing the size (in octets) of the fetched resource. The size
|
||||
includes the response header fields plus the response payload body.
|
||||
|
||||
### `performanceResourceTiming.encodedBodySize`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
A number representing the size (in octets) received from the fetch
|
||||
(HTTP or cache), of the payload body, before removing any applied
|
||||
content-codings.
|
||||
|
||||
### `performanceResourceTiming.decodedBodySize`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number}
|
||||
|
||||
A number representing the size (in octets) received from the fetch
|
||||
(HTTP or cache), of the message body, after removing any applied
|
||||
content-codings.
|
||||
|
||||
### `performanceResourceTiming.toJSON()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Returns a `object` that is the JSON representation of the
|
||||
`PerformanceResourceTiming` object
|
||||
|
||||
## Class: `perf_hooks.PerformanceObserver`
|
||||
|
||||
### `new PerformanceObserver(callback)`
|
||||
@@ -1367,8 +1588,10 @@ dns.promises.resolve('localhost');
|
||||
```
|
||||
|
||||
[Async Hooks]: async_hooks.md
|
||||
[Fetch Timing Info]: https://fetch.spec.whatwg.org/#fetch-timing-info
|
||||
[High Resolution Time]: https://www.w3.org/TR/hr-time-2
|
||||
[Performance Timeline]: https://w3c.github.io/performance-timeline/
|
||||
[Resource Timing]: https://www.w3.org/TR/resource-timing-2/
|
||||
[User Timing]: https://www.w3.org/TR/user-timing/
|
||||
[Web Performance APIs]: https://w3c.github.io/perf-timing-primer/
|
||||
[Worker threads]: worker_threads.md#worker-threads
|
||||
|
||||
@@ -85,15 +85,18 @@ const kSupportedEntryTypes = ObjectFreeze([
|
||||
'mark',
|
||||
'measure',
|
||||
'net',
|
||||
'resource',
|
||||
]);
|
||||
|
||||
// Performance timeline entry Buffers
|
||||
let markEntryBuffer = [];
|
||||
let measureEntryBuffer = [];
|
||||
let resourceTimingBuffer = [];
|
||||
const kMaxPerformanceEntryBuffers = 1e6;
|
||||
const kClearPerformanceEntryBuffers = ObjectFreeze({
|
||||
'mark': 'performance.clearMarks',
|
||||
'measure': 'performance.clearMeasures',
|
||||
'resource': 'performance.clearResourceTimings',
|
||||
});
|
||||
const kWarnedEntryTypes = new SafeMap();
|
||||
|
||||
@@ -340,6 +343,8 @@ function enqueue(entry) {
|
||||
buffer = markEntryBuffer;
|
||||
} else if (entryType === 'measure') {
|
||||
buffer = measureEntryBuffer;
|
||||
} else if (entryType === 'resource') {
|
||||
buffer = resourceTimingBuffer;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -365,16 +370,19 @@ function enqueue(entry) {
|
||||
}
|
||||
|
||||
function clearEntriesFromBuffer(type, name) {
|
||||
if (type !== 'mark' && type !== 'measure') {
|
||||
if (type !== 'mark' && type !== 'measure' && type !== 'resource') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'mark') {
|
||||
markEntryBuffer = name === undefined ?
|
||||
[] : ArrayPrototypeFilter(markEntryBuffer, (entry) => entry.name !== name);
|
||||
} else {
|
||||
} else if (type === 'measure') {
|
||||
measureEntryBuffer = name === undefined ?
|
||||
[] : ArrayPrototypeFilter(measureEntryBuffer, (entry) => entry.name !== name);
|
||||
} else {
|
||||
resourceTimingBuffer = name === undefined ?
|
||||
[] : ArrayPrototypeFilter(resourceTimingBuffer, (entry) => entry.name !== name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,11 +392,13 @@ function filterBufferMapByNameAndType(name, type) {
|
||||
bufferList = markEntryBuffer;
|
||||
} else if (type === 'measure') {
|
||||
bufferList = measureEntryBuffer;
|
||||
} else if (type === 'resource') {
|
||||
bufferList = resourceTimingBuffer;
|
||||
} else if (type !== undefined) {
|
||||
// Unrecognized type;
|
||||
return [];
|
||||
} else {
|
||||
bufferList = ArrayPrototypeConcat(markEntryBuffer, measureEntryBuffer);
|
||||
bufferList = ArrayPrototypeConcat(markEntryBuffer, measureEntryBuffer, resourceTimingBuffer);
|
||||
}
|
||||
if (name !== undefined) {
|
||||
bufferList = ArrayPrototypeFilter(bufferList, (buffer) => buffer.name === name);
|
||||
|
||||
@@ -19,6 +19,8 @@ const {
|
||||
|
||||
const { now } = require('internal/perf/utils');
|
||||
|
||||
const { markResourceTiming } = require('internal/perf/resource_timing');
|
||||
|
||||
const {
|
||||
mark,
|
||||
measure,
|
||||
@@ -82,6 +84,13 @@ function clearMeasures(name) {
|
||||
clearEntriesFromBuffer('measure', name);
|
||||
}
|
||||
|
||||
function clearResourceTimings(name) {
|
||||
if (name !== undefined) {
|
||||
name = `${name}`;
|
||||
}
|
||||
clearEntriesFromBuffer('resource', name);
|
||||
}
|
||||
|
||||
function getEntries() {
|
||||
return filterBufferMapByNameAndType();
|
||||
}
|
||||
@@ -117,6 +126,11 @@ ObjectDefineProperties(Performance.prototype, {
|
||||
enumerable: false,
|
||||
value: clearMeasures,
|
||||
},
|
||||
clearResourceTimings: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: clearResourceTimings,
|
||||
},
|
||||
eventLoopUtilization: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
@@ -152,6 +166,13 @@ ObjectDefineProperties(Performance.prototype, {
|
||||
enumerable: false,
|
||||
value: nodeTiming,
|
||||
},
|
||||
// In the browser, this function is not public. However, it must be used inside fetch
|
||||
// which is a Node.js dependency, not a internal module
|
||||
markResourceTiming: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: markResourceTiming,
|
||||
},
|
||||
now: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
|
||||
179
lib/internal/perf/resource_timing.js
Normal file
179
lib/internal/perf/resource_timing.js
Normal file
@@ -0,0 +1,179 @@
|
||||
'use strict';
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming
|
||||
|
||||
const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
|
||||
const { SymbolToStringTag } = primordials;
|
||||
const assert = require('internal/assert');
|
||||
const { enqueue } = require('internal/perf/observe');
|
||||
const { Symbol, ObjectSetPrototypeOf } = primordials;
|
||||
|
||||
const kCacheMode = Symbol('kCacheMode');
|
||||
const kRequestedUrl = Symbol('kRequestedUrl');
|
||||
const kTimingInfo = Symbol('kTimingInfo');
|
||||
const kInitiatorType = Symbol('kInitiatorType');
|
||||
|
||||
const {
|
||||
codes: {
|
||||
ERR_ILLEGAL_CONSTRUCTOR,
|
||||
}
|
||||
} = require('internal/errors');
|
||||
|
||||
class InternalPerformanceResourceTiming extends InternalPerformanceEntry {
|
||||
constructor(requestedUrl, initiatorType, timingInfo, cacheMode = '') {
|
||||
super(requestedUrl, 'resource');
|
||||
this[kInitiatorType] = initiatorType;
|
||||
this[kRequestedUrl] = requestedUrl;
|
||||
// https://fetch.spec.whatwg.org/#fetch-timing-info
|
||||
// This class is using timingInfo assuming it's already validated.
|
||||
// The spec doesn't say to validate it in the class construction.
|
||||
this[kTimingInfo] = timingInfo;
|
||||
this[kCacheMode] = cacheMode;
|
||||
}
|
||||
|
||||
get [SymbolToStringTag]() {
|
||||
return 'PerformanceResourceTiming';
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this[kRequestedUrl];
|
||||
}
|
||||
|
||||
get startTime() {
|
||||
return this[kTimingInfo].startTime;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this[kTimingInfo].endTime - this[kTimingInfo].startTime;
|
||||
}
|
||||
|
||||
get workerStart() {
|
||||
return this[kTimingInfo].finalServiceWorkerStartTime;
|
||||
}
|
||||
|
||||
get redirectStart() {
|
||||
return this[kTimingInfo].redirectStartTime;
|
||||
}
|
||||
|
||||
get redirectEnd() {
|
||||
return this[kTimingInfo].redirectEndTime;
|
||||
}
|
||||
|
||||
get fetchStart() {
|
||||
return this[kTimingInfo].postRedirectStartTime;
|
||||
}
|
||||
|
||||
get domainLookupStart() {
|
||||
return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupStartTime;
|
||||
}
|
||||
|
||||
get domainLookupEnd() {
|
||||
return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupEndTime;
|
||||
}
|
||||
|
||||
get connectStart() {
|
||||
return this[kTimingInfo].finalConnectionTimingInfo?.connectionStartTime;
|
||||
}
|
||||
|
||||
get connectEnd() {
|
||||
return this[kTimingInfo].finalConnectionTimingInfo?.connectionEndTime;
|
||||
}
|
||||
|
||||
get secureConnectionStart() {
|
||||
return this[kTimingInfo]
|
||||
.finalConnectionTimingInfo?.secureConnectionStartTime;
|
||||
}
|
||||
|
||||
get nextHopProtocol() {
|
||||
return this[kTimingInfo]
|
||||
.finalConnectionTimingInfo?.ALPNNegotiatedProtocol;
|
||||
}
|
||||
|
||||
get requestStart() {
|
||||
return this[kTimingInfo].finalNetworkRequestStartTime;
|
||||
}
|
||||
|
||||
get responseStart() {
|
||||
return this[kTimingInfo].finalNetworkResponseStartTime;
|
||||
}
|
||||
|
||||
get responseEnd() {
|
||||
return this[kTimingInfo].endTime;
|
||||
}
|
||||
|
||||
get encodedBodySize() {
|
||||
return this[kTimingInfo].encodedBodySize;
|
||||
}
|
||||
|
||||
get decodedBodySize() {
|
||||
return this[kTimingInfo].decodedBodySize;
|
||||
}
|
||||
|
||||
get transferSize() {
|
||||
if (this[kCacheMode] === 'local') return 0;
|
||||
if (this[kCacheMode] === 'validated') return 300;
|
||||
|
||||
return this[kTimingInfo].encodedBodySize + 300;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
entryType: this.entryType,
|
||||
startTime: this.startTime,
|
||||
duration: this.duration,
|
||||
initiatorType: this[kInitiatorType],
|
||||
nextHopProtocol: this.nextHopProtocol,
|
||||
workerStart: this.workerStart,
|
||||
redirectStart: this.redirectStart,
|
||||
redirectEnd: this.redirectEnd,
|
||||
fetchStart: this.fetchStart,
|
||||
domainLookupStart: this.domainLookupStart,
|
||||
domainLookupEnd: this.domainLookupEnd,
|
||||
connectStart: this.connectStart,
|
||||
connectEnd: this.connectEnd,
|
||||
secureConnectionStart: this.secureConnectionStart,
|
||||
requestStart: this.requestStart,
|
||||
responseStart: this.responseStart,
|
||||
responseEnd: this.responseEnd,
|
||||
transferSize: this.transferSize,
|
||||
encodedBodySize: this.encodedBodySize,
|
||||
decodedBodySize: this.decodedBodySize,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class PerformanceResourceTiming extends InternalPerformanceResourceTiming {
|
||||
constructor() {
|
||||
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
||||
}
|
||||
}
|
||||
|
||||
// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
|
||||
function markResourceTiming(
|
||||
timingInfo,
|
||||
requestedUrl,
|
||||
initiatorType,
|
||||
global,
|
||||
cacheMode,
|
||||
) {
|
||||
// https://w3c.github.io/resource-timing/#dfn-setup-the-resource-timing-entry
|
||||
assert(
|
||||
cacheMode === '' || cacheMode === 'local',
|
||||
'cache must be an empty string or \'local\'',
|
||||
);
|
||||
const resource = new InternalPerformanceResourceTiming(
|
||||
requestedUrl,
|
||||
initiatorType,
|
||||
timingInfo,
|
||||
cacheMode,
|
||||
);
|
||||
|
||||
ObjectSetPrototypeOf(resource, PerformanceResourceTiming.prototype);
|
||||
enqueue(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PerformanceResourceTiming,
|
||||
markResourceTiming,
|
||||
};
|
||||
@@ -9,6 +9,7 @@ const {
|
||||
} = internalBinding('performance');
|
||||
|
||||
const { PerformanceEntry } = require('internal/perf/performance_entry');
|
||||
const { PerformanceResourceTiming } = require('internal/perf/resource_timing');
|
||||
const {
|
||||
PerformanceObserver,
|
||||
PerformanceObserverEntryList,
|
||||
@@ -31,6 +32,7 @@ module.exports = {
|
||||
PerformanceMeasure,
|
||||
PerformanceObserver,
|
||||
PerformanceObserverEntryList,
|
||||
PerformanceResourceTiming,
|
||||
monitorEventLoopDelay,
|
||||
createHistogram,
|
||||
performance: new InternalPerformance(),
|
||||
|
||||
@@ -99,6 +99,7 @@ const expectedModules = new Set([
|
||||
'NativeModule internal/perf/performance',
|
||||
'NativeModule internal/perf/timerify',
|
||||
'NativeModule internal/perf/usertiming',
|
||||
'NativeModule internal/perf/resource_timing',
|
||||
'NativeModule internal/perf/utils',
|
||||
'NativeModule internal/priority_queue',
|
||||
'NativeModule internal/process/esm_loader',
|
||||
|
||||
265
test/parallel/test-perf-hooks-resourcetiming.js
Normal file
265
test/parallel/test-perf-hooks-resourcetiming.js
Normal file
@@ -0,0 +1,265 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const {
|
||||
PerformanceObserver,
|
||||
PerformanceEntry,
|
||||
PerformanceResourceTiming,
|
||||
performance: {
|
||||
clearResourceTimings,
|
||||
markResourceTiming,
|
||||
},
|
||||
} = require('perf_hooks');
|
||||
|
||||
assert(PerformanceObserver);
|
||||
assert(PerformanceEntry);
|
||||
assert(PerformanceResourceTiming);
|
||||
assert(clearResourceTimings);
|
||||
assert(markResourceTiming);
|
||||
|
||||
function createTimingInfo({
|
||||
startTime = 0,
|
||||
redirectStartTime = 0,
|
||||
redirectEndTime = 0,
|
||||
postRedirectStartTime = 0,
|
||||
finalServiceWorkerStartTime = 0,
|
||||
finalNetworkRequestStartTime = 0,
|
||||
finalNetworkResponseStartTime = 0,
|
||||
endTime = 0,
|
||||
encodedBodySize = 0,
|
||||
decodedBodySize = 0,
|
||||
finalConnectionTimingInfo = null
|
||||
}) {
|
||||
if (finalConnectionTimingInfo !== null) {
|
||||
finalConnectionTimingInfo.domainLookupStartTime =
|
||||
finalConnectionTimingInfo.domainLookupStartTime || 0;
|
||||
finalConnectionTimingInfo.domainLookupEndTime =
|
||||
finalConnectionTimingInfo.domainLookupEndTime || 0;
|
||||
finalConnectionTimingInfo.connectionStartTime =
|
||||
finalConnectionTimingInfo.connectionStartTime || 0;
|
||||
finalConnectionTimingInfo.connectionEndTime =
|
||||
finalConnectionTimingInfo.connectionEndTime || 0;
|
||||
finalConnectionTimingInfo.secureConnectionStartTime =
|
||||
finalConnectionTimingInfo.secureConnectionStartTime || 0;
|
||||
finalConnectionTimingInfo.ALPNNegotiatedProtocol =
|
||||
finalConnectionTimingInfo.ALPNNegotiatedProtocol || [];
|
||||
}
|
||||
return {
|
||||
startTime,
|
||||
redirectStartTime,
|
||||
redirectEndTime,
|
||||
postRedirectStartTime,
|
||||
finalServiceWorkerStartTime,
|
||||
finalNetworkRequestStartTime,
|
||||
finalNetworkResponseStartTime,
|
||||
endTime,
|
||||
encodedBodySize,
|
||||
decodedBodySize,
|
||||
finalConnectionTimingInfo,
|
||||
};
|
||||
}
|
||||
|
||||
// PerformanceResourceTiming should not be initialized externally
|
||||
{
|
||||
assert.throws(() => new PerformanceResourceTiming(), {
|
||||
name: 'TypeError',
|
||||
message: 'Illegal constructor',
|
||||
code: 'ERR_ILLEGAL_CONSTRUCTOR',
|
||||
});
|
||||
}
|
||||
|
||||
// Using performance.getEntries*()
|
||||
{
|
||||
const timingInfo = createTimingInfo({ finalConnectionTimingInfo: {} });
|
||||
const customGlobal = {};
|
||||
const requestedUrl = 'http://localhost:8080';
|
||||
const cacheMode = 'local';
|
||||
const initiatorType = 'fetch';
|
||||
const resource = markResourceTiming(
|
||||
timingInfo,
|
||||
requestedUrl,
|
||||
initiatorType,
|
||||
customGlobal,
|
||||
cacheMode,
|
||||
);
|
||||
|
||||
assert(resource instanceof PerformanceEntry);
|
||||
assert(resource instanceof PerformanceResourceTiming);
|
||||
|
||||
{
|
||||
const entries = performance.getEntries();
|
||||
assert.strictEqual(entries.length, 1);
|
||||
assert(entries[0] instanceof PerformanceResourceTiming);
|
||||
}
|
||||
|
||||
{
|
||||
const entries = performance.getEntriesByType('resource');
|
||||
assert.strictEqual(entries.length, 1);
|
||||
assert(entries[0] instanceof PerformanceResourceTiming);
|
||||
}
|
||||
|
||||
{
|
||||
const entries = performance.getEntriesByName(resource.name);
|
||||
assert.strictEqual(entries.length, 1);
|
||||
assert(entries[0] instanceof PerformanceResourceTiming);
|
||||
}
|
||||
|
||||
clearResourceTimings();
|
||||
assert.strictEqual(performance.getEntries().length, 0);
|
||||
}
|
||||
|
||||
// Assert resource data based in timingInfo
|
||||
|
||||
// default values
|
||||
{
|
||||
const timingInfo = createTimingInfo({ finalConnectionTimingInfo: {} });
|
||||
const customGlobal = {};
|
||||
const requestedUrl = 'http://localhost:8080';
|
||||
const cacheMode = 'local';
|
||||
const initiatorType = 'fetch';
|
||||
const resource = markResourceTiming(
|
||||
timingInfo,
|
||||
requestedUrl,
|
||||
initiatorType,
|
||||
customGlobal,
|
||||
cacheMode,
|
||||
);
|
||||
|
||||
assert(resource instanceof PerformanceEntry);
|
||||
assert(resource instanceof PerformanceResourceTiming);
|
||||
|
||||
assert.strictEqual(resource.entryType, 'resource');
|
||||
assert.strictEqual(resource.name, requestedUrl);
|
||||
assert.ok(typeof resource.cacheMode === 'undefined', 'cacheMode does not have a getter');
|
||||
assert.strictEqual(resource.startTime, timingInfo.startTime);
|
||||
assert.strictEqual(resource.duration, 0);
|
||||
assert.strictEqual(resource.workerStart, 0);
|
||||
assert.strictEqual(resource.redirectStart, 0);
|
||||
assert.strictEqual(resource.redirectEnd, 0);
|
||||
assert.strictEqual(resource.fetchStart, 0);
|
||||
assert.strictEqual(resource.domainLookupStart, 0);
|
||||
assert.strictEqual(resource.domainLookupEnd, 0);
|
||||
assert.strictEqual(resource.connectStart, 0);
|
||||
assert.strictEqual(resource.connectEnd, 0);
|
||||
assert.strictEqual(resource.secureConnectionStart, 0);
|
||||
assert.deepStrictEqual(resource.nextHopProtocol, []);
|
||||
assert.strictEqual(resource.requestStart, 0);
|
||||
assert.strictEqual(resource.responseStart, 0);
|
||||
assert.strictEqual(resource.responseEnd, 0);
|
||||
assert.strictEqual(resource.encodedBodySize, 0);
|
||||
assert.strictEqual(resource.decodedBodySize, 0);
|
||||
assert.strictEqual(resource.transferSize, 0);
|
||||
assert.deepStrictEqual(resource.toJSON(), {
|
||||
name: requestedUrl,
|
||||
entryType: 'resource',
|
||||
startTime: 0,
|
||||
duration: 0,
|
||||
initiatorType,
|
||||
nextHopProtocol: [],
|
||||
workerStart: 0,
|
||||
redirectStart: 0,
|
||||
redirectEnd: 0,
|
||||
fetchStart: 0,
|
||||
domainLookupStart: 0,
|
||||
domainLookupEnd: 0,
|
||||
connectStart: 0,
|
||||
connectEnd: 0,
|
||||
secureConnectionStart: 0,
|
||||
requestStart: 0,
|
||||
responseStart: 0,
|
||||
responseEnd: 0,
|
||||
transferSize: 0,
|
||||
encodedBodySize: 0,
|
||||
decodedBodySize: 0,
|
||||
});
|
||||
|
||||
assert(resource instanceof PerformanceEntry);
|
||||
assert(resource instanceof PerformanceResourceTiming);
|
||||
|
||||
clearResourceTimings();
|
||||
const entries = performance.getEntries();
|
||||
assert.strictEqual(entries.length, 0);
|
||||
}
|
||||
|
||||
// custom getters math
|
||||
{
|
||||
const timingInfo = createTimingInfo({
|
||||
endTime: 100,
|
||||
startTime: 50,
|
||||
encodedBodySize: 150,
|
||||
});
|
||||
const customGlobal = {};
|
||||
const requestedUrl = 'http://localhost:8080';
|
||||
const cacheMode = '';
|
||||
const initiatorType = 'fetch';
|
||||
const resource = markResourceTiming(
|
||||
timingInfo,
|
||||
requestedUrl,
|
||||
initiatorType,
|
||||
customGlobal,
|
||||
cacheMode,
|
||||
);
|
||||
|
||||
assert(resource instanceof PerformanceEntry);
|
||||
assert(resource instanceof PerformanceResourceTiming);
|
||||
|
||||
assert.strictEqual(resource.entryType, 'resource');
|
||||
assert.strictEqual(resource.name, requestedUrl);
|
||||
assert.ok(typeof resource.cacheMode === 'undefined', 'cacheMode does not have a getter');
|
||||
assert.strictEqual(resource.startTime, timingInfo.startTime);
|
||||
// Duration should be the timingInfo endTime - startTime
|
||||
assert.strictEqual(resource.duration, 50);
|
||||
// TransferSize should be encodedBodySize + 300 when cacheMode is empty
|
||||
assert.strictEqual(resource.transferSize, 450);
|
||||
|
||||
assert(resource instanceof PerformanceEntry);
|
||||
assert(resource instanceof PerformanceResourceTiming);
|
||||
|
||||
clearResourceTimings();
|
||||
const entries = performance.getEntries();
|
||||
assert.strictEqual(entries.length, 0);
|
||||
}
|
||||
|
||||
// Using PerformanceObserver
|
||||
{
|
||||
const obs = new PerformanceObserver(common.mustCall((list) => {
|
||||
{
|
||||
const entries = list.getEntries();
|
||||
assert.strictEqual(entries.length, 1);
|
||||
assert(entries[0] instanceof PerformanceResourceTiming);
|
||||
}
|
||||
{
|
||||
const entries = list.getEntriesByType('resource');
|
||||
assert.strictEqual(entries.length, 1);
|
||||
assert(entries[0] instanceof PerformanceResourceTiming);
|
||||
}
|
||||
{
|
||||
const entries = list.getEntriesByName('http://localhost:8080');
|
||||
assert.strictEqual(entries.length, 1);
|
||||
assert(entries[0] instanceof PerformanceResourceTiming);
|
||||
}
|
||||
obs.disconnect();
|
||||
}));
|
||||
obs.observe({ entryTypes: ['resource'] });
|
||||
|
||||
const timingInfo = createTimingInfo({ finalConnectionTimingInfo: {} });
|
||||
const customGlobal = {};
|
||||
const requestedUrl = 'http://localhost:8080';
|
||||
const cacheMode = 'local';
|
||||
const initiatorType = 'fetch';
|
||||
const resource = markResourceTiming(
|
||||
timingInfo,
|
||||
requestedUrl,
|
||||
initiatorType,
|
||||
customGlobal,
|
||||
cacheMode,
|
||||
);
|
||||
|
||||
assert(resource instanceof PerformanceEntry);
|
||||
assert(resource instanceof PerformanceResourceTiming);
|
||||
|
||||
clearResourceTimings();
|
||||
const entries = performance.getEntries();
|
||||
assert.strictEqual(entries.length, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user