From 7448803c9d1e1007c17eebb612ec6d9a3cef372b Mon Sep 17 00:00:00 2001 From: lyon Date: Sat, 20 Jun 2026 12:46:29 +0800 Subject: [PATCH] fix: separate performance problems from sample health --- internal/cloud/web-performance.test.ts | 8 +++++--- internal/cloud/web-performance.ts | 11 +++++++---- web/hwlab-cloud-web/src/views/PerformanceView.vue | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/cloud/web-performance.test.ts b/internal/cloud/web-performance.test.ts index 663b02a0..4701b17d 100644 --- a/internal/cloud/web-performance.test.ts +++ b/internal/cloud/web-performance.test.ts @@ -81,8 +81,9 @@ test("web performance summary exposes user-perceived route latency without high- assert.equal(matchedApi?.backendEvidence?.diagnostic, "backend_low_sample"); assert.equal(matchedApi?.backendEvidence?.responseBytesP95, 4096); assert.ok(summary.apiRoutes.some((row) => row.route === "/v1/agent/chat/result/:id" && row.metric === "api_request" && row.p95 >= 2.5)); - assert.ok(summary.problems.some((row) => row.outcome === "timeout" && row.problem === "low-sample" && row.tone === "source" && row.sourceFamily === "rum_api_timing" && row.aggregationKind === "exact" && row.approximation === "exact")); - assert.ok(summary.longTasks.some((row) => row.metric === "long_task" && row.problem === "low-sample" && row.tone === "source")); + assert.ok(summary.problems.some((row) => row.outcome === "timeout" && row.problem === "timeout" && row.sampleState === "low-sample" && row.sourceFamily === "rum_api_timing" && row.aggregationKind === "exact" && row.approximation === "exact")); + assert.equal(summary.problems.some((row) => row.outcome === "ok" && row.problem === "low-sample"), false); + assert.ok(summary.longTasks.some((row) => row.metric === "long_task" && row.problem === "ok" && row.sampleState === "low-sample" && row.tone === "source")); assert.ok(summary.rows.every((row) => row.windowFrom && row.windowTo && row.generatedAt && row.aggregationKind === "exact" && row.approximation === "exact")); assert.doesNotMatch(json, /trc_secret|sessionId|conversationId|threadId|prompt|api key/iu); }); @@ -245,7 +246,8 @@ test("web performance summary exposes Workbench p75 and low-sample diagnostics w assert.ok(summary.workbenchJourneys.some((row) => row.metric === "submit_to_first_visible" && row.backend === "agentrun-v01/codex" && row.p75 >= row.p50 && row.lowSample === true && row.sampleState === "low-sample")); assert.ok(summary.workbenchEventPhases.some((row) => row.phase === "created_to_append" && row.eventType === "backend" && row.transport === "sse")); assert.ok(summary.workbenchBackendEvents.some((row) => row.metric === "backend_event_to_visible" && row.eventType === "terminal" && row.backend === "agentrun-v01/codex")); - assert.ok(summary.problems.some((row) => row.kind === "workbench_journey" && row.problem === "low-sample")); + assert.equal(summary.summary.problemCount, 0); + assert.equal(summary.problems.some((row) => row.kind === "workbench_journey" && row.problem === "low-sample"), false); assert.doesNotMatch(json, /trc_secret|ses_secret|run_secret|cmd_secret|traceId|sessionId|runId|commandId|conversationId|prompt|api key/iu); }); diff --git a/internal/cloud/web-performance.ts b/internal/cloud/web-performance.ts index 1ea62b6d..948f0bc5 100644 --- a/internal/cloud/web-performance.ts +++ b/internal/cloud/web-performance.ts @@ -551,7 +551,7 @@ export function createWebPerformanceStore(options: WebPerformanceStoreOptions = .sort(sortByProblemThenP95) .slice(0, 12); const problems = [...rows, ...workbenchRows] - .filter((row) => row.tone === "blocked" || row.tone === "warn" || row.outcome !== "ok" || row.lowSample === true) + .filter(isPerformanceProblemRow) .sort(sortByProblemThenP95) .slice(0, 16); const sampleCount = [...aggregate.sampleSeries.values()].reduce((sum, entry) => sum + entry.value, 0); @@ -1293,11 +1293,14 @@ function summaryToneForRow(row: PerformanceSummaryRow, p95: number, count: numbe function summaryProblemForRow(row: PerformanceSummaryRow, p95: number, count: number, tone: string) { if (count <= 0) return "no-sample"; - if (count < LOW_SAMPLE_THRESHOLD) return "low-sample"; if (row.kind === "workbench_journey" || row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible") return workbenchProblem(row.outcome, row.metric, count, tone); return performanceProblem({ metric: row.metric, outcome: row.outcome, status_class: row.statusClass }, p95, count, row.unit === "score" ? "score" : "seconds", tone); } +function isPerformanceProblemRow(row: PerformanceSummaryRow) { + return row.tone === "blocked" || row.tone === "warn" || row.outcome !== "ok" || row.statusClass === "network" || row.statusClass === "5xx" || row.statusClass === "4xx"; +} + function sourceFamilyForRow(row: PerformanceSummaryRow) { if (row.kind === "api") return "rum_api_timing"; if (row.kind === "web_vital") return "rum_web_vitals"; @@ -1505,8 +1508,8 @@ function performanceProblem(labels: Record, p95: number, count: function workbenchProblem(outcome: string, metric: string, count: number, tone: string) { if (count <= 0) return "no-sample"; - if (count < LOW_SAMPLE_THRESHOLD) return "low-sample"; if (outcome !== "ok") return outcome; + if (count < LOW_SAMPLE_THRESHOLD) return "low-sample"; if (tone === "blocked" || tone === "warn") return `slow-${metric}`; return "ok"; } @@ -1732,7 +1735,7 @@ function buildPerformanceDashboard(input: { { id: "health", label: "采集状态", value: collectionHealth.statusLabel, unit: "状态", tone: dashboardTone(collectionHealth.status), detail: collectionHealth.reasonLabel }, { id: "samples", label: "样本数", value: sampleCount, unit: "样本", tone: sampleCount === 0 ? "source" : "ok", detail: `${input.sampleWindow.label} · ${numberField(input.summary.sampleSeries)} 个序列` }, { id: "workbench", label: "工作台序列", value: numberField(input.summary.workbenchJourneySeries) + numberField(input.summary.workbenchEventPhaseSeries) + numberField(input.summary.workbenchBackendEventVisibleSeries), unit: "序列", tone: "ok", detail: `${input.namespace} / ${input.gitopsTarget}` }, - { id: "problems", label: "问题行", value: problemCount, unit: "条", tone: problemCount > 0 ? "warn" : "ok", detail: `低样本阈值 ${numberField(input.summary.lowSampleThreshold)} 个样本` } + { id: "problems", label: "问题行", value: problemCount, unit: "条", tone: problemCount > 0 ? "warn" : "ok", detail: "预警、阻塞或非正常结果" } ], trends, distributions: [ diff --git a/web/hwlab-cloud-web/src/views/PerformanceView.vue b/web/hwlab-cloud-web/src/views/PerformanceView.vue index 457d72a8..36fe39b0 100644 --- a/web/hwlab-cloud-web/src/views/PerformanceView.vue +++ b/web/hwlab-cloud-web/src/views/PerformanceView.vue @@ -596,7 +596,7 @@ function diagnosticToneClass(diagnostic: WebPerformanceCollectionHealthDiagnosti - +