Skip to content

Commit 2173e92

Browse files
committed
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
1 parent c4781ea commit 2173e92

File tree

6 files changed

+455
-3
lines changed

6 files changed

+455
-3
lines changed

lib/internal/perf/observe.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,18 @@ const kSupportedEntryTypes = ObjectFreeze([
8585
'mark',
8686
'measure',
8787
'net',
88+
'resource',
8889
]);
8990

9091
// Performance timeline entry Buffers
9192
let markEntryBuffer = [];
9293
let measureEntryBuffer = [];
94+
let resourceTimingBuffer = [];
9395
const kMaxPerformanceEntryBuffers = 1e6;
9496
const kClearPerformanceEntryBuffers = ObjectFreeze({
9597
'mark': 'performance.clearMarks',
9698
'measure': 'performance.clearMeasures',
99+
'resource': 'performance.clearResourceTimings',
97100
});
98101
const kWarnedEntryTypes = new SafeMap();
99102

@@ -340,6 +343,8 @@ function enqueue(entry) {
340343
buffer = markEntryBuffer;
341344
} else if (entryType === 'measure') {
342345
buffer = measureEntryBuffer;
346+
} else if (entryType === 'resource') {
347+
buffer = resourceTimingBuffer;
343348
} else {
344349
return;
345350
}
@@ -365,16 +370,19 @@ function enqueue(entry) {
365370
}
366371

367372
function clearEntriesFromBuffer(type, name) {
368-
if (type !== 'mark' && type !== 'measure') {
373+
if (type !== 'mark' && type !== 'measure' && type !== 'resource') {
369374
return;
370375
}
371376

372377
if (type === 'mark') {
373378
markEntryBuffer = name === undefined ?
374379
[] : ArrayPrototypeFilter(markEntryBuffer, (entry) => entry.name !== name);
375-
} else {
380+
} else if (type === 'measure') {
376381
measureEntryBuffer = name === undefined ?
377382
[] : ArrayPrototypeFilter(measureEntryBuffer, (entry) => entry.name !== name);
383+
} else {
384+
resourceTimingBuffer = name === undefined ?
385+
[] : ArrayPrototypeFilter(resourceTimingBuffer, (entry) => entry.name !== name);
378386
}
379387
}
380388

@@ -384,11 +392,13 @@ function filterBufferMapByNameAndType(name, type) {
384392
bufferList = markEntryBuffer;
385393
} else if (type === 'measure') {
386394
bufferList = measureEntryBuffer;
395+
} else if (type === 'resource') {
396+
bufferList = resourceTimingBuffer;
387397
} else if (type !== undefined) {
388398
// Unrecognized type;
389399
return [];
390400
} else {
391-
bufferList = ArrayPrototypeConcat(markEntryBuffer, measureEntryBuffer);
401+
bufferList = ArrayPrototypeConcat(markEntryBuffer, measureEntryBuffer, resourceTimingBuffer);
392402
}
393403
if (name !== undefined) {
394404
bufferList = ArrayPrototypeFilter(bufferList, (buffer) => buffer.name === name);

lib/internal/perf/performance.js

+21
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const {
1919

2020
const { now } = require('internal/perf/utils');
2121

22+
const { markResourceTiming } = require('internal/perf/resource_timing');
23+
2224
const {
2325
mark,
2426
measure,
@@ -82,6 +84,13 @@ function clearMeasures(name) {
8284
clearEntriesFromBuffer('measure', name);
8385
}
8486

87+
function clearResourceTimings(name) {
88+
if (name !== undefined) {
89+
name = `${name}`;
90+
}
91+
clearEntriesFromBuffer('resource', name);
92+
}
93+
8594
function getEntries() {
8695
return filterBufferMapByNameAndType();
8796
}
@@ -117,6 +126,11 @@ ObjectDefineProperties(Performance.prototype, {
117126
enumerable: false,
118127
value: clearMeasures,
119128
},
129+
clearResourceTimings: {
130+
configurable: true,
131+
enumerable: false,
132+
value: clearResourceTimings,
133+
},
120134
eventLoopUtilization: {
121135
configurable: true,
122136
enumerable: false,
@@ -152,6 +166,13 @@ ObjectDefineProperties(Performance.prototype, {
152166
enumerable: false,
153167
value: nodeTiming,
154168
},
169+
// In the browser, this function is not public. However, it must be used inside fetch
170+
// which is a Node.js dependency, not a internal module
171+
markResourceTiming: {
172+
configurable: true,
173+
enumerable: false,
174+
value: markResourceTiming,
175+
},
155176
now: {
156177
configurable: true,
157178
enumerable: false,

lib/internal/perf/resource_timing.js

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
'use strict';
2+
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming
3+
4+
const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
5+
const { SymbolToStringTag } = primordials;
6+
const assert = require('internal/assert');
7+
const { enqueue } = require('internal/perf/observe');
8+
const { Symbol } = primordials;
9+
10+
const kCacheMode = Symbol('kCacheMode');
11+
const kRequestedUrl = Symbol('kRequestedUrl');
12+
const kTimingInfo = Symbol('kTimingInfo');
13+
const kInitiatorType = Symbol('kInitiatorType');
14+
15+
class PerformanceResourceTiming extends InternalPerformanceEntry {
16+
constructor(requestedUrl, initiatorType, timingInfo, cacheMode = '') {
17+
super(requestedUrl, 'resource');
18+
this[kInitiatorType] = initiatorType;
19+
this[kRequestedUrl] = requestedUrl;
20+
// https://fetch.spec.whatwg.org/#fetch-timing-info
21+
// This class is using timingInfo assuming it's already validated.
22+
// The spec doesn't say to validate it in the class construction.
23+
this[kTimingInfo] = timingInfo;
24+
this[kCacheMode] = cacheMode;
25+
}
26+
27+
get [SymbolToStringTag]() {
28+
return 'PerformanceResourceTiming';
29+
}
30+
31+
get name() {
32+
return this[kRequestedUrl];
33+
}
34+
35+
get startTime() {
36+
return this[kTimingInfo].startTime;
37+
}
38+
39+
get duration() {
40+
return this[kTimingInfo].endTime - this[kTimingInfo].startTime;
41+
}
42+
43+
get workerStart() {
44+
return this[kTimingInfo].finalServiceWorkerStartTime;
45+
}
46+
47+
get redirectStart() {
48+
return this[kTimingInfo].redirectStartTime;
49+
}
50+
51+
get redirectEnd() {
52+
return this[kTimingInfo].redirectEndTime;
53+
}
54+
55+
get fetchStart() {
56+
return this[kTimingInfo].postRedirectStartTime;
57+
}
58+
59+
get domainLookupStart() {
60+
return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupStartTime;
61+
}
62+
63+
get domainLookupEnd() {
64+
return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupEndTime;
65+
}
66+
67+
get connectStart() {
68+
return this[kTimingInfo].finalConnectionTimingInfo?.connectionStartTime;
69+
}
70+
71+
get connectEnd() {
72+
return this[kTimingInfo].finalConnectionTimingInfo?.connectionEndTime;
73+
}
74+
75+
get secureConnectionStart() {
76+
return this[kTimingInfo]
77+
.finalConnectionTimingInfo?.secureConnectionStartTime;
78+
}
79+
80+
get nextHopProtocol() {
81+
return this[kTimingInfo]
82+
.finalConnectionTimingInfo?.ALPNNegotiatedProtocol;
83+
}
84+
85+
get requestStart() {
86+
return this[kTimingInfo].finalNetworkRequestStartTime;
87+
}
88+
89+
get responseStart() {
90+
return this[kTimingInfo].finalNetworkResponseStartTime;
91+
}
92+
93+
get responseEnd() {
94+
return this[kTimingInfo].endTime;
95+
}
96+
97+
get encodedBodySize() {
98+
return this[kTimingInfo].encodedBodySize;
99+
}
100+
101+
get decodedBodySize() {
102+
return this[kTimingInfo].decodedBodySize;
103+
}
104+
105+
get transferSize() {
106+
if (this[kCacheMode] === 'local') return 0;
107+
if (this[kCacheMode] === 'validated') return 300;
108+
109+
return this[kTimingInfo].encodedBodySize + 300;
110+
}
111+
112+
toJSON() {
113+
return {
114+
name: this.name,
115+
entryType: this.entryType,
116+
startTime: this.startTime,
117+
duration: this.duration,
118+
initiatorType: this[kInitiatorType],
119+
nextHopProtocol: this.nextHopProtocol,
120+
workerStart: this.workerStart,
121+
redirectStart: this.redirectStart,
122+
redirectEnd: this.redirectEnd,
123+
fetchStart: this.fetchStart,
124+
domainLookupStart: this.domainLookupStart,
125+
domainLookupEnd: this.domainLookupEnd,
126+
connectStart: this.connectStart,
127+
connectEnd: this.connectEnd,
128+
secureConnectionStart: this.secureConnectionStart,
129+
requestStart: this.requestStart,
130+
responseStart: this.responseStart,
131+
responseEnd: this.responseEnd,
132+
transferSize: this.transferSize,
133+
encodedBodySize: this.encodedBodySize,
134+
decodedBodySize: this.decodedBodySize,
135+
};
136+
}
137+
}
138+
139+
// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
140+
function markResourceTiming(
141+
timingInfo,
142+
requestedUrl,
143+
initiatorType,
144+
global,
145+
cacheMode,
146+
) {
147+
// https://w3c.github.io/resource-timing/#dfn-setup-the-resource-timing-entry
148+
assert(cacheMode === '' || cacheMode === 'local');
149+
const resource = new PerformanceResourceTiming(
150+
requestedUrl,
151+
initiatorType,
152+
timingInfo,
153+
cacheMode,
154+
);
155+
enqueue(resource);
156+
return resource;
157+
}
158+
159+
module.exports = {
160+
PerformanceResourceTiming,
161+
markResourceTiming,
162+
};

lib/perf_hooks.js

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
} = internalBinding('performance');
1010

1111
const { PerformanceEntry } = require('internal/perf/performance_entry');
12+
const { PerformanceResourceTiming } = require('internal/perf/resource_timing');
1213
const {
1314
PerformanceObserver,
1415
PerformanceObserverEntryList,
@@ -31,6 +32,7 @@ module.exports = {
3132
PerformanceMeasure,
3233
PerformanceObserver,
3334
PerformanceObserverEntryList,
35+
PerformanceResourceTiming,
3436
monitorEventLoopDelay,
3537
createHistogram,
3638
performance: new InternalPerformance(),

test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const expectedModules = new Set([
9999
'NativeModule internal/perf/performance',
100100
'NativeModule internal/perf/timerify',
101101
'NativeModule internal/perf/usertiming',
102+
'NativeModule internal/perf/resource_timing',
102103
'NativeModule internal/perf/utils',
103104
'NativeModule internal/priority_queue',
104105
'NativeModule internal/process/esm_loader',

0 commit comments

Comments
 (0)