Miguel de Benito Delgado f81696886a ✨ Add feature-flag A/B harness to renderer benchmarks
Compare every on/off combination of requested Cargo features over the
Boolean-lattice edges, in Gray-code build order, with a matrix index of
configs, edge comparisons and provenance.

Single runs record explicit feature sets as schemaVersion 2 with resolved
defaults and build environments. Edge comparisons stay diagnostic; edges
clean apart from the intended toggle are marked controlled.

Validate with 68 focused tests, CLI fault checks, dry runs, and container
smoke matrices for branch-b pan cases.

AI-assisted-by: muse-spark-1.3-contributor-free
2026-10-28 19:17:38 +01:00

324 lines
10 KiB
JavaScript

import {
compareObservations,
metricNames,
observations,
statisticalMethod,
STATISTICAL_WARNINGS,
} from "../../resources/wasm-playground/benchmarks/statistics.js";
function record(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function requireValid(condition, message) {
if (!condition) throw new Error(`Invalid benchmark result: ${message}`);
}
export function validateResult(result) {
requireValid(
record(result) && result.schemaVersion === 2,
"expected schemaVersion 2",
);
requireValid(record(result.metadata), "metadata is required");
for (const field of ["environment", "configuration"])
requireValid(
record(result.metadata[field]),
`metadata.${field} is required`,
);
requireValid(Array.isArray(result.cases), "cases must be an array");
const ids = new Set();
for (const benchmarkCase of result.cases) {
requireValid(record(benchmarkCase), "case must be an object");
requireValid(
typeof benchmarkCase.id === "string" &&
benchmarkCase.id.length &&
!ids.has(benchmarkCase.id),
"case IDs must be unique nonempty strings",
);
ids.add(benchmarkCase.id);
for (const field of ["scenarioId", "group", "preparation"])
requireValid(
typeof benchmarkCase[field] === "string" && benchmarkCase[field].length,
`${benchmarkCase.id}.${field} is required`,
);
requireValid(
Number.isInteger(benchmarkCase.scenarioVersion) &&
benchmarkCase.scenarioVersion >= 1,
"scenarioVersion must be a positive integer",
);
requireValid(
Number.isInteger(benchmarkCase.sceneSeed) &&
benchmarkCase.sceneSeed >= 0 &&
benchmarkCase.sceneSeed <= 0xffffffff,
"sceneSeed must be a uint32",
);
for (const field of ["params", "initialView"])
requireValid(
record(benchmarkCase[field]),
`${benchmarkCase.id}.${field} is required`,
);
requireValid(
benchmarkCase.interaction === null || record(benchmarkCase.interaction),
"interaction must be an object or null",
);
requireValid(
["complete", "failed", "interrupted", "unattempted"].includes(
benchmarkCase.status,
),
"unknown case status",
);
requireValid(
Array.isArray(benchmarkCase.attempts),
"attempts must be an array",
);
for (const attempt of benchmarkCase.attempts) {
requireValid(
record(attempt) && ["ok", "invalid", "failed"].includes(attempt.status),
"unknown attempt status",
);
requireValid(
typeof attempt.warmup === "boolean",
"attempt.warmup must be boolean",
);
requireValid(
record(attempt.metrics),
"attempt.metrics must be an object",
);
requireValid(
attempt.invalidMetrics === undefined || record(attempt.invalidMetrics),
"invalidMetrics must be an object",
);
for (const [name, value] of Object.entries(attempt.metrics))
requireValid(
(value === null &&
Object.hasOwn(attempt.invalidMetrics ?? {}, name)) ||
(Number.isFinite(value) && value >= 0),
`metric ${name} must be finite and nonnegative, or explicitly invalid null`,
);
for (const reason of Object.values(attempt.invalidMetrics ?? {}))
requireValid(
typeof reason === "string" && reason.length,
"invalid metric reasons must be nonempty strings",
);
}
}
}
function differences(baseline, candidate, path, mismatches) {
if (record(baseline) && record(candidate)) {
for (const key of [
...new Set([...Object.keys(baseline), ...Object.keys(candidate)]),
].sort())
differences(baseline[key], candidate[key], `${path}.${key}`, mismatches);
} else if (Array.isArray(baseline) && Array.isArray(candidate)) {
if (baseline.length !== candidate.length)
mismatches.push({ path, baseline, candidate });
else
baseline.forEach((value, index) =>
differences(value, candidate[index], `${path}[${index}]`, mismatches),
);
} else if (baseline !== candidate)
mismatches.push({
path,
baseline: baseline ?? null,
candidate: candidate ?? null,
});
}
export class CompatibilityError extends Error {
constructor(mismatches) {
super(
`Incompatible benchmark runs: ${mismatches.map((entry) => entry.path).join(", ")}. Use an explicit diagnostic comparison to inspect mismatches.`,
);
this.name = "CompatibilityError";
this.mismatches = mismatches;
}
}
// Tags the expected per-edge feature difference of a feature-matrix comparison
// without suppressing it: the `metadata.build.features` mismatch stays listed
// but carries the toggling feature. Mutates the comparison's mismatch entries
// in place; no I/O.
export function tagExpectedFeatureDiff(comparison, toggledFeature) {
for (const entry of comparison.mismatches) {
if (entry.path === "metadata.build.features") {
entry.expected = true;
entry.toggledFeature = toggledFeature;
}
}
return comparison;
}
export function compareRuns(
baseline,
candidate,
{ diagnostic = false, ...options } = {},
) {
validateResult(baseline);
validateResult(candidate);
const mismatches = [];
// Compare renderer revisions, but never silently mix debug/profiling builds
// with ordinary release measurements.
for (const field of [
"mode",
"features",
"target",
"defaultFeatures",
"env",
"ambientEnv",
]) {
differences(
baseline.metadata.build?.[field],
candidate.metadata.build?.[field],
`metadata.build.${field}`,
mismatches,
);
}
for (const field of ["environment", "configuration", "metricSemantics"])
differences(
baseline.metadata[field],
candidate.metadata[field],
`metadata.${field}`,
mismatches,
);
const firstCases = new Map(
baseline.cases.map((benchmarkCase) => [benchmarkCase.id, benchmarkCase]),
);
const secondCases = new Map(
candidate.cases.map((benchmarkCase) => [benchmarkCase.id, benchmarkCase]),
);
const caseIds = [
...new Set([...firstCases.keys(), ...secondCases.keys()]),
].sort();
for (const id of caseIds) {
const first = firstCases.get(id);
const second = secondCases.get(id);
if (!first || !second) {
mismatches.push({
path: `cases.${id}`,
baseline: Boolean(first),
candidate: Boolean(second),
});
continue;
}
for (const field of [
"scenarioId",
"scenarioVersion",
"sceneSeed",
"params",
"initialView",
"interaction",
"group",
"preparation",
"environment",
"metricSemantics",
])
differences(
first[field],
second[field],
`cases.${id}.${field}`,
mismatches,
);
// A failed attempt may lack metrics. Only compare present metric sets when
// both cases completed; failures still produce honest sample counts.
if (first.status === "complete" && second.status === "complete")
differences(
metricNames(first),
metricNames(second),
`cases.${id}.metrics`,
mismatches,
);
}
if (mismatches.length && !diagnostic)
throw new CompatibilityError(mismatches);
const runs = Object.fromEntries(
[
["baseline", baseline],
["candidate", candidate],
].map(([name, run]) => [
name,
{
runId: run.runId ?? null,
status: run.status ?? "unknown",
timestamp: run.timestamp ?? null,
git: run.metadata.git ?? null,
},
]),
);
return {
schemaVersion: 1,
runs,
compatible: mismatches.length === 0,
diagnostic,
mismatches,
method: statisticalMethod(options),
warnings: [
...Object.entries(runs)
.filter(([, run]) => run.status !== "complete")
.map(
([name, run]) =>
`The ${name} run is ${run.status}; this comparison covers only available valid observations.`,
),
...(mismatches.length
? [
"DIAGNOSTIC ONLY: these runs are incompatible; differences cannot establish a performance change.",
]
: []),
...STATISTICAL_WARNINGS,
],
cases: caseIds
.filter((id) => firstCases.has(id) && secondCases.has(id))
.map((id) => {
const first = firstCases.get(id);
const second = secondCases.get(id);
const metrics = [
...new Set([...metricNames(first), ...metricNames(second)]),
].sort();
return {
id,
metrics: Object.fromEntries(
metrics.map((metric) => [
metric,
compareObservations(
observations(first, metric),
observations(second, metric),
options,
),
]),
),
};
}),
};
}
export function formatComparison(comparison) {
const lines = [
`Statistics: ${comparison.method.name} v${comparison.method.version}; ${comparison.method.resamples} resamples, seed ${comparison.method.seed}.`,
...comparison.warnings,
];
for (const mismatch of comparison.mismatches)
lines.push(
`MISMATCH ${mismatch.path}: ${JSON.stringify(mismatch.baseline)} -> ${JSON.stringify(mismatch.candidate)}`,
);
const display = (value) => (value === null ? "n/a" : value.toFixed(3));
for (const benchmarkCase of comparison.cases) {
lines.push(benchmarkCase.id);
for (const [name, metric] of Object.entries(benchmarkCase.metrics)) {
lines.push(
` ${name}: median ${display(metric.baseline.median)} -> ${display(metric.candidate.median)}, change ${display(metric.absoluteChange)} (${display(metric.relativeChangePercent)}%); n=${metric.baseline.n}/${metric.candidate.n}`,
);
if (metric.absoluteInterval)
lines.push(
` change interval: [${display(metric.absoluteInterval.low)}, ${display(metric.absoluteInterval.high)}]`,
);
if (metric.relativeIntervalPercent)
lines.push(
` relative interval: [${display(metric.relativeIntervalPercent.low)}, ${display(metric.relativeIntervalPercent.high)}]%`,
);
for (const [field, reason] of Object.entries(metric.suppressed))
lines.push(` ${field}: ${reason}`);
}
}
return lines.join("\n");
}