From 2ce421b27a70992f1f631437fb481950f44b561e Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 12 Mar 2024 16:32:58 -0700 Subject: [PATCH] fix(trace viewer): synchronize monotonic times between context entries (#29908) When displaying remote context trace and local test runner trace together, we should not compare monotonic time between the two. This change reuses existing logic for merging actions timing to also update context boundaries and events by the same time delta. Fixes #29767. --- packages/trace-viewer/src/ui/modelUtil.ts | 26 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/trace-viewer/src/ui/modelUtil.ts b/packages/trace-viewer/src/ui/modelUtil.ts index 1b6b7cdff4..d1f1adbb2f 100644 --- a/packages/trace-viewer/src/ui/modelUtil.ts +++ b/packages/trace-viewer/src/ui/modelUtil.ts @@ -89,11 +89,12 @@ export class MultiTraceModel { this.platform = primaryContext?.platform || ''; this.title = primaryContext?.title || ''; this.options = primaryContext?.options || {}; + // Next call updates all timestamps for all events in non-primary contexts, so it must be done first. + this.actions = mergeActionsAndUpdateTiming(contexts); + this.pages = ([] as PageEntry[]).concat(...contexts.map(c => c.pages)); this.wallTime = contexts.map(c => c.wallTime).reduce((prev, cur) => Math.min(prev || Number.MAX_VALUE, cur!), Number.MAX_VALUE); this.startTime = contexts.map(c => c.startTime).reduce((prev, cur) => Math.min(prev, cur), Number.MAX_VALUE); this.endTime = contexts.map(c => c.endTime).reduce((prev, cur) => Math.max(prev, cur), Number.MIN_VALUE); - this.pages = ([] as PageEntry[]).concat(...contexts.map(c => c.pages)); - this.actions = mergeActions(contexts); this.events = ([] as (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[]).concat(...contexts.map(c => c.events)); this.stdio = ([] as trace.StdioTraceEvent[]).concat(...contexts.map(c => c.stdio)); this.errors = ([] as trace.ErrorTraceEvent[]).concat(...contexts.map(c => c.errors)); @@ -158,7 +159,7 @@ function indexModel(context: ContextEntry) { (event as any)[contextSymbol] = context; } -function mergeActions(contexts: ContextEntry[]) { +function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) { const map = new Map(); // Protocol call aka isPrimary contexts have startTime/endTime as server-side times. @@ -176,12 +177,16 @@ function mergeActions(contexts: ContextEntry[]) { } const nonPrimaryIdToPrimaryId = new Map(); + const nonPrimaryTimeDelta = new Map(); for (const context of nonPrimaryContexts) { for (const action of context.actions) { if (offset) { const duration = action.endTime - action.startTime; - if (action.startTime) - action.startTime = action.wallTime + offset; + if (action.startTime) { + const newStartTime = action.wallTime + offset; + nonPrimaryTimeDelta.set(context, newStartTime - action.startTime); + action.startTime = newStartTime; + } if (action.endTime) action.endTime = action.startTime + duration; } @@ -204,6 +209,17 @@ function mergeActions(contexts: ContextEntry[]) { } } + for (const [context, timeDelta] of nonPrimaryTimeDelta) { + context.startTime += timeDelta; + context.endTime += timeDelta; + for (const event of context.events) + event.time += timeDelta; + for (const page of context.pages) { + for (const frame of page.screencastFrames) + frame.timestamp += timeDelta; + } + } + const result = [...map.values()]; result.sort((a1, a2) => { if (a2.parentId === a1.callId)