@@ -4,10 +4,13 @@ const {
44 ArrayFrom,
55 ArrayIsArray,
66 ArrayPrototypeFilter,
7+ ArrayPrototypeFlatMap,
78 ArrayPrototypeIncludes,
89 ArrayPrototypePush,
10+ ArrayPrototypePushApply,
911 ArrayPrototypeSlice,
1012 ArrayPrototypeSort,
13+ Error,
1114 ObjectDefineProperties,
1215 ObjectFreeze,
1316 ObjectKeys,
@@ -31,6 +34,7 @@ const {
3134const {
3235 InternalPerformanceEntry,
3336 isPerformanceEntry,
37+ kBufferNext,
3438} = require ( 'internal/perf/performance_entry' ) ;
3539
3640const {
@@ -83,6 +87,16 @@ const kSupportedEntryTypes = ObjectFreeze([
8387 'measure' ,
8488] ) ;
8589
90+ // Performance timeline entry Buffers
91+ const markEntryBuffer = createBuffer ( ) ;
92+ const measureEntryBuffer = createBuffer ( ) ;
93+ const kMaxPerformanceEntryBuffers = 1e6 ;
94+ const kClearPerformanceEntryBuffers = ObjectFreeze ( {
95+ 'mark' : 'performance.clearMarks' ,
96+ 'measure' : 'performance.clearMeasures' ,
97+ } ) ;
98+ const kWarnedEntryTypes = new SafeMap ( ) ;
99+
86100const kObservers = new SafeSet ( ) ;
87101const kPending = new SafeSet ( ) ;
88102let isPending = false ;
@@ -190,6 +204,7 @@ class PerformanceObserver {
190204 const {
191205 entryTypes,
192206 type,
207+ buffered,
193208 } = { ...options } ;
194209 if ( entryTypes === undefined && type === undefined )
195210 throw new ERR_MISSING_ARGS ( 'options.entryTypes' , 'options.type' ) ;
@@ -229,6 +244,13 @@ class PerformanceObserver {
229244 return ;
230245 this [ kEntryTypes ] . add ( type ) ;
231246 maybeIncrementObserverCount ( type ) ;
247+ if ( buffered ) {
248+ const entries = filterBufferMapByNameAndType ( undefined , type ) ;
249+ ArrayPrototypePushApply ( this [ kBuffer ] , entries ) ;
250+ kPending . add ( this ) ;
251+ if ( kPending . size )
252+ queuePending ( ) ;
253+ }
232254 }
233255
234256 if ( this [ kEntryTypes ] . size )
@@ -291,6 +313,99 @@ function enqueue(entry) {
291313 for ( const obs of kObservers ) {
292314 obs [ kMaybeBuffer ] ( entry ) ;
293315 }
316+
317+ const entryType = entry . entryType ;
318+ let buffer ;
319+ if ( entryType === 'mark' ) {
320+ buffer = markEntryBuffer ;
321+ } else if ( entryType === 'measure' ) {
322+ buffer = measureEntryBuffer ;
323+ } else {
324+ return ;
325+ }
326+
327+ const count = buffer . count + 1 ;
328+ buffer . count = count ;
329+ if ( count === 1 ) {
330+ buffer . head = entry ;
331+ buffer . tail = entry ;
332+ return ;
333+ }
334+ buffer . tail [ kBufferNext ] = entry ;
335+ buffer . tail = entry ;
336+
337+ if ( count > kMaxPerformanceEntryBuffers &&
338+ ! kWarnedEntryTypes . has ( entryType ) ) {
339+ kWarnedEntryTypes . set ( entryType , true ) ;
340+ // No error code for this since it is a Warning
341+ // eslint-disable-next-line no-restricted-syntax
342+ const w = new Error ( 'Possible perf_hooks memory leak detected. ' +
343+ `${ count } ${ entryType } entries added to the global ` +
344+ 'performance entry buffer. Use ' +
345+ `${ kClearPerformanceEntryBuffers [ entryType ] } to ` +
346+ 'clear the buffer.' ) ;
347+ w . name = 'MaxPerformanceEntryBufferExceededWarning' ;
348+ w . entryType = entryType ;
349+ w . count = count ;
350+ process . emitWarning ( w ) ;
351+ }
352+ }
353+
354+ function clearEntriesFromBuffer ( type , name ) {
355+ let buffer ;
356+ if ( type === 'mark' ) {
357+ buffer = markEntryBuffer ;
358+ } else if ( type === 'measure' ) {
359+ buffer = measureEntryBuffer ;
360+ } else {
361+ return ;
362+ }
363+ if ( name === undefined ) {
364+ resetBuffer ( buffer ) ;
365+ return ;
366+ }
367+
368+ let head = null ;
369+ let tail = null ;
370+ for ( let entry = buffer . head ; entry !== null ; entry = entry [ kBufferNext ] ) {
371+ if ( entry . name !== name ) {
372+ head = head ?? entry ;
373+ tail = entry ;
374+ continue ;
375+ }
376+ if ( tail === null ) {
377+ continue ;
378+ }
379+ tail [ kBufferNext ] = entry [ kBufferNext ] ;
380+ }
381+ buffer . head = head ;
382+ buffer . tail = tail ;
383+ }
384+
385+ function filterBufferMapByNameAndType ( name , type ) {
386+ let bufferList ;
387+ if ( type === 'mark' ) {
388+ bufferList = [ markEntryBuffer ] ;
389+ } else if ( type === 'measure' ) {
390+ bufferList = [ measureEntryBuffer ] ;
391+ } else if ( type !== undefined ) {
392+ // Unrecognized type;
393+ return [ ] ;
394+ } else {
395+ bufferList = [ markEntryBuffer , measureEntryBuffer ] ;
396+ }
397+ return ArrayPrototypeFlatMap ( bufferList ,
398+ ( buffer ) => filterBufferByName ( buffer , name ) ) ;
399+ }
400+
401+ function filterBufferByName ( buffer , name ) {
402+ const arr = [ ] ;
403+ for ( let entry = buffer . head ; entry !== null ; entry = entry [ kBufferNext ] ) {
404+ if ( name === undefined || entry . name === name ) {
405+ ArrayPrototypePush ( arr , entry ) ;
406+ }
407+ }
408+ return arr ;
294409}
295410
296411function observerCallback ( name , type , startTime , duration , details ) {
@@ -338,8 +453,24 @@ function hasObserver(type) {
338453 return observerCounts [ observerType ] > 0 ;
339454}
340455
456+ function createBuffer ( ) {
457+ return {
458+ head : null ,
459+ tail : null ,
460+ count : 0 ,
461+ } ;
462+ }
463+
464+ function resetBuffer ( buffer ) {
465+ buffer . head = null ;
466+ buffer . tail = null ;
467+ buffer . count = 0 ;
468+ }
469+
341470module . exports = {
342471 PerformanceObserver,
343472 enqueue,
344473 hasObserver,
474+ clearEntriesFromBuffer,
475+ filterBufferMapByNameAndType,
345476} ;
0 commit comments