9
9
ArrayPrototypePush,
10
10
ArrayPrototypeSlice,
11
11
ArrayPrototypeSort,
12
+ Error,
12
13
ObjectDefineProperties,
13
14
ObjectFreeze,
14
15
ObjectKeys,
@@ -32,6 +33,7 @@ const {
32
33
const {
33
34
InternalPerformanceEntry,
34
35
isPerformanceEntry,
36
+ kBufferNext,
35
37
} = require ( 'internal/perf/performance_entry' ) ;
36
38
37
39
const {
@@ -83,12 +85,17 @@ const kSupportedEntryTypes = ObjectFreeze([
83
85
'mark' ,
84
86
'measure' ,
85
87
] ) ;
86
- const kTimelineEntryTypes = ObjectFreeze ( [
87
- 'mark' ,
88
- 'measure' ,
89
- ] ) ;
90
88
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
+
92
99
const kObservers = new SafeSet ( ) ;
93
100
const kPending = new SafeSet ( ) ;
94
101
let isPending = false ;
@@ -238,7 +245,7 @@ class PerformanceObserver {
238
245
maybeIncrementObserverCount ( type ) ;
239
246
if ( buffered ) {
240
247
const entries = filterBufferMapByNameAndType ( undefined , type ) ;
241
- this [ kBuffer ] . push ( ...entries ) ;
248
+ ArrayPrototypePush ( this [ kBuffer ] , ...entries ) ;
242
249
kPending . add ( this ) ;
243
250
if ( kPending . size )
244
251
queuePending ( ) ;
@@ -307,50 +314,97 @@ function enqueue(entry) {
307
314
}
308
315
309
316
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 {
311
323
return ;
312
324
}
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
+ }
315
351
}
316
352
317
353
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
+ }
318
362
if ( name === undefined ) {
319
- kPerformanceEntryBuffer . delete ( type ) ;
363
+ resetBuffer ( buffer ) ;
320
364
return ;
321
365
}
322
- let buffer = getEntryBuffer ( type ) ;
323
- buffer = ArrayPrototypeFilter (
324
- buffer ,
325
- ( entry ) => entry . name !== name ) ;
326
- kPerformanceEntryBuffer . set ( type , buffer ) ;
327
- }
328
366
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 ] ;
334
379
}
335
- return buffer ;
380
+ buffer . head = head ;
381
+ buffer . tail = tail ;
336
382
}
337
383
338
384
function filterBufferMapByNameAndType ( name , type ) {
339
385
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 [ ] ;
342
393
} else {
343
- bufferList = ArrayFrom ( kPerformanceEntryBuffer . values ( ) ) ;
394
+ bufferList = [ markEntryBuffer , measureEntryBuffer ] ;
344
395
}
345
396
return ArrayPrototypeFlatMap ( bufferList ,
346
397
( buffer ) => filterBufferByName ( buffer , name ) ) ;
347
398
}
348
399
349
400
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
+ }
352
406
}
353
- return ArrayPrototypeFilter ( buffer , ( it ) => it . name === name ) ;
407
+ return arr ;
354
408
}
355
409
356
410
function observerCallback ( name , type , startTime , duration , details ) {
@@ -398,6 +452,20 @@ function hasObserver(type) {
398
452
return observerCounts [ observerType ] > 0 ;
399
453
}
400
454
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
+
401
469
module . exports = {
402
470
PerformanceObserver,
403
471
enqueue,
0 commit comments