From 702fea6239ac85fe2f01dd9df15c03685fe29697 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:27:42 +0800 Subject: [PATCH] fix(web-probe): avoid loading segment gap overcount (#677) Co-authored-by: Codex --- scripts/src/hwlab-node-impl.ts | 8 +++++--- .../src/hwlab-node-web-observe-runner-source.ts | 16 ++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 530eb15d..b397f06e 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -7819,7 +7819,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec "const slimRunnerError = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, type: clip(v.type, 32), commandId: clip(v.commandId, 80), sampleSeq: v.sampleSeq ?? null, message: clip(v.message, 240), retry: clip(v.retry, 24), retryExhausted: v.retryExhausted === true, lastError: clip(v.lastError, 180) }; };", "const slimJump = (item) => { const v = objectOrNull(item) || {}; return { columnLabel: v.columnLabel ?? null, promptIndex: v.promptIndex ?? null, fromSeq: v.fromSeq ?? null, toSeq: v.toSeq ?? null, fromValue: v.fromValue ?? null, toValue: v.toValue ?? null, delta: v.delta ?? null, sampleDeltaSeconds: v.sampleDeltaSeconds ?? null, allowedIncreaseSeconds: v.allowedIncreaseSeconds ?? null, traceId: v.traceId ?? null }; };", "const slimLoadingOwner = (item) => { const v = objectOrNull(item) || {}; return { ownerKey: clip(v.ownerKey, 120), ownerKind: clip(v.ownerKind, 32), ownerLabel: clip(v.ownerLabel, 120), sampleCount: v.sampleCount ?? null, occurrenceCount: v.occurrenceCount ?? null, maxSimultaneousCount: v.maxSimultaneousCount ?? null, longestContinuousSeconds: v.longestContinuousSeconds ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, promptIndexes: Array.isArray(v.promptIndexes) ? v.promptIndexes.slice(0, 8) : [] }; };", - "const slimLoadingSegment = (item) => { const v = objectOrNull(item) || {}; return { firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, endedAt: v.endedAt ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, durationSeconds: v.durationSeconds ?? null, sampleCount: v.sampleCount ?? null, maxCount: v.maxCount ?? null, ownerCount: v.ownerCount ?? null, ongoing: v.ongoing === true, owners: Array.isArray(v.owners) ? v.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), count: owner?.count ?? null })) : [] }; };", + "const slimLoadingSegment = (item) => { const v = objectOrNull(item) || {}; return { firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, endedAt: v.endedAt ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, durationSeconds: v.durationSeconds ?? null, upperBoundSeconds: v.upperBoundSeconds ?? null, endedGapSeconds: v.endedGapSeconds ?? null, sampleCount: v.sampleCount ?? null, maxCount: v.maxCount ?? null, ownerCount: v.ownerCount ?? null, ongoing: v.ongoing === true, owners: Array.isArray(v.owners) ? v.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), count: owner?.count ?? null })) : [] }; };", "const slimLoadingMetrics = (value) => { const v = objectOrNull(value); if (!v) return null; return { summary: objectOrNull(v.summary), longestSegments: takeHead(v.longestSegments ?? v.segments, 8).map(slimLoadingSegment), owners: takeHead(v.owners, 8).map(slimLoadingOwner), timeline: takeTail(v.timeline, 12).map((item) => { const row = objectOrNull(item) || {}; return { seq: row.seq ?? null, ts: row.ts ?? null, promptIndex: row.promptIndex ?? null, loadingCount: row.loadingCount ?? null, ownerCount: row.ownerCount ?? null, owners: Array.isArray(row.owners) ? row.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), count: owner?.count ?? null })) : [] }; }), valuesRedacted: true }; };", "const srcMetrics = objectOrNull(source?.sampleMetrics);", "const metrics = srcMetrics ? {", @@ -8260,14 +8260,16 @@ function renderWebObserveAnalyzeTable(value: Record): string { ]) : [["-", "-", "-", "-", "-", "-"]]), "", "Loading segments:", - webObserveTable(["DURATION_S", "MAX", "OWNERS", "SEQ", "LAST", "STATUS"], loadingSegments.length > 0 ? loadingSegments.map((item) => [ + webObserveTable(["OBSERVED_S", "UPPER_S", "SAMPLES", "MAX", "OWNERS", "SEQ", "LAST", "STATUS"], loadingSegments.length > 0 ? loadingSegments.map((item) => [ webObserveText(item.durationSeconds), + webObserveText(item.upperBoundSeconds ?? item.durationSeconds), + webObserveText(item.sampleCount), webObserveText(item.maxCount), webObserveText(item.ownerCount), `${webObserveText(item.firstSeq)}..${webObserveText(item.lastSeq)}`, webObserveShort(webObserveText(item.lastAt), 24), item.ongoing === true ? "ongoing" : webObserveShort(webObserveText(item.endedAt), 24), - ]) : [["-", "-", "-", "-", "-", "-"]]), + ]) : [["-", "-", "-", "-", "-", "-", "-", "-"]]), "", "Turn columns:", webObserveTable(["TURN", "PROMPT", "TRACE", "FIRST_SEQ", "LAST_SEQ", "SOURCE"], turnColumns.length > 0 ? turnColumns.map((item) => [ diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index 6ebe8564..67ae3d2c 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -3140,10 +3140,10 @@ function loadingContinuityThresholdMs(events) { const delta = events[index].tsMs - events[index - 1].tsMs; if (Number.isFinite(delta) && delta > 0) deltas.push(delta); } - if (deltas.length === 0) return 15000; + if (deltas.length === 0) return 5000; const sorted = deltas.slice().sort((a, b) => a - b); const median = sorted[Math.floor(sorted.length / 2)]; - return Math.max(15000, Math.round(median * 2.5)); + return Math.min(15000, Math.max(1500, Math.round(median * 2.5))); } function buildLoadingSegments(events, continuityThresholdMs, countForEvent, ownersForEvent) { @@ -3196,9 +3196,11 @@ function buildLoadingSegments(events, continuityThresholdMs, countForEvent, owne function finalizeLoadingSegment(segment, absentEvent) { const startMs = Date.parse(segment.firstAt || ""); - const endAnchor = absentEvent?.ts || segment.lastAt; - const endMs = Date.parse(endAnchor || ""); - const durationSeconds = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs ? Number(((endMs - startMs) / 1000).toFixed(3)) : 0; + const lastMs = Date.parse(segment.lastAt || ""); + const absentMs = Date.parse(absentEvent?.ts || ""); + const durationSeconds = Number.isFinite(startMs) && Number.isFinite(lastMs) && lastMs >= startMs ? Number(((lastMs - startMs) / 1000).toFixed(3)) : 0; + const upperBoundSeconds = Number.isFinite(startMs) && Number.isFinite(absentMs) && absentMs >= startMs ? Number(((absentMs - startMs) / 1000).toFixed(3)) : durationSeconds; + const endedGapSeconds = Number.isFinite(lastMs) && Number.isFinite(absentMs) && absentMs >= lastMs ? Number(((absentMs - lastMs) / 1000).toFixed(3)) : null; return { firstAt: segment.firstAt, lastAt: segment.lastAt, @@ -3207,6 +3209,8 @@ function finalizeLoadingSegment(segment, absentEvent) { lastSeq: segment.lastSeq, endSeq: absentEvent?.seq ?? null, durationSeconds, + upperBoundSeconds, + endedGapSeconds, sampleCount: segment.sampleCount, maxCount: segment.maxCount, ownerCount: segment.ownerKeys.size, @@ -4067,7 +4071,7 @@ function renderMarkdown(report) { ? report.sampleMetrics.rounds.map((item) => "- round " + item.promptIndex + " promptHash=" + (item.promptTextHash || "-") + " samples=" + item.sampleCount + " loadingSamples=" + (item.loadingSamples ?? 0) + " maxLoading=" + (item.maxLoadingCount ?? 0) + " loadingOwners=" + (item.loadingOwnerCount ?? 0) + " totalMax=" + (item.maxTotalElapsedSeconds ?? "-") + " totalLast=" + (item.lastTotalElapsedSeconds ?? "-") + " recentMax=" + (item.maxRecentUpdateSeconds ?? "-") + " recentLast=" + (item.lastRecentUpdateSeconds ?? "-") + " totalDecrease=" + (item.turnTimingTotalElapsedDecreaseCount ?? 0) + " totalForwardJump=" + (item.turnTimingTotalElapsedForwardJumpCount ?? 0) + " totalForwardJumpMax=" + (item.turnTimingTotalElapsedForwardJumpMaxSeconds ?? 0) + " terminalGrowth=" + (item.turnTimingTerminalElapsedGrowthCount ?? 0) + " terminalGrowthMax=" + (item.turnTimingTerminalElapsedGrowthMaxSeconds ?? 0) + " recentJump=" + (item.turnTimingRecentUpdateJumpCount ?? 0) + " recentSawtoothJump=" + (item.turnTimingRecentUpdateSawtoothJumpCount ?? item.turnTimingRecentUpdateJumpCount ?? 0) + " recentStep=" + (item.turnTimingRecentUpdateStepCount ?? 0) + " recentMaxIncrease=" + (item.turnTimingRecentUpdateMaxIncreaseSeconds ?? "-") + " recentMaxExcess=" + (item.turnTimingRecentUpdateMaxExcessSeconds ?? 0) + " recentReset=" + (item.turnTimingRecentUpdateResetCount ?? 0) + " diagnostics=" + item.diagnosticSamples + " terminal=" + item.terminalSamples + " finalText=" + item.finalTextSamples).join("\n") : "- 无轮次指标。"; const loadingSegmentLines = Array.isArray(loading.segments) && loading.segments.length > 0 - ? loading.segments.slice(0, 80).map((item) => "- duration=" + (item.durationSeconds ?? 0) + "s countMax=" + (item.maxCount ?? 0) + " owners=" + (item.ownerCount ?? 0) + " seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " ts=" + (item.firstAt || "-") + ".." + (item.lastAt || "-") + " endedAt=" + (item.endedAt || (item.ongoing ? "ongoing" : "-")) + " ownerLabels=" + ((Array.isArray(item.owners) ? item.owners : []).slice(0, 6).map((owner) => (owner.ownerKind || "-") + ":" + (owner.ownerLabel || "-") + "x" + (owner.count ?? 0)).join(",") || "-")).join("\n") + ? loading.segments.slice(0, 80).map((item) => "- observedDuration=" + (item.durationSeconds ?? 0) + "s upperBound=" + (item.upperBoundSeconds ?? item.durationSeconds ?? 0) + "s endedGap=" + (item.endedGapSeconds ?? "-") + "s samples=" + (item.sampleCount ?? 0) + " countMax=" + (item.maxCount ?? 0) + " owners=" + (item.ownerCount ?? 0) + " seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " ts=" + (item.firstAt || "-") + ".." + (item.lastAt || "-") + " endedAt=" + (item.endedAt || (item.ongoing ? "ongoing" : "-")) + " ownerLabels=" + ((Array.isArray(item.owners) ? item.owners : []).slice(0, 6).map((owner) => (owner.ownerKind || "-") + ":" + (owner.ownerLabel || "-") + "x" + (owner.count ?? 0)).join(",") || "-")).join("\n") : "- 未观察到“加载中”可见区间。"; const loadingOwnerLines = Array.isArray(loading.owners) && loading.owners.length > 0 ? loading.owners.slice(0, 80).map((item) => "- " + (item.ownerKind || "-") + " " + escapeMarkdownCell(item.ownerLabel || item.ownerKey || "-") + " samples=" + (item.sampleCount ?? 0) + " occurrences=" + (item.occurrenceCount ?? 0) + " maxCount=" + (item.maxSimultaneousCount ?? 0) + " longest=" + (item.longestContinuousSeconds ?? 0) + "s seq=" + (item.firstSeq ?? "-") + ".." + (item.lastSeq ?? "-") + " prompts=" + (Array.isArray(item.promptIndexes) ? item.promptIndexes.join(",") : "-")).join("\n")