From b4d4882631ac71f7a5950ffd8dac64795cfb58f8 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:14:52 +0800 Subject: [PATCH] fix(web-probe): bound observe analyzer to recent samples (#744) Co-authored-by: Codex --- .../hwlab-node-web-observe-runner-source.ts | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index 8c45a43e..e3429d81 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -2023,6 +2023,7 @@ import { createInterface } from "node:readline"; const stateDir = path.resolve(process.env.UNIDESK_WEB_OBSERVE_STATE_DIR || process.argv[2] || ".state/web-observe/manual"); const archivePrefix = safeArchivePrefix(process.env.UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX || ""); +const analyzeTailSamples = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES, 360); const alertThresholds = parseAlertThresholds(process.env.UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON); const dataDir = archivePrefix ? path.join(stateDir, "archive") : stateDir; const dataFile = (name) => path.join(dataDir, archivePrefix ? archivePrefix + "-" + name : name); @@ -2030,12 +2031,14 @@ const analysisDir = path.join(stateDir, "analysis"); const reportJsonPath = path.join(analysisDir, "report.json"); const reportMdPath = path.join(analysisDir, "report.md"); const jsonlReadIssues = []; -const samples = await readJsonl(dataFile("samples.jsonl"), { compact: compactSampleForAnalysis }); -const control = await readJsonl(dataFile("control.jsonl")); -const network = await readJsonl(dataFile("network.jsonl")); -const consoleEvents = await readJsonl(dataFile("console.jsonl")); -const errors = await readJsonl(dataFile("errors.jsonl")); -const artifacts = await readJsonl(dataFile("artifacts.jsonl")); +const sourceSamples = await readJsonl(dataFile("samples.jsonl"), { compact: compactSampleForAnalysis }); +const samples = analyzeTailSamples > 0 ? sourceSamples.slice(-analyzeTailSamples) : sourceSamples; +const sampleWindow = sampleTimeWindow(samples, 60_000); +const control = filterRowsByTimeWindow(await readJsonl(dataFile("control.jsonl")), sampleWindow); +const network = filterRowsByTimeWindow(await readJsonl(dataFile("network.jsonl")), sampleWindow); +const consoleEvents = filterRowsByTimeWindow(await readJsonl(dataFile("console.jsonl")), sampleWindow); +const errors = filterRowsByTimeWindow(await readJsonl(dataFile("errors.jsonl")), sampleWindow); +const artifacts = filterRowsByTimeWindow(await readJsonl(dataFile("artifacts.jsonl")), sampleWindow); const manifest = await readJson(path.join(stateDir, "manifest.json")); const heartbeat = await readJson(path.join(stateDir, "heartbeat.json")); @@ -2096,7 +2099,7 @@ const report = { command: "web-probe-observe analyze", generatedAt: new Date().toISOString(), stateDir, - jsonlScope: { mode: archivePrefix ? "archive" : "current", archivePrefix: archivePrefix || null, dataDir, valuesRedacted: true }, + jsonlScope: { mode: archivePrefix ? "archive" : "current", archivePrefix: archivePrefix || null, dataDir, analyzeTailSamples, sourceSampleCount: sourceSamples.length, effectiveSampleCount: samples.length, sampleWindow, valuesRedacted: true }, alertThresholds, manifest: compactManifest(manifest), heartbeat: compactHeartbeat(heartbeat), @@ -2343,6 +2346,26 @@ async function readJsonl(file, options = {}) { } } +function sampleTimeWindow(samples, paddingMs) { + const times = samples + .map((item) => Date.parse(String(item && item.ts || ""))) + .filter((value) => Number.isFinite(value)); + if (times.length === 0) return { startMs: null, endMs: null, startAt: null, endAt: null, paddingMs }; + const startMs = Math.max(0, Math.min(...times) - Math.max(0, paddingMs || 0)); + const endMs = Math.max(...times) + Math.max(0, paddingMs || 0); + return { startMs, endMs, startAt: new Date(startMs).toISOString(), endAt: new Date(endMs).toISOString(), paddingMs }; +} + +function filterRowsByTimeWindow(rows, window) { + if (!window || !Number.isFinite(window.startMs) || !Number.isFinite(window.endMs)) return rows; + return rows.filter((item) => { + const value = item && (item.ts || item.observedAt || item.startedAt || item.finishedAt || item.createdAt || item.updatedAt); + const ms = Date.parse(String(value || "")); + if (!Number.isFinite(ms)) return true; + return ms >= window.startMs && ms <= window.endMs; + }); +} + function compactSampleForAnalysis(sample) { if (!sample || typeof sample !== "object") return sample; return {