chrome-devtools-frontend
Version:
Chrome DevTools UI
814 lines (755 loc) • 26.2 kB
text/typescript
// Copyright 2022 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 * as SDK from '../core/sdk/sdk.js';
import type * as Protocol from '../generated/protocol.js';
import * as Bindings from '../models/bindings/bindings.js';
import * as CPUProfile from '../models/cpu_profile/cpu_profile.js';
import * as Trace from '../models/trace/trace.js';
import * as Workspace from '../models/workspace/workspace.js';
import * as Timeline from '../panels/timeline/timeline.js';
import * as PerfUI from '../ui/legacy/components/perf_ui/perf_ui.js';
import {initializeGlobalVars} from './EnvironmentHelpers.js';
import {TraceLoader} from './TraceLoader.js';
// This mock class is used for instancing a flame chart in the helpers.
// Its implementation is empty because the methods aren't used by the
// helpers, only the mere definition.
export class MockFlameChartDelegate implements PerfUI.FlameChart.FlameChartDelegate {
windowChanged(_startTime: number, _endTime: number, _animate: boolean): void {
}
updateRangeSelection(_startTime: number, _endTime: number): void {
}
updateSelectedGroup(_flameChart: PerfUI.FlameChart.FlameChart, _group: PerfUI.FlameChart.Group|null): void {
}
}
/**
* Draws a set of tracks track in the flame chart using the new system.
* For this to work, every track that will be rendered must have a
* corresponding track appender registered in the
* CompatibilityTracksAppender.
*
* @param traceFileName The name of the trace file to be loaded into the
* flame chart.
* @param trackAppenderNames A Set with the names of the tracks to be
* rendered. For example, Set("Timings").
* @param expanded whether the track should be expanded
* @param trackName optional param to filter tracks by their name.
* @returns a flame chart element and its corresponding data provider.
*/
export async function getMainFlameChartWithTracks(
traceFileName: string, trackAppenderNames: Set<Timeline.CompatibilityTracksAppender.TrackAppenderName>,
expanded: boolean, trackName?: string): Promise<{
flameChart: PerfUI.FlameChart.FlameChart,
dataProvider: Timeline.TimelineFlameChartDataProvider.TimelineFlameChartDataProvider,
}> {
await initializeGlobalVars();
// This function is used to load a component example.
const {parsedTrace} = await TraceLoader.traceEngine(/* context= */ null, traceFileName);
const dataProvider = new Timeline.TimelineFlameChartDataProvider.TimelineFlameChartDataProvider();
// The data provider still needs a reference to the legacy model to
// work properly.
dataProvider.setModel(parsedTrace);
const tracksAppender = dataProvider.compatibilityTracksAppenderInstance();
tracksAppender.setVisibleTracks(trackAppenderNames);
dataProvider.buildFromTrackAppendersForTest(
{filterThreadsByName: trackName, expandedTracks: expanded ? trackAppenderNames : undefined});
const delegate = new MockFlameChartDelegate();
const flameChart = new PerfUI.FlameChart.FlameChart(dataProvider, delegate);
const minTime = Trace.Helpers.Timing.microToMilli(parsedTrace.Meta.traceBounds.min);
const maxTime = Trace.Helpers.Timing.microToMilli(parsedTrace.Meta.traceBounds.max);
flameChart.setWindowTimes(minTime, maxTime);
flameChart.markAsRoot();
flameChart.update();
return {flameChart, dataProvider};
}
/**
* Draws the network track in the flame chart using the legacy system.
*
* @param traceFileName The name of the trace file to be loaded to the flame
* chart.
* @param expanded if the track is expanded
* @returns a flame chart element and its corresponding data provider.
*/
export async function getNetworkFlameChart(traceFileName: string, expanded: boolean): Promise<{
flameChart: PerfUI.FlameChart.FlameChart,
dataProvider: Timeline.TimelineFlameChartNetworkDataProvider.TimelineFlameChartNetworkDataProvider,
}> {
await initializeGlobalVars();
const {parsedTrace} = await TraceLoader.traceEngine(/* context= */ null, traceFileName);
const minTime = Trace.Helpers.Timing.microToMilli(parsedTrace.Meta.traceBounds.min);
const maxTime = Trace.Helpers.Timing.microToMilli(parsedTrace.Meta.traceBounds.max);
const dataProvider = new Timeline.TimelineFlameChartNetworkDataProvider.TimelineFlameChartNetworkDataProvider();
dataProvider.setModel(parsedTrace);
dataProvider.setWindowTimes(minTime, maxTime);
dataProvider.timelineData().groups.forEach(group => {
group.expanded = expanded;
});
const delegate = new MockFlameChartDelegate();
const flameChart = new PerfUI.FlameChart.FlameChart(dataProvider, delegate);
flameChart.setWindowTimes(minTime, maxTime);
flameChart.markAsRoot();
flameChart.update();
return {flameChart, dataProvider};
}
// We create here a cross-test base trace event. It is assumed that each
// test will import this default event and copy-override properties at will.
export const defaultTraceEvent: Trace.Types.Events.Event = {
name: 'process_name',
tid: Trace.Types.Events.ThreadID(0),
pid: Trace.Types.Events.ProcessID(0),
ts: Trace.Types.Timing.Micro(0),
cat: 'test',
ph: Trace.Types.Events.Phase.METADATA,
};
/**
* Gets the tree in a thread.
* @see RendererHandler.ts
*/
export function getTree(thread: Trace.Handlers.ModelHandlers.Renderer.RendererThread):
Trace.Helpers.TreeHelpers.TraceEntryTree {
const tree = thread.tree;
if (!tree) {
assert(false, `Couldn't get tree in thread ${thread.name}`);
}
return tree;
}
/**
* Gets the n-th root from a tree in a thread.
* @see RendererHandler.ts
*/
export function getRootAt(thread: Trace.Handlers.ModelHandlers.Renderer.RendererThread, index: number):
Trace.Helpers.TreeHelpers.TraceEntryNode {
const tree = getTree(thread);
const node = [...tree.roots][index];
if (node === undefined) {
assert(false, `Couldn't get the id of the root at index ${index} in thread ${thread.name}`);
}
return node;
}
/**
* Gets all nodes in a thread. To finish this task, we Walk through all the nodes, starting from the root node.
*/
export function getAllNodes(roots: Set<Trace.Helpers.TreeHelpers.TraceEntryNode>):
Trace.Helpers.TreeHelpers.TraceEntryNode[] {
const allNodes: Trace.Helpers.TreeHelpers.TraceEntryNode[] = [];
const children: Trace.Helpers.TreeHelpers.TraceEntryNode[] = Array.from(roots);
while (children.length > 0) {
const childNode = children.shift();
if (childNode) {
allNodes.push(childNode);
children.push(...childNode.children);
}
}
return allNodes;
}
/**
* Gets the node with an id from a tree in a thread.
* @see RendererHandler.ts
*/
export function getNodeFor(
thread: Trace.Handlers.ModelHandlers.Renderer.RendererThread,
nodeId: Trace.Helpers.TreeHelpers.TraceEntryNodeId): Trace.Helpers.TreeHelpers.TraceEntryNode {
const tree = getTree(thread);
function findNode(
nodes: Set<Trace.Helpers.TreeHelpers.TraceEntryNode>|Trace.Helpers.TreeHelpers.TraceEntryNode[],
nodeId: Trace.Helpers.TreeHelpers.TraceEntryNodeId): Trace.Helpers.TreeHelpers.TraceEntryNode|undefined {
for (const node of nodes) {
const event = node.entry;
if (Trace.Types.Events.isProfileCall(event) && event.nodeId === nodeId) {
return node;
}
return findNode(node.children, nodeId);
}
return undefined;
}
const node = findNode(tree.roots, nodeId);
if (!node) {
assert(false, `Couldn't get the node with id ${nodeId} in thread ${thread.name}`);
}
return node;
}
/**
* Gets all the `events` for the `nodes`.
*/
export function getEventsIn(nodes: IterableIterator<Trace.Helpers.TreeHelpers.TraceEntryNode>):
Trace.Types.Events.Event[] {
return [...nodes].flatMap(node => node ? node.entry : []);
}
/**
* Pretty-prints a tree.
*/
export function prettyPrint(
tree: Trace.Helpers.TreeHelpers.TraceEntryTree,
predicate: (node: Trace.Helpers.TreeHelpers.TraceEntryNode, event: Trace.Types.Events.Event) => boolean = () =>
true,
indentation: number = 2, delimiter: string = ' ', prefix: string = '-', newline: string = '\n',
out: string = ''): string {
let skipped = false;
return printNodes(tree.roots);
function printNodes(nodes: Set<Trace.Helpers.TreeHelpers.TraceEntryNode>|
Trace.Helpers.TreeHelpers.TraceEntryNode[]): string {
for (const node of nodes) {
const event = node.entry;
if (!predicate(node, event)) {
out += `${!skipped ? newline : ''}.`;
skipped = true;
continue;
}
skipped = false;
const spacing = new Array(node.depth * indentation).fill(delimiter).join('');
const eventType = Trace.Types.Events.isDispatch(event) ? `(${event.args.data?.type})` : false;
const jsFunctionName =
Trace.Types.Events.isProfileCall(event) ? `(${event.callFrame.functionName || 'anonymous'})` : false;
const duration = `[${(event.dur || 0) / 1000}ms]`;
const info = [jsFunctionName, eventType, duration].filter(Boolean);
out += `${newline}${spacing}${prefix}${event.name} ${info.join(' ')}`;
out = printNodes(node.children);
}
return out;
}
}
/**
* Builds a mock Complete.
*/
export function makeCompleteEvent(
name: string, ts: number, dur: number, cat: string = '*', pid: number = 0,
tid: number = 0): Trace.Types.Events.Complete {
return {
args: {},
cat,
name,
ph: Trace.Types.Events.Phase.COMPLETE,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(ts),
dur: Trace.Types.Timing.Micro(dur),
};
}
export function makeAsyncStartEvent(
name: string,
ts: number,
pid: number = 0,
tid: number = 0,
): Trace.Types.Events.Async {
return {
args: {},
cat: '*',
name,
ph: Trace.Types.Events.Phase.ASYNC_NESTABLE_START,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(ts),
};
}
export function makeAsyncEndEvent(
name: string,
ts: number,
pid: number = 0,
tid: number = 0,
): Trace.Types.Events.Async {
return {
args: {},
cat: '*',
name,
ph: Trace.Types.Events.Phase.ASYNC_NESTABLE_END,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(ts),
};
}
/**
* Builds a mock flow phase event.
*/
export function makeFlowPhaseEvent(
name: string, ts: number, cat: string = '*',
ph: Trace.Types.Events.Phase.FLOW_START|Trace.Types.Events.Phase.FLOW_END|Trace.Types.Events.Phase.FLOW_STEP,
id: number = 0, pid: number = 0, tid: number = 0): Trace.Types.Events.FlowEvent {
return {
args: {},
cat,
name,
id,
ph,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(ts),
dur: Trace.Types.Timing.Micro(0),
};
}
/**
* Builds flow phase events for a list of events belonging to the same
* flow. `events` must be ordered.
*/
export function makeFlowEvents(events: Trace.Types.Events.Event[], flowId: number = 0): Trace.Types.Events.FlowEvent[] {
const lastEvent = events.at(-1);
const firstEvent = events.at(0);
if (!lastEvent || !firstEvent) {
return [];
}
const flowName = events[0].name;
const flowStart = makeFlowPhaseEvent(
flowName, firstEvent.ts, firstEvent.cat, Trace.Types.Events.Phase.FLOW_START, flowId, firstEvent.pid,
firstEvent.tid);
const flowEnd = makeFlowPhaseEvent(
flowName, lastEvent.ts, lastEvent.cat, Trace.Types.Events.Phase.FLOW_END, flowId, lastEvent.pid, lastEvent.tid);
const flowSteps: Trace.Types.Events.FlowEvent[] = [];
for (let i = 1; i < events.length - 1; i++) {
flowSteps.push(makeFlowPhaseEvent(
flowName, events[i].ts, events[i].cat, Trace.Types.Events.Phase.FLOW_STEP, flowId, events[i].pid,
events[i].tid));
}
return [flowStart, ...flowSteps, flowEnd];
}
/**
* Builds a mock Instant.
*/
export function makeInstantEvent(
name: string, tsMicroseconds: number, cat: string = '', pid: number = 0, tid: number = 0,
s: Trace.Types.Events.Scope = Trace.Types.Events.Scope.THREAD): Trace.Types.Events.Instant {
return {
args: {},
cat,
name,
ph: Trace.Types.Events.Phase.INSTANT,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(tsMicroseconds),
s,
};
}
/**
* Builds a mock Begin.
*/
export function makeBeginEvent(
name: string, ts: number, cat: string = '*', pid: number = 0, tid: number = 0): Trace.Types.Events.Begin {
return {
args: {},
cat,
name,
ph: Trace.Types.Events.Phase.BEGIN,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(ts),
};
}
/**
* Builds a mock End.
*/
export function makeEndEvent(
name: string, ts: number, cat: string = '*', pid: number = 0, tid: number = 0): Trace.Types.Events.End {
return {
args: {},
cat,
name,
ph: Trace.Types.Events.Phase.END,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(ts),
};
}
export function makeProfileCall(
functionName: string, tsUs: number, durUs: number, pid: number = 0, tid: number = 0, nodeId: number = 0,
url: string = ''): Trace.Types.Events.SyntheticProfileCall {
return {
cat: '',
name: 'ProfileCall',
nodeId,
sampleIndex: 0,
profileId: Trace.Types.Events.ProfileID('fake-profile-id'),
ph: Trace.Types.Events.Phase.COMPLETE,
pid: Trace.Types.Events.ProcessID(pid),
tid: Trace.Types.Events.ThreadID(tid),
ts: Trace.Types.Timing.Micro(tsUs),
dur: Trace.Types.Timing.Micro(durUs),
callFrame: {
functionName,
scriptId: '' as Protocol.Runtime.ScriptId,
url,
lineNumber: -1,
columnNumber: -1,
},
args: {},
};
}
export const DevToolsTimelineCategory = 'disabled-by-default-devtools.timeline';
/**
* Mocks an object compatible with the return type of the
* RendererHandler using only an array of ordered entries.
*/
export function makeMockRendererHandlerData(entries: Trace.Types.Events.Event[], pid: number = 1, tid: number = 1):
Trace.Handlers.ModelHandlers.Renderer.RendererHandlerData {
const {tree, entryToNode} = Trace.Helpers.TreeHelpers.treify(entries, {filter: {has: () => true}});
const mockThread: Trace.Handlers.ModelHandlers.Renderer.RendererThread = {
tree,
name: 'thread',
entries,
profileCalls: entries.filter(Trace.Types.Events.isProfileCall),
layoutEvents: entries.filter(Trace.Types.Events.isLayout),
updateLayoutTreeEvents: entries.filter(Trace.Types.Events.isUpdateLayoutTree),
};
const mockProcess: Trace.Handlers.ModelHandlers.Renderer.RendererProcess = {
url: 'url',
isOnMainFrame: true,
threads: new Map([[tid as Trace.Types.Events.ThreadID, mockThread]]),
};
const renderereEvents: Trace.Types.Events.RendererEvent[] = [];
for (const entry of entries) {
if (Trace.Types.Events.isRendererEvent(entry)) {
renderereEvents.push(entry);
}
}
return {
processes: new Map([[pid as Trace.Types.Events.ProcessID, mockProcess]]),
compositorTileWorkers: new Map(),
entryToNode,
allTraceEntries: renderereEvents,
entityMappings: {
entityByEvent: new Map(),
eventsByEntity: new Map(),
createdEntityCache: new Map(),
},
};
}
/**
* Mocks an object compatible with the return type of the
* SamplesHandler using only an array of ordered profile calls.
*/
export function makeMockSamplesHandlerData(profileCalls: Trace.Types.Events.SyntheticProfileCall[]):
Trace.Handlers.ModelHandlers.Samples.SamplesHandlerData {
const {tree, entryToNode} = Trace.Helpers.TreeHelpers.treify(profileCalls, {filter: {has: () => true}});
const profile: Protocol.Profiler.Profile = {
nodes: [],
startTime: profileCalls.at(0)?.ts || Trace.Types.Timing.Micro(0),
endTime: profileCalls.at(-1)?.ts || Trace.Types.Timing.Micro(10e5),
samples: [],
timeDeltas: [],
};
const nodesIds = new Map<number, Protocol.Profiler.ProfileNode>();
const lastTimestamp = profile.startTime;
for (const profileCall of profileCalls) {
let node = nodesIds.get(profileCall.nodeId);
if (!node) {
node = {
id: profileCall.nodeId,
callFrame: profileCall.callFrame,
};
profile.nodes.push(node);
nodesIds.set(profileCall.nodeId, node);
}
profile.samples?.push(node.id);
const timeDelta = profileCall.ts - lastTimestamp;
profile.timeDeltas?.push(timeDelta);
}
const profileData = {
rawProfile: profile,
parsedProfile: new CPUProfile.CPUProfileDataModel.CPUProfileDataModel(profile),
profileCalls,
profileTree: tree,
profileId: Trace.Types.Events.ProfileID('fake-profile-id'),
};
const profilesInThread = new Map([[1 as Trace.Types.Events.ThreadID, profileData]]);
return {
profilesInProcess: new Map([[1 as Trace.Types.Events.ProcessID, profilesInThread]]),
entryToNode,
};
}
export function makeMockEntityData(events: Trace.Types.Events.Event[]): Trace.Handlers.Helpers.EntityMappings {
const eventsByEntity = new Map<Trace.Handlers.Helpers.Entity, Trace.Types.Events.Event[]>();
const entityByEvent = new Map<Trace.Types.Events.Event, Trace.Handlers.Helpers.Entity>();
const createdEntityCache = new Map<string, Trace.Handlers.Helpers.Entity>();
events.forEach(event => {
const entity = Trace.Handlers.Helpers.getEntityForEvent(event, createdEntityCache);
if (!entity) {
return;
}
if (eventsByEntity.has(entity)) {
const events = eventsByEntity.get(entity) ?? [];
events?.push(event);
} else {
eventsByEntity.set(entity, [event]);
}
entityByEvent.set(event, entity);
});
return {eventsByEntity, entityByEvent, createdEntityCache};
}
export class FakeFlameChartProvider implements PerfUI.FlameChart.FlameChartDataProvider {
minimumBoundary(): number {
return 0;
}
hasTrackConfigurationMode(): boolean {
return false;
}
totalTime(): number {
return 100;
}
formatValue(value: number): string {
return value.toString();
}
maxStackDepth(): number {
return 3;
}
preparePopoverElement(_entryIndex: number): Element|null {
return null;
}
canJumpToEntry(_entryIndex: number): boolean {
return false;
}
entryTitle(entryIndex: number): string|null {
return `Entry ${entryIndex}`;
}
entryFont(_entryIndex: number): string|null {
return null;
}
entryColor(entryIndex: number): string {
return [
'lightblue',
'lightpink',
'yellow',
'lightgray',
'lightgreen',
'lightsalmon',
'orange',
'pink',
][entryIndex % 8];
}
decorateEntry(): boolean {
return false;
}
forceDecoration(_entryIndex: number): boolean {
return false;
}
textColor(_entryIndex: number): string {
return 'black';
}
timelineData(): PerfUI.FlameChart.FlameChartTimelineData|null {
return PerfUI.FlameChart.FlameChartTimelineData.createEmpty();
}
}
export function getMainThread(data: Trace.Handlers.ModelHandlers.Renderer.RendererHandlerData):
Trace.Handlers.ModelHandlers.Renderer.RendererThread {
let mainThread: Trace.Handlers.ModelHandlers.Renderer.RendererThread|null = null;
for (const [, process] of data.processes) {
for (const [, thread] of process.threads) {
if (thread.name === 'CrRendererMain') {
mainThread = thread;
break;
}
}
}
if (!mainThread) {
throw new Error('Could not find main thread.');
}
return mainThread;
}
type ParsedTrace = Trace.Handlers.Types.ParsedTrace;
export function getBaseTraceParseModelData(overrides: Partial<ParsedTrace> = {}): ParsedTrace {
return {
Animations: {animations: []},
AnimationFrames: {
animationFrames: [],
presentationForFrame: new Map(),
},
DOMStats: {
domStatsByFrameId: new Map(),
},
LayoutShifts: {
clusters: [],
clustersByNavigationId: new Map(),
sessionMaxScore: 0,
clsWindowID: 0,
prePaintEvents: [],
layoutInvalidationEvents: [],
scheduleStyleInvalidationEvents: [],
styleRecalcInvalidationEvents: [],
renderFrameImplCreateChildFrameEvents: [],
domLoadingEvents: [],
layoutImageUnsizedEvents: [],
beginRemoteFontLoadEvents: [],
scoreRecords: [],
backendNodeIds: [],
paintImageEvents: [],
},
Meta: {
traceBounds: {
min: Trace.Types.Timing.Micro(0),
max: Trace.Types.Timing.Micro(100),
range: Trace.Types.Timing.Micro(100),
},
browserProcessId: Trace.Types.Events.ProcessID(-1),
browserThreadId: Trace.Types.Events.ThreadID(-1),
gpuProcessId: Trace.Types.Events.ProcessID(-1),
gpuThreadId: Trace.Types.Events.ThreadID(-1),
threadsInProcess: new Map(),
navigationsByFrameId: new Map(),
navigationsByNavigationId: new Map(),
mainFrameId: '',
mainFrameURL: '',
rendererProcessesByFrame: new Map(),
topLevelRendererIds: new Set(),
frameByProcessId: new Map(),
mainFrameNavigations: [],
traceIsGeneric: false,
processNames: new Map(),
},
Renderer: {
processes: new Map(),
compositorTileWorkers: new Map(),
entryToNode: new Map(),
allTraceEntries: [],
entityMappings: {
entityByEvent: new Map(),
eventsByEntity: new Map(),
createdEntityCache: new Map(),
},
},
Screenshots: {
legacySyntheticScreenshots: [],
screenshots: [],
},
Samples: {
entryToNode: new Map(),
profilesInProcess: new Map(),
},
PageLoadMetrics: {metricScoresByFrameId: new Map(), allMarkerEvents: []},
UserInteractions: {
allEvents: [],
interactionEvents: [],
beginCommitCompositorFrameEvents: [],
parseMetaViewportEvents: [],
interactionEventsWithNoNesting: [],
longestInteractionEvent: null,
interactionsOverThreshold: new Set(),
},
NetworkRequests: {
byId: new Map(),
eventToInitiator: new Map(),
byOrigin: new Map(),
byTime: [],
webSocket: [],
entityMappings: {
entityByEvent: new Map(),
eventsByEntity: new Map(),
createdEntityCache: new Map(),
},
},
GPU: {
mainGPUThreadTasks: [],
},
UserTimings: {
consoleTimings: [],
performanceMarks: [],
performanceMeasures: [],
timestampEvents: [],
measureTraceByTraceId: new Map(),
},
LargestImagePaint: {lcpRequestByNavigation: new Map()},
LargestTextPaint: new Map(),
AuctionWorklets: {
worklets: new Map(),
},
ExtensionTraceData: {
entryToNode: new Map(),
extensionMarkers: [],
extensionTrackData: [],
syntheticConsoleEntriesForTimingsTrack: [],
},
Frames: {
frames: [],
framesById: {},
},
ImagePainting: {
paintImageByDrawLazyPixelRef: new Map(),
paintImageForEvent: new Map(),
paintImageEventForUrl: new Map(),
},
Initiators: {
eventToInitiator: new Map(),
initiatorToEvents: new Map(),
},
Invalidations: {
invalidationCountForEvent: new Map(),
invalidationsForEvent: new Map(),
},
LayerTree: {
paints: [],
paintsToSnapshots: new Map(),
snapshots: [],
},
Memory: {
updateCountersByProcess: new Map(),
},
PageFrames: {
frames: new Map(),
},
SelectorStats: {
dataForUpdateLayoutEvent: new Map(),
},
ServerTimings: {
serverTimings: [],
},
Warnings: {
perEvent: new Map(),
perWarning: new Map(),
},
Workers: {
workerIdByThread: new Map(),
workerSessionIdEvents: [],
workerURLById: new Map(),
},
Flows: {
flows: [],
},
AsyncJSCalls: {
schedulerToRunEntryPoints: new Map(),
asyncCallToScheduler: new Map(),
},
...overrides,
};
}
/**
* A helper that will query the given array of events and find the first event
* matching the predicate. It will also assert that a match is found, which
* saves the need to do that for every test.
*/
export function getEventOfType<T extends Trace.Types.Events.Event>(
events: Trace.Types.Events.Event[], predicate: (e: Trace.Types.Events.Event) => e is T): T {
const match = events.find(predicate);
if (!match) {
throw new Error('Failed to find matching event of type.');
}
return match;
}
/**
* The Performance Panel is integrated with the IgnoreListManager so in tests
* that render a flame chart or a track appender, it needs to be setup to avoid
* errors.
*/
export function setupIgnoreListManagerEnvironment(): {
ignoreListManager: Bindings.IgnoreListManager.IgnoreListManager,
} {
const targetManager = SDK.TargetManager.TargetManager.instance({forceNew: true});
const workspace = Workspace.Workspace.WorkspaceImpl.instance({forceNew: true});
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({
forceNew: true,
resourceMapping,
targetManager,
});
const ignoreListManager = Bindings.IgnoreListManager.IgnoreListManager.instance({
forceNew: true,
debuggerWorkspaceBinding,
});
return {ignoreListManager};
}
export function microsecondsTraceWindow(min: number, max: number): Trace.Types.Timing.TraceWindowMicro {
return Trace.Helpers.Timing.traceWindowFromMicroSeconds(
min as Trace.Types.Timing.Micro,
max as Trace.Types.Timing.Micro,
);
}
export function microseconds(x: number): Trace.Types.Timing.Micro {
return Trace.Types.Timing.Micro(x);
}
export function milliseconds(x: number): Trace.Types.Timing.Milli {
return Trace.Types.Timing.Milli(x);
}