Skip to content

Commit 0e38fba

Browse files
legendecasruyadorno
authored andcommitted
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 c0b160c commit 0e38fba

10 files changed

+339
-11
lines changed

doc/api/perf_hooks.md

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

311+
### `performance.setResourceTimingBufferSize(maxSize)`
312+
313+
<!-- YAML
314+
added: REPLACEME
315+
-->
316+
317+
Sets the global performance resource timing buffer size to the specified number
318+
of "resource" type performance entry objects.
319+
320+
By default the max buffer size is set to 250.
321+
311322
### `performance.timeOrigin`
312323

313324
<!-- YAML
@@ -383,6 +394,18 @@ added: v16.1.0
383394
An object which is JSON representation of the `performance` object. It
384395
is similar to [`window.performance.toJSON`][] in browsers.
385396

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

388411
<!-- 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)