Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 798a6ed

Browse files
committedAug 21, 2022
perf_hooks: add resourcetiming buffer limit
Add WebPerf API `performance.setResourceTimingBufferSize` and event `'resourcetimingbufferfull'` support. The resource timing entries are added to the global performance timeline buffer automatically when using fetch. If users are not proactively cleaning these events, it can grow without limit. Apply the https://www.w3.org/TR/timing-entrytypes-registry/ default resource timing buffer max size so that the buffer can be limited to not grow indefinitely. PR-URL: #44220 Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent 0d46cf6 commit 798a6ed

10 files changed

+339
-11
lines changed
 

‎doc/api/perf_hooks.md

+23
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,17 @@ added: v8.5.0
312312
Returns the current high resolution millisecond timestamp, where 0 represents
313313
the start of the current `node` process.
314314

315+
### `performance.setResourceTimingBufferSize(maxSize)`
316+
317+
<!-- YAML
318+
added: REPLACEME
319+
-->
320+
321+
Sets the global performance resource timing buffer size to the specified number
322+
of "resource" type performance entry objects.
323+
324+
By default the max buffer size is set to 250.
325+
315326
### `performance.timeOrigin`
316327

317328
<!-- YAML
@@ -387,6 +398,18 @@ added: v16.1.0
387398
An object which is JSON representation of the `performance` object. It
388399
is similar to [`window.performance.toJSON`][] in browsers.
389400

401+
#### Event: `'resourcetimingbufferfull'`
402+
403+
<!-- YAML
404+
added: REPLACEME
405+
-->
406+
407+
The `'resourcetimingbufferfull'` event is fired when the global performance
408+
resource timing buffer is full. Adjust resource timing buffer size with
409+
`performance.setResourceTimingBufferSize()` or clear the buffer with
410+
`performance.clearResourceTimings()` in the event listener to allow
411+
more entries to be added to the performance timeline buffer.
412+
390413
## Class: `PerformanceEntry`
391414

392415
<!-- YAML

‎lib/internal/bootstrap/browser.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ defineOperation(globalThis, 'btoa', buffer.btoa);
7373
exposeInterface(globalThis, 'Blob', buffer.Blob);
7474

7575
// https://www.w3.org/TR/hr-time-2/#the-performance-attribute
76+
const perf_hooks = require('perf_hooks');
77+
exposeInterface(globalThis, 'Performance', perf_hooks.Performance);
7678
defineReplacableAttribute(globalThis, 'performance',
77-
require('perf_hooks').performance);
79+
perf_hooks.performance);
7880

7981
function createGlobalConsole() {
8082
const consoleFromNode =

‎lib/internal/perf/observe.js

+79-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const {
1111
ArrayPrototypeSort,
1212
ArrayPrototypeConcat,
1313
Error,
14+
MathMax,
15+
MathMin,
1416
ObjectDefineProperties,
1517
ObjectFreeze,
1618
ObjectKeys,
@@ -95,11 +97,17 @@ const kSupportedEntryTypes = ObjectFreeze([
9597
let markEntryBuffer = [];
9698
let measureEntryBuffer = [];
9799
let resourceTimingBuffer = [];
98-
const kMaxPerformanceEntryBuffers = 1e6;
100+
let resourceTimingSecondaryBuffer = [];
101+
const kPerformanceEntryBufferWarnSize = 1e6;
102+
// https://www.w3.org/TR/timing-entrytypes-registry/#registry
103+
// Default buffer limit for resource timing entries.
104+
let resourceTimingBufferSizeLimit = 250;
105+
let dispatchBufferFull;
106+
let resourceTimingBufferFullPending = false;
107+
99108
const kClearPerformanceEntryBuffers = ObjectFreeze({
100109
'mark': 'performance.clearMarks',
101110
'measure': 'performance.clearMeasures',
102-
'resource': 'performance.clearResourceTimings',
103111
});
104112
const kWarnedEntryTypes = new SafeMap();
105113

@@ -332,30 +340,38 @@ class PerformanceObserver {
332340
}
333341
}
334342

343+
/**
344+
* https://www.w3.org/TR/performance-timeline/#dfn-queue-a-performanceentry
345+
*
346+
* Add the performance entry to the interested performance observer's queue.
347+
*/
335348
function enqueue(entry) {
336349
if (!isPerformanceEntry(entry))
337350
throw new ERR_INVALID_ARG_TYPE('entry', 'PerformanceEntry', entry);
338351

339352
for (const obs of kObservers) {
340353
obs[kMaybeBuffer](entry);
341354
}
355+
}
342356

357+
/**
358+
* Add the user timing entry to the global buffer.
359+
*/
360+
function bufferUserTiming(entry) {
343361
const entryType = entry.entryType;
344362
let buffer;
345363
if (entryType === 'mark') {
346364
buffer = markEntryBuffer;
347365
} else if (entryType === 'measure') {
348366
buffer = measureEntryBuffer;
349-
} else if (entryType === 'resource') {
350-
buffer = resourceTimingBuffer;
351367
} else {
352368
return;
353369
}
354370

355371
ArrayPrototypePush(buffer, entry);
356372
const count = buffer.length;
357373

358-
if (count > kMaxPerformanceEntryBuffers &&
374+
if (count > kPerformanceEntryBufferWarnSize &&
359375
!kWarnedEntryTypes.has(entryType)) {
360376
kWarnedEntryTypes.set(entryType, true);
361377
// No error code for this since it is a Warning
@@ -372,6 +388,59 @@ function enqueue(entry) {
372388
}
373389
}
374390

391+
/**
392+
* Add the resource timing entry to the global buffer if the buffer size is not
393+
* exceeding the buffer limit, or dispatch a buffer full event on the global
394+
* performance object.
395+
*
396+
* See also https://www.w3.org/TR/resource-timing-2/#dfn-add-a-performanceresourcetiming-entry
397+
*/
398+
function bufferResourceTiming(entry) {
399+
if (resourceTimingBuffer.length < resourceTimingBufferSizeLimit && !resourceTimingBufferFullPending) {
400+
ArrayPrototypePush(resourceTimingBuffer, entry);
401+
return;
402+
}
403+
404+
if (!resourceTimingBufferFullPending) {
405+
resourceTimingBufferFullPending = true;
406+
setImmediate(() => {
407+
while (resourceTimingSecondaryBuffer.length > 0) {
408+
const excessNumberBefore = resourceTimingSecondaryBuffer.length;
409+
dispatchBufferFull('resourcetimingbufferfull');
410+
411+
// Calculate the number of items to be pushed to the global buffer.
412+
const numbersToPreserve = MathMax(
413+
MathMin(resourceTimingBufferSizeLimit - resourceTimingBuffer.length, resourceTimingSecondaryBuffer.length),
414+
0
415+
);
416+
const excessNumberAfter = resourceTimingSecondaryBuffer.length - numbersToPreserve;
417+
for (let idx = 0; idx < numbersToPreserve; idx++) {
418+
ArrayPrototypePush(resourceTimingBuffer, resourceTimingSecondaryBuffer[idx]);
419+
}
420+
421+
if (excessNumberBefore <= excessNumberAfter) {
422+
resourceTimingSecondaryBuffer = [];
423+
}
424+
}
425+
resourceTimingBufferFullPending = false;
426+
});
427+
}
428+
429+
ArrayPrototypePush(resourceTimingSecondaryBuffer, entry);
430+
}
431+
432+
// https://w3c.github.io/resource-timing/#dom-performance-setresourcetimingbuffersize
433+
function setResourceTimingBufferSize(maxSize) {
434+
// If the maxSize parameter is less than resource timing buffer current
435+
// size, no PerformanceResourceTiming objects are to be removed from the
436+
// performance entry buffer.
437+
resourceTimingBufferSizeLimit = maxSize;
438+
}
439+
440+
function setDispatchBufferFull(fn) {
441+
dispatchBufferFull = fn;
442+
}
443+
375444
function clearEntriesFromBuffer(type, name) {
376445
if (type !== 'mark' && type !== 'measure' && type !== 'resource') {
377446
return;
@@ -492,4 +561,9 @@ module.exports = {
492561
filterBufferMapByNameAndType,
493562
startPerf,
494563
stopPerf,
564+
565+
bufferUserTiming,
566+
bufferResourceTiming,
567+
setResourceTimingBufferSize,
568+
setDispatchBufferFull,
495569
};

‎lib/internal/perf/performance.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const {
1515

1616
const {
1717
EventTarget,
18+
Event,
19+
kTrustEvent,
1820
} = require('internal/event_target');
1921

2022
const { now } = require('internal/perf/utils');
@@ -29,6 +31,8 @@ const {
2931
const {
3032
clearEntriesFromBuffer,
3133
filterBufferMapByNameAndType,
34+
setResourceTimingBufferSize,
35+
setDispatchBufferFull,
3236
} = require('internal/perf/observe');
3337

3438
const { eventLoopUtilization } = require('internal/perf/event_loop_utilization');
@@ -190,6 +194,12 @@ ObjectDefineProperties(Performance.prototype, {
190194
enumerable: false,
191195
value: now,
192196
},
197+
setResourceTimingBufferSize: {
198+
__proto__: null,
199+
configurable: true,
200+
enumerable: false,
201+
value: setResourceTimingBufferSize
202+
},
193203
timerify: {
194204
__proto__: null,
195205
configurable: true,
@@ -223,7 +233,18 @@ function refreshTimeOrigin() {
223233
});
224234
}
225235

236+
const performance = new InternalPerformance();
237+
238+
function dispatchBufferFull(type) {
239+
const event = new Event(type, {
240+
[kTrustEvent]: true
241+
});
242+
performance.dispatchEvent(event);
243+
}
244+
setDispatchBufferFull(dispatchBufferFull);
245+
226246
module.exports = {
227-
InternalPerformance,
247+
Performance,
248+
performance,
228249
refreshTimeOrigin
229250
};

‎lib/internal/perf/resource_timing.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
55
const { SymbolToStringTag } = primordials;
66
const assert = require('internal/assert');
7-
const { enqueue } = require('internal/perf/observe');
7+
const { enqueue, bufferResourceTiming } = require('internal/perf/observe');
88
const { Symbol, ObjectSetPrototypeOf } = primordials;
99

1010
const kCacheMode = Symbol('kCacheMode');
@@ -174,6 +174,7 @@ function markResourceTiming(
174174

175175
ObjectSetPrototypeOf(resource, PerformanceResourceTiming.prototype);
176176
enqueue(resource);
177+
bufferResourceTiming(resource);
177178
return resource;
178179
}
179180

‎lib/internal/perf/usertiming.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {
99

1010
const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
1111
const { now } = require('internal/perf/utils');
12-
const { enqueue } = require('internal/perf/observe');
12+
const { enqueue, bufferUserTiming } = require('internal/perf/observe');
1313
const nodeTiming = require('internal/perf/nodetiming');
1414

1515
const {
@@ -97,6 +97,7 @@ class PerformanceMeasure extends InternalPerformanceEntry {
9797
function mark(name, options = kEmptyObject) {
9898
const mark = new PerformanceMark(name, options);
9999
enqueue(mark);
100+
bufferUserTiming(mark);
100101
return mark;
101102
}
102103

@@ -161,6 +162,7 @@ function measure(name, startOrMeasureOptions, endMark) {
161162
detail = detail != null ? structuredClone(detail) : null;
162163
const measure = new PerformanceMeasure(name, start, duration, detail);
163164
enqueue(measure);
165+
bufferUserTiming(measure);
164166
return measure;
165167
}
166168

‎lib/perf_hooks.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ const {
1818
PerformanceMark,
1919
PerformanceMeasure,
2020
} = require('internal/perf/usertiming');
21-
const { InternalPerformance } = require('internal/perf/performance');
21+
const {
22+
Performance,
23+
performance,
24+
} = require('internal/perf/performance');
2225

2326
const {
2427
createHistogram
@@ -27,6 +30,7 @@ const {
2730
const monitorEventLoopDelay = require('internal/perf/event_loop_delay');
2831

2932
module.exports = {
33+
Performance,
3034
PerformanceEntry,
3135
PerformanceMark,
3236
PerformanceMeasure,
@@ -35,7 +39,7 @@ module.exports = {
3539
PerformanceResourceTiming,
3640
monitorEventLoopDelay,
3741
createHistogram,
38-
performance: new InternalPerformance(),
42+
performance,
3943
};
4044

4145
ObjectDefineProperty(module.exports, 'constants', {

‎test/common/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ if (global.gc) {
289289
knownGlobals.push(global.gc);
290290
}
291291

292+
if (global.Performance) {
293+
knownGlobals.push(global.Performance);
294+
}
292295
if (global.performance) {
293296
knownGlobals.push(global.performance);
294297
}

0 commit comments

Comments
 (0)
Please sign in to comment.