33// found in the LICENSE file.
44
55import * as i18n from '../../../core/i18n/i18n.js' ;
6+ import * as Platform from '../../../core/platform/platform.js' ;
67import type * as Protocol from '../../../generated/protocol.js' ;
8+ import * as Extras from '../extras/extras.js' ;
79import type * as Handlers from '../handlers/handlers.js' ;
8- import type { Warning } from '../handlers/WarningsHandler.js' ;
910import * as Helpers from '../helpers/helpers.js' ;
1011import * as Types from '../types/types.js' ;
1112
1213import {
13- type BottomUpCallStack ,
14- type ForcedReflowAggregatedData ,
1514 InsightCategory ,
1615 InsightKeys ,
1716 type InsightModel ,
@@ -41,6 +40,10 @@ export const UIStrings = {
4140 * @description Text to describe the total reflow time
4241 */
4342 totalReflowTime : 'Total reflow time' ,
43+ /**
44+ * @description Text to describe CPU processor tasks that could not be attributed to any specific source code.
45+ */
46+ unattributed : 'Unattributed' ,
4447} as const ;
4548
4649const str_ = i18n . i18n . registerUIStrings ( 'models/trace/insights/ForcedReflow.ts' , UIStrings ) ;
@@ -51,49 +54,39 @@ export type ForcedReflowInsightModel = InsightModel<typeof UIStrings, {
5154 aggregatedBottomUpData : BottomUpCallStack [ ] ,
5255} > ;
5356
54- function aggregateForcedReflow (
55- data : Map < Warning , Types . Events . Event [ ] > ,
56- entryToNodeMap : Map < Types . Events . Event , Helpers . TreeHelpers . TraceEntryNode > ) :
57- [ ForcedReflowAggregatedData | undefined , BottomUpCallStack [ ] ] {
57+ export interface BottomUpCallStack {
58+ /**
59+ * `null` indicates that this data is for unattributed force reflows.
60+ */
61+ bottomUpData : Types . Events . CallFrame | Protocol . Runtime . CallFrame | null ;
62+ totalTime : number ;
63+ relatedEvents : Types . Events . Event [ ] ;
64+ }
65+
66+ export interface ForcedReflowAggregatedData {
67+ topLevelFunctionCall : Types . Events . CallFrame | Protocol . Runtime . CallFrame ;
68+ totalReflowTime : number ;
69+ topLevelFunctionCallEvents : Types . Events . Event [ ] ;
70+ }
71+
72+ function getCallFrameId ( callFrame : Types . Events . CallFrame | Protocol . Runtime . CallFrame ) : string {
73+ return callFrame . scriptId + ':' + callFrame . lineNumber + ':' + callFrame . columnNumber ;
74+ }
75+
76+ function getLargestTopLevelFunctionData (
77+ forcedReflowEvents : Types . Events . Event [ ] , traceParsedData : Handlers . Types . ParsedTrace ) : ForcedReflowAggregatedData |
78+ undefined {
79+ const entryToNodeMap = traceParsedData . Renderer . entryToNode ;
5880 const dataByTopLevelFunction = new Map < string , ForcedReflowAggregatedData > ( ) ;
59- const bottomUpDataMap = new Map < string , BottomUpCallStack > ( ) ;
60- const forcedReflowEvents = data . get ( 'FORCED_REFLOW' ) ;
61- if ( ! forcedReflowEvents || forcedReflowEvents . length === 0 ) {
62- return [ undefined , [ ] ] ;
81+ if ( forcedReflowEvents . length === 0 ) {
82+ return ;
6383 }
6484
65- forcedReflowEvents . forEach ( e => {
85+ for ( const event of forcedReflowEvents ) {
6686 // Gather the stack traces by searching in the tree
67- const traceNode = entryToNodeMap . get ( e ) ;
68-
87+ const traceNode = entryToNodeMap . get ( event ) ;
6988 if ( ! traceNode ) {
70- return ;
71- }
72- // Compute call stack fully
73- const bottomUpData : Array < Types . Events . CallFrame | Protocol . Runtime . CallFrame > = [ ] ;
74- let currentNode = traceNode ;
75- let previousNode ;
76- const childStack : Protocol . Runtime . CallFrame [ ] = [ ] ;
77-
78- // Some profileCalls maybe constructed as its children in hierarchy tree
79- while ( currentNode . children . length > 0 ) {
80- const childNode = currentNode . children [ 0 ] ;
81- if ( ! previousNode ) {
82- previousNode = childNode ;
83- }
84- const eventData = childNode . entry ;
85- if ( Types . Events . isProfileCall ( eventData ) ) {
86- childStack . push ( eventData . callFrame ) ;
87- }
88- currentNode = childNode ;
89- }
90-
91- // In order to avoid too much information, we only contain 2 levels bottomUp data,
92- while ( childStack . length > 0 && bottomUpData . length < 2 ) {
93- const traceData = childStack . pop ( ) ;
94- if ( traceData ) {
95- bottomUpData . push ( traceData ) ;
96- }
89+ continue ;
9790 }
9891
9992 let node = traceNode . parent ;
@@ -102,88 +95,43 @@ function aggregateForcedReflow(
10295 while ( node ) {
10396 const eventData = node . entry ;
10497 if ( Types . Events . isProfileCall ( eventData ) ) {
105- if ( bottomUpData . length < 2 ) {
106- bottomUpData . push ( eventData . callFrame ) ;
107- }
98+ topLevelFunctionCall = eventData . callFrame ;
99+ topLevelFunctionCallEvent = eventData ;
108100 } else {
109101 // We have finished searching bottom up data
110102 if ( Types . Events . isFunctionCall ( eventData ) && eventData . args . data &&
111103 Types . Events . objectIsCallFrame ( eventData . args . data ) ) {
112104 topLevelFunctionCall = eventData . args . data ;
113105 topLevelFunctionCallEvent = eventData ;
114- if ( bottomUpData . length === 0 ) {
115- bottomUpData . push ( topLevelFunctionCall ) ;
116- }
117- } else {
118- // Sometimes the top level task can be other JSInvocation event
119- // then we use the top level profile call as topLevelFunctionCall's data
120- const previousData = previousNode ?. entry ;
121- if ( previousData && Types . Events . isProfileCall ( previousData ) ) {
122- topLevelFunctionCall = previousData . callFrame ;
123- topLevelFunctionCallEvent = previousNode ?. entry ;
124- }
125106 }
126107 break ;
127108 }
128- previousNode = node ;
129109 node = node . parent ;
130110 }
131111
132- if ( ! topLevelFunctionCall || ! topLevelFunctionCallEvent || bottomUpData . length === 0 ) {
133- return ;
134- }
135- const bottomUpDataId =
136- bottomUpData [ 0 ] . scriptId + ':' + bottomUpData [ 0 ] . lineNumber + ':' + bottomUpData [ 0 ] . columnNumber + ':' ;
137-
138- const data = bottomUpDataMap . get ( bottomUpDataId ) ?? {
139- bottomUpData : bottomUpData [ 0 ] ,
140- totalTime : 0 ,
141- relatedEvents : [ ] ,
142- } ;
143- data . totalTime += ( e . dur ?? 0 ) ;
144- data . relatedEvents . push ( e ) ;
145- bottomUpDataMap . set ( bottomUpDataId , data ) ;
146-
147- const aggregatedDataId =
148- topLevelFunctionCall . scriptId + ':' + topLevelFunctionCall . lineNumber + ':' + topLevelFunctionCall . columnNumber ;
149- if ( ! dataByTopLevelFunction . has ( aggregatedDataId ) ) {
150- dataByTopLevelFunction . set ( aggregatedDataId , {
151- topLevelFunctionCall,
152- totalReflowTime : 0 ,
153- bottomUpData : new Set < string > ( ) ,
154- topLevelFunctionCallEvents : [ ] ,
155- } ) ;
112+ if ( ! topLevelFunctionCall || ! topLevelFunctionCallEvent ) {
113+ continue ;
156114 }
157- const aggregatedData = dataByTopLevelFunction . get ( aggregatedDataId ) ;
158- if ( aggregatedData ) {
159- aggregatedData . totalReflowTime += ( e . dur ?? 0 ) ;
160- aggregatedData . bottomUpData . add ( bottomUpDataId ) ;
161- aggregatedData . topLevelFunctionCallEvents . push ( topLevelFunctionCallEvent ) ;
162- }
163- } ) ;
164115
165- let topTimeConsumingDataId = '' ;
166- let maxTime = 0 ;
167- dataByTopLevelFunction . forEach ( ( value , key ) => {
168- if ( value . totalReflowTime > maxTime ) {
169- maxTime = value . totalReflowTime ;
170- topTimeConsumingDataId = key ;
116+ const aggregatedDataId = getCallFrameId ( topLevelFunctionCall ) ;
117+ const aggregatedData =
118+ Platform . MapUtilities . getWithDefault ( dataByTopLevelFunction , aggregatedDataId , ( ) => ( {
119+ topLevelFunctionCall,
120+ totalReflowTime : 0 ,
121+ topLevelFunctionCallEvents : [ ] ,
122+ } ) ) ;
123+ aggregatedData . totalReflowTime += ( event . dur ?? 0 ) ;
124+ aggregatedData . topLevelFunctionCallEvents . push ( topLevelFunctionCallEvent ) ;
125+ }
126+
127+ let topTimeConsumingData : ForcedReflowAggregatedData | undefined = undefined ;
128+ dataByTopLevelFunction . forEach ( data => {
129+ if ( ! topTimeConsumingData || data . totalReflowTime > topTimeConsumingData . totalReflowTime ) {
130+ topTimeConsumingData = data ;
171131 }
172132 } ) ;
173133
174- const aggregatedBottomUpData : BottomUpCallStack [ ] = [ ] ;
175- const topLevelFunctionCallData = dataByTopLevelFunction . get ( topTimeConsumingDataId ) ;
176- const dataSet = dataByTopLevelFunction . get ( topTimeConsumingDataId ) ?. bottomUpData ;
177- if ( dataSet ) {
178- dataSet . forEach ( value => {
179- const callStackData = bottomUpDataMap . get ( value ) ;
180- if ( callStackData && callStackData . totalTime > Helpers . Timing . milliToMicro ( Types . Timing . Milli ( 1 ) ) ) {
181- aggregatedBottomUpData . push ( callStackData ) ;
182- }
183- } ) ;
184- }
185-
186- return [ topLevelFunctionCallData , aggregatedBottomUpData ] ;
134+ return topTimeConsumingData ;
187135}
188136
189137function finalize ( partialModel : PartialInsightModel < ForcedReflowInsightModel > ) : ForcedReflowInsightModel {
@@ -193,32 +141,50 @@ function finalize(partialModel: PartialInsightModel<ForcedReflowInsightModel>):
193141 title : i18nString ( UIStrings . title ) ,
194142 description : i18nString ( UIStrings . description ) ,
195143 category : InsightCategory . ALL ,
196- state : partialModel . topLevelFunctionCallData !== undefined && partialModel . aggregatedBottomUpData . length !== 0 ?
197- 'fail' :
198- 'pass' ,
144+ state : partialModel . aggregatedBottomUpData . length !== 0 ? 'fail' : 'pass' ,
199145 ...partialModel ,
200146 } ;
201147}
202148
149+ function getBottomCallFrameForEvent ( event : Types . Events . Event , traceParsedData : Handlers . Types . ParsedTrace ) :
150+ Types . Events . CallFrame | Protocol . Runtime . CallFrame | null {
151+ const profileStackTrace = Extras . StackTraceForEvent . get ( event , traceParsedData ) ;
152+ const eventStackTrace = Helpers . Trace . getZeroIndexedStackTraceForEvent ( event ) ;
153+
154+ return profileStackTrace ?. callFrames [ 0 ] ?? eventStackTrace ?. [ 0 ] ?? null ;
155+ }
156+
203157export function generateInsight (
204- traceParsedData : Handlers . Types . ParsedTrace , _context : InsightSetContext ) : ForcedReflowInsightModel {
205- const warningsData = traceParsedData . Warnings ;
206- const entryToNodeMap = traceParsedData . Renderer . entryToNode ;
158+ traceParsedData : Handlers . Types . ParsedTrace , context : InsightSetContext ) : ForcedReflowInsightModel {
159+ const isWithinContext = ( event : Types . Events . Event ) : boolean => {
160+ const frameId = Helpers . Trace . frameIDForEvent ( event ) ;
161+ if ( frameId !== context . frameId ) {
162+ return false ;
163+ }
207164
208- if ( ! warningsData ) {
209- throw new Error ( 'no warnings data' ) ;
210- }
165+ return Helpers . Timing . eventIsInBounds ( event , context . bounds ) ;
166+ } ;
211167
212- if ( ! entryToNodeMap ) {
213- throw new Error ( 'no renderer data' ) ;
168+ const bottomUpDataMap = new Map < string , BottomUpCallStack > ( ) ;
169+ const events = traceParsedData . Warnings . perWarning . get ( 'FORCED_REFLOW' ) ?. filter ( isWithinContext ) ?? [ ] ;
170+ for ( const event of events ) {
171+ const bottomCallFrame = getBottomCallFrameForEvent ( event , traceParsedData ) ;
172+ const bottomCallId = bottomCallFrame ? getCallFrameId ( bottomCallFrame ) : 'UNATTRIBUTED' ;
173+ const bottomUpData =
174+ Platform . MapUtilities . getWithDefault ( bottomUpDataMap , bottomCallId , ( ) => ( {
175+ bottomUpData : bottomCallFrame ,
176+ totalTime : 0 ,
177+ relatedEvents : [ ] ,
178+ } ) ) ;
179+ bottomUpData . totalTime += event . dur ?? 0 ;
180+ bottomUpData . relatedEvents . push ( event ) ;
214181 }
215182
216- const [ topLevelFunctionCallData , aggregatedBottomUpData ] =
217- aggregateForcedReflow ( warningsData . perWarning , entryToNodeMap ) ;
183+ const topLevelFunctionCallData = getLargestTopLevelFunctionData ( events , traceParsedData ) ;
218184
219185 return finalize ( {
220- relatedEvents : topLevelFunctionCallData ?. topLevelFunctionCallEvents ,
186+ relatedEvents : events ,
221187 topLevelFunctionCallData,
222- aggregatedBottomUpData,
188+ aggregatedBottomUpData : [ ... bottomUpDataMap . values ( ) ] ,
223189 } ) ;
224190}
0 commit comments