@@ -255,7 +255,7 @@ async function runOnce(electronPath, scenario, mockServer, verbose, runIndex, ru
255255 let extHostInspector = null ;
256256 /** @type {{ usedSize: number, totalSize: number } | null } */
257257 let extHostHeapBefore = null ;
258- /** @type {Omit<RunMetrics, 'majorGCs' | 'minorGCs' | 'gcDurationMs' | 'longTaskCount' | 'timeToUIUpdated' | 'timeToFirstToken' | 'timeToComplete' | 'instructionCollectionTime' | 'agentInvokeTime' | 'hasInternalMarks' | 'internalFirstToken'> | null } */
258+ /** @type {Omit<RunMetrics, 'majorGCs' | 'minorGCs' | 'gcDurationMs' | 'longTaskCount' | 'longAnimationFrameCount' | 'longAnimationFrameTotalMs' | ' timeToUIUpdated' | 'timeToFirstToken' | 'timeToComplete' | 'instructionCollectionTime' | 'agentInvokeTime' | 'hasInternalMarks' | 'internalFirstToken'> | null } */
259259 let partialMetrics = null ;
260260 // Timing vars hoisted for access in post-close trace parsing
261261 let submitTime = 0 ;
@@ -365,26 +365,6 @@ async function runOnce(electronPath, scenario, mockServer, verbose, runIndex, ru
365365 await cdp . send ( 'Profiler.enable' ) ;
366366 await cdp . send ( 'Profiler.start' ) ;
367367
368- // Install a PerformanceObserver for Long Animation Frames (LoAF)
369- // to capture frame-level jank that longTaskCount alone misses.
370- await window . evaluate ( ( ) => {
371- // @ts -ignore
372- globalThis . _chatLoAFEntries = [ ] ;
373- try {
374- // @ts -ignore
375- globalThis . _chatLoAFObserver = new PerformanceObserver ( ( list ) => {
376- for ( const entry of list . getEntries ( ) ) {
377- // @ts -ignore
378- globalThis . _chatLoAFEntries . push ( { duration : entry . duration , startTime : entry . startTime } ) ;
379- }
380- } ) ;
381- // @ts -ignore
382- globalThis . _chatLoAFObserver . observe ( { type : 'long-animation-frame' , buffered : false } ) ;
383- } catch {
384- // long-animation-frame not supported in this build — metrics will be 0
385- }
386- } ) ;
387-
388368 // Submit
389369 const completionsBefore = mockServer . completionCount ( ) ;
390370 submitTime = Date . now ( ) ;
@@ -505,21 +485,6 @@ async function runOnce(electronPath, scenario, mockServer, verbose, runIndex, ru
505485 console . log ( ` [debug] Client-side timing: firstResponse=${ firstResponseTime - submitTime } ms, complete=${ responseCompleteTime - submitTime } ms` ) ;
506486 }
507487
508- // Collect Long Animation Frame entries and tear down the observer
509- const loafData = await window . evaluate ( ( ) => {
510- // @ts -ignore
511- if ( globalThis . _chatLoAFObserver ) { globalThis . _chatLoAFObserver . disconnect ( ) ; }
512- // @ts -ignore
513- const entries = globalThis . _chatLoAFEntries ?? [ ] ;
514- // @ts -ignore
515- delete globalThis . _chatLoAFEntries ;
516- // @ts -ignore
517- delete globalThis . _chatLoAFObserver ;
518- const count = entries . length ;
519- const totalMs = entries . reduce ( ( /** @type {number } */ sum , /** @type {any } */ e ) => sum + e . duration , 0 ) ;
520- return { count, totalMs } ;
521- } ) ;
522-
523488 const heapAfter = /** @type {any } */ ( await cdp . send ( 'Runtime.getHeapUsage' ) ) ;
524489 const metricsAfter = await cdp . send ( 'Performance.getMetrics' ) ;
525490
@@ -617,8 +582,6 @@ async function runOnce(electronPath, scenario, mockServer, verbose, runIndex, ru
617582 layoutCount : getMetric ( metricsAfter , 'LayoutCount' ) - getMetric ( metricsBefore , 'LayoutCount' ) ,
618583 recalcStyleCount : getMetric ( metricsAfter , 'RecalcStyleCount' ) - getMetric ( metricsBefore , 'RecalcStyleCount' ) ,
619584 forcedReflowCount : getMetric ( metricsAfter , 'ForcedStyleRecalcs' ) - getMetric ( metricsBefore , 'ForcedStyleRecalcs' ) ,
620- longAnimationFrameCount : loafData . count ,
621- longAnimationFrameTotalMs : Math . round ( loafData . totalMs * 100 ) / 100 ,
622585 frameCount : getMetric ( metricsAfter , 'FrameCount' ) - getMetric ( metricsBefore , 'FrameCount' ) ,
623586 compositeLayers : getMetric ( metricsAfter , 'CompositeLayers' ) - getMetric ( metricsBefore , 'CompositeLayers' ) ,
624587 paintCount : getMetric ( metricsAfter , 'PaintCount' ) - getMetric ( metricsBefore , 'PaintCount' ) ,
@@ -693,6 +656,30 @@ async function runOnce(electronPath, scenario, mockServer, verbose, runIndex, ru
693656 if ( event . name === 'RunTask' && event . dur && event . dur > 50_000 ) { longTaskCount ++ ; }
694657 }
695658
659+ // Parse Long Animation Frame (LoAF) events from devtools.timeline trace.
660+ // AnimationFrame events use async flow pairs (ph:'s' start, ph:'f' finish)
661+ // with matching ids. Compute duration from each s→f pair.
662+ let longAnimationFrameCount = 0 ;
663+ let longAnimationFrameTotalMs = 0 ;
664+ {
665+ /** @type {Map<number, number> } */
666+ const frameStarts = new Map ( ) ;
667+ for ( const event of traceEvents ) {
668+ if ( event . cat === 'devtools.timeline' && event . name === 'AnimationFrame' ) {
669+ if ( event . ph === 's' ) {
670+ frameStarts . set ( event . id , event . ts ) ;
671+ } else if ( event . ph === 'f' && frameStarts . has ( event . id ) ) {
672+ const durationMs = ( event . ts - frameStarts . get ( event . id ) ) / 1000 ;
673+ frameStarts . delete ( event . id ) ;
674+ if ( durationMs > 50 ) {
675+ longAnimationFrameCount ++ ;
676+ longAnimationFrameTotalMs += durationMs ;
677+ }
678+ }
679+ }
680+ }
681+ }
682+
696683 return {
697684 ...partialMetrics ,
698685 timeToUIUpdated, timeToFirstToken, timeToComplete, instructionCollectionTime, agentInvokeTime,
@@ -701,6 +688,8 @@ async function runOnce(electronPath, scenario, mockServer, verbose, runIndex, ru
701688 majorGCs, minorGCs,
702689 gcDurationMs : Math . round ( gcDurationMs * 100 ) / 100 ,
703690 longTaskCount,
691+ longAnimationFrameCount,
692+ longAnimationFrameTotalMs : Math . round ( longAnimationFrameTotalMs * 100 ) / 100 ,
704693 } ;
705694}
706695
@@ -970,6 +959,7 @@ function generateCISummary(jsonReport, baseline, opts) {
970959 else if ( v . verdict === 'improved' ) { verdictDisplay = '\u2B06\uFE0F improved' ; }
971960 else if ( v . verdict === 'ok' ) { verdictDisplay = '\u2705 ok' ; }
972961 else if ( v . verdict === 'noise' ) { verdictDisplay = '\uD83C\uDF2B\uFE0F noise' ; }
962+ else if ( v . verdict === 'info' ) { verdictDisplay = '\u2139\uFE0F' ; }
973963 lines . push ( `| ${ v . metric } | ${ v . basStr } | ${ v . curStr } | ${ pct } | ${ v . pValue } | ${ verdictDisplay } |` ) ;
974964 }
975965 lines . push ( '' ) ;
0 commit comments