UNPKG

chrome-devtools-frontend

Version:
198 lines (185 loc) • 6.89 kB
// 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 Helpers from '../helpers/helpers.js'; import * as Types from '../types/types.js'; /** * IMPORTANT! * See UserTimings.md in this directory for some handy documentation on * UserTimings and the trace events we parse currently. **/ let syntheticEvents: Array<Types.Events.SyntheticEventPair<Types.Events.PairableAsync>> = []; // There are two events dispatched for performance.measure calls: one to // represent the measured timing in the tracing clock (which we type as // PerformanceMeasure) and another one for the call itself (which we // type as UserTimingMeasure). The two events corresponding to the same // call are linked together by a common trace_id. The reason two events // are dispatched is because the first was originally added with the // implementation of the performance.measure API and it uses an // overridden timestamp and duration. To prevent breaking potential deps // created since then, a second event was added instead of changing the // params of the first. const measureTraceByTraceId = new Map<number, Types.Events.UserTimingMeasure>(); const performanceMeasureEvents: Types.Events.PerformanceMeasure[] = []; const performanceMarkEvents: Types.Events.PerformanceMark[] = []; const consoleTimings: Array<Types.Events.ConsoleTimeBegin|Types.Events.ConsoleTimeEnd> = []; const timestampEvents: Types.Events.ConsoleTimeStamp[] = []; export interface UserTimingsData { /** * Events triggered with the performance.measure() API. * https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure */ performanceMeasures: readonly Types.Events.SyntheticUserTimingPair[]; /** * Events triggered with the performance.mark() API. * https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark */ performanceMarks: readonly Types.Events.PerformanceMark[]; /** * Events triggered with the console.time(), console.timeEnd() and * console.timeLog() API. * https://developer.mozilla.org/en-US/docs/Web/API/console/time */ consoleTimings: readonly Types.Events.SyntheticConsoleTimingPair[]; /** * Events triggered with the console.timeStamp() API * https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp */ timestampEvents: readonly Types.Events.ConsoleTimeStamp[]; /** * Events triggered to trace the call to performance.measure itself, * cached by trace_id. */ measureTraceByTraceId: Map<number, Types.Events.UserTimingMeasure>; } export function reset(): void { syntheticEvents.length = 0; performanceMeasureEvents.length = 0; performanceMarkEvents.length = 0; consoleTimings.length = 0; timestampEvents.length = 0; measureTraceByTraceId.clear(); } const resourceTimingNames = [ 'workerStart', 'redirectStart', 'redirectEnd', 'fetchStart', 'domainLookupStart', 'domainLookupEnd', 'connectStart', 'connectEnd', 'secureConnectionStart', 'requestStart', 'responseStart', 'responseEnd', ]; const navTimingNames = [ 'navigationStart', 'unloadEventStart', 'unloadEventEnd', 'redirectStart', 'redirectEnd', 'fetchStart', 'commitNavigationEnd', 'domainLookupStart', 'domainLookupEnd', 'connectStart', 'connectEnd', 'secureConnectionStart', 'requestStart', 'responseStart', 'responseEnd', 'domLoading', 'domInteractive', 'domContentLoadedEventStart', 'domContentLoadedEventEnd', 'domComplete', 'loadEventStart', 'loadEventEnd', ]; // These are events dispatched under the blink.user_timing category // but that the user didn't add. Filter them out so that they do not // Appear in the timings track (they still appear in the main thread // flame chart). const ignoredNames = [...resourceTimingNames, ...navTimingNames]; /** * Similar to the default {@see Helpers.Trace.eventTimeComparator} * but with a twist: * In case of equal start and end times, always put the second event * first. * * Explanation: * User timing entries come as trace events dispatched when * performance.measure/mark is called. The trace events buffered in * devtools frontend are sorted by the start time. If their start time * is the same, then the event for the first call will appear first. * * When entries are meant to be stacked, the corresponding * performance.measure calls usually are done in bottom-up direction: * calls for children first and for parent later (because the call * is usually done when the measured task is over). This means that * when two user timing events have the start and end time, usually the * second event is the parent of the first. Hence the switch. * */ function userTimingComparator( a: Helpers.Trace.TimeSpan, b: Helpers.Trace.TimeSpan, originalArray: Helpers.Trace.TimeSpan[]): number { const aBeginTime = a.ts; const bBeginTime = b.ts; if (aBeginTime < bBeginTime) { return -1; } if (aBeginTime > bBeginTime) { return 1; } const aDuration = a.dur ?? 0; const bDuration = b.dur ?? 0; const aEndTime = aBeginTime + aDuration; const bEndTime = bBeginTime + bDuration; if (aEndTime > bEndTime) { return -1; } if (aEndTime < bEndTime) { return 1; } // Prefer the event located in a further position in the original array. return originalArray.indexOf(b) - originalArray.indexOf(a); } export function handleEvent(event: Types.Events.Event): void { if (ignoredNames.includes(event.name)) { return; } if (Types.Events.isUserTimingMeasure(event)) { measureTraceByTraceId.set(event.args.traceId, event); } if (Types.Events.isPerformanceMeasure(event)) { performanceMeasureEvents.push(event); return; } if (Types.Events.isPerformanceMark(event)) { performanceMarkEvents.push(event); } if (Types.Events.isConsoleTime(event)) { consoleTimings.push(event); } if (Types.Events.isConsoleTimeStamp(event)) { timestampEvents.push(event); } } export async function finalize(): Promise<void> { const asyncEvents = [...performanceMeasureEvents, ...consoleTimings]; syntheticEvents = Helpers.Trace.createMatchedSortedSyntheticEvents(asyncEvents); syntheticEvents = syntheticEvents.sort((a, b) => userTimingComparator(a, b, [...syntheticEvents])); } export function data(): UserTimingsData { return { performanceMeasures: syntheticEvents.filter(e => e.cat === 'blink.user_timing') as Types.Events.SyntheticUserTimingPair[], consoleTimings: syntheticEvents.filter(e => e.cat === 'blink.console') as Types.Events.SyntheticConsoleTimingPair[], // TODO(crbug/41484172): UserTimingsHandler.test.ts fails if this is not copied. performanceMarks: [...performanceMarkEvents], timestampEvents: [...timestampEvents], measureTraceByTraceId: new Map(measureTraceByTraceId), }; }