@@ -61,6 +61,7 @@ export interface ParseOptions {
6161 * @default false
6262 */
6363 isCPUProfile ?: boolean ;
64+ metadata ?: Types . File . MetaData ;
6465}
6566
6667export class TraceProcessor extends EventTarget {
@@ -173,7 +174,7 @@ export class TraceProcessor extends EventTarget {
173174 this . #status = Status . PARSING ;
174175 await this . #computeParsedTrace( traceEvents ) ;
175176 if ( this . #data && ! options . isCPUProfile ) { // We do not calculate insights for CPU Profiles.
176- this . #computeInsights( this . #data, traceEvents ) ;
177+ this . #computeInsights( this . #data, traceEvents , options ) ;
177178 }
178179 this . #status = Status . FINISHED_PARSING ;
179180 } catch ( e ) {
@@ -342,9 +343,99 @@ export class TraceProcessor extends EventTarget {
342343 return { graph, simulator, metrics} ;
343344 }
344345
345- #computeInsightSets(
346+ /**
347+ * Sort the insight models based on the impact of each insight's estimated savings, additionally weighted by the
348+ * worst metrics according to field data (if present).
349+ */
350+ #sortInsightSet(
351+ insights : Insights . Types . TraceInsightSets , insightSet : Insights . Types . InsightSet , options : ParseOptions ) : void {
352+ // The initial order of the insights is alphabetical, based on `front_end/models/trace/insights/Models.ts`.
353+ // The order here provides a baseline that groups insights in a more logical way.
354+ const baselineOrder : Record < keyof Insights . Types . InsightModels , null > = {
355+ InteractionToNextPaint : null ,
356+ LCPPhases : null ,
357+ LCPDiscovery : null ,
358+ CLSCulprits : null ,
359+ RenderBlocking : null ,
360+ ImageDelivery : null ,
361+ DocumentLatency : null ,
362+ FontDisplay : null ,
363+ Viewport : null ,
364+ DOMSize : null ,
365+ ThirdParties : null ,
366+ SlowCSSSelector : null ,
367+ } ;
368+
369+ // Determine the weights for each metric based on field data, utilizing the same scoring curve that Lighthouse uses.
370+ const weights = Insights . Common . calculateMetricWeightsForSorting ( insightSet , options . metadata ?? null ) ;
371+
372+ // Normalize the estimated savings to a single number, weighted by its relative impact
373+ // to the page experience based on the same scoring curve that Lighthouse uses.
374+ const observedLcp = Insights . Common . getLCP ( insights , insightSet . id ) ?. value ;
375+ const observedInp = Insights . Common . getINP ( insights , insightSet . id ) ?. value ;
376+ const observedCls = Insights . Common . getCLS ( insights , insightSet . id ) . value ;
377+ const observedLcpScore =
378+ observedLcp !== undefined ? Insights . Common . evaluateLCPMetricScore ( observedLcp ) : undefined ;
379+ const observedInpScore =
380+ observedInp !== undefined ? Insights . Common . evaluateINPMetricScore ( observedInp ) : undefined ;
381+ const observedClsScore = Insights . Common . evaluateCLSMetricScore ( observedCls ) ;
382+
383+ const insightToSortingRank = new Map < string , number > ( ) ;
384+ for ( const [ name , model ] of Object . entries ( insightSet . model ) ) {
385+ const lcp = model . metricSavings ?. LCP ?? 0 ;
386+ const inp = model . metricSavings ?. INP ?? 0 ;
387+ const cls = model . metricSavings ?. CLS ?? 0 ;
388+
389+ const lcpPostSavings = observedLcp !== undefined ? Math . max ( 0 , observedLcp - lcp ) : undefined ;
390+ const inpPostSavings = observedInp !== undefined ? Math . max ( 0 , observedInp - inp ) : undefined ;
391+ const clsPostSavings = observedCls !== undefined ? Math . max ( 0 , observedCls - cls ) : undefined ;
392+
393+ let score = 0 ;
394+ if ( weights . lcp && lcp && observedLcpScore !== undefined && lcpPostSavings !== undefined ) {
395+ score += weights . lcp * ( Insights . Common . evaluateLCPMetricScore ( lcpPostSavings ) - observedLcpScore ) ;
396+ }
397+ if ( weights . inp && inp && observedInpScore !== undefined && inpPostSavings !== undefined ) {
398+ score += weights . inp * ( Insights . Common . evaluateINPMetricScore ( inpPostSavings ) - observedInpScore ) ;
399+ }
400+ if ( weights . cls && cls && observedClsScore !== undefined && clsPostSavings !== undefined ) {
401+ score += weights . cls * ( Insights . Common . evaluateCLSMetricScore ( clsPostSavings ) - observedClsScore ) ;
402+ }
403+
404+ insightToSortingRank . set ( name , score ) ;
405+ }
406+
407+ // Now perform the actual sorting.
408+ const baselineOrderKeys = Object . keys ( baselineOrder ) ;
409+ const orderedKeys = Object . keys ( insightSet . model ) ;
410+ orderedKeys . sort ( ( a , b ) => {
411+ const a1 = baselineOrderKeys . indexOf ( a ) ;
412+ const b1 = baselineOrderKeys . indexOf ( b ) ;
413+ if ( a1 >= 0 && b1 >= 0 ) {
414+ return a1 - b1 ;
415+ }
416+ if ( a1 >= 0 ) {
417+ return - 1 ;
418+ }
419+ if ( b1 >= 0 ) {
420+ return 1 ;
421+ }
422+ return 0 ;
423+ } ) ;
424+ orderedKeys . sort ( ( a , b ) => ( insightToSortingRank . get ( b ) ?? 0 ) - ( insightToSortingRank . get ( a ) ?? 0 ) ) ;
425+
426+ const newModel = { } as Insights . Types . InsightModels ;
427+ for ( const key of orderedKeys as Array < keyof Insights . Types . InsightModels > ) {
428+ const model = insightSet . model [ key ] ;
429+ // @ts -expect-error Maybe someday typescript will be powerful enough to handle this.
430+ newModel [ key ] = model ;
431+ }
432+ insightSet . model = newModel ;
433+ }
434+
435+ #computeInsightSet(
346436 insights : Insights . Types . TraceInsightSets , parsedTrace : Handlers . Types . ParsedTrace ,
347- insightRunners : Partial < typeof Insights . Models > , context : Insights . Types . InsightSetContext ) : void {
437+ insightRunners : Partial < typeof Insights . Models > , context : Insights . Types . InsightSetContext ,
438+ options : ParseOptions ) : void {
348439 const model = { } as Insights . Types . InsightSet [ 'model' ] ;
349440
350441 for ( const [ name , insight ] of Object . entries ( insightRunners ) ) {
@@ -376,21 +467,24 @@ export class TraceProcessor extends EventTarget {
376467 return ;
377468 }
378469
379- const insightSets = {
470+ const insightSet : Insights . Types . InsightSet = {
380471 id,
381472 url,
382473 navigation,
383474 frameId : context . frameId ,
384475 bounds : context . bounds ,
385476 model,
386477 } ;
387- insights . set ( insightSets . id , insightSets ) ;
478+ insights . set ( insightSet . id , insightSet ) ;
479+ this . #sortInsightSet( insights , insightSet , options ) ;
388480 }
389481
390482 /**
391483 * Run all the insights and set the result to `#insights`.
392484 */
393- #computeInsights( parsedTrace : Handlers . Types . ParsedTrace , traceEvents : readonly Types . Events . Event [ ] ) : void {
485+ #computeInsights(
486+ parsedTrace : Handlers . Types . ParsedTrace , traceEvents : readonly Types . Events . Event [ ] ,
487+ options : ParseOptions ) : void {
394488 this . #insights = new Map ( ) ;
395489
396490 const enabledInsightRunners = TraceProcessor . getEnabledInsightRunners ( parsedTrace ) ;
@@ -410,15 +504,15 @@ export class TraceProcessor extends EventTarget {
410504 bounds,
411505 frameId : parsedTrace . Meta . mainFrameId ,
412506 } ;
413- this . #computeInsightSets ( this . #insights, parsedTrace , enabledInsightRunners , context ) ;
507+ this . #computeInsightSet ( this . #insights, parsedTrace , enabledInsightRunners , context , options ) ;
414508 }
415509 // If threshold is not met, then the very beginning of the trace is ignored by the insights engine.
416510 } else {
417511 const context : Insights . Types . InsightSetContext = {
418512 bounds : parsedTrace . Meta . traceBounds ,
419513 frameId : parsedTrace . Meta . mainFrameId ,
420514 } ;
421- this . #computeInsightSets ( this . #insights, parsedTrace , enabledInsightRunners , context ) ;
515+ this . #computeInsightSet ( this . #insights, parsedTrace , enabledInsightRunners , context , options ) ;
422516 }
423517
424518 // Now run the insights for each navigation in isolation.
@@ -466,7 +560,7 @@ export class TraceProcessor extends EventTarget {
466560 lantern,
467561 } ;
468562
469- this . #computeInsightSets ( this . #insights, parsedTrace , enabledInsightRunners , context ) ;
563+ this . #computeInsightSet ( this . #insights, parsedTrace , enabledInsightRunners , context , options ) ;
470564 }
471565 }
472566}
0 commit comments