Merge pull request #1694 from pikasTech/codex/hwlab-1691-attribution

fix: scope workbench performance attribution health
This commit is contained in:
Lyon
2026-06-20 12:33:55 +08:00
committed by GitHub
3 changed files with 59 additions and 6 deletions
+45
View File
@@ -249,6 +249,51 @@ test("web performance summary exposes Workbench p75 and low-sample diagnostics w
assert.doesNotMatch(json, /trc_secret|ses_secret|run_secret|cmd_secret|traceId|sessionId|runId|commandId|conversationId|prompt|api key/iu);
});
test("web performance collection health only requires backend attribution for backend-scoped Workbench series", () => {
const frontendOnly = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v03", HWLAB_GITOPS_TARGET: "v03" } });
frontendOnly.record({
schemaVersion: "hwlab-web-performance-v2",
page: "/workbench",
events: Array.from({ length: 5 }, () => ({
kind: "workbench_journey",
journey: "workbench_open_first_visible",
route: "/workbench",
cache: "warm",
authState: "warm",
visibility: "foreground",
outcome: "ok",
valueMs: 120
}))
});
const frontendSummary = frontendOnly.summary();
assert.equal(frontendSummary.collectionHealth.status, "fresh");
assert.equal(frontendSummary.collectionHealth.unknownAttribution.workbenchRows, 1);
assert.equal(frontendSummary.collectionHealth.unknownAttribution.attributableRows, 0);
assert.equal(frontendSummary.collectionHealth.unknownAttribution.rowsWithUnknown, 0);
assert.equal(frontendSummary.collectionHealth.diagnostics.some((item) => item.code === "unknown_attribution"), false);
const backendScoped = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v03", HWLAB_GITOPS_TARGET: "v03" } });
backendScoped.record({
schemaVersion: "hwlab-web-performance-v2",
page: "/workbench/sessions/ses_secret",
events: Array.from({ length: 5 }, () => ({
kind: "workbench_journey",
journey: "submit_to_first_visible",
route: "/workbench/sessions/ses_secret",
entry: "existing",
visibility: "foreground",
outcome: "ok",
valueMs: 600
}))
});
const backendSummary = backendScoped.summary();
assert.equal(backendSummary.collectionHealth.status, "degraded");
assert.equal(backendSummary.collectionHealth.unknownAttribution.attributableRows, 1);
assert.equal(backendSummary.collectionHealth.unknownAttribution.rowsWithUnknown, 1);
assert.equal(backendSummary.collectionHealth.unknownAttribution.samplesWithUnknown, 5);
assert.equal(backendSummary.collectionHealth.diagnostics.some((item) => item.code === "unknown_attribution"), true);
});
test("web performance store accepts Workbench UI lifecycle events without high-cardinality labels", () => {
const store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v03", HWLAB_GITOPS_TARGET: "v03" } });
const result = store.record({
+13 -5
View File
@@ -287,6 +287,7 @@ interface PerformanceCollectionHealth {
sourceFamilyCounts: Array<{ sourceFamily: string; rowCount: number; sampleCount: number }>;
unknownAttribution: {
workbenchRows: number;
attributableRows: number;
rowsWithUnknown: number;
samplesWithUnknown: number;
backendUnknownRows: number;
@@ -1623,11 +1624,18 @@ function collectionSourceFamilyCounts(rows: PerformanceSummaryRow[]) {
function collectionUnknownAttribution(rows: PerformanceSummaryRow[]) {
const workbenchRows = rows.filter((row) => row.kind === "workbench_journey" || row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible");
const backendUnknownRows = workbenchRows.filter((row) => unknownAttributionValue(row.backend)).length;
const transportUnknownRows = workbenchRows.filter((row) => unknownAttributionValue(row.transport)).length;
const rowsWithUnknown = workbenchRows.filter((row) => unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)).length;
const samplesWithUnknown = workbenchRows.reduce((sum, row) => sum + ((unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)) ? Math.max(0, Number(row.count) || 0) : 0), 0);
return { workbenchRows: workbenchRows.length, rowsWithUnknown, samplesWithUnknown, backendUnknownRows, transportUnknownRows };
const attributionRows = workbenchRows.filter(rowNeedsWorkbenchAttribution);
const backendUnknownRows = attributionRows.filter((row) => unknownAttributionValue(row.backend)).length;
const transportUnknownRows = attributionRows.filter((row) => unknownAttributionValue(row.transport)).length;
const rowsWithUnknown = attributionRows.filter((row) => unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)).length;
const samplesWithUnknown = attributionRows.reduce((sum, row) => sum + ((unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)) ? Math.max(0, Number(row.count) || 0) : 0), 0);
return { workbenchRows: workbenchRows.length, attributableRows: attributionRows.length, rowsWithUnknown, samplesWithUnknown, backendUnknownRows, transportUnknownRows };
}
function rowNeedsWorkbenchAttribution(row: PerformanceSummaryRow) {
if (row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible") return true;
if (row.kind !== "workbench_journey") return false;
return row.metric === "submit_to_first_visible" || row.metric === "submit_to_failure" || row.metric === "backend_event_to_visible";
}
function unknownAttributionValue(value: unknown) {
+1 -1
View File
@@ -468,7 +468,7 @@ export interface WebPerformanceCollectionHealth extends Record<string, unknown>
secondsSinceLastSample?: number | null;
windowCoverageSeconds?: number | null;
sourceFamilyCounts?: Array<{ sourceFamily?: string; rowCount?: number; sampleCount?: number; [key: string]: unknown }>;
unknownAttribution?: { workbenchRows?: number; rowsWithUnknown?: number; samplesWithUnknown?: number; backendUnknownRows?: number; transportUnknownRows?: number; [key: string]: unknown };
unknownAttribution?: { workbenchRows?: number; attributableRows?: number; rowsWithUnknown?: number; samplesWithUnknown?: number; backendUnknownRows?: number; transportUnknownRows?: number; [key: string]: unknown };
diagnostics?: WebPerformanceCollectionHealthDiagnostic[];
}