UNPKG

chrome-devtools-frontend

Version:
372 lines (317 loc) • 14 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 {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js'; import {microsecondsTraceWindow} from '../../../testing/TraceHelpers.js'; import {TraceLoader} from '../../../testing/TraceLoader.js'; import * as Trace from '../trace.js'; function milliToMicro(value: number) { return Trace.Types.Timing.Micro(value * 1000); } describeWithEnvironment('Timing helpers', () => { describe('Timing conversions', () => { it('can convert milliseconds to microseconds', () => { const input = Trace.Types.Timing.Milli(1); const expected = Trace.Types.Timing.Micro(1000); assert.strictEqual(Trace.Helpers.Timing.milliToMicro(input), expected); }); it('can convert seconds to milliseconds', () => { const input = Trace.Types.Timing.Seconds(1); const expected = Trace.Types.Timing.Milli(1000); assert.strictEqual(Trace.Helpers.Timing.secondsToMilli(input), expected); }); it('can convert seconds to microseconds', () => { const input = Trace.Types.Timing.Seconds(1); // 1 Second = 1000 Milliseconds // 1000 Milliseconds = 1,000,000 Microseconds const expected = Trace.Types.Timing.Micro(1_000_000); assert.strictEqual(Trace.Helpers.Timing.secondsToMicro(input), expected); }); it('can convert microSeconds milliseconds', () => { const input = Trace.Types.Timing.Micro(1_000_000); const expected = Trace.Types.Timing.Milli(1_000); assert.strictEqual(Trace.Helpers.Timing.microToMilli(input), expected); }); }); it('eventTimingsMicroSeconds returns the right numbers', async () => { const event = { ts: 10, dur: 5, } as unknown as Trace.Types.Events.Event; assert.deepEqual(Trace.Helpers.Timing.eventTimingsMicroSeconds(event), { startTime: Trace.Types.Timing.Micro(10), endTime: Trace.Types.Timing.Micro(15), duration: Trace.Types.Timing.Micro(5), }); }); it('eventTimingsMilliSeconds returns the right numbers', async () => { const event = { ts: 10_000, dur: 5_000, } as unknown as Trace.Types.Events.Event; assert.deepEqual(Trace.Helpers.Timing.eventTimingsMilliSeconds(event), { startTime: Trace.Types.Timing.Milli(10), endTime: Trace.Types.Timing.Milli(15), duration: Trace.Types.Timing.Milli(5), }); }); describe('timeStampForEventAdjustedByClosestNavigation', () => { it('can use the navigation ID to adjust the time correctly', async function() { const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev.json.gz'); const lcpEvent = parsedTrace.PageLoadMetrics.allMarkerEvents.find(event => { // Just one LCP Event so we do not need to worry about ordering and finding the right one. return event.name === 'largestContentfulPaint::Candidate'; }); if (!lcpEvent) { throw new Error('Could not find LCP event'); } // Ensure we are testing the navigationID path! assert.exists(lcpEvent.args.data?.navigationId); const adjustedTime = Trace.Helpers.Timing.timeStampForEventAdjustedByClosestNavigation( lcpEvent, parsedTrace.Meta.traceBounds, parsedTrace.Meta.navigationsByNavigationId, parsedTrace.Meta.navigationsByFrameId, ); const unadjustedTime = Trace.Helpers.Timing.microToMilli( Trace.Types.Timing.Micro(lcpEvent.ts - parsedTrace.Meta.traceBounds.min), ); assert.strictEqual(unadjustedTime.toFixed(2), String(130.31)); // To make the assertion easier to read. const timeAsMS = Trace.Helpers.Timing.microToMilli(adjustedTime); assert.strictEqual(timeAsMS.toFixed(2), String(118.44)); }); it('can use the frame ID to adjust the time correctly', async function() { const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev.json.gz'); const dclEvent = parsedTrace.PageLoadMetrics.allMarkerEvents.find(event => { return event.name === 'MarkDOMContent' && event.args.data?.frame === parsedTrace.Meta.mainFrameId; }); if (!dclEvent) { throw new Error('Could not find DCL event'); } // Ensure we are testing the frameID path! assert.isUndefined(dclEvent.args.data?.navigationId); const unadjustedTime = Trace.Helpers.Timing.microToMilli( Trace.Types.Timing.Micro(dclEvent.ts - parsedTrace.Meta.traceBounds.min), ); assert.strictEqual(unadjustedTime.toFixed(2), String(190.79)); const adjustedTime = Trace.Helpers.Timing.timeStampForEventAdjustedByClosestNavigation( dclEvent, parsedTrace.Meta.traceBounds, parsedTrace.Meta.navigationsByNavigationId, parsedTrace.Meta.navigationsByFrameId, ); // To make the assertion easier to read. const timeAsMS = Trace.Helpers.Timing.microToMilli(adjustedTime); assert.strictEqual(timeAsMS.toFixed(2), String(178.92)); }); }); describe('expandWindowByPercentOrToOneMillisecond', () => { it('can expand trace window by a percentage', async function() { const traceWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds( milliToMicro(40), milliToMicro(60), ); const maxTraceWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds( milliToMicro(0), milliToMicro(100), ); const expandedTraceWindow = Trace.Helpers.Timing.expandWindowByPercentOrToOneMillisecond(traceWindow, maxTraceWindow, 50); // Since initial window was 20ms, make sure the that it is 30ms after being expanded by 50% assert.strictEqual(expandedTraceWindow.range, 30000); // min and max bounds are expanded by 25% from the initial window bounds assert.strictEqual(expandedTraceWindow.min, 35000); assert.strictEqual(expandedTraceWindow.max, 65000); }); it('if the expanded window is smaller than 1 millisecond, expands it to 1 millisecond ', async function() { // Trace window that is smaller than 1 millisecond const traceWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds( Trace.Types.Timing.Micro(1000), Trace.Types.Timing.Micro(1500), ); const maxTraceWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds( milliToMicro(0), milliToMicro(100), ); const expandedTraceWindow = Trace.Helpers.Timing.expandWindowByPercentOrToOneMillisecond(traceWindow, maxTraceWindow, 5); // Make sure the window was expanded to 1 millisecond instead of 5 percent. assert.strictEqual(expandedTraceWindow.range, 1000); // The middle of the window should not change assert.strictEqual( (traceWindow.max + traceWindow.min) / 2, (expandedTraceWindow.max + expandedTraceWindow.min) / 2); assert.strictEqual(expandedTraceWindow.min, 750); assert.strictEqual(expandedTraceWindow.max, 1750); }); it('window does not expand past the provided max window', async function() { const traceWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds( milliToMicro(5), milliToMicro(55), ); const maxTraceWindow = Trace.Helpers.Timing.traceWindowFromMicroSeconds( milliToMicro(0), milliToMicro(100), ); const expandedTraceWindow = Trace.Helpers.Timing.expandWindowByPercentOrToOneMillisecond(traceWindow, maxTraceWindow, 50); assert.strictEqual(expandedTraceWindow.range, 67500); // Since the expanded window min bound would be smaller than the max window min bound, the expanded window min should be equal to the max window min assert.strictEqual(expandedTraceWindow.min, 0); assert.strictEqual(expandedTraceWindow.max, 67500); }); }); describe('BoundsIncludeTimeRange', () => { const {boundsIncludeTimeRange, traceWindowFromMicroSeconds} = Trace.Helpers.Timing; it('is false for an event that is outside the LHS of the visible bounds', () => { const bounds = traceWindowFromMicroSeconds( milliToMicro(50), milliToMicro(100), ); const timeRange = traceWindowFromMicroSeconds( milliToMicro(10), milliToMicro(20), ); assert.isFalse(boundsIncludeTimeRange({ bounds, timeRange, })); }); it('is false for an event that is outside the RHS of the visible bounds', () => { const bounds = traceWindowFromMicroSeconds( milliToMicro(50), milliToMicro(100), ); const timeRange = traceWindowFromMicroSeconds( milliToMicro(101), milliToMicro(200), ); assert.isFalse(boundsIncludeTimeRange({ bounds, timeRange, })); }); it('is true for an event that overlaps the LHS of the bounds', () => { const bounds = traceWindowFromMicroSeconds( milliToMicro(50), milliToMicro(100), ); const timeRange = traceWindowFromMicroSeconds( milliToMicro(0), milliToMicro(52), ); assert.isTrue(boundsIncludeTimeRange({ bounds, timeRange, })); }); it('is true for an event that overlaps the RHS of the bounds', () => { const bounds = traceWindowFromMicroSeconds( milliToMicro(50), milliToMicro(100), ); const timeRange = traceWindowFromMicroSeconds( milliToMicro(99), milliToMicro(101), ); assert.isTrue(boundsIncludeTimeRange({ bounds, timeRange, })); }); it('is true for an event that is entirely within the bounds', () => { const bounds = traceWindowFromMicroSeconds( milliToMicro(50), milliToMicro(100), ); const timeRange = traceWindowFromMicroSeconds( milliToMicro(51), milliToMicro(75), ); assert.isTrue(boundsIncludeTimeRange({ bounds, timeRange, })); }); it('is true for an event that is larger than the bounds', () => { const bounds = traceWindowFromMicroSeconds( milliToMicro(50), milliToMicro(100), ); const timeRange = traceWindowFromMicroSeconds( milliToMicro(0), milliToMicro(200), ); assert.isTrue(boundsIncludeTimeRange({ bounds, timeRange, })); }); }); describe('timestampIsInBounds', () => { const {eventIsInBounds} = Trace.Helpers.Timing; const {Micro: MicroSeconds} = Trace.Types.Timing; const bounds: Trace.Types.Timing.TraceWindowMicro = { min: MicroSeconds(100), max: MicroSeconds(200), range: MicroSeconds(100), }; const makeEvent = (ts: number, dur: number) => ({ ts: Trace.Types.Timing.Micro(ts), dur: Trace.Types.Timing.Micro(dur), }) as unknown as Trace.Types.Events.Event; // Left boundary assert.isTrue(eventIsInBounds(makeEvent(101, 1), bounds)); assert.isTrue(eventIsInBounds(makeEvent(100, 1), bounds)); assert.isTrue(eventIsInBounds(makeEvent(99, 1), bounds)); assert.isTrue(eventIsInBounds(makeEvent(150, 500), bounds)); assert.isFalse(eventIsInBounds(makeEvent(98, 1), bounds)); assert.isFalse(eventIsInBounds(makeEvent(0, 1), bounds)); assert.isFalse(eventIsInBounds(makeEvent(0, 0), bounds)); // Right boundary assert.isTrue(eventIsInBounds(makeEvent(199, 1), bounds)); assert.isTrue(eventIsInBounds(makeEvent(200, 1), bounds)); assert.isFalse(eventIsInBounds(makeEvent(201, 1), bounds)); assert.isFalse(eventIsInBounds(makeEvent(300, 50), bounds)); }); describe('timestampIsInBounds', () => { const {timestampIsInBounds} = Trace.Helpers.Timing; const {Micro: MicroSeconds} = Trace.Types.Timing; it('is true if the value is in the bounds and false otherwise', async () => { const bounds: Trace.Types.Timing.TraceWindowMicro = { min: MicroSeconds(1), max: MicroSeconds(10), range: MicroSeconds(9), }; assert.isTrue(timestampIsInBounds(bounds, MicroSeconds(1))); assert.isTrue(timestampIsInBounds(bounds, MicroSeconds(5))); assert.isTrue(timestampIsInBounds(bounds, MicroSeconds(10))); assert.isFalse(timestampIsInBounds(bounds, MicroSeconds(0))); assert.isFalse(timestampIsInBounds(bounds, MicroSeconds(11))); }); }); describe('WindowFitsInsideBounds', () => { const {windowFitsInsideBounds} = Trace.Helpers.Timing; const {Micro: MicroSeconds} = Trace.Types.Timing; const bounds: Trace.Types.Timing.TraceWindowMicro = { min: MicroSeconds(5), max: MicroSeconds(15), range: MicroSeconds(10), }; it('is true if the window fits within the bounds', () => { assert.isTrue(windowFitsInsideBounds({window: microsecondsTraceWindow(5, 8), bounds})); assert.isTrue(windowFitsInsideBounds({window: microsecondsTraceWindow(5, 14), bounds})); assert.isTrue(windowFitsInsideBounds({window: microsecondsTraceWindow(5, 15), bounds})); }); it('is false if the window does not fully fit within the bounds', () => { // Outside the left hand edge assert.isFalse(windowFitsInsideBounds({window: microsecondsTraceWindow(0, 8), bounds})); // Outside the right hand edge assert.isFalse(windowFitsInsideBounds({window: microsecondsTraceWindow(10, 20), bounds})); // Outside entirely before assert.isFalse(windowFitsInsideBounds({window: microsecondsTraceWindow(0, 5), bounds})); // Outside entirely after assert.isFalse(windowFitsInsideBounds({window: microsecondsTraceWindow(20, 25), bounds})); }); }); });