From 382d345a3e1035549929f0d613245b3ead6e06de Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Wed, 24 Jun 2026 03:26:12 +0800 Subject: [PATCH] fix(web-probe): scope round timing to prompt turns (#769) Co-authored-by: Codex --- .../hwlab-node-web-observe-runner-source.ts | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index f349acfb..98db13db 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -3983,6 +3983,8 @@ function buildSampleMetrics(samples, control) { const loading = buildLoadingMetrics(samples, timeline); const sessionRailTitles = buildSessionRailTitleMetrics(samples, timeline); const rounds = buildRoundMetricSummaries(timeline, promptCommands, { + columns: turnTiming.columns, + rows: turnTiming.rows, nonMonotonic: turnTimingNonMonotonic, elapsedZeroResets: turnTimingElapsedZeroResets, totalElapsedForwardJumps: turnTimingTotalElapsedForwardJumps, @@ -5757,6 +5759,8 @@ function firstTraceId(texts) { function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) { const rounds = []; + const timingColumns = Array.isArray(timing.columns) ? timing.columns : []; + const timingRows = Array.isArray(timing.rows) ? timing.rows : []; const nonMonotonic = Array.isArray(timing.nonMonotonic) ? timing.nonMonotonic : []; const totalElapsedForwardJumps = Array.isArray(timing.totalElapsedForwardJumps) ? timing.totalElapsedForwardJumps : []; const elapsedZeroResets = Array.isArray(timing.elapsedZeroResets) ? timing.elapsedZeroResets : []; @@ -5766,8 +5770,14 @@ function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) { for (let index = 0; index < promptCommands.length; index += 1) { const promptIndex = index + 1; const items = timeline.filter((item) => item.promptIndex === promptIndex); - const totalElapsed = items.map((item) => item.totalElapsedSeconds).filter((value) => value !== null); - const recentUpdate = items.map((item) => item.recentUpdateSeconds).filter((value) => value !== null); + const aggregateTotalElapsed = items.map((item) => item.totalElapsedSeconds).filter((value) => value !== null); + const aggregateRecentUpdate = items.map((item) => item.recentUpdateSeconds).filter((value) => value !== null); + const promptTurnTiming = roundPromptTurnTimingValues(timingRows, timingColumns, promptIndex, { + firstSeq: items[0]?.seq ?? null, + firstSampleAt: items[0]?.ts ?? null + }); + const totalElapsed = promptTurnTiming.totalElapsed.length > 0 ? promptTurnTiming.totalElapsed : aggregateTotalElapsed; + const recentUpdate = promptTurnTiming.recentUpdate.length > 0 ? promptTurnTiming.recentUpdate : aggregateRecentUpdate; const loadingCounts = items.map((item) => Number(item.loadingCount ?? 0)).filter(Number.isFinite); const loadingOwners = new Set(); for (const item of items) { @@ -5801,9 +5811,9 @@ function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) { maxLoadingCount: loadingCounts.length > 0 ? Math.max(...loadingCounts) : 0, loadingOwnerCount: loadingOwners.size, maxTotalElapsedSeconds: totalElapsed.length > 0 ? Math.max(...totalElapsed) : null, - lastTotalElapsedSeconds: lastNonNull(items.map((item) => item.totalElapsedSeconds)), + lastTotalElapsedSeconds: lastNonNull(totalElapsed), maxRecentUpdateSeconds: recentUpdate.length > 0 ? Math.max(...recentUpdate) : null, - lastRecentUpdateSeconds: lastNonNull(items.map((item) => item.recentUpdateSeconds)), + lastRecentUpdateSeconds: lastNonNull(recentUpdate), diagnosticSamples: items.filter((item) => item.diagnosticSeen).length, terminalSamples: items.filter((item) => item.terminalSeen).length, finalTextSamples: items.filter((item) => item.finalResultTextSeen).length, @@ -5826,6 +5836,38 @@ function buildRoundMetricSummaries(timeline, promptCommands, timing = {}) { return rounds; } +function roundPromptTurnTimingValues(rows, columns, promptIndex, round = {}) { + const roundFirstSeq = Number(round.firstSeq); + const roundFirstMs = Date.parse(String(round.firstSampleAt ?? "")); + const seqSlack = 3; + const timeSlackMs = 3000; + const promptColumnIds = new Set(columns + .filter((column) => { + if (Number(column?.promptIndex) === Number(promptIndex)) return true; + const firstSeq = Number(column?.firstSeq); + if (Number.isFinite(roundFirstSeq) && Number.isFinite(firstSeq) && firstSeq >= roundFirstSeq - seqSlack) return true; + const firstMs = Date.parse(String(column?.firstTs ?? "")); + return Number.isFinite(roundFirstMs) && Number.isFinite(firstMs) && firstMs >= roundFirstMs - timeSlackMs; + }) + .map((column) => String(column?.id || "")) + .filter(Boolean)); + if (promptColumnIds.size === 0) return { totalElapsed: [], recentUpdate: [] }; + const totalElapsed = []; + const recentUpdate = []; + for (const row of rows) { + if (Number(row?.promptIndex ?? 0) !== Number(promptIndex)) continue; + const cells = row?.cells && typeof row.cells === "object" ? row.cells : {}; + for (const [columnId, cell] of Object.entries(cells)) { + if (!promptColumnIds.has(columnId)) continue; + const total = Number(cell?.totalElapsedSeconds); + if (Number.isFinite(total)) totalElapsed.push(total); + const recent = Number(cell?.recentUpdateSeconds); + if (Number.isFinite(recent)) recentUpdate.push(recent); + } + } + return { totalElapsed, recentUpdate }; +} + function sampleTexts(sample) { const rows = []; for (const group of [sample?.messages, sample?.traceRows, sample?.diagnostics]) {