[RPP] Simplify RendererHandler implementation Working on merging samples with trace events I noted some opportunities to simplify: 1. Remove code we don't use. a. Styles recalc initiator calculation b. "Hot" functions calculation This is stuff we don't need at the moment. If in the future we need to implement it, I think it would be better to do it somewhere else, otherwise we risk making the renderer handler a new TimelineModel in terms of having all the core timeline code. 2. Store a reference to the event on each RendererEventNode. Instead of storing its index in the original trace event array. This way we don't have to query through multiple data structures to go from node to event. 3. Remove synthetic "totalTime" property in renderer events. It's equivalent to the already existing "dur" property. 4. Other small simplifications throughout the code. Bug: 1449191 Change-Id: Ic8274cd501d22a6a1cab3163ed7c1bb1c38d8e8b Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4596066 Reviewed-by: Jack Franklin <jacktfranklin@chromium.org> Commit-Queue: Andres Olivares <andoli@chromium.org>
diff --git a/front_end/models/trace/handlers/RendererHandler.ts b/front_end/models/trace/handlers/RendererHandler.ts index 837fd29..0b2b8a7 100644 --- a/front_end/models/trace/handlers/RendererHandler.ts +++ b/front_end/models/trace/handlers/RendererHandler.ts
@@ -6,18 +6,15 @@ import * as Helpers from '../helpers/helpers.js'; import {data as metaHandlerData} from './MetaHandler.js'; -import { - buildStackTraceAsCallFramesFromId, - data as samplesData, - getAllHotFunctionsBetweenTimestamps, -} from './SamplesHandler.js'; import {KnownEventName, KNOWN_EVENTS, type TraceEventHandlerName, HandlerState} from './types.js'; import * as Types from '../types/types.js'; const processes = new Map<Types.TraceEvents.ProcessID, RendererProcess>(); -const traceEventToNode = new Map<RendererEvent, RendererEventNode>(); -const allRendererEvents: RendererEvent[] = []; +const traceEventToNode = new Map<RendererTraceEvent, RendererEventNode>(); +const allRendererEvents: RendererTraceEvent[] = []; +let nodeIdCount = 0; +const makeRendererEventNodeId = (): RendererEventNodeId => (++nodeIdCount) as RendererEventNodeId; let handlerState = HandlerState.UNINITIALIZED; @@ -38,18 +35,14 @@ maxDepth: 0, }); -const makeEmptyRendererEventNode = (eventIndex: number): RendererEventNode => ({ - eventIndex, +const makeEmptyRendererEventNode = (event: RendererTraceEvent, id: RendererEventNodeId): RendererEventNode => ({ + event, + id, parentId: null, childrenIds: new Set(), depth: 0, }); -const makeRendererEventNodeIdGenerator = (): () => RendererEventNodeId => { - let i = 0; - return (): RendererEventNodeId => i++ as RendererEventNodeId; -}; - const getOrCreateRendererProcess = (processes: Map<Types.TraceEvents.ProcessID, RendererProcess>, pid: Types.TraceEvents.ProcessID): RendererProcess => { @@ -64,6 +57,7 @@ processes.clear(); traceEventToNode.clear(); allRendererEvents.length = 0; + nodeIdCount = -1; handlerState = HandlerState.UNINITIALIZED; } @@ -292,69 +286,46 @@ * Complexity: O(n), where n = number of events */ export function treify( - events: RendererEvent[], options: {filter: {has: (name: KnownEventName) => boolean}}): RendererEventTree { + events: RendererTraceEvent[], options: {filter: {has: (name: KnownEventName) => boolean}}): RendererEventTree { const stack = []; + // Reset the node id counter for every new renderer. + nodeIdCount = -1; const tree = makeEmptyRendererEventTree(); - const makeRendererEventNodeId = makeRendererEventNodeIdGenerator(); - let lastScheduleStyleRecalcEvent: RendererEvent|null = null; - let lastInvalidateLayout: RendererEvent|null = null; - let lastForcedStyleRecalc: RendererEvent|null = null; - for (let i = 0; i < events.length; i++) { const event = events[i]; - buildHotFunctionsStackTracesForTask(event); - - if (event.name === KnownEventName.ScheduleStyleRecalculation) { - lastScheduleStyleRecalcEvent = event; - } - if (event.name === KnownEventName.InvalidateLayout) { - lastInvalidateLayout = event; - } - if (event.name === KnownEventName.RecalculateStyles) { - lastForcedStyleRecalc = event; - } // If the current event should not be part of the tree, then simply proceed // with the next event. if (!options.filter.has(event.name as KnownEventName)) { continue; } - const duration = Types.TraceEvents.isTraceEventInstant(event) ? 0 : event.dur; + const duration = event.dur || 0; + const nodeId = makeRendererEventNodeId(); + const node = makeEmptyRendererEventNode(event, nodeId); // If the parent stack is empty, then the current event is a root. Create a // node for it, mark it as a root, then proceed with the next event. if (stack.length === 0) { - const node = makeEmptyRendererEventNode(i); - const nodeId = makeRendererEventNodeId(); tree.nodes.set(nodeId, node); tree.roots.add(nodeId); - event.totalTime = Types.Timing.MicroSeconds(duration); event.selfTime = Types.Timing.MicroSeconds(duration); - stack.push(nodeId); + stack.push(node); tree.maxDepth = Math.max(tree.maxDepth, stack.length); traceEventToNode.set(event, node); continue; } - const parentNodeId = stack[stack.length - 1]; - if (parentNodeId === undefined) { - throw new Error('Impossible: no parent node id found in the stack'); + const parentNode = stack.at(-1); + if (parentNode === undefined) { + throw new Error('Impossible: no parent node found in the stack'); } - const parentNode = tree.nodes.get(parentNodeId); - if (!parentNode) { - throw new Error('Impossible: no parent node exists for the given id'); - } - - const parentEvent = events[parentNode.eventIndex]; - if (!parentEvent) { - throw new Error('Impossible: no parent event exists for the given node'); - } + const parentEvent = parentNode.event; const begin = event.ts; const parentBegin = parentEvent.ts; - const parentDuration = Types.TraceEvents.isTraceEventInstant(parentEvent) ? 0 : parentEvent.dur; + const parentDuration = parentEvent.dur || 0; const end = begin + duration; const parentEnd = parentBegin + parentDuration; // Check the relationship between the parent event at the top of the stack, @@ -378,6 +349,8 @@ if (startsAfterParent) { stack.pop(); i--; + // The last created node has been discarded, so discard this id. + nodeIdCount--; continue; } @@ -392,105 +365,21 @@ // contained within the parent event. Create a node for the current // event, establish the parent/child relationship, then proceed with the // next event. - const node = makeEmptyRendererEventNode(i); - const nodeId = makeRendererEventNodeId(); tree.nodes.set(nodeId, node); node.depth = stack.length; - node.parentId = parentNodeId; + node.parentId = parentNode.id; parentNode.childrenIds.add(nodeId); event.selfTime = Types.Timing.MicroSeconds(duration); - event.totalTime = Types.Timing.MicroSeconds(duration); if (parentEvent.selfTime !== undefined) { - parentEvent.selfTime = Types.Timing.MicroSeconds(parentEvent.selfTime - event.totalTime); + parentEvent.selfTime = Types.Timing.MicroSeconds(parentEvent.selfTime - (event.dur || 0)); } - stack.push(nodeId); + stack.push(node); tree.maxDepth = Math.max(tree.maxDepth, stack.length); traceEventToNode.set(event, node); - // For checking if an event is a forced layout, this point in the process - // is the perfect spot. - // An event is a forced layout if it is in a subset of all events (see - // EVENTS_THAT_MAY_BE_FORCED_LAYOUTS constant) AND if its parent event is - // not a RunTask. Rather than navigate the tree and perform this check - // later on, we can do this at this point, where we already have done the - // work to calculate the event and its parent event. - checkIfEventIsForcedLayoutAndStore( - event, lastScheduleStyleRecalcEvent, lastInvalidateLayout, lastForcedStyleRecalc); } return tree; } -function checkIfEventIsForcedLayoutAndStore( - event: RendererEvent, lastScheduleStyleRecalcEvent: RendererEvent|null, lastInvalidateLayout: RendererEvent|null, - lastForcedStyleRecalc: RendererEvent|null): void { - // Forced re relayout - if (FORCED_LAYOUT_EVENT_NAMES.has(event.name as KnownEventName)) { - if (lastInvalidateLayout) { - // By default, the initiator of a forced re layout is the last InvalidateLayout event. - event.initiator = lastInvalidateLayout; - } - - // However if the last InvalidateLayout ended before the last forced styles recalc, - // set the initiator to be the last ScheduleStylesRecalc. - const lastForcedStyleEndTime = lastForcedStyleRecalc && lastForcedStyleRecalc.ts + (lastForcedStyleRecalc.dur || 0); - const hasInitiator = lastScheduleStyleRecalcEvent && lastInvalidateLayout && lastForcedStyleEndTime; - if (hasInitiator && lastForcedStyleEndTime > lastInvalidateLayout.ts) { - event.initiator = lastScheduleStyleRecalcEvent; - } - } - - // Forced styles recalc - if (FORCED_RECALC_STYLE_EVENTS.has(event.name as KnownEventName)) { - lastForcedStyleRecalc = event; - if (lastScheduleStyleRecalcEvent) { - // By default, the initiator of a forced styles recalc is the last ScheduleStylesRecalc event. - event.initiator = lastScheduleStyleRecalcEvent; - } - } -} - -function eventIsLongTask(event: RendererEvent, mustBeOnMainFrame: boolean): boolean { - if (event.name !== KnownEventName.RunTask) { - return false; - } - const eventProcess = processes.get(event.pid); - const isOnMainFrame = Boolean(eventProcess && eventProcess.isOnMainFrame); - if (!isOnMainFrame && mustBeOnMainFrame) { - return false; - } - - const errorTimeThreshold = Helpers.Timing.millisecondsToMicroseconds(Types.Timing.MilliSeconds(50)); - const eventDuration = Types.TraceEvents.isTraceEventInstant(event) ? 0 : event.dur; - return eventDuration > errorTimeThreshold; -} - -export function buildHotFunctionsStackTracesForTask(task: RendererEvent): void { - if (!eventIsLongTask(task, true)) { - // Don't spend time computing this data for tasks that aren't long. - return; - } - const {processes} = samplesData(); - const thread = processes.get(task.pid)?.threads.get(task.tid); - // This threshold is temporarily set to 0 until so that at the moment - // we want to always show what functions were executed in a long task. - // In the future however, as we provide more sophisticated details - // for long tasks, we should set the threshold to a more valuable value. - const HOT_FUNCTION_MIN_SELF_PERCENTAGE = 0; - const calls = thread?.calls; - const taskStart = task.ts; - const taskEnd = Types.Timing.MicroSeconds(task.ts + (task.dur || 0)); - const hotFunctions = - calls && getAllHotFunctionsBetweenTimestamps(calls, taskStart, taskEnd, HOT_FUNCTION_MIN_SELF_PERCENTAGE); - const tree = thread?.tree; - if (!hotFunctions || !tree) { - return; - } - // Store the top 10 hot functions. - const MAX_HOT_FUNCTION_COUNT = 10; - task.hotFunctionsStackTraces = - hotFunctions.slice(0, MAX_HOT_FUNCTION_COUNT) - .map(hotFunction => buildStackTraceAsCallFramesFromId(tree, hotFunction.stackFrame.nodeId).reverse()); -} - export const FORCED_LAYOUT_EVENT_NAMES = new Set([ KnownEventName.Layout, ]); @@ -506,8 +395,8 @@ export interface RendererHandlerData { processes: Map<Types.TraceEvents.ProcessID, RendererProcess>; - traceEventToNode: Map<RendererEvent, RendererEventNode>; - allRendererEvents: RendererEvent[]; + traceEventToNode: Map<RendererTraceEvent, RendererEventNode>; + allRendererEvents: RendererTraceEvent[]; } export interface RendererProcess { @@ -520,18 +409,18 @@ export interface RendererThread { name: string|null; - events: RendererEvent[]; + events: RendererTraceEvent[]; tree?: RendererEventTree; } interface RendererEventData { selfTime: Types.Timing.MicroSeconds; - totalTime: Types.Timing.MicroSeconds; - initiator: RendererEvent; + initiator: RendererTraceEvent; + parent?: RendererTraceEvent; hotFunctionsStackTraces: Types.TraceEvents.TraceEventCallFrame[][]; } -export type RendererEvent = Types.TraceEvents.TraceEventRendererData&Partial<RendererEventData>; +export type RendererTraceEvent = Types.TraceEvents.TraceEventRendererData&Partial<RendererEventData>; export interface RendererEventTree { nodes: Map<RendererEventNodeId, RendererEventNode>; @@ -540,8 +429,9 @@ } export interface RendererEventNode { - eventIndex: number; + event: RendererTraceEvent; depth: number; + id: RendererEventNodeId; parentId?: RendererEventNodeId|null; childrenIds: Set<RendererEventNodeId>; }
diff --git a/test/unittests/front_end/helpers/TraceHelpers.ts b/test/unittests/front_end/helpers/TraceHelpers.ts index 6fe51df..06868f8 100644 --- a/test/unittests/front_end/helpers/TraceHelpers.ts +++ b/test/unittests/front_end/helpers/TraceHelpers.ts
@@ -376,31 +376,15 @@ } /** - * Gets the event for a node from a tree in a thread. - * @see RendererHandler.ts - */ -export function getEventFor( - thread: TraceModel.Handlers.ModelHandlers.Renderer.RendererThread, - node: TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNode): - TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent { - const event = thread.events[node.eventIndex]; - if (!event) { - assert(false, `Couldn't get the event at index ${node.eventIndex} for node in thread ${thread.name}`); - return null as never; - } - return event; -} - -/** * Gets all the `events` for the `nodes` with `ids`. */ export function getEventsIn( ids: IterableIterator<TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNodeId>, nodes: Map<TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNodeId, - TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNode>, - events: TraceModel.Types.TraceEvents.TraceEventData[]): TraceModel.Types.TraceEvents.TraceEventData[] { - return [...ids].map(id => nodes.get(id)).flatMap(node => node ? [events[node.eventIndex]] : []); + TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNode>): + TraceModel.Types.TraceEvents.TraceEventData[] { + return [...ids].map(id => nodes.get(id)).flatMap(node => node ? node.event : []); } /** * Pretty-prints the tree in a thread. @@ -410,13 +394,13 @@ nodes: Set<TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNodeId>, predicate: ( node: TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNode, - event: TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent) => boolean = () => true, + event: TraceModel.Handlers.ModelHandlers.Renderer.RendererTraceEvent) => boolean = () => true, indentation: number = 2, delimiter: string = ' ', prefix: string = '-', newline: string = '\n', out: string = ''): string { let skipped = false; for (const nodeId of nodes) { const node = getNodeFor(thread, nodeId); - const event = getEventFor(thread, node); + const event = node.event; if (!predicate(node, event)) { out += `${!skipped ? newline : ''}.`; skipped = true;
diff --git a/test/unittests/front_end/models/trace/handlers/RendererHandler_test.ts b/test/unittests/front_end/models/trace/handlers/RendererHandler_test.ts index 0c7f17c..5a0ca3c 100644 --- a/test/unittests/front_end/models/trace/handlers/RendererHandler_test.ts +++ b/test/unittests/front_end/models/trace/handlers/RendererHandler_test.ts
@@ -7,11 +7,9 @@ import * as TraceModel from '../../../../../../front_end/models/trace/trace.js'; import { loadEventsFromTraceFile, - getEventFor, getEventsIn, getRootAt, makeCompleteEvent, - makeCompleteEventInMilliseconds, makeInstantEvent, prettyPrint, setTraceModelTimeout, @@ -26,6 +24,7 @@ async function handleEventsFromTraceFile( file: string, handleSamples = false): Promise<TraceModel.Handlers.ModelHandlers.Renderer.RendererHandlerData> { const traceEvents = await loadEventsFromTraceFile(file); + TraceModel.Handlers.ModelHandlers.Renderer.reset(); TraceModel.Handlers.ModelHandlers.Meta.initialize(); TraceModel.Handlers.ModelHandlers.Samples.initialize(); TraceModel.Handlers.ModelHandlers.Renderer.initialize(); @@ -180,7 +179,6 @@ assert(false, 'Main thread has no tree of events'); return; } - assert.deepEqual([...tree.roots], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18]); }); @@ -201,13 +199,13 @@ } const isRoot = (node: TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNode) => node.depth === 0; - const isInstant = (event: TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent) => + const isInstant = (event: TraceModel.Handlers.ModelHandlers.Renderer.RendererTraceEvent) => TraceModel.Types.TraceEvents.isTraceEventInstant(event); - const isLong = (event: TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent) => + const isLong = (event: TraceModel.Handlers.ModelHandlers.Renderer.RendererTraceEvent) => TraceModel.Types.TraceEvents.isTraceEventComplete(event) && event.dur > 1000; const isIncluded = (node: TraceModel.Handlers.ModelHandlers.Renderer.RendererEventNode, - event: TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent) => + event: TraceModel.Handlers.ModelHandlers.Renderer.RendererTraceEvent) => !isRoot(node) || (isInstant(event) || isLong(event)); assert.strictEqual(prettyPrint(thread, tree.roots, isIncluded), ` @@ -370,7 +368,7 @@ return; } - const event0 = getEventFor(thread, getRootAt(thread, 0)); + const event0 = getRootAt(thread, 0).event; assert.deepEqual(event0 as unknown, { 'args': {}, 'cat': 'disabled-by-default-devtools.timeline', @@ -382,11 +380,10 @@ 'tid': 1, 'ts': 643492822363, 'tts': 291450, - 'totalTime': 132, 'selfTime': 132, }); - const event1 = getEventFor(thread, getRootAt(thread, 1)); + const event1 = getRootAt(thread, 1).event; assert.deepEqual(event1 as unknown, { 'args': {}, 'cat': 'disabled-by-default-devtools.timeline', @@ -398,11 +395,10 @@ 'tid': 1, 'ts': 643492822500, 'tts': 291586, - 'totalTime': 4, 'selfTime': 4, }); - const eventLast = getEventFor(thread, getRootAt(thread, tree.roots.size - 1)); + const eventLast = getRootAt(thread, tree.roots.size - 1).event; assert.deepEqual(eventLast as unknown, { 'args': {}, 'cat': 'disabled-by-default-devtools.timeline', @@ -414,7 +410,6 @@ 'tid': 1, 'ts': 643499551460, 'tts': 949032, - 'totalTime': 67, 'selfTime': 67, }); }); @@ -435,7 +430,7 @@ return; } - const event0 = getEventFor(thread, getRootAt(thread, 0)); + const event0 = getRootAt(thread, 0).event; assert.deepEqual(event0 as unknown, { 'args': {}, 'cat': 'disabled-by-default-devtools.timeline', @@ -447,11 +442,10 @@ 'tid': 1, 'ts': 643492822099, 'tts': 62157, - 'totalTime': 130, 'selfTime': 130, }); - const event1 = getEventFor(thread, getRootAt(thread, 1)); + const event1 = getRootAt(thread, 1).event; assert.deepEqual(event1 as unknown, { 'args': {}, 'cat': 'disabled-by-default-devtools.timeline', @@ -463,11 +457,10 @@ 'tid': 1, 'ts': 643492822234, 'tts': 62291, - 'totalTime': 5, 'selfTime': 5, }); - const eventLast = getEventFor(thread, getRootAt(thread, tree.roots.size - 1)); + const eventLast = getRootAt(thread, tree.roots.size - 1).event; assert.deepEqual(eventLast as unknown, { 'args': {'data': {'type': 'unload'}}, 'cat': 'devtools.timeline', @@ -479,7 +472,6 @@ 'tid': 1, 'ts': 643494154134, 'tts': 63878, - 'totalTime': 7, 'selfTime': 7, }); }); @@ -577,7 +569,7 @@ assert.strictEqual(tree.maxDepth, 3, 'Got the correct tree max depth'); - const rootsEvents = [...tree.roots].map(id => tree.nodes.get(id)).map(n => n ? data[n.eventIndex] : null); + const rootsEvents = [...tree.roots].map(id => tree.nodes.get(id)).map(n => n ? n.event : null); assert.deepEqual(rootsEvents.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'A', 'ts': 0, 'dur': 10}, {'name': 'E', 'ts': 11, 'dur': 3}, @@ -590,13 +582,13 @@ return; } - const childrenOfA = getEventsIn(nodeA.childrenIds.values(), tree.nodes, data); + const childrenOfA = getEventsIn(nodeA.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfA.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'B', 'ts': 1, 'dur': 3}, {'name': 'D', 'ts': 5, 'dur': 3}, ]); - const childrenOfE = getEventsIn(nodeE.childrenIds.values(), tree.nodes, data); + const childrenOfE = getEventsIn(nodeE.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfE, []); const nodeB = tree.nodes.get([...nodeA.childrenIds][0]); @@ -606,12 +598,12 @@ return; } - const childrenOfB = getEventsIn(nodeB.childrenIds.values(), tree.nodes, data); + const childrenOfB = getEventsIn(nodeB.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfB.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'C', 'ts': 2, 'dur': 1}, ]); - const childrenOfD = getEventsIn(nodeD.childrenIds.values(), tree.nodes, data); + const childrenOfD = getEventsIn(nodeD.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfD, []); const nodeC = tree.nodes.get([...nodeB.childrenIds][0]); @@ -620,7 +612,7 @@ return; } - const childrenOfC = getEventsIn(nodeC.childrenIds.values(), tree.nodes, data); + const childrenOfC = getEventsIn(nodeC.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfC, []); }); @@ -644,7 +636,7 @@ assert.strictEqual(tree.maxDepth, 2, 'Got the correct tree max depth'); - const rootsEvents = [...tree.roots].map(id => tree.nodes.get(id)).map(n => n ? data[n.eventIndex] : null); + const rootsEvents = [...tree.roots].map(id => tree.nodes.get(id)).map(n => n ? n.event : null); assert.deepEqual(rootsEvents.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'A', 'ts': 0, 'dur': 10}, ]); @@ -655,7 +647,7 @@ return; } - const childrenOfA = getEventsIn(nodeA.childrenIds.values(), tree.nodes, data); + const childrenOfA = getEventsIn(nodeA.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfA.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'D', 'ts': 5, 'dur': 3}, ]); @@ -666,7 +658,7 @@ return; } - const childrenOfD = getEventsIn(nodeD.childrenIds.values(), tree.nodes, data); + const childrenOfD = getEventsIn(nodeD.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfD, []); }); @@ -689,7 +681,7 @@ assert.strictEqual(tree.maxDepth, 3, 'Got the correct tree max depth'); - const rootsEvents = [...tree.roots].map(id => tree.nodes.get(id)).map(n => n ? data[n.eventIndex] : null); + const rootsEvents = [...tree.roots].map(id => tree.nodes.get(id)).map(n => n ? n.event : null); assert.deepEqual(rootsEvents.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'A', 'ts': 0, 'dur': 10}, {'name': 'E', 'ts': 10, 'dur': 3}, @@ -702,13 +694,13 @@ return; } - const childrenOfA = getEventsIn(nodeA.childrenIds.values(), tree.nodes, data); + const childrenOfA = getEventsIn(nodeA.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfA.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'B', 'ts': 0, 'dur': 3}, {'name': 'D', 'ts': 3, 'dur': 3}, ]); - const childrenOfE = getEventsIn(nodeE.childrenIds.values(), tree.nodes, data); + const childrenOfE = getEventsIn(nodeE.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfE, []); const nodeB = tree.nodes.get([...nodeA.childrenIds][0]); @@ -718,12 +710,12 @@ return; } - const childrenOfB = getEventsIn(nodeB.childrenIds.values(), tree.nodes, data); + const childrenOfB = getEventsIn(nodeB.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfB.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'C', 'ts': 2, 'dur': 1}, ]); - const childrenOfD = getEventsIn(nodeD.childrenIds.values(), tree.nodes, data); + const childrenOfD = getEventsIn(nodeD.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfD, []); const nodeC = tree.nodes.get([...nodeB.childrenIds][0]); @@ -732,7 +724,7 @@ return; } - const childrenOfC = getEventsIn(nodeC.childrenIds.values(), tree.nodes, data); + const childrenOfC = getEventsIn(nodeC.childrenIds.values(), tree.nodes); assert.deepEqual(childrenOfC, []); }); @@ -748,7 +740,7 @@ makeCompleteEvent('D', 3, 3), // 3..6 (starts when B finishes) makeCompleteEvent('C', 2, 1), // 2..3 (finishes when B finishes) makeCompleteEvent('E', 10, 3), // 10..13 (starts when A finishes) - ] as TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent[]; + ] as TraceModel.Handlers.ModelHandlers.Renderer.RendererTraceEvent[]; TraceModel.Helpers.Trace.sortTraceEventsInPlace(data); const tree = TraceModel.Handlers.ModelHandlers.Renderer.treify(data, {filter: {has: () => true}}); @@ -759,16 +751,16 @@ assert(false, 'Root nodes were not found'); return; } - const taskA = data[nodeA.eventIndex]; - const taskE = data[nodeE.eventIndex]; + const taskA = nodeA.event; + const taskE = nodeE.event; const nodeD = tree.nodes.get([...nodeA.childrenIds][1]); const nodeB = tree.nodes.get([...nodeA.childrenIds][0]); if (!nodeB || !nodeD) { assert(false, 'Child nodes were not found'); return; } - const taskD = data[nodeD.eventIndex]; - const taskB = data[nodeB.eventIndex]; + const taskD = nodeD.event; + const taskB = nodeB.event; const nodeC = tree.nodes.get([...nodeB.childrenIds][0]); @@ -776,9 +768,9 @@ assert(false, 'Child nodes were not found'); return; } - const taskC = data[nodeC.eventIndex]; + const taskC = nodeC.event; - const taskCTotalTime = taskC.totalTime; + const taskCTotalTime = taskC.dur; if (taskCTotalTime === undefined) { assert.fail('Total time for task was not found'); return; @@ -786,7 +778,7 @@ assert.strictEqual(taskCTotalTime, TraceModel.Types.Timing.MicroSeconds(1)); assert.strictEqual(taskC.selfTime, taskCTotalTime); - const taskBTotalTime = taskB.totalTime; + const taskBTotalTime = taskB.dur; if (taskBTotalTime === undefined) { assert.fail('Total time for task was not found'); return; @@ -794,7 +786,7 @@ assert.strictEqual(taskBTotalTime, TraceModel.Types.Timing.MicroSeconds(3)); assert.strictEqual(taskB.selfTime, TraceModel.Types.Timing.MicroSeconds(taskBTotalTime - taskCTotalTime)); - const taskDTotalTime = taskD.totalTime; + const taskDTotalTime = taskD.dur; if (taskDTotalTime === undefined) { assert.fail('Total time for task was not found'); return; @@ -802,7 +794,7 @@ assert.strictEqual(taskDTotalTime, TraceModel.Types.Timing.MicroSeconds(3)); assert.strictEqual(taskD.selfTime, taskDTotalTime); - const taskATotalTime = taskA.totalTime; + const taskATotalTime = taskA.dur; if (taskATotalTime === undefined) { assert.fail('Total time for task was not found'); return; @@ -811,7 +803,7 @@ assert.strictEqual( taskA.selfTime, TraceModel.Types.Timing.MicroSeconds(taskATotalTime - taskBTotalTime - taskDTotalTime)); - const taskETotalTime = taskE.totalTime; + const taskETotalTime = taskE.dur; if (taskETotalTime === undefined) { assert.fail('Total time for task was not found'); return; @@ -820,117 +812,6 @@ assert.strictEqual(taskD.selfTime, taskETotalTime); }); - it('sets the last InvalidateLayout as the initator of a Layout event', async () => { - /** - * |---------- Task A ------------||--------- Task B -----------| - * |--SSR--| |-- IL --||-- Layout --| - * SSR = ScheduleStyleRecalculation - * RS = RecalculateStyles - * IL = InvalidateLayout - * L = Layout - */ - const data = [ - makeCompleteEventInMilliseconds('A', 0, 10_000), // 0..10 - makeCompleteEventInMilliseconds( - TraceModel.Handlers.Types.KnownEventName.ScheduleStyleRecalculation, 1_000, 2_000), // 1..3 - makeCompleteEventInMilliseconds('B', 10_000, 8_000), // 10..18 - makeCompleteEventInMilliseconds(TraceModel.Handlers.Types.KnownEventName.InvalidateLayout, 11_000, 0), // 11 - makeCompleteEventInMilliseconds(TraceModel.Handlers.Types.KnownEventName.Layout, 12_000, 5_000), // 12..17 - ] as TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent[]; - - TraceModel.Helpers.Trace.sortTraceEventsInPlace(data); - const tree = TraceModel.Handlers.ModelHandlers.Renderer.treify(data, {filter: {has: () => true}}); - - const nodeB = tree.nodes.get([...tree.roots][1]); - if (!nodeB) { - assert(false, 'Root nodes were not found'); - return; - } - - const layoutNode = tree.nodes.get([...nodeB.childrenIds][1]); - if (!layoutNode) { - assert(false, 'Child nodes were not found'); - return; - } - const layoutTask = data[layoutNode.eventIndex]; - assert.deepEqual(layoutTask.initiator, data[3]); - }); - - it('sets the last ScheduleStyleRecalculation as the initiator of a Layout event if the last InvalidateLayout was before the last RecalculateStyles', - async () => { - /** - * |---------------------------- Task A ----------------------------| - * |-- IL --| |--SSR--| |--RS--| |-- Layout --| - * SSR = ScheduleStyleRecalculation - * RS = RecalculateStyles - * IL = InvalidateLayout - * L = Layout - */ - const data = [ - makeCompleteEventInMilliseconds('A', 0, 10_000), // 0..10 - makeCompleteEventInMilliseconds(TraceModel.Handlers.Types.KnownEventName.InvalidateLayout, 1_000, 0), // 1 - makeCompleteEventInMilliseconds( - TraceModel.Handlers.Types.KnownEventName.ScheduleStyleRecalculation, 2_000, 1_000), // 2 .. 3 - makeCompleteEventInMilliseconds( - TraceModel.Handlers.Types.KnownEventName.RecalculateStyles, 4_000, 1_000), // 4 .. 5 - makeCompleteEventInMilliseconds(TraceModel.Handlers.Types.KnownEventName.Layout, 6_000, 1_000), // 6 .. 7 - ] as TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent[]; - - TraceModel.Helpers.Trace.sortTraceEventsInPlace(data); - const tree = TraceModel.Handlers.ModelHandlers.Renderer.treify(data, {filter: {has: () => true}}); - - const nodeA = tree.nodes.get([...tree.roots][0]); - if (!nodeA) { - assert(false, 'Root nodes were not found'); - return; - } - - const layoutNode = tree.nodes.get([...nodeA.childrenIds][3]); - if (!layoutNode) { - assert(false, 'Child nodes were not found'); - return; - } - const layoutTask = data[layoutNode.eventIndex]; - assert.deepEqual(layoutTask.initiator, data[2]); - }); - - it('sets the last ScheduleStyleRecalculation as the initiatior of a RecalculateStyles event', async () => { - /** - * |---------------------------- Task A ----------------------------| - * |-- IL --| |--SSR--| |--RS--| |-- Layout --| - * SSR = ScheduleStyleRecalculation - * RS = RecalculateStyles - * IL = InvalidateLayout - * L = Layout - */ - const data = [ - makeCompleteEventInMilliseconds('A', 0, 10_000), // 0..10 - makeCompleteEventInMilliseconds(TraceModel.Handlers.Types.KnownEventName.InvalidateLayout, 1_000, 0), // 1 - makeCompleteEventInMilliseconds( - TraceModel.Handlers.Types.KnownEventName.ScheduleStyleRecalculation, 2_000, 1_000), // 2 .. 3 - makeCompleteEventInMilliseconds( - TraceModel.Handlers.Types.KnownEventName.RecalculateStyles, 4_000, 1_000), // 4 .. 5 - makeCompleteEventInMilliseconds(TraceModel.Handlers.Types.KnownEventName.Layout, 6_000, 1_000), // 6 .. 7 - ] as TraceModel.Handlers.ModelHandlers.Renderer.RendererEvent[]; - - TraceModel.Helpers.Trace.sortTraceEventsInPlace(data); - const tree = TraceModel.Handlers.ModelHandlers.Renderer.treify(data, {filter: {has: () => true}}); - - const nodeA = tree.nodes.get([...tree.roots][0]); - if (!nodeA) { - assert(false, 'Root nodes were not found'); - return; - } - - const layoutNode = tree.nodes.get([...nodeA.childrenIds][2]); - if (!layoutNode) { - assert(false, 'Child nodes were not found'); - return; - } - const layoutTask = data[layoutNode.eventIndex]; - assert.deepEqual(layoutTask.initiator, data[2]); - }); - it('can sanitize multiple processes', async () => { const processes = new Map([ [ @@ -1022,13 +903,13 @@ assert.strictEqual(firstThread.tree.maxDepth, 3, 'Got the correct tree max depth for the first thread'); assert.strictEqual(secondThread.tree.maxDepth, 3, 'Got the correct tree max depth for the second thread'); - const firstRoots = getEventsIn(firstThread.tree.roots.values(), firstThread.tree.nodes, firstThread.events); + const firstRoots = getEventsIn(firstThread.tree.roots.values(), firstThread.tree.nodes); assert.deepEqual(firstRoots.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'A', 'ts': 0, 'dur': 10}, {'name': 'E', 'ts': 11, 'dur': 3}, ]); - const secondRoots = getEventsIn(secondThread.tree.roots.values(), secondThread.tree.nodes, secondThread.events); + const secondRoots = getEventsIn(secondThread.tree.roots.values(), secondThread.tree.nodes); assert.deepEqual(secondRoots.map(e => e ? {name: e.name, ts: e.ts, dur: e.dur} : null) as unknown[], [ {'name': 'F', 'ts': 0, 'dur': 3}, {'name': 'G', 'ts': 3, 'dur': 10}, @@ -1140,68 +1021,4 @@ const renderers = await handleEventsFromTraceFile('multiple-navigations-with-iframes.json.gz'); assert.strictEqual(renderers.traceEventToNode.size, 2564); }); - - describe('buildHotFunctionsStackTracesForTask', () => { - it('correctly assigns the function call stacks to a long task', async () => { - // TODO: re-write this so that we can pull out a long task without relying on Insights to get it. - const rendererData = await handleEventsFromTraceFile('slow-interaction-button-click.json.gz', true); - const longTask = rendererData.allRendererEvents.find(event => { - return event.name === 'RunTask' && event.dur === 115766; - }); - if (!longTask || !longTask.dur) { - assert.fail('Failed to find the expected long task'); - return; - } - const {processes} = TraceModel.Handlers.ModelHandlers.Samples.data(); - const thread = processes.get(longTask.pid)?.threads.get(longTask.tid); - const calls = thread?.calls; - const taskStart = longTask.ts; - const taskEnd = TraceModel.Types.Timing.MicroSeconds(longTask.ts + (longTask.dur || 0)); - if (!calls) { - assert.fail('Could not get calls for a task.'); - return; - } - const hotFunctionsFromSamples = - TraceModel.Handlers.ModelHandlers.Samples.getAllHotFunctionsBetweenTimestamps(calls, taskStart, taskEnd, 0) - .slice(0, 10); - if (!longTask.hotFunctionsStackTraces) { - assert.fail('Could not get hot functions for a task.'); - return; - } - // Compare the Samples data with the data assigned to the trace event. - assert.isNotNull(longTask.hotFunctionsStackTraces); - assert.strictEqual(longTask.hotFunctionsStackTraces.length, hotFunctionsFromSamples?.length); - assert.isAtMost(longTask.hotFunctionsStackTraces.length, 10); - - // Compare the stack trace of each hot function. - for (let i = 0; i < longTask.hotFunctionsStackTraces.length; i++) { - // The stack trace of this function. - const stackTrace = longTask.hotFunctionsStackTraces[i]; - let nodeId = hotFunctionsFromSamples[i].stackFrame.nodeId; - let callFrame = thread?.tree?.nodes.get(nodeId)?.callFrame; - if (!callFrame) { - assert.fail('Could not get a function\'s call frame.'); - return; - } - // Move up on the assigned stack trace comparing frame by frame - // with the original data. - for (let j = 0; j < stackTrace.length; j++) { - assert.strictEqual(JSON.stringify(stackTrace[j]), JSON.stringify(callFrame)); - if (j === stackTrace.length - 1) { - // We have reached the bottom of the stack trace, so there are no parent nodes left. - continue; - } - // Move up on the stack trace by getting this node's parent. - const parentId = thread?.tree?.nodes.get(nodeId)?.parentId; - const parentCallFrame = parentId && thread?.tree?.nodes.get(parentId)?.callFrame; - if (!parentCallFrame) { - assert.fail('Could not get a function\'s call frame.'); - return; - } - nodeId = parentId; - callFrame = parentCallFrame; - } - } - }); - }); });