fix: separate performance problems from sample health

This commit is contained in:
lyon
2026-06-20 12:46:29 +08:00
parent c12e488680
commit 7448803c9d
3 changed files with 13 additions and 8 deletions
+5 -3
View File
@@ -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);
});
+7 -4
View File
@@ -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<string, string>, 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: [
@@ -596,7 +596,7 @@ function diagnosticToneClass(diagnostic: WebPerformanceCollectionHealthDiagnosti
</section>
</template>
<TablePageLayout title="性能明细" subtitle="来自同一 summary 的问题行和可访问表格 drill-down">
<TablePageLayout title="性能明细" subtitle="来自同一 summary 的性能明细和可访问表格 drill-down">
<template #actions><button class="table-action" type="button" :disabled="table.loading.value" @click="table.reload">刷新</button></template>
<DataTable :columns="detailColumns" :rows="detailRows" :row-key="rowKey" empty-text="暂无性能样本请先打开工作台或执行一次真实页面操作后刷新">
<template #cell-metric="{ row }"><strong>{{ metricLabel(row as WebPerformanceRow) }}</strong><small>{{ kindLabel((row as WebPerformanceRow).kind) }}</small></template>