Skip to content

Commit 1195838

Browse files
committed
fixup! perf_hooks: web performance timeline compliance
1 parent 72b9e2f commit 1195838

File tree

5 files changed

+142
-90
lines changed

5 files changed

+142
-90
lines changed

benchmark/perf_hooks/usertiming.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,30 @@ const {
88
} = require('perf_hooks');
99

1010
const bench = common.createBenchmark(main, {
11-
n: [1e5]
11+
n: [1e5],
12+
observe: ['all', 'measure'],
1213
});
1314

1415
function test() {
1516
performance.mark('a');
16-
setImmediate(() => {
17-
performance.mark('b');
18-
performance.measure('a to b', 'a', 'b');
19-
});
17+
performance.mark('b');
18+
performance.measure('a to b', 'a', 'b');
2019
}
2120

22-
function main({ n }) {
21+
function main({ n, observe }) {
22+
performance.clearMarks();
23+
// performance.clearMeasures is not implemented on main branch.
24+
performance.clearMeasures?.();
25+
const entryTypes = observe === 'all' ?
26+
[ 'mark', 'measure' ] :
27+
[ observe ];
2328
const obs = new PerformanceObserver(() => {
2429
bench.end(n);
2530
});
26-
obs.observe({ entryTypes: ['measure'], buffered: true });
31+
obs.observe({ entryTypes, buffered: true });
2732

2833
bench.start();
29-
for (let i = 0; i < n; i++)
34+
performance.mark('start');
35+
for (let i = 0; i < 1e5; i++)
3036
test();
3137
}

lib/internal/perf/observe.js

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
ArrayPrototypePush,
1010
ArrayPrototypeSlice,
1111
ArrayPrototypeSort,
12+
Error,
1213
ObjectDefineProperties,
1314
ObjectFreeze,
1415
ObjectKeys,
@@ -32,6 +33,7 @@ const {
3233
const {
3334
InternalPerformanceEntry,
3435
isPerformanceEntry,
36+
kBufferNext,
3537
} = require('internal/perf/performance_entry');
3638

3739
const {
@@ -83,12 +85,17 @@ const kSupportedEntryTypes = ObjectFreeze([
8385
'mark',
8486
'measure',
8587
]);
86-
const kTimelineEntryTypes = ObjectFreeze([
87-
'mark',
88-
'measure',
89-
]);
9088

91-
const kPerformanceEntryBuffer = new SafeMap();
89+
// Performance timeline entry Buffers
90+
const markEntryBuffer = createBuffer();
91+
const measureEntryBuffer = createBuffer();
92+
const kMaxPerformanceEntryBuffers = 1e6;
93+
const kClearPerformanceEntryBuffers = ObjectFreeze({
94+
'mark': 'performance.clearMarks',
95+
'measure': 'performance.clearMeasures',
96+
});
97+
const kWarnedEntryTypes = new SafeMap();
98+
9299
const kObservers = new SafeSet();
93100
const kPending = new SafeSet();
94101
let isPending = false;
@@ -238,7 +245,7 @@ class PerformanceObserver {
238245
maybeIncrementObserverCount(type);
239246
if (buffered) {
240247
const entries = filterBufferMapByNameAndType(undefined, type);
241-
this[kBuffer].push(...entries);
248+
ArrayPrototypePush(this[kBuffer], ...entries);
242249
kPending.add(this);
243250
if (kPending.size)
244251
queuePending();
@@ -307,50 +314,97 @@ function enqueue(entry) {
307314
}
308315

309316
const entryType = entry.entryType;
310-
if (!kTimelineEntryTypes.includes(entryType)) {
317+
let buffer;
318+
if (entryType === 'mark') {
319+
buffer = markEntryBuffer;
320+
} else if (entryType === 'measure') {
321+
buffer = measureEntryBuffer;
322+
} else {
311323
return;
312324
}
313-
const buffer = getEntryBuffer(entryType);
314-
buffer.push(entry);
325+
326+
const count = buffer.count + 1;
327+
buffer.count = count;
328+
if (count === 1) {
329+
buffer.head = entry;
330+
buffer.tail = entry;
331+
return;
332+
}
333+
buffer.tail[kBufferNext] = entry;
334+
buffer.tail = entry;
335+
336+
if (count > kMaxPerformanceEntryBuffers &&
337+
!kWarnedEntryTypes.has(entryType)) {
338+
kWarnedEntryTypes.set(entryType, true);
339+
// No error code for this since it is a Warning
340+
// eslint-disable-next-line no-restricted-syntax
341+
const w = new Error('Possible perf_hooks memory leak detected. ' +
342+
`${count} ${entryType} entries added to the global ` +
343+
'performance entry buffer. Use ' +
344+
`${kClearPerformanceEntryBuffers[entryType]} to ` +
345+
'clear the buffer.');
346+
w.name = 'MaxPerformanceEntryBufferExceededWarning';
347+
w.entryType = entryType;
348+
w.count = count;
349+
process.emitWarning(w);
350+
}
315351
}
316352

317353
function clearEntriesFromBuffer(type, name) {
354+
let buffer;
355+
if (type === 'mark') {
356+
buffer = markEntryBuffer;
357+
} else if (type === 'measure') {
358+
buffer = measureEntryBuffer;
359+
} else {
360+
return;
361+
}
318362
if (name === undefined) {
319-
kPerformanceEntryBuffer.delete(type);
363+
resetBuffer(buffer);
320364
return;
321365
}
322-
let buffer = getEntryBuffer(type);
323-
buffer = ArrayPrototypeFilter(
324-
buffer,
325-
(entry) => entry.name !== name);
326-
kPerformanceEntryBuffer.set(type, buffer);
327-
}
328366

329-
function getEntryBuffer(type) {
330-
let buffer = kPerformanceEntryBuffer.get(type);
331-
if (buffer === undefined) {
332-
buffer = [];
333-
kPerformanceEntryBuffer.set(type, buffer);
367+
let head = null;
368+
let tail = null;
369+
for (let entry = buffer.head; entry !== null; entry = entry[kBufferNext]) {
370+
if (entry.name !== name) {
371+
head = head ?? entry;
372+
tail = entry;
373+
continue;
374+
}
375+
if (tail === null) {
376+
continue;
377+
}
378+
tail[kBufferNext] = entry[kBufferNext];
334379
}
335-
return buffer;
380+
buffer.head = head;
381+
buffer.tail = tail;
336382
}
337383

338384
function filterBufferMapByNameAndType(name, type) {
339385
let bufferList;
340-
if (type !== undefined) {
341-
bufferList = kPerformanceEntryBuffer.get(type) ?? [];
386+
if (type === 'mark') {
387+
bufferList = [markEntryBuffer];
388+
} else if (type === 'measure') {
389+
bufferList = [measureEntryBuffer];
390+
} else if (type !== undefined) {
391+
// Unrecognized type;
392+
return [];
342393
} else {
343-
bufferList = ArrayFrom(kPerformanceEntryBuffer.values());
394+
bufferList = [markEntryBuffer, measureEntryBuffer];
344395
}
345396
return ArrayPrototypeFlatMap(bufferList,
346397
(buffer) => filterBufferByName(buffer, name));
347398
}
348399

349400
function filterBufferByName(buffer, name) {
350-
if (name === undefined) {
351-
return buffer;
401+
const arr = [];
402+
for (let entry = buffer.head; entry !== null; entry = entry[kBufferNext]) {
403+
if (name === undefined || entry.name === name) {
404+
arr.push(entry);
405+
}
352406
}
353-
return ArrayPrototypeFilter(buffer, (it) => it.name === name);
407+
return arr;
354408
}
355409

356410
function observerCallback(name, type, startTime, duration, details) {
@@ -398,6 +452,20 @@ function hasObserver(type) {
398452
return observerCounts[observerType] > 0;
399453
}
400454

455+
function createBuffer() {
456+
return {
457+
head: null,
458+
tail: null,
459+
count: 0,
460+
};
461+
}
462+
463+
function resetBuffer(buffer) {
464+
buffer.head = null;
465+
buffer.tail = null;
466+
buffer.count = 0;
467+
}
468+
401469
module.exports = {
402470
PerformanceObserver,
403471
enqueue,

lib/internal/perf/performance_entry.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const kType = Symbol('kType');
1717
const kStart = Symbol('kStart');
1818
const kDuration = Symbol('kDuration');
1919
const kDetail = Symbol('kDetail');
20+
const kBufferNext = Symbol('kBufferNext');
2021

2122
function isPerformanceEntry(obj) {
2223
return obj?.[kName] !== undefined;
@@ -67,6 +68,7 @@ class InternalPerformanceEntry {
6768
this[kStart] = start;
6869
this[kDuration] = duration;
6970
this[kDetail] = detail;
71+
this[kBufferNext] = null;
7072
}
7173
}
7274

@@ -79,4 +81,5 @@ module.exports = {
7981
InternalPerformanceEntry,
8082
PerformanceEntry,
8183
isPerformanceEntry,
84+
kBufferNext,
8285
};

0 commit comments

Comments
 (0)