| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Platform from '../../../core/platform/platform.js'; |
| import * as Types from '../types/types.js'; |
| |
| import {getNavigationForTraceEvent} from './Trace.js'; |
| |
| export const milliToMicro = (value: Types.Timing.Milli): Types.Timing.Micro => Types.Timing.Micro(value * 1000); |
| |
| export const secondsToMilli = (value: Types.Timing.Seconds): Types.Timing.Milli => Types.Timing.Milli(value * 1000); |
| |
| export const secondsToMicro = (value: Types.Timing.Seconds): Types.Timing.Micro => milliToMicro(secondsToMilli(value)); |
| |
| export const microToMilli = (value: Types.Timing.Micro): Types.Timing.Milli => Types.Timing.Milli(value / 1000); |
| |
| export const microToSeconds = (value: Types.Timing.Micro): Types.Timing.Seconds => |
| Types.Timing.Seconds(value / 1000 / 1000); |
| |
| export function timeStampForEventAdjustedByClosestNavigation( |
| event: Types.Events.Event, |
| traceBounds: Types.Timing.TraceWindowMicro, |
| navigationsByNavigationId: Map<string, Types.Events.NavigationStart>, |
| softNavigationsById: Map<number, Types.Events.SoftNavigationStart>, |
| navigationsByFrameId: Map<string, Types.Events.NavigationStart[]>, |
| ): Types.Timing.Micro { |
| let eventTimeStamp = event.ts - traceBounds.min; |
| if (event.name === Types.Events.Name.MARK_LCP_CANDIDATE_FOR_SOFT_NAVIGATION && |
| event.args?.data?.performanceTimelineNavigationId) { |
| const navigationForEvent = softNavigationsById.get(event.args.data.performanceTimelineNavigationId); |
| if (navigationForEvent) { |
| eventTimeStamp = event.ts - navigationForEvent.ts; |
| } |
| } else if (event.args?.data?.navigationId) { |
| const navigationForEvent = navigationsByNavigationId.get(event.args.data.navigationId); |
| if (navigationForEvent) { |
| eventTimeStamp = event.ts - navigationForEvent.ts; |
| } |
| } else if (event.args?.data?.frame) { |
| const navigationForEvent = getNavigationForTraceEvent(event, event.args.data.frame, navigationsByFrameId); |
| if (navigationForEvent) { |
| eventTimeStamp = event.ts - navigationForEvent.ts; |
| } |
| } |
| return Types.Timing.Micro(eventTimeStamp); |
| } |
| |
| /** |
| * Expands the trace window by a provided percentage or, if it the expanded window is smaller than 1 millisecond, expands it to 1 millisecond. |
| * If the expanded window is outside of the max trace window, cut the overflowing bound to the max trace window bound. |
| **/ |
| export function expandWindowByPercentOrToOneMillisecond( |
| annotationWindow: Types.Timing.TraceWindowMicro, maxTraceWindow: Types.Timing.TraceWindowMicro, |
| percentage: number): Types.Timing.TraceWindowMicro { |
| // Expand min and max of the window by half of the provided percentage. That way, in total, the window will be expanded by the provided percentage. |
| let newMin = annotationWindow.min - annotationWindow.range * (percentage / 100) / 2; |
| let newMax = annotationWindow.max + annotationWindow.range * (percentage / 100) / 2; |
| |
| if (newMax - newMin < 1_000) { |
| const rangeMiddle = (annotationWindow.min + annotationWindow.max) / 2; |
| newMin = rangeMiddle - 500; |
| newMax = rangeMiddle + 500; |
| } |
| |
| newMin = Math.max(newMin, maxTraceWindow.min); |
| newMax = Math.min(newMax, maxTraceWindow.max); |
| |
| const expandedWindow: Types.Timing.TraceWindowMicro = { |
| min: Types.Timing.Micro(newMin), |
| max: Types.Timing.Micro(newMax), |
| range: Types.Timing.Micro(newMax - newMin), |
| }; |
| |
| return expandedWindow; |
| } |
| |
| export interface EventTimingsData<ValueType extends Types.Timing.Micro|Types.Timing.Milli|Types.Timing.Seconds, > { |
| startTime: ValueType; |
| endTime: ValueType; |
| duration: ValueType; |
| } |
| |
| export function eventTimingsMicroSeconds(event: Types.Events.Event): EventTimingsData<Types.Timing.Micro> { |
| return { |
| startTime: event.ts, |
| endTime: (event.ts + (event.dur ?? 0)) as Types.Timing.Micro, |
| duration: (event.dur || 0) as Types.Timing.Micro, |
| }; |
| } |
| export function eventTimingsMilliSeconds(event: Types.Events.Event): EventTimingsData<Types.Timing.Milli> { |
| return { |
| startTime: (event.ts / 1000) as Types.Timing.Milli, |
| endTime: (event.ts + (event.dur ?? 0)) / 1000 as Types.Timing.Milli, |
| duration: (event.dur || 0) / 1000 as Types.Timing.Milli, |
| }; |
| } |
| |
| export function traceWindowMilliSeconds(bounds: Types.Timing.TraceWindowMicro): Types.Timing.TraceWindowMilli { |
| return { |
| min: microToMilli(bounds.min), |
| max: microToMilli(bounds.max), |
| range: microToMilli(bounds.range), |
| }; |
| } |
| |
| export function traceWindowMicroSecondsToMilliSeconds(bounds: Types.Timing.TraceWindowMicro): |
| Types.Timing.TraceWindowMilli { |
| return { |
| min: microToMilli(bounds.min), |
| max: microToMilli(bounds.max), |
| range: microToMilli(bounds.range), |
| }; |
| } |
| |
| export function traceWindowFromMilliSeconds( |
| min: Types.Timing.Milli, max: Types.Timing.Milli): Types.Timing.TraceWindowMicro { |
| const traceWindow: Types.Timing.TraceWindowMicro = { |
| min: milliToMicro(min), |
| max: milliToMicro(max), |
| range: Types.Timing.Micro(milliToMicro(max) - milliToMicro(min)), |
| }; |
| return traceWindow; |
| } |
| |
| export function traceWindowFromMicroSeconds( |
| min: Types.Timing.Micro, max: Types.Timing.Micro): Types.Timing.TraceWindowMicro { |
| const traceWindow: Types.Timing.TraceWindowMicro = { |
| min, |
| max, |
| range: (max - min) as Types.Timing.Micro, |
| }; |
| return traceWindow; |
| } |
| |
| export function traceWindowFromEvent(event: Types.Events.Event): Types.Timing.TraceWindowMicro { |
| return { |
| min: event.ts, |
| max: event.ts + (event.dur ?? 0) as Types.Timing.Micro, |
| range: event.dur ?? 0 as Types.Timing.Micro, |
| }; |
| } |
| |
| export function traceWindowFromOverlay(overlay: Types.Overlays.Overlay): Types.Timing.TraceWindowMicro|null { |
| switch (overlay.type) { |
| case 'ENTRY_LABEL': |
| case 'ENTRY_OUTLINE': |
| case 'ENTRY_SELECTED': { |
| return traceWindowFromEvent(overlay.entry); |
| } |
| |
| case 'TIMESPAN_BREAKDOWN': { |
| const windows = overlay.sections.map(s => s.bounds); |
| if (overlay.entry) { |
| windows.push(traceWindowFromEvent(overlay.entry)); |
| } |
| return combineTraceWindowsMicro(windows); |
| } |
| |
| case 'CANDY_STRIPED_TIME_RANGE': |
| case 'TIME_RANGE': { |
| return structuredClone(overlay.bounds); |
| } |
| |
| case 'ENTRIES_LINK': { |
| const from = traceWindowFromEvent(overlay.entryFrom); |
| if (!overlay.entryTo) { |
| return from; |
| } |
| |
| const to = traceWindowFromEvent(overlay.entryTo); |
| return combineTraceWindowsMicro([from, to]); |
| } |
| |
| case 'TIMESTAMP_MARKER': |
| return traceWindowFromMicroSeconds(overlay.timestamp, overlay.timestamp); |
| case 'TIMINGS_MARKER': |
| return traceWindowFromMicroSeconds(overlay.adjustedTimestamp, overlay.adjustedTimestamp); |
| case 'BOTTOM_INFO_BAR': |
| return null; |
| |
| default: |
| Platform.TypeScriptUtilities.assertNever(overlay, `Unexpected overlay ${overlay}`); |
| } |
| } |
| |
| /** |
| * Combines (as in a union) multiple windows into one. |
| */ |
| export function combineTraceWindowsMicro(windows: Types.Timing.TraceWindowMicro[]): Types.Timing.TraceWindowMicro|null { |
| if (!windows.length) { |
| return null; |
| } |
| |
| const result: Types.Timing.TraceWindowMicro = structuredClone(windows[0]); |
| for (const bounds of windows.slice(1)) { |
| result.min = Math.min(result.min, bounds.min) as Types.Timing.Micro; |
| result.max = Math.max(result.max, bounds.max) as Types.Timing.Micro; |
| } |
| |
| result.range = result.max - result.min as Types.Timing.Micro; |
| |
| return result; |
| } |
| |
| export interface BoundsIncludeTimeRange { |
| timeRange: Types.Timing.TraceWindowMicro; |
| bounds: Types.Timing.TraceWindowMicro; |
| } |
| |
| /** |
| * Checks to see if the timeRange is within the bounds. By "within" we mean |
| * "has any overlap": |
| * |------------------------| |
| * == no overlap (entirely before) |
| * ========= overlap |
| * ========= overlap |
| * ========= overlap |
| * ==== no overlap (entirely after) |
| * ============================== overlap (time range is larger than bounds) |
| * |------------------------| |
| */ |
| export function boundsIncludeTimeRange(data: BoundsIncludeTimeRange): boolean { |
| const {min: visibleMin, max: visibleMax} = data.bounds; |
| const {min: rangeMin, max: rangeMax} = data.timeRange; |
| |
| return visibleMin <= rangeMax && visibleMax >= rangeMin; |
| } |
| |
| /** Checks to see if the event is within or overlaps the bounds */ |
| export function eventIsInBounds(event: Types.Events.Event, bounds: Types.Timing.TraceWindowMicro): boolean { |
| const startTime = event.ts; |
| return startTime <= bounds.max && bounds.min < (startTime + (event.dur ?? 0)); |
| } |
| |
| export function timestampIsInBounds(bounds: Types.Timing.TraceWindowMicro, timestamp: Types.Timing.Micro): boolean { |
| return timestamp >= bounds.min && timestamp <= bounds.max; |
| } |
| |
| export interface WindowFitsInsideBounds { |
| window: Types.Timing.TraceWindowMicro; |
| bounds: Types.Timing.TraceWindowMicro; |
| } |
| |
| /** |
| * Returns true if the window fits entirely within the bounds. |
| * Note that if the window is equivalent to the bounds, that is considered to fit |
| */ |
| export function windowFitsInsideBounds(data: WindowFitsInsideBounds): boolean { |
| return data.window.min >= data.bounds.min && data.window.max <= data.bounds.max; |
| } |
| |
| export function windowsEqual(w1: Types.Timing.TraceWindowMicro, w2: Types.Timing.TraceWindowMicro): boolean { |
| return w1.min === w2.min && w1.max === w2.max; |
| } |