Merge pull request #1694 from pikasTech/codex/hwlab-1691-attribution
fix: scope workbench performance attribution health
This commit is contained in:
@@ -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);
|
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", () => {
|
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 store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v03", HWLAB_GITOPS_TARGET: "v03" } });
|
||||||
const result = store.record({
|
const result = store.record({
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ interface PerformanceCollectionHealth {
|
|||||||
sourceFamilyCounts: Array<{ sourceFamily: string; rowCount: number; sampleCount: number }>;
|
sourceFamilyCounts: Array<{ sourceFamily: string; rowCount: number; sampleCount: number }>;
|
||||||
unknownAttribution: {
|
unknownAttribution: {
|
||||||
workbenchRows: number;
|
workbenchRows: number;
|
||||||
|
attributableRows: number;
|
||||||
rowsWithUnknown: number;
|
rowsWithUnknown: number;
|
||||||
samplesWithUnknown: number;
|
samplesWithUnknown: number;
|
||||||
backendUnknownRows: number;
|
backendUnknownRows: number;
|
||||||
@@ -1623,11 +1624,18 @@ function collectionSourceFamilyCounts(rows: PerformanceSummaryRow[]) {
|
|||||||
|
|
||||||
function collectionUnknownAttribution(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 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 attributionRows = workbenchRows.filter(rowNeedsWorkbenchAttribution);
|
||||||
const transportUnknownRows = workbenchRows.filter((row) => unknownAttributionValue(row.transport)).length;
|
const backendUnknownRows = attributionRows.filter((row) => unknownAttributionValue(row.backend)).length;
|
||||||
const rowsWithUnknown = workbenchRows.filter((row) => unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)).length;
|
const transportUnknownRows = attributionRows.filter((row) => 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);
|
const rowsWithUnknown = attributionRows.filter((row) => unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)).length;
|
||||||
return { workbenchRows: workbenchRows.length, rowsWithUnknown, samplesWithUnknown, backendUnknownRows, transportUnknownRows };
|
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) {
|
function unknownAttributionValue(value: unknown) {
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ export interface WebPerformanceCollectionHealth extends Record<string, unknown>
|
|||||||
secondsSinceLastSample?: number | null;
|
secondsSinceLastSample?: number | null;
|
||||||
windowCoverageSeconds?: number | null;
|
windowCoverageSeconds?: number | null;
|
||||||
sourceFamilyCounts?: Array<{ sourceFamily?: string; rowCount?: number; sampleCount?: number; [key: string]: unknown }>;
|
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[];
|
diagnostics?: WebPerformanceCollectionHealthDiagnostic[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user