@@ -37,64 +37,102 @@ export interface TraceEntryNode {
3737 children : TraceEntryNode [ ] ;
3838}
3939
40- /** Node in a graph simplified for AI Assistance processing. The graph mirrors the TraceEntryNode one. */
40+ export interface AINodeSerialized {
41+ name : string ;
42+ dur ?: number ;
43+ self ?: number ;
44+ children ?: AINodeSerialized [ ] ;
45+ url ?: string ;
46+ selected ?: boolean ;
47+ }
48+
49+ /**
50+ * Node in a graph simplified for AI Assistance processing. The graph mirrors the TraceEntryNode one.
51+ * Huge tip of the hat to Victor Porof for prototyping this with some great work: https://crrev.com/c/5711249
52+ */
4153export class AINode {
54+ // event: Types.Events.Event; // Set in the constructor.
55+ name : string ;
56+ duration ?: Types . Timing . MilliSeconds ;
57+ selfDuration ?: Types . Timing . MilliSeconds ;
4258 id ?: TraceEntryNodeId ;
43- domain ?: string ;
44- line ?: number ;
45- column ?: number ;
46- function ?: string ;
4759 children ?: AINode [ ] ;
60+ url ?: string ;
4861 selected ?: boolean ;
4962
50- constructor (
51- public type : string , public start : Types . Timing . MilliSeconds , public end ?: Types . Timing . MilliSeconds ,
52- public totalTime ?: Types . Timing . MilliSeconds , public selfTime ?: Types . Timing . MilliSeconds ) {
53- }
63+ constructor ( public event : Types . Events . Event ) {
64+ this . name = event . name ;
65+ this . duration = event . dur === undefined ? undefined : microSecondsToMilliseconds ( event . dur ) ;
5466
55- static #fromTraceEvent( event : Types . Events . Event ) : AINode {
56- const start = microSecondsToMilliseconds ( event . ts ) ;
57- const duration = event . dur === undefined ? undefined : microSecondsToMilliseconds ( event . dur ) ;
58- const aiNode = new AINode ( event . name , start , duration ) ;
5967 if ( Types . Events . isProfileCall ( event ) ) {
60- aiNode . function = event . callFrame . functionName || '(anonymous)' ;
61- try {
62- const url = new URL ( event . callFrame . url ) ;
63- aiNode . domain = url . origin ;
64- aiNode . line = event . callFrame . lineNumber ;
65- aiNode . column = event . callFrame . columnNumber ;
66- } catch ( e ) {
67- }
68+ this . name = event . callFrame . functionName || '(anonymous)' ;
69+ this . url = event . callFrame . url ;
6870 }
69- return aiNode ;
71+ }
72+
73+ // Manually handle how nodes in this tree are serialized. We'll drop serveral properties that we don't need in the JSON string.
74+ // FYI: toJSON() is invoked implicitly via JSON.stringify()
75+ toJSON ( ) : AINodeSerialized {
76+ return {
77+ selected : this . selected ,
78+ name : this . name ,
79+ url : this . url ,
80+ // Round milliseconds because we don't need the precision
81+ dur : this . duration === undefined ? undefined : Math . round ( this . duration * 10 ) / 10 ,
82+ self : this . selfDuration === undefined ? undefined : Math . round ( this . selfDuration * 10 ) / 10 ,
83+ children : this . children ?. length ? this . children : undefined ,
84+ } ;
85+ }
86+
87+ static #fromTraceEvent( event : Types . Events . Event ) : AINode {
88+ return new AINode ( event ) ;
7089 }
7190
7291 /**
73- * Builds a AINode tree from a TraceEntryNode tree and marks the selected node.
92+ * Builds a TraceEntryNodeForAI tree from a node and marks the selected node. Primary entrypoint from EntriesFilter
7493 */
75- static #fromEntryNodeAndTree( node : TraceEntryNode , selectedNode : TraceEntryNode ) : AINode {
76- const aiNode = AINode . #fromTraceEvent( node . entry ) ;
77- aiNode . id = node . id ;
78- if ( node === selectedNode ) {
79- aiNode . selected = true ;
80- }
81- aiNode . selfTime = node . selfTime === undefined ? undefined : microSecondsToMilliseconds ( node . selfTime ) ;
82- for ( const child of node . children ) {
83- aiNode . children ??= [ ] ;
84- aiNode . children . push ( AINode . #fromEntryNodeAndTree( child , selectedNode ) ) ;
94+ static fromEntryNode ( selectedNode : TraceEntryNode , entryIsVisibleInTimeline : ( event : Types . Events . Event ) => boolean ) :
95+ AINode {
96+ /**
97+ * Builds a AINode tree from a TraceEntryNode tree and marks the selected node.
98+ */
99+ function fromEntryNodeAndTree ( node : TraceEntryNode ) : AINode {
100+ const aiNode = AINode . #fromTraceEvent( node . entry ) ;
101+ aiNode . id = node . id ;
102+ if ( node === selectedNode ) {
103+ aiNode . selected = true ;
104+ }
105+ aiNode . selfDuration = node . selfTime === undefined ? undefined : microSecondsToMilliseconds ( node . selfTime ) ;
106+ for ( const child of node . children ) {
107+ aiNode . children ??= [ ] ;
108+ aiNode . children . push ( fromEntryNodeAndTree ( child ) ) ;
109+ }
110+ return aiNode ;
85111 }
86- return aiNode ;
87- }
88112
89- static fromEntryNode ( selectedNode : TraceEntryNode ) : AINode {
90- function getRoot ( node : TraceEntryNode ) : TraceEntryNode {
91- if ( node . parent ) {
92- return getRoot ( node . parent ) ;
113+ function findTopMostVisibleAncestor ( node : TraceEntryNode ) : TraceEntryNode {
114+ const parentNodes = [ node ] ;
115+ let parent = node . parent ;
116+ while ( parent ) {
117+ parentNodes . unshift ( parent ) ;
118+ parent = parent . parent ;
93119 }
94- return node ;
120+ return parentNodes . find ( node => entryIsVisibleInTimeline ( node . entry ) ) ?? node ;
95121 }
96122
97- return AINode . #fromEntryNodeAndTree( getRoot ( selectedNode ) , selectedNode ) ;
123+ const topMostVisibleRoot = findTopMostVisibleAncestor ( selectedNode ) ;
124+ const aiNode = fromEntryNodeAndTree ( topMostVisibleRoot ) ;
125+
126+ // If our root wasn't visible, this could return an array of multiple RunTasks.
127+ // But with a visible root, we safely get back the exact same root, now with its descendent tree updated.
128+ // Filter to ensure our tree here only has "visible" entries
129+ const [ filteredAiNodeRoot ] = AINode . #filterRecursive( [ aiNode ] , node => {
130+ if ( node . event . name === 'V8.CompileCode' || node . event . name === 'UpdateCounters' ) {
131+ return false ;
132+ }
133+ return entryIsVisibleInTimeline ( node . event ) ;
134+ } ) ;
135+ return filteredAiNodeRoot ;
98136 }
99137
100138 static getSelectedNodeWithinTree ( node : AINode ) : AINode | null {
@@ -112,6 +150,59 @@ export class AINode {
112150 }
113151 return null ;
114152 }
153+
154+ static #filterRecursive( list : AINode [ ] , predicate : ( node : AINode ) => boolean ) : AINode [ ] {
155+ let done ;
156+ do {
157+ done = true ;
158+ const filtered : AINode [ ] = [ ] ;
159+ for ( const node of list ) {
160+ if ( predicate ( node ) ) {
161+ // Keep it
162+ filtered . push ( node ) ;
163+ } else if ( node . children ) {
164+ filtered . push ( ...node . children ) ;
165+ done = false ;
166+ }
167+ }
168+ list = filtered ;
169+ } while ( ! done ) ;
170+
171+ for ( const node of list ) {
172+ if ( node . children ) {
173+ node . children = AINode . #filterRecursive( node . children , predicate ) ;
174+ }
175+ }
176+ return list ;
177+ }
178+
179+ static #removeInexpensiveNodesRecursively(
180+ list : AINode [ ] ,
181+ options ?: { minDuration ?: number , minSelf ?: number , minJsDuration ?: number , minJsSelf ?: number } ) : AINode [ ] {
182+ const minDuration = options ?. minDuration ?? 0 ;
183+ const minSelf = options ?. minSelf ?? 0 ;
184+ const minJsDuration = options ?. minJsDuration ?? 0 ;
185+ const minJsSelf = options ?. minJsSelf ?? 0 ;
186+
187+ const isJS = ( node : AINode ) : boolean => Boolean ( node . url ) ;
188+ const longEnough = ( node : AINode ) : boolean =>
189+ node . duration === undefined || node . duration >= ( isJS ( node ) ? minJsDuration : minDuration ) ;
190+ const selfLongEnough = ( node : AINode ) : boolean =>
191+ node . selfDuration === undefined || node . selfDuration >= ( isJS ( node ) ? minJsSelf : minSelf ) ;
192+
193+ return AINode . #filterRecursive( list , node => longEnough ( node ) && selfLongEnough ( node ) ) ;
194+ }
195+
196+ // Invoked from DrJonesPerformanceAgent
197+ sanitize ( ) : void {
198+ if ( this . children ) {
199+ this . children = AINode . #removeInexpensiveNodesRecursively( this . children , {
200+ minDuration : Types . Timing . MilliSeconds ( 1 ) ,
201+ minJsDuration : Types . Timing . MilliSeconds ( 1 ) ,
202+ minJsSelf : Types . Timing . MilliSeconds ( 0.1 ) ,
203+ } ) ;
204+ }
205+ }
115206}
116207
117208class TraceEntryNodeIdTag {
0 commit comments