fix(web-probe): bound observe analyzer to recent samples (#744)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-23 18:14:52 +08:00
committed by GitHub
parent 313187fcdf
commit b4d4882631
@@ -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 {