Skip to content

Commit 0a47b98

Browse files
jasnellMylesBorins
authored andcommitted
perf_hooks: add warning when too many entries in the timeline
PR-URL: #18087 Reviewed-By: Matteo Collina <[email protected]>
1 parent a07cd06 commit 0a47b98

File tree

3 files changed

+88
-4
lines changed

3 files changed

+88
-4
lines changed

doc/api/perf_hooks.md

+14
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ Creates a new `PerformanceMark` entry in the Performance Timeline. A
125125
`performanceEntry.duration` is always `0`. Performance marks are used
126126
to mark specific significant moments in the Performance Timeline.
127127

128+
### performance.maxEntries
129+
<!-- YAML
130+
added: REPLACEME
131+
-->
132+
133+
Value: {number}
134+
135+
The maximum number of Performance Entry items that should be added to the
136+
Performance Timeline. This limit is not strictly enforced, but a process
137+
warning will be emitted if the number of entries in the timeline exceeds
138+
this limit.
139+
140+
Defaults to 150.
141+
128142
### performance.measure(name, startMark, endMark)
129143
<!-- YAML
130144
added: v8.5.0

lib/perf_hooks.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ const kClearEntry = Symbol('clear-entry');
5353
const kGetEntries = Symbol('get-entries');
5454
const kIndex = Symbol('index');
5555
const kMarks = Symbol('marks');
56+
const kCount = Symbol('count');
57+
const kMaxCount = Symbol('max-count');
58+
const kDefaultMaxCount = 150;
5659

5760
observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_MARK] = 1;
5861
observerCounts[NODE_PERFORMANCE_ENTRY_TYPE_MEASURE] = 1;
@@ -250,20 +253,32 @@ const nodeTiming = new PerformanceNodeTiming();
250253
// Maintains a list of entries as a linked list stored in insertion order.
251254
class PerformanceObserverEntryList {
252255
constructor() {
253-
Object.defineProperty(this, kEntries, {
254-
writable: true,
255-
enumerable: false,
256-
value: {}
256+
Object.defineProperties(this, {
257+
[kEntries]: {
258+
writable: true,
259+
enumerable: false,
260+
value: {}
261+
},
262+
[kCount]: {
263+
writable: true,
264+
enumerable: false,
265+
value: 0
266+
}
257267
});
258268
L.init(this[kEntries]);
259269
}
260270

261271
[kInsertEntry](entry) {
262272
const item = { entry };
263273
L.append(this[kEntries], item);
274+
this[kCount]++;
264275
this[kIndexEntry](item);
265276
}
266277

278+
get length() {
279+
return this[kCount];
280+
}
281+
267282
[kIndexEntry](entry) {
268283
// Default implementation does nothing
269284
}
@@ -384,9 +399,22 @@ class Performance extends PerformanceObserverEntryList {
384399
this[kIndex] = {
385400
[kMarks]: new Set()
386401
};
402+
this[kMaxCount] = kDefaultMaxCount;
387403
this[kInsertEntry](nodeTiming);
388404
}
389405

406+
set maxEntries(val) {
407+
if (typeof val !== 'number' || val >>> 0 !== val) {
408+
const errors = lazyErrors();
409+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'val', 'number');
410+
}
411+
this[kMaxCount] = Math.max(1, val >>> 0);
412+
}
413+
414+
get maxEntries() {
415+
return this[kMaxCount];
416+
}
417+
390418
[kIndexEntry](item) {
391419
const index = this[kIndex];
392420
const type = item.entry.entryType;
@@ -397,6 +425,17 @@ class Performance extends PerformanceObserverEntryList {
397425
}
398426
const entry = item.entry;
399427
L.append(items, { entry, item });
428+
const count = this[kCount];
429+
if (count > this[kMaxCount]) {
430+
const text = count === 1 ? 'is 1 entry' : `are ${count} entries`;
431+
process.emitWarning('Possible perf_hooks memory leak detected. ' +
432+
`There ${text} in the ` +
433+
'Performance Timeline. Use the clear methods ' +
434+
'to remove entries that are no longer needed or ' +
435+
'set performance.maxEntries equal to a higher ' +
436+
'value (currently the maxEntries is ' +
437+
`${this[kMaxCount]}).`);
438+
}
400439
}
401440

402441
[kClearEntry](type, name) {
@@ -411,10 +450,12 @@ class Performance extends PerformanceObserverEntryList {
411450
if (entry.name === `${name}`) {
412451
L.remove(item); // remove from the index
413452
L.remove(item.item); // remove from the master
453+
this[kCount]--;
414454
}
415455
} else {
416456
L.remove(item); // remove from the index
417457
L.remove(item.item); // remove from the master
458+
this[kCount]--;
418459
}
419460
item = next;
420461
}
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Flags: --no-warnings
2+
'use strict';
3+
4+
const common = require('../common');
5+
const { performance } = require('perf_hooks');
6+
const assert = require('assert');
7+
8+
assert.strictEqual(performance.length, 1);
9+
assert.strictEqual(performance.maxEntries, 150);
10+
11+
performance.maxEntries = 1;
12+
13+
[-1, 0xffffffff + 1, '', null, undefined, Infinity].forEach((i) => {
14+
common.expectsError(
15+
() => performance.maxEntries = i,
16+
{
17+
code: 'ERR_INVALID_ARG_TYPE',
18+
type: TypeError
19+
}
20+
);
21+
});
22+
23+
common.expectWarning('Warning', [
24+
'Possible perf_hooks memory leak detected. There are 2 entries in the ' +
25+
'Performance Timeline. Use the clear methods to remove entries that are no ' +
26+
'longer needed or set performance.maxEntries equal to a higher value ' +
27+
'(currently the maxEntries is 1).']);
28+
29+
performance.mark('test');

0 commit comments

Comments
 (0)