UNPKG

chrome-devtools-frontend

Version:
490 lines (422 loc) • 20 kB
// Copyright 2023 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type * as Protocol from '../../../generated/protocol.js'; import * as Timeline from '../../../panels/timeline/timeline.js'; import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js'; import { getMainThread, makeCompleteEvent, makeProfileCall, } from '../../../testing/TraceHelpers.js'; import {TraceLoader} from '../../../testing/TraceLoader.js'; import * as Trace from '../trace.js'; import * as TraceTree from './TraceTree.js'; describeWithEnvironment('TraceTree', () => { describe('TopDownRootNode', () => { it('builds the root node and its children properly from an event tree', () => { // This builds the following tree: // |------------------ROOT---------------------------| // |-----A----| |-----B-----| |----------C---------| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); const eventC = makeCompleteEvent('Event C', 150_000, 100_000); const events = [ eventA, eventB, eventC, ]; const root = new TraceTree.TopDownRootNode(events, { filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const children = root.children(); assert.strictEqual(children.size, 3); const nodesIterator = children.values(); assert.strictEqual(nodesIterator.next().value!.event, eventA); assert.strictEqual(nodesIterator.next().value!.event, eventB); assert.strictEqual(nodesIterator.next().value!.event, eventC); }); it('builds a top-down tree from an event tree with multiple levels 1', () => { // This builds the following tree: // |------------ROOT-----------| // |-----A----| |-----B-----| // |-C-| |-D-| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventC = makeCompleteEvent('Event C', 0, 10_000); const eventD = makeCompleteEvent('Event D', 10_000, 10_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); // Events must be in order. const events = [ eventA, eventC, eventD, eventB, ]; const root = new TraceTree.TopDownRootNode(events, { filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const rootChildren = root.children(); assert.strictEqual(rootChildren.size, 2); const rootChildIterator = rootChildren.values(); const nodeA = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeA.event, eventA); assert.strictEqual(rootChildIterator.next().value!.event, eventB); const nodeAChildren = nodeA.children(); assert.strictEqual(nodeAChildren.size, 2); const nodeAChildIterator = nodeAChildren.values(); assert.strictEqual(nodeAChildIterator.next().value!.event, eventC); assert.strictEqual(nodeAChildIterator.next().value!.event, eventD); }); it('builds a top-down tree from an event tree with multiple levels 2', () => { // This builds the following tree: // |------------ROOT-----------| // |-----A----| |-----B-----| // |-C-| |-D-| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); const eventC = makeCompleteEvent('Event C', 50_000, 10_000); const eventD = makeCompleteEvent('Event D', 60_000, 10_000); // Events must be in order. const events = [ eventA, eventB, eventC, eventD, ]; const root = new TraceTree.TopDownRootNode(events, { filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const rootChildren = root.children(); assert.strictEqual(rootChildren.size, 2); const rootChildIterator = rootChildren.values(); assert.strictEqual(rootChildIterator.next().value!.event, eventA); const nodeB = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeB.event, eventB); const nodeBChildren = nodeB.children(); assert.strictEqual(nodeBChildren.size, 2); const nodeBChildIterator = nodeBChildren.values(); assert.strictEqual(nodeBChildIterator.next().value!.event, eventC); assert.strictEqual(nodeBChildIterator.next().value!.event, eventD); }); it('calculates the self time for each node in an event tree correctly', () => { // This builds the following tree: // |------------ROOT-----------| // |-----A----| |-------B------| // |-C-| |--D--| // |-E-| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); const eventC = makeCompleteEvent('Event C', 50_000, 10_000); const eventD = makeCompleteEvent('Event D', 60_000, 10_000); const eventE = makeCompleteEvent('Event E', 60_000, 5_000); // Events must be in order. const events = [ eventA, eventB, eventC, eventD, eventE, ]; const root = new TraceTree.TopDownRootNode(events, { filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const rootChildren = root.children(); assert.strictEqual(rootChildren.size, 2); const rootChildIterator = rootChildren.values(); assert.strictEqual(rootChildIterator.next().value!.selfTime, Trace.Helpers.Timing.microToMilli(eventA.dur)); const nodeB = rootChildIterator.next().value as TraceTree.TopDownNode; const nodeBSelfTime = Trace.Types.Timing.Micro(eventB.dur - eventC.dur - eventD.dur); assert.strictEqual(nodeB.selfTime, Trace.Helpers.Timing.microToMilli(nodeBSelfTime)); const nodeBChildren = nodeB.children(); assert.strictEqual(nodeBChildren.size, 2); const nodeBChildIterator = nodeBChildren.values(); assert.strictEqual(nodeBChildIterator.next().value!.selfTime, Trace.Helpers.Timing.microToMilli(eventC.dur)); const nodeD = nodeBChildIterator.next().value!; const nodeDSelfTime = Trace.Types.Timing.Micro(eventD.dur - eventE.dur); assert.strictEqual(nodeD.selfTime, Trace.Helpers.Timing.microToMilli(nodeDSelfTime)); const nodeDChildren = nodeD.children(); assert.strictEqual(nodeDChildren.size, 1); const nodeDChildIterator = nodeDChildren.values(); const nodeE = nodeDChildIterator.next().value!; assert.strictEqual(nodeE.selfTime, Trace.Helpers.Timing.microToMilli(eventE.dur)); }); }); describe('BottomUpRootNode', () => { it('builds the root node and its children properly from an event tree', () => { // This builds the following tree: // |------------------ROOT---------------------------| // |-----A----| |-----B-----| |----------C---------| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); const eventC = makeCompleteEvent('Event C', 150_000, 100_000); const events = [ eventA, eventB, eventC, ]; const root = new TraceTree.TopDownRootNode(events, { filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const children = root.children(); assert.strictEqual(children.size, 3); const nodesIterator = children.values(); assert.strictEqual(nodesIterator.next().value!.event, eventA); assert.strictEqual(nodesIterator.next().value!.event, eventB); assert.strictEqual(nodesIterator.next().value!.event, eventC); }); it('builds a bottom up tree from an event tree with multiple levels 1', () => { // This builds the following tree: // |------------ROOT-----------| // |-C-||-D-| // |-----A----| |-----B-----| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventC = makeCompleteEvent('Event C', 0, 10_000); const eventD = makeCompleteEvent('Event D', 10_000, 10_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); // Events must be in order. const events = [ eventA, eventC, eventD, eventB, ]; const root = new TraceTree.BottomUpRootNode(events, { textFilter: new Trace.Extras.TraceFilter.InvisibleEventsFilter([]), filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const rootChildren = root.children(); assert.strictEqual(rootChildren.size, 4); const rootChildIterator = rootChildren.values(); const nodeC = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeC.event, eventC); const nodeD = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeD.event, eventD); const nodeA = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeA.event, eventA); const nodeB = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeB.event, eventB); const nodeCChildren = nodeC.children(); assert.strictEqual(nodeCChildren.size, 1); const nodeCChildIterator = nodeCChildren.values(); assert.strictEqual(nodeCChildIterator.next().value!.event, eventA); const nodeDChildren = nodeC.children(); assert.strictEqual(nodeDChildren.size, 1); const nodeDChildIterator = nodeDChildren.values(); assert.strictEqual(nodeDChildIterator.next().value!.event, eventA); const nodeAChildren = nodeA.children(); assert.strictEqual(nodeAChildren.size, 0); const nodeBChildren = nodeB.children(); assert.strictEqual(nodeBChildren.size, 0); }); it('builds a tree from an event tree with multiple levels 2', () => { // This builds the following tree: // |------------ROOT-----------| // |-C-||-D-| // |-----A----| |-----B-----| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); const eventC = makeCompleteEvent('Event C', 50_000, 10_000); const eventD = makeCompleteEvent('Event D', 60_000, 10_000); // Events must be in order. const events = [ eventA, eventB, eventC, eventD, ]; const root = new TraceTree.BottomUpRootNode(events, { textFilter: new Trace.Extras.TraceFilter.InvisibleEventsFilter([]), filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const rootChildren = root.children(); assert.strictEqual(rootChildren.size, 4); const rootChildIterator = rootChildren.values(); const nodeA = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeA.event, eventA); const nodeC = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeC.event, eventC); const nodeD = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeD.event, eventD); const nodeB = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeB.event, eventB); const nodeCChildren = nodeC.children(); assert.strictEqual(nodeCChildren.size, 1); const nodeCChildIterator = nodeCChildren.values(); assert.strictEqual(nodeCChildIterator.next().value!.event, eventB); const nodeDChildren = nodeC.children(); assert.strictEqual(nodeDChildren.size, 1); const nodeDChildIterator = nodeDChildren.values(); assert.strictEqual(nodeDChildIterator.next().value!.event, eventB); const nodeAChildren = nodeA.children(); assert.strictEqual(nodeAChildren.size, 0); const nodeBChildren = nodeB.children(); assert.strictEqual(nodeBChildren.size, 0); }); it('calculates the self time for each node in an event tree correctly', () => { // This builds the following tree: // |------------ROOT-----------| // |-E-| // |-C-||--D--| // |-----A----| |-------B------| const eventA = makeCompleteEvent('Event A', 0, 40_000); const eventB = makeCompleteEvent('Event B', 50_000, 50_000); const eventC = makeCompleteEvent('Event C', 50_000, 10_000); const eventD = makeCompleteEvent('Event D', 60_000, 10_000); const eventE = makeCompleteEvent('Event E', 60_000, 5_000); // Events must be in order. const events = [ eventA, eventB, eventC, eventD, eventE, ]; const root = new TraceTree.BottomUpRootNode(events, { textFilter: new Trace.Extras.TraceFilter.InvisibleEventsFilter([]), filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), }); const rootChildren = root.children(); assert.strictEqual(rootChildren.size, 5); const rootChildIterator = rootChildren.values(); assert.strictEqual(rootChildIterator.next().value!.selfTime, Trace.Helpers.Timing.microToMilli(eventA.dur)); const nodeC = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeC.selfTime, Trace.Helpers.Timing.microToMilli(eventC.dur)); const nodeE = rootChildIterator.next().value as TraceTree.TopDownNode; assert.strictEqual(nodeE.selfTime, Trace.Helpers.Timing.microToMilli(eventE.dur)); const nodeD = rootChildIterator.next().value as TraceTree.TopDownNode; const nodeDSelfTime = Trace.Types.Timing.Micro(eventD.dur - eventE.dur); assert.strictEqual(nodeD.selfTime, Trace.Helpers.Timing.microToMilli(nodeDSelfTime)); const nodeB = rootChildIterator.next().value as TraceTree.TopDownNode; const nodeBSelfTime = Trace.Types.Timing.Micro(eventB.dur - eventC.dur - eventD.dur); assert.strictEqual(nodeB.selfTime, Trace.Helpers.Timing.microToMilli(nodeBSelfTime)); }); it('correctly keeps ProfileCall nodes and uses them to build up the tree', async function() { const {parsedTrace} = await TraceLoader.traceEngine(this, 'mainWasm_profile.json.gz'); const mainThread = getMainThread(parsedTrace.Renderer); const bounds = Trace.Helpers.Timing.traceWindowMilliSeconds(parsedTrace.Meta.traceBounds); // Replicate the filters as they would be when rendering in the actual panel. const textFilter = new Timeline.TimelineFilters.TimelineRegExp(); const modelFilters = [ Timeline.TimelineUIUtils.TimelineUIUtils.visibleEventsFilter(), new Trace.Extras.TraceFilter.ExclusiveNameFilter([ Trace.Types.Events.Name.RUN_TASK, ]), ]; const root = new TraceTree.BottomUpRootNode(mainThread.entries, { textFilter, filters: modelFilters, startTime: bounds.min, endTime: bounds.max, }); const rootChildren = root.children(); const values = Array.from(rootChildren.values()); // Find the list of profile calls that have been calculated as the top level rows in the Bottom Up table. const profileCalls = values .filter( node => node.event && Trace.Types.Events.isProfileCall(node.event) && node.event.callFrame.functionName.length > 0) .map(n => n.event as Trace.Types.Events.SyntheticProfileCall); const functionNames = profileCalls.map(entry => entry.callFrame.functionName); assert.deepEqual( functionNames, ['fetch', 'getTime', 'wasm-to-js::l-imports.getTime', 'mainWasm', 'js-to-wasm::i']); }); }); describe('generateEventID', () => { it('generates the right ID for new engine profile call events', async function() { const {parsedTrace} = await TraceLoader.traceEngine(this, 'react-hello-world.json.gz'); const mainThread = getMainThread(parsedTrace.Renderer); const profileCallEntry = mainThread.entries.find(entry => { return Trace.Types.Events.isProfileCall(entry) && entry.callFrame.functionName === 'performConcurrentWorkOnRoot'; }); if (!profileCallEntry) { throw new Error('Could not find a profile call'); } const eventId = TraceTree.generateEventID(profileCallEntry); assert.strictEqual(eventId, 'f:performConcurrentWorkOnRoot@7'); }); it('generates the right ID for new engine native profile call events', async function() { const {parsedTrace} = await TraceLoader.traceEngine(this, 'invalid-animation-events.json.gz', { ...Trace.Types.Configuration.defaults(), includeRuntimeCallStats: true, }); const mainThread = getMainThread(parsedTrace.Renderer); const profileCallEntry = mainThread.entries.find(entry => { return Trace.Types.Events.isProfileCall(entry) && entry.callFrame.url === 'native V8Runtime'; }); if (!profileCallEntry) { throw new Error('Could not find a profile call'); } const eventId = TraceTree.generateEventID(profileCallEntry); assert.strictEqual(eventId, 'f:Compile@0'); }); it('correctly groups events with eventGroupIdCallback when using forceGroupIdCallback', () => { // This builds the following tree: // |------------ROOT-----------| // |-----A----| |-----B-----| // |-C-| |-D-| |-E-| // Third party 1 const eventC = makeProfileCall('func', 0, 10_000); const eventD = makeCompleteEvent('event D', 10_000, 10_000); // Third party 2 const eventA = makeProfileCall('func', 0, 40_000); const eventB = makeCompleteEvent('event D', 50_000, 40_000); const eventE = makeCompleteEvent('event D', 50_000, 5_000); // Events must be in order. const events = [ eventA, eventC, eventD, eventB, eventE, ]; const root = new TraceTree.BottomUpRootNode(events, { textFilter: new Trace.Extras.TraceFilter.InvisibleEventsFilter([]), filters: [], startTime: Trace.Types.Timing.Milli(0), endTime: Trace.Types.Timing.Milli(200_000), eventGroupIdCallback: event => { if (event === eventC || event === eventD) { return 'thirdParty1'; } return 'thirdParty2'; }, forceGroupIdCallback: true, }); const rootChildren = root.children(); // 2 top nodes for each third party assert.strictEqual(rootChildren.size, 2); const children = Array.from(rootChildren.values()) as TraceTree.BottomUpNode[]; const first = children[0]; const second = children[1]; assert.strictEqual(first.id, 'thirdParty1'); assert.lengthOf(first.events, 2); assert.strictEqual(second.id, 'thirdParty2'); assert.lengthOf(second.events, 3); }); }); describe('eventStackFrame', () => { it('extracts the stackFrame for ProfileCalls', async function() { const event = makeProfileCall('somefunc', 100, 10, undefined, undefined, undefined, 'https://x.com/file.mjs'); const stackFrame = TraceTree.eventStackFrame(event) as Protocol.Runtime.CallFrame; assert.strictEqual(stackFrame.functionName, 'somefunc'); assert.strictEqual(stackFrame.url, 'https://x.com/file.mjs'); }); }); });