fix: separate performance problems from sample health
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user