diff --git a/scripts/src/hwlab-node-web-observe-analyzer-source.ts b/scripts/src/hwlab-node-web-observe-analyzer-source.ts index 979ac3b0..7aa718a4 100644 --- a/scripts/src/hwlab-node-web-observe-analyzer-source.ts +++ b/scripts/src/hwlab-node-web-observe-analyzer-source.ts @@ -1742,6 +1742,12 @@ function sessionInvariantCanarySessionIds(control) { const ids = new Set(); for (const row of control || []) { const detail = objectValue(row?.detail); + if (row?.type === "newSession" && row?.phase === "completed") { + const sessionId = stringOrNull(detail.sessionId) + ?? stringOrNull(detail.result?.sessionId) + ?? stringOrNull(detail.createSession?.createdSessionId); + if (sessionId) ids.add(sessionId); + } const canarySessionId = stringOrNull(detail.canarySessionId); if (canarySessionId) ids.add(canarySessionId); } @@ -2373,9 +2379,10 @@ function compactApiDomLagForOutput(report) { }; } -function detectWorkbenchInPlaceProjectionLag(samples, network) { - const terminalTraceMissing = detectWorkbenchTerminalTraceMissing(samples); - const terminalApiDomLag = detectWorkbenchTerminalApiDomLag(samples, network); +function detectWorkbenchInPlaceProjectionLag(samples, network, control = []) { + const canarySessionIds = sessionInvariantCanarySessionIds(control); + const terminalTraceMissing = detectWorkbenchTerminalTraceMissing(samples, canarySessionIds); + const terminalApiDomLag = detectWorkbenchTerminalApiDomLag(samples, network, canarySessionIds); return { terminalTraceMissing, terminalApiDomLag, @@ -2383,12 +2390,13 @@ function detectWorkbenchInPlaceProjectionLag(samples, network) { }; } -function detectWorkbenchTerminalTraceMissing(samples) { +function detectWorkbenchTerminalTraceMissing(samples, canarySessionIds = new Set()) { const budgetMs = Number.isFinite(Number(alertThresholds.sameOriginApiSlowMs)) ? Number(alertThresholds.sameOriginApiSlowMs) : 10_000; const rows = []; const open = new Map(); const sortedSamples = (Array.isArray(samples) ? samples : []) .filter(isWorkbenchPathSample) + .filter((sample) => workbenchSampleMatchesCanarySessions(sample, canarySessionIds)) .slice() .sort((a, b) => Date.parse(a?.ts || "") - Date.parse(b?.ts || "")); const closeSegment = (key, lastSample = null) => { @@ -2446,11 +2454,12 @@ function detectWorkbenchTerminalTraceMissing(samples) { return rows; } -function detectWorkbenchTerminalApiDomLag(samples, network) { +function detectWorkbenchTerminalApiDomLag(samples, network, canarySessionIds = new Set()) { const windowMs = 30_000; const budgetMs = Number.isFinite(Number(alertThresholds.sameOriginApiSlowMs)) ? Number(alertThresholds.sameOriginApiSlowMs) : 10_000; const sampleRows = (Array.isArray(samples) ? samples : []) .filter(isWorkbenchPathSample) + .filter((sample) => workbenchSampleMatchesCanarySessions(sample, canarySessionIds)) .map((sample) => ({ sample, tsMs: Date.parse(sample?.ts || ""), pageKey: samplePageKey(sample) })) .filter((item) => Number.isFinite(item.tsMs)) .sort((a, b) => a.tsMs - b.tsMs); @@ -2462,6 +2471,7 @@ function detectWorkbenchTerminalApiDomLag(samples, network) { } const terminalEvents = (Array.isArray(network) ? network : []) .map(compactWorkbenchTerminalApiEvent) + .filter((item) => workbenchTerminalEventMatchesCanarySessions(item, canarySessionIds)) .filter((item) => item !== null); const overBudget = []; for (const event of terminalEvents) { @@ -2513,6 +2523,19 @@ function detectWorkbenchTerminalApiDomLag(samples, network) { }; } +function workbenchSampleMatchesCanarySessions(sample, canarySessionIds) { + if (!(canarySessionIds instanceof Set) || canarySessionIds.size === 0) return true; + const sessionIds = [sample?.routeSessionId, sample?.activeSessionId].filter(Boolean).map(String); + return sessionIds.some((id) => canarySessionIds.has(id)); +} + +function workbenchTerminalEventMatchesCanarySessions(event, canarySessionIds) { + if (!event) return false; + if (!(canarySessionIds instanceof Set) || canarySessionIds.size === 0) return true; + const eventSessionIds = Array.isArray(event.sessionIds) ? event.sessionIds.map(String) : []; + return eventSessionIds.some((id) => canarySessionIds.has(id)); +} + function compactWorkbenchTerminalApiEvent(item) { if (!item || item.type !== "response" || item.observerInitiated === true) return null; const summary = objectValue(item.bodySummary); @@ -2771,7 +2794,7 @@ function buildFindings(samples, control, network, errors, sampleMetrics, promptN const traceTerminal = samples.some((item) => Array.isArray(item.traceRows) && item.traceRows.some((row) => isTerminalTraceText((row.status || "") + " " + (row.textPreview || "")))); const traceSeen = samples.some((item) => Array.isArray(item.traceRows) && item.traceRows.length > 0); if (traceSeen && !traceTerminal) findings.push({ id: "trace-without-terminal", severity: "amber", summary: "trace rows were visible but no terminal status was sampled", firstTraceSample: ref(samples.find((item) => Array.isArray(item.traceRows) && item.traceRows.length > 0)) }); - const workbenchInPlaceProjectionLag = detectWorkbenchInPlaceProjectionLag(samples, network); + const workbenchInPlaceProjectionLag = detectWorkbenchInPlaceProjectionLag(samples, network, control); if (workbenchInPlaceProjectionLag.terminalTraceMissing.length > 0) findings.push({ id: "workbench-terminal-trace-not-hydrated-in-place", severity: "red",