From 71c73da1bc29b232c9b07f661d1188fee7aa0434 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:59:38 +0800 Subject: [PATCH] fix(web-probe): focus analysis after latest new session (#833) Co-authored-by: Codex --- scripts/src/hwlab-node-impl.ts | 6 ++- .../hwlab-node-web-observe-runner-source.ts | 46 +++++++++++++++---- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 4ea2cdb1..b95aaa2a 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -8678,6 +8678,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec " const minimal = {", " ok: compact.ok,", " counts: compact.counts ?? null,", + " jsonlScope: compact.jsonlScope ?? null,", " analysisWindow: compact.analysisWindow ?? null,", " archiveSummary: compact.archiveSummary ?? null,", " sampleMetrics: compact.sampleMetrics ? {", @@ -8745,6 +8746,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec " const tiny = {", " ok: compact.ok,", " counts: compact.counts ?? null,", + " jsonlScope: compact.jsonlScope ?? null,", " analysisWindow: compact.analysisWindow ?? null,", " archiveSummary: compact.archiveSummary ?? null,", " sampleMetrics: compact.sampleMetrics ? {", @@ -8832,7 +8834,7 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec " output = compactOutput(ultratiny);", " }", " if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {", - " output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ?? null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });", + " output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, jsonlScope: compact.jsonlScope ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ?? null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });", " }", " }", "}", @@ -9166,7 +9168,7 @@ function renderWebObserveAnalyzeTable(value: Record): string { webObserveText(value.id), webObserveText(value.node), webObserveText(value.lane), - webObserveShort([webObserveText(jsonlScope?.mode ?? "current"), jsonlScope?.archivePrefix ? webObserveText(jsonlScope.archivePrefix) : ""].filter((part) => part && part !== "-").join(":"), 36), + webObserveShort([webObserveText(jsonlScope?.mode ?? "current"), webObserveText(record(jsonlScope?.focus)?.mode), jsonlScope?.archivePrefix ? webObserveText(jsonlScope.archivePrefix) : ""].filter((part) => part && part !== "-").join(":"), 36), webObserveText(counts?.samples), webObserveText(counts?.control), webObserveText(counts?.network), diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index efc67c9e..7793713f 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -2116,15 +2116,18 @@ const reportJsonPath = path.join(analysisDir, "report.json"); const reportMdPath = path.join(analysisDir, "report.md"); const jsonlReadIssues = []; const sourceSamples = await readJsonl(dataFile("samples.jsonl"), { compact: compactSampleForAnalysis, tail: analyzeTailSamples }); -const samples = sourceSamples; -const sampleWindow = sampleTimeWindow(samples, 60_000); const relatedJsonlTailLimit = analyzeTailSamples > 0 ? Math.max(1200, analyzeTailSamples * 50) : 0; const smallJsonlTailLimit = analyzeTailSamples > 0 ? Math.max(500, analyzeTailSamples * 8) : 0; -const control = filterRowsByTimeWindow(await readJsonl(dataFile("control.jsonl"), { tail: relatedJsonlTailLimit }), sampleWindow); -const network = filterRowsByTimeWindow(await readJsonl(dataFile("network.jsonl"), { tail: relatedJsonlTailLimit }), sampleWindow); -const consoleEvents = filterRowsByTimeWindow(await readJsonl(dataFile("console.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow); -const errors = filterRowsByTimeWindow(await readJsonl(dataFile("errors.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow); -const artifacts = filterRowsByTimeWindow(await readJsonl(dataFile("artifacts.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow); +const sourceSampleWindow = sampleTimeWindow(sourceSamples, 60_000); +const sourceControl = filterRowsByTimeWindow(await readJsonl(dataFile("control.jsonl"), { tail: relatedJsonlTailLimit }), sourceSampleWindow); +const analysisFocus = analysisFocusFromControl(sourceControl); +const samples = applyAnalysisFocus(sourceSamples, analysisFocus); +const sampleWindow = sampleTimeWindow(samples, 60_000); +const control = applyAnalysisFocus(filterRowsByTimeWindow(sourceControl, sampleWindow), analysisFocus, 1000); +const network = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("network.jsonl"), { tail: relatedJsonlTailLimit }), sampleWindow), analysisFocus); +const consoleEvents = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("console.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus); +const errors = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("errors.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus); +const artifacts = applyAnalysisFocus(filterRowsByTimeWindow(await readJsonl(dataFile("artifacts.jsonl"), { tail: smallJsonlTailLimit }), sampleWindow), analysisFocus); const manifest = await readJson(path.join(stateDir, "manifest.json")); const heartbeat = await readJson(path.join(stateDir, "heartbeat.json")); @@ -2185,7 +2188,7 @@ const report = { command: "web-probe-observe analyze", generatedAt: new Date().toISOString(), stateDir, - jsonlScope: { mode: archivePrefix ? "archive" : "current", archivePrefix: archivePrefix || null, dataDir, analyzeTailSamples, sourceSampleCount: sourceSamples.length, effectiveSampleCount: samples.length, sampleWindow, valuesRedacted: true }, + jsonlScope: { mode: archivePrefix ? "archive" : "current", archivePrefix: archivePrefix || null, dataDir, analyzeTailSamples, sourceSampleCount: sourceSamples.length, effectiveSampleCount: samples.length, sampleWindow, focus: analysisFocus, valuesRedacted: true }, alertThresholds, manifest: compactManifest(manifest), heartbeat: compactHeartbeat(heartbeat), @@ -2601,6 +2604,33 @@ function filterRowsByTimeWindow(rows, window) { }); } +function analysisFocusFromControl(control) { + const completedNewSessions = (Array.isArray(control) ? control : []) + .filter((item) => item?.type === "newSession" && item?.phase === "completed" && Number.isFinite(Date.parse(String(item.ts || "")))) + .sort((a, b) => Date.parse(String(a.ts || "")) - Date.parse(String(b.ts || ""))); + const latest = completedNewSessions.at(-1) ?? null; + if (!latest) return { mode: "all", reason: "no-new-session-command", startAt: null, startMs: null, commandId: null, valuesRedacted: true }; + const startMs = Date.parse(String(latest.ts)); + return { + mode: "after-new-session", + reason: "latest-completed-new-session", + startAt: new Date(startMs).toISOString(), + startMs, + commandId: latest.commandId ?? null, + valuesRedacted: true + }; +} + +function applyAnalysisFocus(rows, focus, graceMs = 0) { + if (!focus || !Number.isFinite(focus.startMs)) return rows; + const minMs = focus.startMs - Math.max(0, Number(graceMs) || 0); + return (Array.isArray(rows) ? rows : []).filter((item) => { + const value = item && (item.ts || item.observedAt || item.startedAt || item.finishedAt || item.createdAt || item.updatedAt); + const ms = Date.parse(String(value || "")); + return Number.isFinite(ms) ? ms >= minMs : true; + }); +} + function compactSampleForAnalysis(sample) { if (!sample || typeof sample !== "object") return sample; return {