@cosyte/bench 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contract.ts","../src/suite.ts","../src/stats.ts","../src/compare.ts","../src/results.ts","../src/spawn.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * The benchmark constants, frozen.\n *\n * Every value here is fixed by the performance standard,\n * `documentation/decisions/0005-the-performance-standard.md` in the `config` repo, which says for\n * each one whether it was measured or chosen, and on what basis. The warmup rule is not here: it\n * is `PERF_CONTRACT` in `@cosyte/test-utils/perf` (ADR 0001 section 2), which `measure.ts` imports\n * and the build bundles, so the scaling gate and the benchmarks warm up by one rule kept in one\n * place.\n *\n * They are not tuning knobs and cannot be overridden per package. A comparison's false alarm rate\n * was measured with these values; change one and the measurement no longer describes the tool.\n *\n * @packageDocumentation\n */\n\n/** The benchmark constants. See {@link BENCH_CONTRACT}. */\nexport interface BenchContractShape {\n /** Timed samples per case per process. Each sample is whole passes over the corpus. */\n readonly SAMPLES: number;\n /** Minimum timed length of one sample, in milliseconds. A sample runs whole passes until it is reached. */\n readonly SAMPLE_MIN_MS: number;\n /** Rounds in one base against head comparison. Each round runs both sides once, in fresh processes. */\n readonly ROUNDS: number;\n /** One-sided error allowed at each end of the comparison's interval on the median ratio. */\n readonly ALPHA: number;\n /** The smallest slowdown a comparison will call a regression, as a fraction: 0.03 is 3%. */\n readonly THRESHOLD: number;\n /** Time budget for the latency calls of one case, in milliseconds. */\n readonly LATENCY_BUDGET_MS: number;\n /** Most latency calls taken for one case. */\n readonly LATENCY_MAX_CALLS: number;\n /** Fewest latency calls taken for one case, whatever the budget says. */\n readonly LATENCY_MIN_CALLS: number;\n /** A percentile q is reported only when at least this many calls lie above it: n(1 - q) >= 10. */\n readonly LATENCY_TAIL_CALLS: number;\n /** The median call must be at least this many timer reads long, or latency is skipped. */\n readonly LATENCY_TIMER_MULTIPLE: number;\n /** Most operations in the first allocation window. Halved while a garbage collection lands in a window. */\n readonly ALLOC_WINDOW_OPS: number;\n /** The first allocation window is cut to about this many milliseconds of operations, from the warmup's pace. */\n readonly ALLOC_WINDOW_MS: number;\n /** The young generation size the allocation process runs with, in MiB, so a window rarely collects. */\n readonly ALLOC_SEMI_SPACE_MIB: number;\n /** Calls measured one by one for the peak heap. */\n readonly PEAK_CALLS: number;\n /** Rounds in the long run that checks the heap stays flat. */\n readonly LONG_RUN_ROUNDS: number;\n /** Rounds at the start of the long run left out of the slope, while caches and compiled code settle. */\n readonly LONG_RUN_SETTLE_ROUNDS: number;\n /** Minimum length of one long run round, in milliseconds, fixed as a number of operations by the first. */\n readonly LONG_RUN_ROUND_MS: number;\n /** A long run whose fitted growth over the measured rounds reaches this many bytes is reported as growing. */\n readonly LONG_RUN_GROWTH_BYTES: number;\n /** A garbage collection round that reclaims less than this, in bytes, counts as settled (ADR 0001 section 8). */\n readonly GC_SETTLE_BYTES: number;\n /** Consecutive settled rounds needed (ADR 0001 section 8). */\n readonly GC_SETTLE_ROUNDS: number;\n /** Most garbage collection rounds before giving up (ADR 0001 section 8). */\n readonly GC_MAX_ROUNDS: number;\n /** Fresh processes timed for the cold import, for the target and for an empty module each. */\n readonly IMPORT_RUNS: number;\n /** The timing comparison may become a required check when A/A false alarms are under this rate. */\n readonly REQUIRED_FALSE_ALARM_RATE: number;\n /** The confidence that the false alarm rate is under the line, for the rule above. */\n readonly REQUIRED_CONFIDENCE: number;\n}\n\n/**\n * The frozen benchmark constants.\n *\n * @example\n * ```ts\n * import { BENCH_CONTRACT } from \"@cosyte/bench\";\n *\n * BENCH_CONTRACT.ROUNDS; // => 12\n * BENCH_CONTRACT.THRESHOLD; // => 0.03\n * ```\n */\nexport const BENCH_CONTRACT: BenchContractShape = Object.freeze({\n SAMPLES: 5,\n SAMPLE_MIN_MS: 50,\n ROUNDS: 12,\n ALPHA: 0.05,\n THRESHOLD: 0.03,\n LATENCY_BUDGET_MS: 2_000,\n LATENCY_MAX_CALLS: 5_000,\n LATENCY_MIN_CALLS: 20,\n LATENCY_TAIL_CALLS: 10,\n LATENCY_TIMER_MULTIPLE: 20,\n ALLOC_WINDOW_OPS: 256,\n ALLOC_WINDOW_MS: 100,\n ALLOC_SEMI_SPACE_MIB: 64,\n PEAK_CALLS: 20,\n LONG_RUN_ROUNDS: 30,\n LONG_RUN_SETTLE_ROUNDS: 5,\n LONG_RUN_ROUND_MS: 100,\n LONG_RUN_GROWTH_BYTES: 64 * 1024,\n GC_SETTLE_BYTES: 64 * 1024,\n GC_SETTLE_ROUNDS: 2,\n GC_MAX_ROUNDS: 7,\n IMPORT_RUNS: 20,\n REQUIRED_FALSE_ALARM_RATE: 0.01,\n REQUIRED_CONFIDENCE: 0.95,\n});\n","/**\n * How a package describes its benchmarks.\n *\n * A suite is a module whose default export is {@link defineSuite}'s result. It is handed the module\n * under test (`bind`) rather than importing it, so one suite file measures the base build and the\n * head build of a comparison. Every corpus is built in code (`build`): the runner never takes a data\n * file, which is how its inputs stay synthetic by construction.\n *\n * @packageDocumentation\n */\n\n/** One corpus: the inputs one pass runs over, built outside every timed region. */\nexport interface BenchCorpus<TInput> {\n /** The inputs. One pass calls the measured function once on each. */\n readonly inputs: readonly TInput[];\n /** Bytes one pass handles, when it means something. Turns operations a second into bytes a second. */\n readonly bytes?: number;\n /** Called once the process has finished with this corpus, for example to remove a temporary directory. */\n readonly teardown?: () => void;\n}\n\n/** One input size of a benchmark: small, large, or anything between. */\nexport interface BenchSize<TInput> {\n /** How a reader names this size, for example `\"1 KiB\"`. Unique within its benchmark. */\n readonly label: string;\n /** Build the corpus. Called once per process, before anything is timed. */\n readonly build: () => BenchCorpus<TInput>;\n /**\n * Whether the base against head comparison runs this size. Defaults to `true`. Set `false` for a\n * size whose processes are too slow for a pull request's time budget; a reading still measures it.\n */\n readonly compare?: boolean;\n}\n\n/** One benchmark: a function measured at one or more input sizes. */\nexport interface Benchmark<TTarget, TInput, TResult> {\n /** Unique within the suite. */\n readonly name: string;\n /** The sizes, smallest first. Each size is measured on its own. */\n readonly sizes: readonly BenchSize<TInput>[];\n /** Take the imported module under test and return the function to measure. */\n readonly bind: (target: TTarget) => (input: TInput) => TResult;\n /**\n * Reduce a result to a number summed into a sink the runner checks, so the compiler cannot delete\n * the measured call. Defaults to 1 for each result that is not `null` or `undefined`; supply it\n * when the function returns nothing. The sum must grow over a measurement, so return a positive\n * number for at least some results; a run whose sink did not grow is refused.\n */\n readonly weigh?: (result: TResult) => number;\n /** The size label the long run (is the heap flat?) uses. Defaults to the first size. */\n readonly longRun?: string;\n}\n\n/** A benchmark with its input and result types erased, as a suite holds it. */\nexport type AnyBenchmark<TTarget> = Benchmark<TTarget, unknown, unknown>;\n\n/** A suite: the benchmarks of one package. */\nexport interface BenchSuite<TTarget> {\n /** Shown in every report, for example `\"@cosyte/script-utils phi-scan\"`. */\n readonly name: string;\n /** At least one. */\n readonly benchmarks: readonly AnyBenchmark<TTarget>[];\n}\n\n/**\n * Type one benchmark. Only a type helper: it returns its argument, with the input and result types\n * erased so benchmarks of different shapes can sit in one suite.\n *\n * @example\n * ```ts\n * import { defineBenchmark } from \"@cosyte/bench\";\n *\n * const b = defineBenchmark({\n * name: \"split\",\n * sizes: [{ label: \"1 KiB\", build: () => ({ inputs: [\"a|b\".repeat(341)] }) }],\n * bind: () => (s: string) => s.split(\"|\"),\n * });\n * b.name; // => \"split\"\n * ```\n */\nexport function defineBenchmark<TTarget, TInput, TResult>(\n benchmark: Benchmark<TTarget, TInput, TResult>,\n): AnyBenchmark<TTarget> {\n return benchmark as unknown as AnyBenchmark<TTarget>;\n}\n\n/**\n * Check a suite and freeze it. Throws a `TypeError` naming the problem: an empty name, no\n * benchmarks, a repeated name or label, a benchmark with no sizes, or a `longRun` naming no size.\n *\n * @example\n * ```ts\n * import { defineBenchmark, defineSuite } from \"@cosyte/bench\";\n *\n * export default defineSuite({\n * name: \"example\",\n * benchmarks: [\n * defineBenchmark({\n * name: \"split\",\n * sizes: [{ label: \"1 KiB\", build: () => ({ inputs: [\"a|b\".repeat(341)] }) }],\n * bind: () => (s: string) => s.split(\"|\"),\n * }),\n * ],\n * });\n * ```\n */\nexport function defineSuite<TTarget>(suite: BenchSuite<TTarget>): BenchSuite<TTarget> {\n return checkSuite(suite);\n}\n\nfunction fail(message: string): never {\n throw new TypeError(`@cosyte/bench: ${message}`);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction checkName(value: unknown, what: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") fail(`${what} must be a non-empty string`);\n return value;\n}\n\n/**\n * Check that a value is a suite, as a suite module's default export must be. The messages name\n * benchmarks and sizes, never an input.\n *\n * @internal\n */\nexport function checkSuite(value: unknown): BenchSuite<unknown> {\n if (!isRecord(value)) fail(\"a suite must be an object made by defineSuite\");\n const name = checkName(value[\"name\"], \"the suite name\");\n const benchmarks = value[\"benchmarks\"];\n if (!Array.isArray(benchmarks) || benchmarks.length === 0) {\n fail(`suite \"${name}\" must have at least one benchmark`);\n }\n const seen = new Set<string>();\n for (const b of benchmarks as unknown[]) {\n if (!isRecord(b)) fail(`suite \"${name}\" holds a benchmark that is not an object`);\n const bName = checkName(b[\"name\"], `a benchmark name in suite \"${name}\"`);\n if (seen.has(bName)) fail(`suite \"${name}\" has two benchmarks named \"${bName}\"`);\n seen.add(bName);\n if (typeof b[\"bind\"] !== \"function\") fail(`benchmark \"${bName}\" has no bind function`);\n if (b[\"weigh\"] !== undefined && typeof b[\"weigh\"] !== \"function\") {\n fail(`benchmark \"${bName}\" has a weigh that is not a function`);\n }\n const sizes = b[\"sizes\"];\n if (!Array.isArray(sizes) || sizes.length === 0) fail(`benchmark \"${bName}\" has no sizes`);\n const labels = new Set<string>();\n for (const s of sizes as unknown[]) {\n if (!isRecord(s)) fail(`benchmark \"${bName}\" holds a size that is not an object`);\n const label = checkName(s[\"label\"], `a size label in benchmark \"${bName}\"`);\n if (labels.has(label)) fail(`benchmark \"${bName}\" has two sizes labelled \"${label}\"`);\n labels.add(label);\n if (typeof s[\"build\"] !== \"function\")\n fail(`size \"${label}\" of \"${bName}\" has no build function`);\n if (s[\"compare\"] !== undefined && typeof s[\"compare\"] !== \"boolean\") {\n fail(`size \"${label}\" of \"${bName}\" has a compare that is not true or false`);\n }\n }\n if (b[\"longRun\"] !== undefined && !labels.has(b[\"longRun\"] as string)) {\n fail(`benchmark \"${bName}\" names a longRun size it does not have`);\n }\n }\n return Object.freeze(value) as unknown as BenchSuite<unknown>;\n}\n\n/** One case: a benchmark at one size. */\nexport interface CaseRef {\n readonly benchmark: string;\n readonly size: string;\n}\n\n/**\n * Every case in a suite, in suite order, optionally kept to the ones `only` names. A name in\n * `only` matches a whole benchmark (`\"scan\"`) or one case (`\"scan @ 1 KiB\"`). A name that matches\n * nothing throws, so a typo cannot quietly measure less than was asked. With `forComparison`, a\n * size marked `compare: false` is left out.\n *\n * @internal\n */\nexport function listCases(\n suite: BenchSuite<unknown>,\n only: readonly string[] = [],\n forComparison = false,\n): CaseRef[] {\n const all: CaseRef[] = [];\n for (const b of suite.benchmarks) {\n for (const s of b.sizes) {\n if (!forComparison || s.compare !== false) all.push({ benchmark: b.name, size: s.label });\n }\n }\n if (only.length === 0) return all;\n for (const o of only) {\n if (!all.some((c) => c.benchmark === o || caseId(c) === o)) {\n fail(`--only \"${o}\" names no benchmark or case in suite \"${suite.name}\"`);\n }\n }\n return all.filter((c) => only.includes(c.benchmark) || only.includes(caseId(c)));\n}\n\n/**\n * How a case is named in reports and in `--only`: `benchmark @ size`.\n *\n * @internal\n */\nexport function caseId(c: CaseRef): string {\n return `${c.benchmark} @ ${c.size}`;\n}\n\n/**\n * Find a case's benchmark and size in a suite. Throws when either is missing.\n *\n * @internal\n */\nexport function findCase(\n suite: BenchSuite<unknown>,\n c: CaseRef,\n): { benchmark: AnyBenchmark<unknown>; size: BenchSize<unknown> } {\n const benchmark = suite.benchmarks.find((b) => b.name === c.benchmark);\n const size = benchmark?.sizes.find((s) => s.label === c.size);\n // No benchmark means no size either, so one check covers both.\n if (size === undefined) fail(`suite \"${suite.name}\" has no case \"${caseId(c)}\"`);\n return { benchmark: benchmark as AnyBenchmark<unknown>, size };\n}\n","/**\n * The statistics the benchmarks report and judge by. Plain arithmetic with no dependencies, each\n * function small enough to check by hand against the worked numbers in its tests.\n *\n * @packageDocumentation\n */\n\n/**\n * A sorted copy, ascending. The input is not touched.\n *\n * @internal\n */\nexport function sortedCopy(xs: readonly number[]): number[] {\n return [...xs].sort((a, b) => a - b);\n}\n\n/**\n * The median: the middle value, or the mean of the two middle values. `NaN` for an empty vector,\n * so an empty measurement can never read as a fast one.\n *\n * @internal\n */\nexport function median(xs: readonly number[]): number {\n const s = sortedCopy(xs);\n const mid = Math.floor(s.length / 2);\n // An even length of at least 2 has both middle values. An empty vector takes the even branch and\n // reads two missing values, which is `NaN`: the empty answer, by arithmetic rather than a check.\n return s.length % 2 === 1\n ? (s[mid] as number)\n : ((s[mid - 1] as number) + (s[mid] as number)) / 2;\n}\n\n/**\n * A percentile by nearest rank: the smallest value with at least a fraction `q` of the values at or\n * below it. No interpolation, so every reported percentile is a value that was observed.\n *\n * @internal\n */\nexport function nearestRank(xs: readonly number[], q: number): number {\n if (xs.length === 0) return Number.NaN;\n const s = sortedCopy(xs);\n const rank = Math.max(1, Math.ceil(q * s.length));\n return s[rank - 1] as number;\n}\n\n/**\n * `P(X <= k)` for `X ~ Binomial(n, p)`, summed in log space so it stays finite for large `n`, for\n * `0 < p < 1` (every caller's range). A negative `k` sums nothing and is 0; a `k` past `n` sums all\n * of it and is 1.\n *\n * @internal\n */\nexport function binomialCdf(k: number, n: number, p: number): number {\n const logP = Math.log(p);\n const logQ = Math.log1p(-p);\n let logCoef = 0;\n let total = 0;\n for (let i = 0; i <= Math.min(k, n); i++) {\n if (i > 0) logCoef += Math.log(n - i + 1) - Math.log(i);\n total += Math.exp(logCoef + i * logP + (n - i) * logQ);\n }\n return Math.min(1, total);\n}\n\n/**\n * The sign test's rank for an interval on the median of `n` values: the largest `k` such that the\n * `k`-th smallest value lies below the true median with probability at most `alpha`, that is\n * `P(Binomial(n, 1/2) <= k - 1) <= alpha`. `0` when no rank qualifies (too few values).\n *\n * @internal\n */\nexport function signTestRank(n: number, alpha: number): number {\n let k = 0;\n while (k < n && binomialCdf(k, n, 0.5) <= alpha) k++;\n return k;\n}\n\n/** An interval on a median, from the sign test. */\nexport interface MedianInterval {\n /** The `rank`-th smallest value. */\n readonly low: number;\n /** The `rank`-th largest value. */\n readonly high: number;\n /** Which order statistics bound it. */\n readonly rank: number;\n /** The chance that each end alone misses the median, exactly: `P(Binomial(n, 1/2) <= rank - 1)`. */\n readonly tail: number;\n}\n\n/**\n * The distribution-free interval on the median of independent values: the `k`-th smallest and the\n * `k`-th largest, with `k` from {@link signTestRank}. It assumes nothing about the shape of the\n * noise, which on a shared runner is one-sided and heavy-tailed. `null` when there are too few\n * values for any rank to meet `alpha`.\n *\n * @internal\n */\nexport function signTestInterval(xs: readonly number[], alpha: number): MedianInterval | null {\n const rank = signTestRank(xs.length, alpha);\n if (rank === 0) return null;\n const s = sortedCopy(xs);\n // `rank` is at least 1 and at most `n`, so both order statistics exist.\n return {\n low: s[rank - 1] as number,\n high: s[s.length - rank] as number,\n rank,\n tail: binomialCdf(rank - 1, xs.length, 0.5),\n };\n}\n\n/**\n * The exact (Clopper-Pearson) one-sided upper confidence bound on a rate, from `k` events in `n`\n * trials: the largest rate `p` at which seeing `k` or fewer is still at least `1 - confidence`\n * likely. With no events this is `1 - (1 - confidence)^(1/n)`, about `3 / n` at 95%.\n *\n * @internal\n */\nexport function rateUpperBound(k: number, n: number, confidence: number): number {\n // No trials say nothing. Every trial an event bisects to 1 on its own.\n if (n === 0) return 1;\n const alpha = 1 - confidence;\n let lo = k / n;\n let hi = 1;\n // Stryker disable next-line EqualityOperator: a 101st halving moves the bound by under 2^-100, below a double's resolution.\n for (let i = 0; i < 100; i++) {\n const mid = (lo + hi) / 2;\n // Stryker disable next-line EqualityOperator: `>` and `>=` differ only where the tail equals alpha exactly, which bisection over doubles does not land on.\n if (binomialCdf(k, n, mid) > alpha) lo = mid;\n else hi = mid;\n }\n return hi;\n}\n\n/**\n * The fewest trials, all without an event, whose {@link rateUpperBound} is under `rate`: how long an\n * A/A calibration has to run before a clean record can show a false alarm rate under `rate`.\n *\n * @internal\n */\nexport function trialsForZeroEvents(rate: number, confidence: number): number {\n return Math.ceil(Math.log(1 - confidence) / Math.log1p(-rate));\n}\n\n/**\n * The Theil-Sen slope: the median of the slopes between every pair of points with distinct `x`.\n * One wild point moves it little, which matters for a heap reading that a late collection can\n * spike. `NaN` with fewer than two distinct `x`.\n *\n * @internal\n */\nexport function theilSenSlope(xs: readonly number[], ys: readonly number[]): number {\n // Pairs only exist where both vectors do; a caller passes vectors of one length.\n const slopes: number[] = [];\n xs.forEach((xi, i) => {\n for (let j = i + 1; j < xs.length; j++) {\n const dx = (xs[j] as number) - xi;\n if (dx !== 0) slopes.push(((ys[j] as number) - (ys[i] as number)) / dx);\n }\n });\n return median(slopes);\n}\n\n/**\n * Round to a number of decimal places, for readable reports. The full values stay in\n * the sample vectors.\n *\n * @internal\n */\nexport function round(n: number, places = 4): number {\n const f = 10 ** places;\n return Math.round(n * f) / f;\n}\n","/**\n * The base against head comparison, and its calibration.\n *\n * A comparison is `ROUNDS` rounds. In each round every case runs once on the base build and once on\n * the head build, each in a fresh process, back to back; even rounds run the base first, odd rounds\n * the head. A drift in the machine over the run lands on both sides of a round, seconds apart, and\n * cancels in their ratio; an order effect lands on the head in half the rounds and on the base in\n * the other half, so it widens the spread of the ratios instead of biasing their median.\n *\n * Each round gives one ratio per case, `head / base` of the two processes' fastest samples. The\n * fastest, not the median, because the noise on a shared machine is one-sided (another tenant only\n * ever slows a sample down), and ADR 0001 section 1 measured `min` as the one estimator that keeps a\n * stall on one side out of a ratio. The verdict reads the sign test's interval on the median of the\n * per-round ratios, which assumes nothing about the shape of the noise, only that rounds are\n * independent.\n *\n * @packageDocumentation\n */\n\nimport type { Job, JobResult } from \"./child.js\";\nimport { BENCH_CONTRACT } from \"./contract.js\";\nimport {\n median,\n rateUpperBound,\n round,\n signTestInterval,\n trialsForZeroEvents,\n type MedianInterval,\n} from \"./stats.js\";\nimport { caseId, listCases, type BenchSuite, type CaseRef } from \"./suite.js\";\n\n/** What a comparison says about one case. */\nexport type Verdict = \"regression\" | \"improvement\" | \"no-regression\" | \"inconclusive\" | \"skipped\";\n\n/**\n * Judge one case from its per-round ratios.\n *\n * - `regression`: the whole interval is above `1 + THRESHOLD`. The head is slower by more than the\n * threshold, with each end of the interval wrong at most `ALPHA` of the time.\n * - `improvement`: the whole interval is below `1 / (1 + THRESHOLD)`.\n * - `no-regression`: the whole interval is at or below `1 + THRESHOLD`: a slowdown larger than\n * the threshold is ruled out.\n * - `inconclusive`: the interval straddles `1 + THRESHOLD`, or there are too few rounds for one.\n * Not a pass and not a failure: the rounds disagreed too much to tell.\n *\n * @example\n * ```ts\n * import { judge } from \"@cosyte/bench\";\n *\n * judge([1.1, 1.12, 1.09, 1.11, 1.1, 1.13, 1.1, 1.08, 1.12, 1.1, 1.11, 1.09]).verdict; // => \"regression\"\n * judge([1, 1.01, 0.99, 1, 1.02, 0.98, 1, 1.01, 0.99, 1, 1, 1.01]).verdict; // => \"no-regression\"\n * ```\n */\nexport function judge(\n ratios: readonly number[],\n threshold: number = BENCH_CONTRACT.THRESHOLD,\n alpha: number = BENCH_CONTRACT.ALPHA,\n): { verdict: Exclude<Verdict, \"skipped\">; interval: MedianInterval | null; medianRatio: number } {\n const interval = signTestInterval(ratios, alpha);\n const medianRatio = median(ratios);\n if (interval === null) return { verdict: \"inconclusive\", interval, medianRatio };\n const up = 1 + threshold;\n let verdict: Exclude<Verdict, \"skipped\">;\n if (interval.low > up) verdict = \"regression\";\n else if (interval.high < 1 / up) verdict = \"improvement\";\n else if (interval.high <= up) verdict = \"no-regression\";\n else verdict = \"inconclusive\";\n return { verdict, interval, medianRatio };\n}\n\n/** One case of a comparison. */\nexport interface CaseComparison extends CaseRef {\n /** Each round's base process: the fastest of its samples, milliseconds per operation. */\n readonly baseMsPerOp: readonly number[];\n /** Each round's head process: the fastest of its samples, milliseconds per operation. */\n readonly headMsPerOp: readonly number[];\n /** Each round's base samples, whole, so another estimator can be judged on the same runs. */\n readonly baseSamples: readonly (readonly number[])[];\n /** Each round's head samples, whole. */\n readonly headSamples: readonly (readonly number[])[];\n /** Each round's `head / base`. */\n readonly ratios: readonly number[];\n readonly medianRatio: number | null;\n readonly interval: MedianInterval | null;\n readonly verdict: Verdict;\n /** Why the case was skipped, when it was. A skip is not a pass. */\n readonly skipReason: string | null;\n}\n\n/** A whole comparison. */\nexport interface ComparisonBody {\n readonly rounds: number;\n readonly threshold: number;\n readonly alpha: number;\n /** The slowdown injected on the head side, for calibration. Zero in a real comparison. */\n readonly inject: number;\n readonly cases: readonly CaseComparison[];\n}\n\n/**\n * How the comparison runs one job. The real one starts a fresh process.\n *\n * @internal\n */\nexport type JobRunner = (job: Job) => Promise<JobResult>;\n\n/** What {@link compareBuilds} needs. */\nexport interface CompareOptions {\n /** The suite file, an absolute path. */\n readonly suiteFile: string;\n /** The suite, already loaded from `suiteFile`, to list its cases. */\n readonly suite: BenchSuite<unknown>;\n /** The base build's entry, an absolute path. */\n readonly base: string;\n /** The head build's entry, an absolute path. */\n readonly head: string;\n /** Keep to these benchmarks or cases. */\n readonly only?: readonly string[];\n /** A known slowdown on the head side, for calibration only. */\n readonly inject?: number;\n /** Progress lines, for a person watching. */\n readonly onProgress?: (line: string) => void;\n /** Runs one measuring job: `runInChild` for real. */\n readonly runJob: JobRunner;\n}\n\n/**\n * Compare a base build against a head build, interleaved, `ROUNDS` rounds.\n *\n * @internal\n */\nexport async function compareBuilds(options: CompareOptions): Promise<ComparisonBody> {\n const runJob = options.runJob;\n const inject = options.inject ?? 0;\n const cases = listCases(options.suite, options.only, true);\n const state = cases.map((c) => ({\n c,\n rounds: [] as { base: readonly number[]; head: readonly number[] }[],\n skip: null as string | null,\n }));\n for (let r = 0; r < BENCH_CONTRACT.ROUNDS; r++) {\n for (const s of state) {\n if (s.skip !== null) continue;\n const sides: (\"base\" | \"head\")[] = r % 2 === 0 ? [\"base\", \"head\"] : [\"head\", \"base\"];\n const got = new Map<\"base\" | \"head\", readonly number[]>();\n for (const side of sides) {\n const result = await runJob({\n mode: \"timing\",\n suite: options.suiteFile,\n target: side === \"base\" ? options.base : options.head,\n case: s.c,\n latency: false,\n inject: side === \"head\" ? inject : 0,\n });\n if (result.mode !== \"timing\")\n throw new Error(\"@cosyte/bench: a timing job returned another kind\");\n if (result.status === \"skipped\") {\n s.skip = `${side}: ${result.reason}`;\n break;\n }\n got.set(side, result.samplesMsPerOp);\n }\n const base = got.get(\"base\");\n const head = got.get(\"head\");\n // Both sides or neither: a round that skipped half way is not a round.\n if (base !== undefined && head !== undefined) s.rounds.push({ base, head });\n }\n options.onProgress?.(`round ${String(r + 1)} of ${String(BENCH_CONTRACT.ROUNDS)} done`);\n }\n return {\n rounds: BENCH_CONTRACT.ROUNDS,\n threshold: BENCH_CONTRACT.THRESHOLD,\n alpha: BENCH_CONTRACT.ALPHA,\n inject,\n cases: state.map(({ c, rounds, skip }) => {\n const recorded = {\n baseMsPerOp: rounds.map((p) => Math.min(...p.base)),\n headMsPerOp: rounds.map((p) => Math.min(...p.head)),\n baseSamples: rounds.map((p) => p.base),\n headSamples: rounds.map((p) => p.head),\n };\n if (skip !== null) {\n return {\n ...c,\n ...recorded,\n ratios: [],\n medianRatio: null,\n interval: null,\n verdict: \"skipped\",\n skipReason: skip,\n };\n }\n const ratios = rounds.map((p) => round(Math.min(...p.head) / Math.min(...p.base), 6));\n const j = judge(ratios);\n return {\n ...c,\n ...recorded,\n ratios,\n medianRatio: round(j.medianRatio, 6),\n interval: j.interval,\n verdict: j.verdict,\n skipReason: null,\n };\n }),\n };\n}\n\n/**\n * The exit code a comparison earns: 1 when any case is a regression, else 2 when any case was\n * skipped (it could not be measured), else 0.\n *\n * @internal\n */\nexport function comparisonExitCode(body: ComparisonBody): number {\n if (body.cases.some((c) => c.verdict === \"regression\")) return 1;\n if (body.cases.some((c) => c.verdict === \"skipped\")) return 2;\n return 0;\n}\n\n/** A rate with its exact upper bound. */\nexport interface CountedRate {\n readonly events: number;\n readonly trials: number;\n readonly rate: number;\n /** The one-sided upper bound at `REQUIRED_CONFIDENCE` (Clopper-Pearson). */\n readonly upperBound: number;\n}\n\nfunction counted(events: number, trials: number): CountedRate {\n return {\n events,\n trials,\n rate: trials === 0 ? 0 : round(events / trials, 6),\n upperBound: round(rateUpperBound(events, trials, BENCH_CONTRACT.REQUIRED_CONFIDENCE), 6),\n };\n}\n\n/** The ratio one case was observed at over a whole calibration. */\nexport interface ObservedRatio extends CaseRef {\n /** Measured rounds, pooled over every comparison that measured the case. */\n readonly rounds: number;\n /**\n * The median of those rounds' `head / base` ratios, or `null` when no round measured. Under an\n * injected slowdown this is the slowdown the head side was actually measured at, which is not\n * `1 + inject` (see `sampleThroughput`): a detection rate is read against it.\n */\n readonly medianRatio: number | null;\n}\n\n/** A calibration: many comparisons of known outcome. */\nexport interface CalibrationBody {\n /** The slowdown injected on the head side. Zero is an A/A calibration: every regression is a false alarm. */\n readonly inject: number;\n readonly comparisons: number;\n /** Each comparison's cases, kept whole so a different rule can be judged against the same runs. */\n readonly runs: readonly (readonly CaseComparison[])[];\n /** Case verdicts of `regression`, out of the case verdicts that measured. A skipped case is not a trial. */\n readonly perCase: CountedRate;\n /**\n * Comparisons with at least one `regression`, out of the comparisons whose outcome is known: every\n * case measured, or a regression was called. A comparison that skipped a case and called no\n * regression is neither a pass nor a trial.\n */\n readonly perComparison: CountedRate;\n /** Comparisons with at least one skipped case, out of all comparisons. A required check is red on each (exit 2). */\n readonly skippedComparisons: CountedRate;\n /** Case verdicts by kind. */\n readonly verdicts: Readonly<Record<Verdict, number>>;\n /** Each case's observed ratio, in the order the cases first appear. */\n readonly observed: readonly ObservedRatio[];\n /** For an A/A calibration: whether the timing comparison may now be a required check, and why. */\n readonly requiredRule: {\n readonly applies: boolean;\n readonly met: boolean;\n readonly reason: string;\n };\n}\n\n/**\n * Summarize calibration runs. With `inject` zero, every `regression` is a false alarm, and the rule\n * for making the comparison a required check is judged. A required check on an unchanged pull\n * request is wrongly red for either of two reasons, so the rule bounds both at\n * `REQUIRED_CONFIDENCE`: the per-comparison false alarm rate (over the comparisons whose outcome is\n * known) and the rate of comparisons that skipped a case (over all of them) must each have an upper\n * bound under `REQUIRED_FALSE_ALARM_RATE`. Comparisons that measured nothing count toward neither a\n * pass nor a trial, so they can never meet it. With `inject` above zero every `regression` is a\n * detection, and the rule does not apply.\n *\n * @internal\n */\nexport function summarizeCalibration(\n inject: number,\n runs: readonly (readonly CaseComparison[])[],\n): CalibrationBody {\n const verdicts: Record<Verdict, number> = {\n regression: 0,\n improvement: 0,\n \"no-regression\": 0,\n inconclusive: 0,\n skipped: 0,\n };\n const pooled = new Map<string, { ref: CaseRef; ratios: number[] }>();\n let caseTrials = 0;\n let caseEvents = 0;\n let compTrials = 0;\n let compEvents = 0;\n let compSkipped = 0;\n for (const run of runs) {\n let alarmed = false;\n let skipped = false;\n for (const c of run) {\n verdicts[c.verdict]++;\n const id = caseId(c);\n const pool = pooled.get(id) ?? { ref: { benchmark: c.benchmark, size: c.size }, ratios: [] };\n pooled.set(id, pool);\n if (c.verdict === \"skipped\") {\n skipped = true;\n continue;\n }\n pool.ratios.push(...c.ratios);\n caseTrials++;\n if (c.verdict === \"regression\") {\n caseEvents++;\n alarmed = true;\n }\n }\n if (skipped) compSkipped++;\n if (alarmed || !skipped) compTrials++;\n if (alarmed) compEvents++;\n }\n const perComparison = counted(compEvents, compTrials);\n const skippedComparisons = counted(compSkipped, runs.length);\n const line = BENCH_CONTRACT.REQUIRED_FALSE_ALARM_RATE;\n const needed = trialsForZeroEvents(line, BENCH_CONTRACT.REQUIRED_CONFIDENCE);\n // Four decimals of a percentage: a bound of 0.9969% must not print as \"1%\" beside \"under 1%\".\n const pct = (x: number) => `${String(round(x * 100, 4))}%`;\n // Stryker disable next-line EqualityOperator: an exact Clopper-Pearson bound of exactly 1% is not reached by any whole count of comparisons.\n const alarmsUnder = perComparison.upperBound < line;\n // Stryker disable next-line EqualityOperator: the same bound, on the same whole counts.\n const skipsUnder = skippedComparisons.upperBound < line;\n const alarms = `${String(compEvents)} false alarm(s) in ${String(compTrials)} A/A comparisons that measured every case or called a regression`;\n const skips = `${String(compSkipped)} of ${String(runs.length)} comparisons skipped a case`;\n let requiredRule: CalibrationBody[\"requiredRule\"];\n if (inject !== 0) {\n requiredRule = {\n applies: false,\n met: false,\n reason: \"a calibration with an injected slowdown measures detection, not false alarms\",\n };\n } else if (alarmsUnder && skipsUnder) {\n requiredRule = {\n applies: true,\n met: true,\n reason: `${alarms}, and ${skips}: both rates are under ${pct(line)} with ${pct(BENCH_CONTRACT.REQUIRED_CONFIDENCE)} confidence (upper bounds ${pct(perComparison.upperBound)} and ${pct(skippedComparisons.upperBound)})`,\n };\n } else {\n const why: string[] = [];\n if (!alarmsUnder) {\n why.push(\n `${alarms}: the upper bound ${pct(perComparison.upperBound)} is not under ${pct(line)}, and a clean record needs at least ${String(needed)} of them`,\n );\n }\n if (!skipsUnder) {\n why.push(\n `${skips}, and a required check is red on each: the upper bound ${pct(skippedComparisons.upperBound)} on that rate is not under ${pct(line)}`,\n );\n }\n requiredRule = { applies: true, met: false, reason: why.join(\"; \") };\n }\n return {\n inject,\n comparisons: runs.length,\n runs,\n perCase: counted(caseEvents, caseTrials),\n perComparison,\n skippedComparisons,\n verdicts,\n observed: [...pooled.values()].map(({ ref, ratios }) => ({\n ...ref,\n rounds: ratios.length,\n medianRatio: ratios.length === 0 ? null : round(median(ratios), 6),\n })),\n requiredRule,\n };\n}\n\n/** What {@link calibrateComparison} needs: a comparison's options and how many to run. */\nexport interface CalibrateOptions extends CompareOptions {\n readonly comparisons: number;\n}\n\n/**\n * Run `comparisons` full comparisons and summarize them. Base and head should be the same build (two\n * copies, ideally in two directories, so a difference in where the code sits is part of what is\n * calibrated); `inject` makes the head side slower by a known fraction.\n *\n * @internal\n */\nexport async function calibrateComparison(options: CalibrateOptions): Promise<CalibrationBody> {\n const runs: CaseComparison[][] = [];\n for (let k = 0; k < options.comparisons; k++) {\n const { onProgress: _quiet, ...quiet } = options;\n const body = await compareBuilds(quiet);\n runs.push([...body.cases]);\n const alarms = body.cases.filter((c) => c.verdict === \"regression\").map(caseId);\n options.onProgress?.(\n `comparison ${String(k + 1)} of ${String(options.comparisons)}: ` +\n (alarms.length === 0 ? \"no regression verdict\" : `regression on ${alarms.join(\", \")}`),\n );\n }\n return summarizeCalibration(options.inject ?? 0, runs);\n}\n","/**\n * The results document: one JSON file per reading, comparison or calibration, in a versioned shape a\n * results repository can keep for years and read back.\n *\n * The shape is fixed by a JSON Schema ({@link RESULTS_SCHEMA}, also shipped as\n * `schema/results-v1.schema.json`). Version 1 only grows: a new field may appear, no field changes\n * meaning or goes away, and readers ignore fields they do not know. Anything else is version 2.\n *\n * @packageDocumentation\n */\n\nimport { createRequire } from \"node:module\";\n\nimport type { CalibrationBody, ComparisonBody } from \"./compare.js\";\nimport { BENCH_CONTRACT, type BenchContractShape } from \"./contract.js\";\nimport type { Environment } from \"./environment.js\";\nimport { WARMUP } from \"./measure.js\";\nimport type { ReadingBody } from \"./reading.js\";\n\n/** The version this package writes and reads. */\nexport const SCHEMA_VERSION = 1;\n\n/** Which build a document describes. */\nexport interface BuildRef {\n /** The entry measured, relative to the working directory. */\n readonly target: string;\n /** The commit it was built from, when known. */\n readonly commit: string | null;\n}\n\ninterface DocumentBase {\n readonly schemaVersion: 1;\n readonly tool: { readonly name: \"@cosyte/bench\"; readonly version: string };\n /** When the document was written, ISO 8601 UTC. */\n readonly createdAt: string;\n readonly suite: { readonly name: string; readonly file: string };\n readonly environment: Environment;\n /** The constants in force, so a document can be judged against the method that made it. */\n readonly contract: { readonly bench: BenchContractShape; readonly warmup: typeof WARMUP };\n}\n\n/** A results document. */\nexport type ResultsDocument = DocumentBase &\n (\n | { readonly kind: \"reading\"; readonly subject: BuildRef; readonly reading: ReadingBody }\n | {\n readonly kind: \"comparison\";\n readonly base: BuildRef;\n readonly head: BuildRef;\n readonly comparison: ComparisonBody;\n }\n | {\n readonly kind: \"calibration\";\n readonly base: BuildRef;\n readonly head: BuildRef;\n readonly calibration: CalibrationBody;\n }\n );\n\n/**\n * This package's version, read from its own manifest.\n *\n * @internal\n */\nexport function toolVersion(): string {\n const require = createRequire(import.meta.url);\n return (require(\"../package.json\") as { version: string }).version;\n}\n\n/**\n * The parts every document shares.\n *\n * @internal\n */\nexport function documentBase(\n suite: { name: string; file: string },\n environment: Environment,\n now: Date = new Date(),\n): DocumentBase {\n return {\n schemaVersion: SCHEMA_VERSION,\n tool: { name: \"@cosyte/bench\", version: toolVersion() },\n createdAt: now.toISOString(),\n suite,\n environment,\n contract: { bench: BENCH_CONTRACT, warmup: WARMUP },\n };\n}\n\n/** The JSON Schema keywords the results schema uses. */\ninterface Schema {\n readonly $ref?: string;\n readonly $defs?: Readonly<Record<string, Schema>>;\n readonly const?: unknown;\n readonly enum?: readonly unknown[];\n readonly type?: string | readonly string[];\n readonly minimum?: number;\n readonly required?: readonly string[];\n readonly properties?: Readonly<Record<string, Schema>>;\n readonly items?: Schema;\n readonly oneOf?: readonly Schema[];\n}\n\nconst num = { type: \"number\" } as const;\nconst int = { type: \"integer\", minimum: 0 } as const;\nconst str = { type: \"string\" } as const;\nconst strOrNull = { type: [\"string\", \"null\"] } as const;\nconst numOrNull = { type: [\"number\", \"null\"] } as const;\nconst nums = { type: \"array\", items: num } as const;\nconst skipped = {\n type: \"object\",\n required: [\"status\", \"reason\"],\n properties: { status: { const: \"skipped\" }, reason: str },\n} as const;\nconst measuredOrSkipped = (measured: Schema): Schema => ({ oneOf: [measured, skipped] });\nconst orNull = (s: Schema): Schema => ({ oneOf: [{ type: \"null\" }, s] });\n\n/**\n * The results schema, version 1, as JSON Schema (draft 2020-12). It holds what every reader relies\n * on; a document may carry more.\n *\n * @example\n * ```ts\n * import { RESULTS_SCHEMA } from \"@cosyte/bench\";\n *\n * RESULTS_SCHEMA.$id; // => \"https://github.com/cosyte/config/blob/main/packages/bench/schema/results-v1.schema.json\"\n * ```\n */\nexport const RESULTS_SCHEMA = {\n $schema: \"https://json-schema.org/draft/2020-12/schema\",\n $id: \"https://github.com/cosyte/config/blob/main/packages/bench/schema/results-v1.schema.json\",\n title: \"@cosyte/bench results, version 1\",\n type: \"object\",\n required: [\"schemaVersion\", \"kind\", \"tool\", \"createdAt\", \"suite\", \"environment\", \"contract\"],\n properties: {\n schemaVersion: { const: 1 },\n kind: { enum: [\"reading\", \"comparison\", \"calibration\"] },\n tool: {\n type: \"object\",\n required: [\"name\", \"version\"],\n properties: { name: { const: \"@cosyte/bench\" }, version: str },\n },\n createdAt: str,\n suite: { type: \"object\", required: [\"name\", \"file\"], properties: { name: str, file: str } },\n environment: { $ref: \"#/$defs/environment\" },\n contract: {\n type: \"object\",\n required: [\"bench\", \"warmup\"],\n properties: { bench: { type: \"object\" }, warmup: { type: \"object\" } },\n },\n },\n oneOf: [\n {\n required: [\"subject\", \"reading\"],\n properties: {\n kind: { const: \"reading\" },\n subject: { $ref: \"#/$defs/build\" },\n reading: { $ref: \"#/$defs/reading\" },\n },\n },\n {\n required: [\"base\", \"head\", \"comparison\"],\n properties: {\n kind: { const: \"comparison\" },\n base: { $ref: \"#/$defs/build\" },\n head: { $ref: \"#/$defs/build\" },\n comparison: { $ref: \"#/$defs/comparison\" },\n },\n },\n {\n required: [\"base\", \"head\", \"calibration\"],\n properties: {\n kind: { const: \"calibration\" },\n base: { $ref: \"#/$defs/build\" },\n head: { $ref: \"#/$defs/build\" },\n calibration: { $ref: \"#/$defs/calibration\" },\n },\n },\n ],\n $defs: {\n build: {\n type: \"object\",\n required: [\"target\", \"commit\"],\n properties: { target: str, commit: strOrNull },\n },\n environment: {\n type: \"object\",\n required: [\n \"node\",\n \"v8\",\n \"platform\",\n \"arch\",\n \"osRelease\",\n \"cpuModel\",\n \"logicalCpus\",\n \"totalMemoryBytes\",\n \"cgroupCpuMax\",\n \"runner\",\n ],\n properties: {\n node: str,\n v8: str,\n platform: str,\n arch: str,\n osRelease: str,\n cpuModel: strOrNull,\n logicalCpus: int,\n totalMemoryBytes: int,\n cgroupCpuMax: strOrNull,\n // Added within version 1, so not required: a document written before it is still valid.\n loadAverage: nums,\n runner: {\n type: \"object\",\n required: [\"ci\", \"provider\", \"class\"],\n properties: { ci: { type: \"boolean\" }, provider: strOrNull, class: str },\n },\n },\n },\n interval: orNull({\n type: \"object\",\n required: [\"low\", \"high\", \"rank\", \"tail\"],\n properties: { low: num, high: num, rank: int, tail: num },\n }),\n caseComparison: {\n type: \"object\",\n required: [\n \"benchmark\",\n \"size\",\n \"baseMsPerOp\",\n \"headMsPerOp\",\n \"ratios\",\n \"medianRatio\",\n \"interval\",\n \"verdict\",\n \"skipReason\",\n ],\n properties: {\n benchmark: str,\n size: str,\n baseMsPerOp: nums,\n headMsPerOp: nums,\n ratios: nums,\n medianRatio: numOrNull,\n interval: { $ref: \"#/$defs/interval\" },\n verdict: {\n enum: [\"regression\", \"improvement\", \"no-regression\", \"inconclusive\", \"skipped\"],\n },\n skipReason: strOrNull,\n },\n },\n comparison: {\n type: \"object\",\n required: [\"rounds\", \"threshold\", \"alpha\", \"inject\", \"cases\"],\n properties: {\n rounds: int,\n threshold: num,\n alpha: num,\n inject: num,\n cases: { type: \"array\", items: { $ref: \"#/$defs/caseComparison\" } },\n },\n },\n rate: {\n type: \"object\",\n required: [\"events\", \"trials\", \"rate\", \"upperBound\"],\n properties: { events: int, trials: int, rate: num, upperBound: num },\n },\n calibration: {\n type: \"object\",\n required: [\n \"inject\",\n \"comparisons\",\n \"runs\",\n \"perCase\",\n \"perComparison\",\n \"verdicts\",\n \"requiredRule\",\n ],\n properties: {\n inject: num,\n comparisons: int,\n runs: {\n type: \"array\",\n items: { type: \"array\", items: { $ref: \"#/$defs/caseComparison\" } },\n },\n perCase: { $ref: \"#/$defs/rate\" },\n perComparison: { $ref: \"#/$defs/rate\" },\n // Added within version 1, so optional: documents written before it validate unchanged.\n skippedComparisons: { $ref: \"#/$defs/rate\" },\n verdicts: { type: \"object\" },\n observed: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"benchmark\", \"size\", \"rounds\", \"medianRatio\"],\n properties: { benchmark: str, size: str, rounds: int, medianRatio: numOrNull },\n },\n },\n requiredRule: {\n type: \"object\",\n required: [\"applies\", \"met\", \"reason\"],\n properties: { applies: { type: \"boolean\" }, met: { type: \"boolean\" }, reason: str },\n },\n },\n },\n reading: {\n type: \"object\",\n required: [\"processes\", \"cases\", \"importCost\"],\n properties: {\n processes: int,\n cases: { type: \"array\", items: { $ref: \"#/$defs/caseReading\" } },\n importCost: orNull({\n type: \"object\",\n required: [\n \"runs\",\n \"coldImportMs\",\n \"medianMs\",\n \"p95Ms\",\n \"emptyImportMs\",\n \"emptyMedianMs\",\n \"files\",\n \"totalBytes\",\n \"totalGzipBytes\",\n ],\n properties: {\n runs: int,\n coldImportMs: nums,\n medianMs: num,\n p95Ms: num,\n emptyImportMs: nums,\n emptyMedianMs: num,\n files: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"path\", \"bytes\", \"gzipBytes\"],\n properties: { path: str, bytes: int, gzipBytes: int },\n },\n },\n totalBytes: int,\n totalGzipBytes: int,\n },\n }),\n },\n },\n caseReading: {\n type: \"object\",\n required: [\n \"benchmark\",\n \"size\",\n \"inputsPerPass\",\n \"bytesPerPass\",\n \"throughput\",\n \"latency\",\n \"allocations\",\n \"peak\",\n \"longRun\",\n ],\n properties: {\n benchmark: str,\n size: str,\n inputsPerPass: int,\n bytesPerPass: numOrNull,\n throughput: measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"processes\", \"medianMsPerOp\", \"opsPerSecond\", \"bytesPerSecond\"],\n properties: {\n status: { const: \"measured\" },\n processes: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"samplesMsPerOp\", \"medianMsPerOp\"],\n properties: { samplesMsPerOp: nums, medianMsPerOp: num },\n },\n },\n medianMsPerOp: num,\n opsPerSecond: num,\n bytesPerSecond: numOrNull,\n },\n }),\n latency: orNull({\n type: \"object\",\n required: [\"status\", \"calls\", \"timerOverheadNs\", \"p50Ns\"],\n properties: {\n status: { enum: [\"measured\", \"skipped\"] },\n calls: int,\n timerOverheadNs: num,\n p50Ns: numOrNull,\n p95Ns: numOrNull,\n p99Ns: numOrNull,\n maxNs: num,\n },\n }),\n allocations: orNull(\n measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"windowOps\", \"heapBytesPerOp\", \"externalBytesPerOp\"],\n properties: {\n status: { const: \"measured\" },\n windowOps: int,\n heapBytesPerOp: num,\n externalBytesPerOp: num,\n },\n }),\n ),\n peak: orNull(\n measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"calls\", \"peakHeapBytes\", \"medianPeakHeapBytes\"],\n properties: {\n status: { const: \"measured\" },\n calls: int,\n peakHeapBytes: num,\n medianPeakHeapBytes: num,\n },\n }),\n ),\n longRun: orNull(\n measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"verdict\", \"ops\", \"opsPerRound\", \"settledHeap\", \"slopeBytesPerOp\"],\n properties: {\n status: { const: \"measured\" },\n verdict: { enum: [\"flat\", \"growing\"] },\n ops: int,\n opsPerRound: int,\n settledHeap: nums,\n slopeBytesPerOp: num,\n },\n }),\n ),\n },\n },\n },\n} as const;\n\nfunction typeOf(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n if (Number.isInteger(value)) return \"integer\";\n return typeof value;\n}\n\nfunction typeMatches(value: unknown, type: string): boolean {\n const actual = typeOf(value);\n return actual === type || (type === \"number\" && actual === \"integer\");\n}\n\nfunction resolveRef(ref: string, root: Schema): Schema {\n const target = root.$defs?.[ref.replace(\"#/$defs/\", \"\")];\n if (target === undefined) throw new Error(`schema has no definition ${ref}`);\n return target;\n}\n\n/**\n * Check a value against the subset of JSON Schema the results schema uses (`type`, `const`, `enum`,\n * `required`, `properties`, `items`, `minimum`, `oneOf`, `$ref` into `$defs`) and return every\n * problem, each with its JSON pointer. An empty list means the value conforms.\n *\n * @internal\n */\nexport function validate(value: unknown, schema: Schema, root: Schema = schema, at = \"\"): string[] {\n const problems: string[] = [];\n const here = at === \"\" ? \"/\" : at;\n if (typeof schema.$ref === \"string\")\n return validate(value, resolveRef(schema.$ref, root), root, at);\n if (\"const\" in schema && value !== schema.const)\n problems.push(`${here}: must be ${JSON.stringify(schema.const)}`);\n if (schema.enum !== undefined && !schema.enum.includes(value)) {\n problems.push(\n `${here}: must be one of ${schema.enum.map((e) => JSON.stringify(e)).join(\", \")}`,\n );\n }\n if (schema.type !== undefined) {\n const types: readonly string[] = typeof schema.type === \"string\" ? [schema.type] : schema.type;\n if (!types.some((t) => typeMatches(value, t))) {\n problems.push(`${here}: must be ${types.join(\" or \")}, is ${typeOf(value)}`);\n return problems;\n }\n }\n if (typeof value === \"number\" && value < (schema.minimum ?? -Infinity)) {\n problems.push(`${here}: must be at least ${String(schema.minimum)}`);\n }\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n const record = value as Record<string, unknown>;\n for (const key of schema.required ?? []) {\n if (!(key in record)) problems.push(`${here}: is missing \"${key}\"`);\n }\n for (const [key, sub] of Object.entries(schema.properties ?? {})) {\n if (key in record) problems.push(...validate(record[key], sub, root, `${at}/${key}`));\n }\n }\n if (Array.isArray(value) && schema.items !== undefined) {\n const items = schema.items;\n value.forEach((item, i) => problems.push(...validate(item, items, root, `${at}/${String(i)}`)));\n }\n if (schema.oneOf !== undefined) {\n const results = schema.oneOf.map((s) => validate(value, s, root, at));\n const passing = results.filter((r) => r.length === 0).length;\n if (passing !== 1) {\n problems.push(\n `${here}: must match exactly one of ${String(results.length)} shapes, matches ${String(passing)}`,\n );\n // The nearest shape's problems say what to fix. With two or more matching, the nearest has\n // none, so this adds nothing.\n problems.push(...results.reduce((a, b) => (b.length < a.length ? b : a)));\n }\n }\n return problems;\n}\n\n/**\n * Parse and check a results document. Throws a `TypeError` listing every problem when the text is\n * not JSON, is a version this package does not read, or does not match the schema.\n *\n * @example\n * ```ts\n * import { readResults } from \"@cosyte/bench\";\n *\n * try {\n * readResults('{\"schemaVersion\": 2}');\n * } catch (error) {\n * String(error).includes(\"version 2\"); // => true\n * }\n * ```\n */\nexport function readResults(text: string): ResultsDocument {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n throw new TypeError(\"@cosyte/bench: the results are not JSON\");\n }\n const version = (value as { schemaVersion?: unknown } | null)?.schemaVersion;\n if (version !== SCHEMA_VERSION) {\n throw new TypeError(\n `@cosyte/bench: results version ${String(version)} is not one this package reads (${String(SCHEMA_VERSION)})`,\n );\n }\n const problems = validate(value, RESULTS_SCHEMA);\n if (problems.length > 0)\n throw new TypeError(\n `@cosyte/bench: the results do not match the schema:\\n${problems.join(\"\\n\")}`,\n );\n return value as ResultsDocument;\n}\n","/**\n * Starting measuring processes and reading what they measured.\n *\n * @packageDocumentation\n */\n\nimport { spawn } from \"node:child_process\";\nimport { mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { Job, JobResult } from \"./child.js\";\nimport { BENCH_CONTRACT } from \"./contract.js\";\n\n/**\n * A run that could not produce a measurement. The command line reports it and exits 2.\n *\n * @example\n * ```ts\n * import { BenchError } from \"@cosyte/bench\";\n *\n * new BenchError(\"no result\").name; // => \"BenchError\"\n * ```\n */\nexport class BenchError extends Error {\n override readonly name = \"BenchError\";\n}\n\n/**\n * Where the built measuring processes live, beside this module in `dist`.\n *\n * @internal\n */\nexport const ENTRIES = Object.freeze({\n child: fileURLToPath(new URL(\"./child-entry.mjs\", import.meta.url)),\n importer: fileURLToPath(new URL(\"./import-entry.mjs\", import.meta.url)),\n});\n\n/**\n * How measuring processes are started. The defaults are the real ones; tests point `entry` at a\n * stand-in.\n *\n * @internal\n */\nexport interface SpawnOptions {\n /** The script the process runs. */\n readonly entry?: string;\n /** Flags for `node` before the script. */\n readonly execArgv?: readonly string[];\n /** Killed after this long, in milliseconds. */\n readonly timeoutMs?: number;\n}\n\n/**\n * The environment a measuring process gets: this one's, less what would change what it measures.\n * `NODE_V8_COVERAGE` would instrument it (a test runner sets it) and `NODE_COMPILE_CACHE` would\n * make a cold import warm.\n *\n * @internal\n */\nexport function childEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {\n const out = { ...env };\n delete out[\"NODE_V8_COVERAGE\"];\n delete out[\"NODE_COMPILE_CACHE\"];\n return out;\n}\n\n/** What a process said, raw. */\ninterface ProcessOutcome {\n readonly code: number | null;\n readonly signal: string | null;\n readonly stderr: string;\n readonly timedOut: boolean;\n}\n\nconst STDERR_LIMIT = 16 * 1024;\n\n/**\n * Run `node <execArgv> <entry> <args>` with stdout discarded (the code under test may print), and\n * keep the last 16 KiB of stderr for a diagnostic.\n *\n * @internal\n */\nexport function runNode(\n entry: string,\n args: readonly string[],\n execArgv: readonly string[],\n timeoutMs: number,\n execPath: string = process.execPath,\n): Promise<ProcessOutcome> {\n return new Promise((resolve, reject) => {\n const child = spawn(execPath, [...execArgv, entry, ...args], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n env: childEnv(),\n });\n let stderr = \"\";\n let timedOut = false;\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr = (stderr + String(chunk)).slice(-STDERR_LIMIT);\n });\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGKILL\");\n }, timeoutMs);\n child.on(\"error\", (error) => {\n // Stryker disable next-line all: an uncleared timer only keeps the event loop alive until it fires; killing a process that is gone does nothing.\n clearTimeout(timer);\n reject(error);\n });\n child.on(\"close\", (code, signal) => {\n // Stryker disable next-line all: as above, the timer's only effect after the process ended is to hold the event loop.\n clearTimeout(timer);\n resolve({ code, signal, stderr, timedOut });\n });\n });\n}\n\n/**\n * The flags a job's process starts with. Memory jobs need `gc()`; the allocation job also gets a\n * young generation large enough that a window of operations rarely collects.\n *\n * @internal\n */\nexport function flagsFor(job: Job): string[] {\n if (job.mode === \"timing\") return [];\n if (job.mode === \"heap\") return [\"--expose-gc\"];\n const mib = String(BENCH_CONTRACT.ALLOC_SEMI_SPACE_MIB);\n return [\"--expose-gc\", `--min-semi-space-size=${mib}`, `--max-semi-space-size=${mib}`];\n}\n\nfunction describe(job: Job): string {\n return `${job.mode} job for \"${job.case.benchmark} @ ${job.case.size}\"`;\n}\n\n/**\n * Run one job in a fresh process and return what it measured. Throws {@link BenchError} when the\n * process timed out, died, or wrote no result, with the tail of its stderr.\n *\n * @internal\n */\nexport async function runInChild(job: Job, options: SpawnOptions = {}): Promise<JobResult> {\n // Stryker disable next-line StringLiteral: the prefix only names the temporary directory.\n const dir = mkdtempSync(join(tmpdir(), \"cosyte-bench-\"));\n try {\n const jobFile = join(dir, \"job.json\");\n const outFile = join(dir, \"out.json\");\n writeFileSync(jobFile, JSON.stringify(job));\n const outcome = await runNode(\n options.entry ?? ENTRIES.child,\n [jobFile, outFile],\n options.execArgv ?? flagsFor(job),\n options.timeoutMs ?? 600_000,\n );\n if (outcome.timedOut) throw new BenchError(`the ${describe(job)} timed out`);\n let text: string;\n try {\n text = String(readFileSync(outFile));\n } catch {\n const how = outcome.signal === null ? `exit ${String(outcome.code)}` : outcome.signal;\n throw new BenchError(\n `the ${describe(job)} ended (${how}) without a result. Its stderr ends:\\n${outcome.stderr}`,\n );\n }\n const out = JSON.parse(text) as { ok: boolean; result?: JobResult; error?: string };\n if (!out.ok || out.result === undefined) {\n throw new BenchError(`the ${describe(job)} could not measure: ${String(out.error)}`);\n }\n return out.result;\n } finally {\n rmSync(dir, { recursive: true });\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/contract.ts","../src/suite.ts","../src/stats.ts","../src/compare.ts","../src/reading.ts","../src/results.ts","../src/spawn.ts"],"names":[],"mappings":";;;;;;;;AAsGO,IAAM,cAAA,GAAqC,OAAO,MAAA,CAAO;AAAA,EAC9D,OAAA,EAAS,CAAA;AAAA,EACT,aAAA,EAAe,EAAA;AAAA,EACf,MAAA,EAAQ,EAAA;AAAA,EACR,KAAA,EAAO,IAAA;AAAA,EACP,SAAA,EAAW,IAAA;AAAA,EACX,iBAAA,EAAmB,GAAA;AAAA,EACnB,iBAAA,EAAmB,GAAA;AAAA,EACnB,iBAAA,EAAmB,EAAA;AAAA,EACnB,kBAAA,EAAoB,EAAA;AAAA,EACpB,sBAAA,EAAwB,EAAA;AAAA,EACxB,gBAAA,EAAkB,GAAA;AAAA,EAClB,eAAA,EAAiB,GAAA;AAAA,EACjB,oBAAA,EAAsB,EAAA;AAAA,EACtB,UAAA,EAAY,EAAA;AAAA,EACZ,mBAAmB,EAAA,GAAK,IAAA;AAAA,EACxB,eAAA,EAAiB,EAAA;AAAA,EACjB,sBAAA,EAAwB,CAAA;AAAA,EACxB,iBAAA,EAAmB,GAAA;AAAA,EACnB,uBAAuB,EAAA,GAAK,IAAA;AAAA,EAC5B,iBAAiB,EAAA,GAAK,IAAA;AAAA,EACtB,gBAAA,EAAkB,CAAA;AAAA,EAClB,aAAA,EAAe,CAAA;AAAA,EACf,WAAA,EAAa,EAAA;AAAA,EACb,aAAA,EAAe,EAAA;AAAA,EACf,mBAAA,EAAqB,CAAA;AAAA,EACrB,mBAAA,EAAqB,GAAA;AAAA,EACrB,eAAA,EAAiB,GAAA;AAAA,EACjB,yBAAA,EAA2B,IAAA;AAAA,EAC3B,mBAAA,EAAqB;AACvB,CAAC;;;ACpDM,SAAS,gBACd,SAAA,EACuB;AACvB,EAAA,OAAO,SAAA;AACT;AAsBO,SAAS,YAAqB,KAAA,EAAiD;AACpF,EAAA,OAAO,WAAW,KAAK,CAAA;AACzB;AAEA,SAAS,KAAK,OAAA,EAAwB;AACpC,EAAA,MAAM,IAAI,SAAA,CAAU,CAAA,eAAA,EAAkB,OAAO,CAAA,CAAE,CAAA;AACjD;AAEA,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAEA,SAAS,SAAA,CAAU,OAAgB,IAAA,EAAsB;AACvD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,IAAA,OAAW,EAAA,EAAI,IAAA,CAAK,CAAA,EAAG,IAAI,CAAA,2BAAA,CAA6B,CAAA;AAC/F,EAAA,OAAO,KAAA;AACT;AAQO,SAAS,WAAW,KAAA,EAAqC;AAC9D,EAAA,IAAI,CAAC,QAAA,CAAS,KAAK,CAAA,OAAQ,+CAA+C,CAAA;AAC1E,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,KAAA,CAAM,MAAM,GAAG,gBAAgB,CAAA;AACtD,EAAA,MAAM,UAAA,GAAa,MAAM,YAAY,CAAA;AACrC,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAU,CAAA,IAAK,UAAA,CAAW,WAAW,CAAA,EAAG;AACzD,IAAA,IAAA,CAAK,CAAA,OAAA,EAAU,IAAI,CAAA,kCAAA,CAAoC,CAAA;AAAA,EACzD;AACA,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,KAAK,UAAA,EAAyB;AACvC,IAAA,IAAI,CAAC,QAAA,CAAS,CAAC,GAAG,IAAA,CAAK,CAAA,OAAA,EAAU,IAAI,CAAA,yCAAA,CAA2C,CAAA;AAChF,IAAA,MAAM,QAAQ,SAAA,CAAU,CAAA,CAAE,MAAM,CAAA,EAAG,CAAA,2BAAA,EAA8B,IAAI,CAAA,CAAA,CAAG,CAAA;AACxE,IAAA,IAAI,IAAA,CAAK,IAAI,KAAK,CAAA,OAAQ,CAAA,OAAA,EAAU,IAAI,CAAA,4BAAA,EAA+B,KAAK,CAAA,CAAA,CAAG,CAAA;AAC/E,IAAA,IAAA,CAAK,IAAI,KAAK,CAAA;AACd,IAAA,IAAI,OAAO,EAAE,MAAM,CAAA,KAAM,YAAY,IAAA,CAAK,CAAA,WAAA,EAAc,KAAK,CAAA,sBAAA,CAAwB,CAAA;AACrF,IAAA,IAAI,CAAA,CAAE,OAAO,CAAA,KAAM,MAAA,IAAa,OAAO,CAAA,CAAE,OAAO,MAAM,UAAA,EAAY;AAChE,MAAA,IAAA,CAAK,CAAA,WAAA,EAAc,KAAK,CAAA,oCAAA,CAAsC,CAAA;AAAA,IAChE;AACA,IAAA,MAAM,KAAA,GAAQ,EAAE,OAAO,CAAA;AACvB,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,IAAA,CAAK,CAAA,WAAA,EAAc,KAAK,CAAA,cAAA,CAAgB,CAAA;AACzF,IAAA,MAAM,MAAA,uBAAa,GAAA,EAAY;AAC/B,IAAA,KAAA,MAAW,KAAK,KAAA,EAAoB;AAClC,MAAA,IAAI,CAAC,QAAA,CAAS,CAAC,GAAG,IAAA,CAAK,CAAA,WAAA,EAAc,KAAK,CAAA,oCAAA,CAAsC,CAAA;AAChF,MAAA,MAAM,QAAQ,SAAA,CAAU,CAAA,CAAE,OAAO,CAAA,EAAG,CAAA,2BAAA,EAA8B,KAAK,CAAA,CAAA,CAAG,CAAA;AAC1E,MAAA,IAAI,MAAA,CAAO,IAAI,KAAK,CAAA,OAAQ,CAAA,WAAA,EAAc,KAAK,CAAA,0BAAA,EAA6B,KAAK,CAAA,CAAA,CAAG,CAAA;AACpF,MAAA,MAAA,CAAO,IAAI,KAAK,CAAA;AAChB,MAAA,IAAI,OAAO,CAAA,CAAE,OAAO,CAAA,KAAM,UAAA;AACxB,QAAA,IAAA,CAAK,CAAA,MAAA,EAAS,KAAK,CAAA,MAAA,EAAS,KAAK,CAAA,uBAAA,CAAyB,CAAA;AAC5D,MAAA,IAAI,CAAA,CAAE,SAAS,CAAA,KAAM,MAAA,IAAa,OAAO,CAAA,CAAE,SAAS,MAAM,SAAA,EAAW;AACnE,QAAA,IAAA,CAAK,CAAA,MAAA,EAAS,KAAK,CAAA,MAAA,EAAS,KAAK,CAAA,yCAAA,CAA2C,CAAA;AAAA,MAC9E;AAAA,IACF;AACA,IAAA,IAAI,CAAA,CAAE,SAAS,CAAA,KAAM,MAAA,IAAa,CAAC,OAAO,GAAA,CAAI,CAAA,CAAE,SAAS,CAAW,CAAA,EAAG;AACrE,MAAA,IAAA,CAAK,CAAA,WAAA,EAAc,KAAK,CAAA,uCAAA,CAAyC,CAAA;AAAA,IACnE;AAAA,EACF;AACA,EAAA,OAAO,MAAA,CAAO,OAAO,KAAK,CAAA;AAC5B;;;ACzJO,SAAS,WAAW,EAAA,EAAiC;AAC1D,EAAA,OAAO,CAAC,GAAG,EAAE,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAC,CAAA;AACrC;AAQO,SAAS,OAAO,EAAA,EAA+B;AACpD,EAAA,MAAM,CAAA,GAAI,WAAW,EAAE,CAAA;AACvB,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,CAAA,CAAE,SAAS,CAAC,CAAA;AAGnC,EAAA,OAAO,CAAA,CAAE,MAAA,GAAS,CAAA,KAAM,CAAA,GACnB,CAAA,CAAE,GAAG,CAAA,GAAA,CACJ,CAAA,CAAE,GAAA,GAAM,CAAC,CAAA,GAAgB,CAAA,CAAE,GAAG,CAAA,IAAgB,CAAA;AACtD;AAsBO,SAAS,WAAA,CAAY,CAAA,EAAW,CAAA,EAAW,CAAA,EAAmB;AACnE,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AACvB,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAC1B,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,IAAK,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,GAAG,CAAA,EAAA,EAAK;AACxC,IAAA,IAAI,CAAA,GAAI,CAAA,EAAG,OAAA,IAAW,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,CAAA,GAAI,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AACtD,IAAA,KAAA,IAAS,KAAK,GAAA,CAAI,OAAA,GAAU,IAAI,IAAA,GAAA,CAAQ,CAAA,GAAI,KAAK,IAAI,CAAA;AAAA,EACvD;AACA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,CAAA;AAC1B;AASO,SAAS,YAAA,CAAa,GAAW,KAAA,EAAuB;AAC7D,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,OAAO,IAAI,CAAA,IAAK,WAAA,CAAY,GAAG,CAAA,EAAG,GAAG,KAAK,KAAA,EAAO,CAAA,EAAA;AACjD,EAAA,OAAO,CAAA;AACT;AAsBO,SAAS,gBAAA,CAAiB,IAAuB,KAAA,EAAsC;AAC5F,EAAA,MAAM,IAAA,GAAO,YAAA,CAAa,EAAA,CAAG,MAAA,EAAQ,KAAK,CAAA;AAC1C,EAAA,IAAI,IAAA,KAAS,GAAG,OAAO,IAAA;AACvB,EAAA,MAAM,CAAA,GAAI,WAAW,EAAE,CAAA;AAEvB,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,CAAA,CAAE,IAAA,GAAO,CAAC,CAAA;AAAA,IACf,IAAA,EAAM,CAAA,CAAE,CAAA,CAAE,MAAA,GAAS,IAAI,CAAA;AAAA,IACvB,IAAA;AAAA,IACA,MAAM,WAAA,CAAY,IAAA,GAAO,CAAA,EAAG,EAAA,CAAG,QAAQ,GAAG;AAAA,GAC5C;AACF;;;ACtDO,SAAS,MACd,MAAA,EACA,SAAA,GAAoB,eAAe,SAAA,EACnC,KAAA,GAAgB,eAAe,KAAA,EACiE;AAChG,EAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,MAAA,EAAQ,KAAK,CAAA;AAC/C,EAAA,MAAM,WAAA,GAAc,OAAO,MAAM,CAAA;AACjC,EAAA,IAAI,aAAa,IAAA,EAAM,OAAO,EAAE,OAAA,EAAS,cAAA,EAAgB,UAAU,WAAA,EAAY;AAC/E,EAAA,MAAM,KAAK,CAAA,GAAI,SAAA;AACf,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI,QAAA,CAAS,GAAA,GAAM,EAAA,EAAI,OAAA,GAAU,YAAA;AAAA,OAAA,IACxB,QAAA,CAAS,IAAA,GAAO,CAAA,GAAI,EAAA,EAAI,OAAA,GAAU,aAAA;AAAA,OAAA,IAClC,QAAA,CAAS,IAAA,IAAQ,EAAA,EAAI,OAAA,GAAU,eAAA;AAAA,OACnC,OAAA,GAAU,cAAA;AACf,EAAA,OAAO,EAAE,OAAA,EAAS,QAAA,EAAU,WAAA,EAAY;AAC1C;;;AC6GO,SAAS,gBAAgB,CAAA,EAA+B;AAC7D,EAAA,MAAM,IAAI,CAAA,CAAE,IAAA;AACZ,EAAA,OAAO,CAAA,KAAM,QAAQ,CAAA,CAAE,MAAA,KAAW,aAAa,IAAA,GAAO,QAAA,CAAS,GAAG,CAAC,CAAA;AACrE;AAOO,SAAS,QAAA,CACd,GACA,CAAA,EACQ;AACR,EAAA,MAAM,GAAA,GAAM,CAAA,CAAE,cAAA,IAAkB,CAAA,CAAE,aAAA;AAClC,EAAA,IAAI,CAAA,CAAE,YAAA,KAAiB,IAAA,EAAM,OAAO,GAAA;AAEpC,EAAA,MAAM,QAAA,GAAW,CAAA,CAAE,YAAA,GAAe,CAAA,CAAE,aAAA;AACpC,EAAA,IAAI,QAAA,IAAY,cAAA,CAAe,iBAAA,EAAmB,OAAO,GAAA;AACzD,EAAA,OAAO,EAAE,8BAAA,IAAkC,GAAA;AAC7C;AClLO,IAAM,cAAA,GAAiB;AAmF9B,IAAM,GAAA,GAAM,EAAE,IAAA,EAAM,QAAA,EAAS;AAC7B,IAAM,GAAA,GAAM,EAAE,IAAA,EAAM,SAAA,EAAW,SAAS,CAAA,EAAE;AAC1C,IAAM,GAAA,GAAM,EAAE,IAAA,EAAM,QAAA,EAAS;AAC7B,IAAM,YAAY,EAAE,IAAA,EAAM,CAAC,QAAA,EAAU,MAAM,CAAA,EAAE;AAC7C,IAAM,YAAY,EAAE,IAAA,EAAM,CAAC,QAAA,EAAU,MAAM,CAAA,EAAE;AAC7C,IAAM,IAAA,GAAO,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,GAAA,EAAI;AACzC,IAAM,OAAA,GAAU;AAAA,EACd,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,CAAC,QAAA,EAAU,QAAQ,CAAA;AAAA,EAC7B,UAAA,EAAY,EAAE,MAAA,EAAQ,EAAE,OAAO,SAAA,EAAU,EAAG,QAAQ,GAAA;AACtD,CAAA;AACA,IAAM,iBAAA,GAAoB,CAAC,QAAA,MAA8B,EAAE,OAAO,CAAC,QAAA,EAAU,OAAO,CAAA,EAAE,CAAA;AACtF,IAAM,OAAO,EAAE,IAAA,EAAM,CAAC,UAAA,EAAY,OAAO,CAAA,EAAE;AAC3C,IAAM,MAAA,GAAS,CAAC,CAAA,MAAuB,EAAE,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,MAAA,EAAO,EAAG,CAAC,CAAA,EAAE,CAAA;AAa/D,IAAM,cAAA,GAAiB;AAAA,EAC5B,OAAA,EAAS,8CAAA;AAAA,EACT,GAAA,EAAK,yFAAA;AAAA,EACL,KAAA,EAAO,kCAAA;AAAA,EACP,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,CAAC,eAAA,EAAiB,MAAA,EAAQ,QAAQ,WAAA,EAAa,OAAA,EAAS,eAAe,UAAU,CAAA;AAAA,EAC3F,UAAA,EAAY;AAAA,IACV,aAAA,EAAe,EAAE,KAAA,EAAO,CAAA,EAAE;AAAA,IAC1B,MAAM,EAAE,IAAA,EAAM,CAAC,SAAA,EAAW,YAAA,EAAc,aAAa,CAAA,EAAE;AAAA,IACvD,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,MAAA,EAAQ,SAAS,CAAA;AAAA,MAC5B,UAAA,EAAY,EAAE,IAAA,EAAM,EAAE,OAAO,eAAA,EAAgB,EAAG,SAAS,GAAA;AAAI,KAC/D;AAAA,IACA,SAAA,EAAW,GAAA;AAAA,IACX,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,CAAC,MAAA,EAAQ,MAAM,CAAA,EAAG,YAAY,EAAE,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,KAAI,EAAE;AAAA,IAC1F,WAAA,EAAa,EAAE,IAAA,EAAM,qBAAA,EAAsB;AAAA,IAC3C,QAAA,EAAU;AAAA,MACR,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,OAAA,EAAS,QAAQ,CAAA;AAAA,MAC5B,UAAA,EAAY,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAS,EAAG,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAS;AAAE;AACtE,GACF;AAAA,EACA,KAAA,EAAO;AAAA,IACL;AAAA,MACE,QAAA,EAAU,CAAC,SAAA,EAAW,SAAS,CAAA;AAAA,MAC/B,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,EAAE,KAAA,EAAO,SAAA,EAAU;AAAA,QACzB,OAAA,EAAS,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,QACjC,OAAA,EAAS,EAAE,IAAA,EAAM,iBAAA;AAAkB;AACrC,KACF;AAAA,IACA;AAAA,MACE,QAAA,EAAU,CAAC,MAAA,EAAQ,MAAA,EAAQ,YAAY,CAAA;AAAA,MACvC,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,EAAE,KAAA,EAAO,YAAA,EAAa;AAAA,QAC5B,IAAA,EAAM,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,QAC9B,IAAA,EAAM,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,QAC9B,UAAA,EAAY,EAAE,IAAA,EAAM,oBAAA;AAAqB;AAC3C,KACF;AAAA,IACA;AAAA,MACE,QAAA,EAAU,CAAC,MAAA,EAAQ,MAAA,EAAQ,aAAa,CAAA;AAAA,MACxC,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,EAAE,KAAA,EAAO,aAAA,EAAc;AAAA,QAC7B,IAAA,EAAM,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,QAC9B,IAAA,EAAM,EAAE,IAAA,EAAM,eAAA,EAAgB;AAAA,QAC9B,WAAA,EAAa,EAAE,IAAA,EAAM,qBAAA;AAAsB;AAC7C;AACF,GACF;AAAA,EACA,KAAA,EAAO;AAAA,IACL,KAAA,EAAO;AAAA,MACL,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,QAAA,EAAU,QAAQ,CAAA;AAAA,MAC7B,UAAA,EAAY,EAAE,MAAA,EAAQ,GAAA,EAAK,QAAQ,SAAA;AAAU,KAC/C;AAAA,IACA,WAAA,EAAa;AAAA,MACX,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU;AAAA,QACR,MAAA;AAAA,QACA,IAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAA;AAAA,QACA,WAAA;AAAA,QACA,UAAA;AAAA,QACA,aAAA;AAAA,QACA,kBAAA;AAAA,QACA,cAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,GAAA;AAAA,QACN,EAAA,EAAI,GAAA;AAAA,QACJ,QAAA,EAAU,GAAA;AAAA,QACV,IAAA,EAAM,GAAA;AAAA,QACN,SAAA,EAAW,GAAA;AAAA,QACX,QAAA,EAAU,SAAA;AAAA,QACV,WAAA,EAAa,GAAA;AAAA,QACb,gBAAA,EAAkB,GAAA;AAAA,QAClB,YAAA,EAAc,SAAA;AAAA;AAAA,QAEd,WAAA,EAAa,IAAA;AAAA,QACb,MAAA,EAAQ;AAAA,UACN,IAAA,EAAM,QAAA;AAAA,UACN,QAAA,EAAU,CAAC,IAAA,EAAM,UAAA,EAAY,OAAO,CAAA;AAAA,UACpC,UAAA,EAAY,EAAE,EAAA,EAAI,EAAE,IAAA,EAAM,WAAU,EAAG,QAAA,EAAU,SAAA,EAAW,KAAA,EAAO,GAAA;AAAI;AACzE;AACF,KACF;AAAA,IACA,UAAU,MAAA,CAAO;AAAA,MACf,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,KAAA,EAAO,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAAA,MACxC,UAAA,EAAY,EAAE,GAAA,EAAK,GAAA,EAAK,MAAM,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,GAAA;AAAI,KACzD,CAAA;AAAA,IACD,cAAA,EAAgB;AAAA,MACd,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU;AAAA,QACR,WAAA;AAAA,QACA,MAAA;AAAA,QACA,aAAA;AAAA,QACA,aAAA;AAAA,QACA,QAAA;AAAA,QACA,aAAA;AAAA,QACA,UAAA;AAAA,QACA,SAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,UAAA,EAAY;AAAA,QACV,SAAA,EAAW,GAAA;AAAA,QACX,IAAA,EAAM,GAAA;AAAA,QACN,WAAA,EAAa,IAAA;AAAA,QACb,WAAA,EAAa,IAAA;AAAA,QACb,MAAA,EAAQ,IAAA;AAAA,QACR,WAAA,EAAa,SAAA;AAAA;AAAA,QAEb,SAAA,EAAW,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,IAAA,EAAK;AAAA,QACxC,SAAA,EAAW,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,IAAA,EAAK;AAAA,QACxC,QAAA,EAAU,EAAE,IAAA,EAAM,kBAAA,EAAmB;AAAA,QACrC,OAAA,EAAS;AAAA,UACP,MAAM,CAAC,YAAA,EAAc,aAAA,EAAe,eAAA,EAAiB,gBAAgB,SAAS;AAAA,SAChF;AAAA,QACA,UAAA,EAAY;AAAA;AACd,KACF;AAAA,IACA,UAAA,EAAY;AAAA,MACV,IAAA,EAAM,QAAA;AAAA,MACN,UAAU,CAAC,QAAA,EAAU,WAAA,EAAa,OAAA,EAAS,UAAU,OAAO,CAAA;AAAA,MAC5D,UAAA,EAAY;AAAA,QACV,MAAA,EAAQ,GAAA;AAAA,QACR,SAAA,EAAW,GAAA;AAAA,QACX,KAAA,EAAO,GAAA;AAAA,QACP,MAAA,EAAQ,GAAA;AAAA,QACR,KAAA,EAAO,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,0BAAyB;AAAE;AACpE,KACF;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,QAAA,EAAU,QAAA,EAAU,QAAQ,YAAY,CAAA;AAAA,MACnD,UAAA,EAAY,EAAE,MAAA,EAAQ,GAAA,EAAK,QAAQ,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,UAAA,EAAY,GAAA;AAAI,KACrE;AAAA,IACA,WAAA,EAAa;AAAA,MACX,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU;AAAA,QACR,QAAA;AAAA,QACA,aAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,eAAA;AAAA,QACA,UAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,UAAA,EAAY;AAAA,QACV,MAAA,EAAQ,GAAA;AAAA,QACR,WAAA,EAAa,GAAA;AAAA,QACb,IAAA,EAAM;AAAA,UACJ,IAAA,EAAM,OAAA;AAAA,UACN,KAAA,EAAO,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,0BAAyB;AAAE,SACpE;AAAA,QACA,OAAA,EAAS,EAAE,IAAA,EAAM,cAAA,EAAe;AAAA,QAChC,aAAA,EAAe,EAAE,IAAA,EAAM,cAAA,EAAe;AAAA;AAAA,QAEtC,kBAAA,EAAoB,EAAE,IAAA,EAAM,cAAA,EAAe;AAAA,QAC3C,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,QAC3B,QAAA,EAAU;AAAA,UACR,IAAA,EAAM,OAAA;AAAA,UACN,KAAA,EAAO;AAAA,YACL,IAAA,EAAM,QAAA;AAAA,YACN,QAAA,EAAU,CAAC,WAAA,EAAa,MAAA,EAAQ,UAAU,aAAa,CAAA;AAAA,YACvD,UAAA,EAAY,EAAE,SAAA,EAAW,GAAA,EAAK,MAAM,GAAA,EAAK,MAAA,EAAQ,GAAA,EAAK,WAAA,EAAa,SAAA;AAAU;AAC/E,SACF;AAAA,QACA,YAAA,EAAc;AAAA,UACZ,IAAA,EAAM,QAAA;AAAA,UACN,QAAA,EAAU,CAAC,SAAA,EAAW,KAAA,EAAO,QAAQ,CAAA;AAAA,UACrC,UAAA,EAAY,EAAE,OAAA,EAAS,EAAE,IAAA,EAAM,SAAA,EAAU,EAAG,GAAA,EAAK,EAAE,IAAA,EAAM,SAAA,EAAU,EAAG,QAAQ,GAAA;AAAI;AACpF;AACF,KACF;AAAA,IACA,MAAM,iBAAA,CAAkB;AAAA,MACtB,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,QAAA,EAAU,OAAA,EAAS,iBAAiB,qBAAqB,CAAA;AAAA,MACpE,UAAA,EAAY;AAAA,QACV,MAAA,EAAQ,EAAE,KAAA,EAAO,UAAA,EAAW;AAAA,QAC5B,KAAA,EAAO,GAAA;AAAA,QACP,aAAA,EAAe,GAAA;AAAA,QACf,mBAAA,EAAqB,GAAA;AAAA;AAAA,QAErB,iBAAA,EAAmB,GAAA;AAAA,QACnB,uBAAA,EAAyB,GAAA;AAAA,QACzB,cAAA,EAAgB,GAAA;AAAA,QAChB,oBAAA,EAAsB,GAAA;AAAA,QACtB,uBAAA,EAAyB,GAAA;AAAA,QACzB,6BAAA,EAA+B,GAAA;AAAA,QAC/B,2BAAA,EAA6B,GAAA;AAAA,QAC7B,iCAAA,EAAmC,GAAA;AAAA,QACnC,wBAAA,EAA0B,GAAA;AAAA,QAC1B,8BAAA,EAAgC;AAAA;AAClC,KACD,CAAA;AAAA,IACD,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,OAAA,EAAS,WAAA,EAAa,QAAQ,SAAS,CAAA;AAAA,MAClD,UAAA,EAAY;AAAA,QACV,OAAO,EAAE,IAAA,EAAM,CAAC,QAAA,EAAU,QAAA,EAAU,OAAO,CAAA,EAAE;AAAA,QAC7C,SAAA,EAAW,SAAA;AAAA,QACX,IAAA,EAAM,SAAA;AAAA,QACN,OAAA,EAAS;AAAA;AACX,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU,CAAC,WAAA,EAAa,OAAA,EAAS,YAAY,CAAA;AAAA,MAC7C,UAAA,EAAY;AAAA,QACV,SAAA,EAAW,GAAA;AAAA,QACX,KAAA,EAAO,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,uBAAsB,EAAE;AAAA;AAAA,QAE/D,WAAA,EAAa,MAAA,CAAO,EAAE,IAAA,EAAM,gBAAgB,CAAA;AAAA,QAC5C,QAAA,EAAU,EAAE,IAAA,EAAM,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,mBAAkB,EAAE;AAAA,QAC9D,YAAY,MAAA,CAAO;AAAA,UACjB,IAAA,EAAM,QAAA;AAAA,UACN,QAAA,EAAU;AAAA,YACR,MAAA;AAAA,YACA,cAAA;AAAA,YACA,UAAA;AAAA,YACA,OAAA;AAAA,YACA,eAAA;AAAA,YACA,eAAA;AAAA,YACA,OAAA;AAAA,YACA,YAAA;AAAA,YACA;AAAA,WACF;AAAA,UACA,UAAA,EAAY;AAAA,YACV,IAAA,EAAM,GAAA;AAAA,YACN,YAAA,EAAc,IAAA;AAAA,YACd,QAAA,EAAU,GAAA;AAAA,YACV,KAAA,EAAO,GAAA;AAAA,YACP,aAAA,EAAe,IAAA;AAAA,YACf,aAAA,EAAe,GAAA;AAAA,YACf,KAAA,EAAO;AAAA,cACL,IAAA,EAAM,OAAA;AAAA,cACN,KAAA,EAAO;AAAA,gBACL,IAAA,EAAM,QAAA;AAAA,gBACN,QAAA,EAAU,CAAC,MAAA,EAAQ,OAAA,EAAS,WAAW,CAAA;AAAA,gBACvC,YAAY,EAAE,IAAA,EAAM,KAAK,KAAA,EAAO,GAAA,EAAK,WAAW,GAAA;AAAI;AACtD,aACF;AAAA,YACA,UAAA,EAAY,GAAA;AAAA,YACZ,cAAA,EAAgB;AAAA;AAClB,SACD;AAAA;AACH,KACF;AAAA,IACA,WAAA,EAAa;AAAA,MACX,IAAA,EAAM,QAAA;AAAA,MACN,QAAA,EAAU;AAAA,QACR,WAAA;AAAA,QACA,MAAA;AAAA,QACA,eAAA;AAAA,QACA,cAAA;AAAA,QACA,YAAA;AAAA,QACA,SAAA;AAAA,QACA,aAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,UAAA,EAAY;AAAA,QACV,SAAA,EAAW,GAAA;AAAA,QACX,IAAA,EAAM,GAAA;AAAA,QACN,aAAA,EAAe,GAAA;AAAA,QACf,YAAA,EAAc,SAAA;AAAA,QACd,YAAY,iBAAA,CAAkB;AAAA,UAC5B,IAAA,EAAM,QAAA;AAAA,UACN,UAAU,CAAC,QAAA,EAAU,WAAA,EAAa,eAAA,EAAiB,gBAAgB,gBAAgB,CAAA;AAAA,UACnF,UAAA,EAAY;AAAA,YACV,MAAA,EAAQ,EAAE,KAAA,EAAO,UAAA,EAAW;AAAA,YAC5B,SAAA,EAAW;AAAA,cACT,IAAA,EAAM,OAAA;AAAA,cACN,KAAA,EAAO;AAAA,gBACL,IAAA,EAAM,QAAA;AAAA,gBACN,QAAA,EAAU,CAAC,gBAAA,EAAkB,eAAe,CAAA;AAAA;AAAA,gBAE5C,YAAY,EAAE,cAAA,EAAgB,IAAA,EAAM,aAAA,EAAe,KAAK,IAAA;AAAK;AAC/D,aACF;AAAA,YACA,aAAA,EAAe,GAAA;AAAA,YACf,YAAA,EAAc,GAAA;AAAA,YACd,cAAA,EAAgB;AAAA;AAClB,SACD,CAAA;AAAA,QACD,SAAS,MAAA,CAAO;AAAA,UACd,IAAA,EAAM,QAAA;AAAA,UACN,QAAA,EAAU,CAAC,QAAA,EAAU,OAAA,EAAS,mBAAmB,OAAO,CAAA;AAAA,UACxD,UAAA,EAAY;AAAA,YACV,QAAQ,EAAE,IAAA,EAAM,CAAC,UAAA,EAAY,SAAS,CAAA,EAAE;AAAA,YACxC,KAAA,EAAO,GAAA;AAAA,YACP,eAAA,EAAiB,GAAA;AAAA,YACjB,KAAA,EAAO,SAAA;AAAA,YACP,KAAA,EAAO,SAAA;AAAA,YACP,KAAA,EAAO,SAAA;AAAA,YACP,KAAA,EAAO;AAAA;AACT,SACD,CAAA;AAAA,QACD,WAAA,EAAa,MAAA;AAAA,UACX,iBAAA,CAAkB;AAAA,YAChB,IAAA,EAAM,QAAA;AAAA,YACN,QAAA,EAAU,CAAC,QAAA,EAAU,WAAA,EAAa,kBAAkB,oBAAoB,CAAA;AAAA,YACxE,UAAA,EAAY;AAAA,cACV,MAAA,EAAQ,EAAE,KAAA,EAAO,UAAA,EAAW;AAAA;AAAA,cAE5B,QAAQ,EAAE,IAAA,EAAM,CAAC,cAAA,EAAgB,cAAc,CAAA,EAAE;AAAA,cACjD,WAAA,EAAa,GAAA;AAAA,cACb,SAAA,EAAW,GAAA;AAAA,cACX,cAAA,EAAgB,GAAA;AAAA,cAChB,kBAAA,EAAoB;AAAA;AACtB,WACD;AAAA,SACH;AAAA,QACA,IAAA,EAAM,MAAA,CAAO,EAAE,IAAA,EAAM,gBAAgB,CAAA;AAAA,QACrC,OAAA,EAAS,MAAA;AAAA,UACP,iBAAA,CAAkB;AAAA,YAChB,IAAA,EAAM,QAAA;AAAA,YACN,UAAU,CAAC,QAAA,EAAU,WAAW,KAAA,EAAO,aAAA,EAAe,eAAe,iBAAiB,CAAA;AAAA,YACtF,UAAA,EAAY;AAAA,cACV,MAAA,EAAQ,EAAE,KAAA,EAAO,UAAA,EAAW;AAAA,cAC5B,SAAS,EAAE,IAAA,EAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,EAAE;AAAA,cACrC,GAAA,EAAK,GAAA;AAAA,cACL,WAAA,EAAa,GAAA;AAAA,cACb,WAAA,EAAa,IAAA;AAAA,cACb,eAAA,EAAiB;AAAA;AACnB,WACD;AAAA;AACH;AACF;AACF;AAEJ;AAEA,SAAS,OAAO,KAAA,EAAwB;AACtC,EAAA,IAAI,KAAA,KAAU,MAAM,OAAO,MAAA;AAC3B,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAO,OAAA;AACjC,EAAA,IAAI,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,EAAG,OAAO,SAAA;AACpC,EAAA,OAAO,OAAO,KAAA;AAChB;AAEA,SAAS,WAAA,CAAY,OAAgB,IAAA,EAAuB;AAC1D,EAAA,MAAM,MAAA,GAAS,OAAO,KAAK,CAAA;AAC3B,EAAA,OAAO,MAAA,KAAW,IAAA,IAAS,IAAA,KAAS,QAAA,IAAY,MAAA,KAAW,SAAA;AAC7D;AAEA,SAAS,UAAA,CAAW,KAAa,IAAA,EAAsB;AACrD,EAAA,MAAM,SAAS,IAAA,CAAK,KAAA,GAAQ,IAAI,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAC,CAAA;AACvD,EAAA,IAAI,WAAW,MAAA,EAAW,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,GAAG,CAAA,CAAE,CAAA;AAC3E,EAAA,OAAO,MAAA;AACT;AASO,SAAS,SAAS,KAAA,EAAgB,MAAA,EAAgB,IAAA,GAAe,MAAA,EAAQ,KAAK,EAAA,EAAc;AACjG,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,MAAM,IAAA,GAAO,EAAA,KAAO,EAAA,GAAK,GAAA,GAAM,EAAA;AAC/B,EAAA,IAAI,OAAO,OAAO,IAAA,KAAS,QAAA;AACzB,IAAA,OAAO,QAAA,CAAS,OAAO,UAAA,CAAW,MAAA,CAAO,MAAM,IAAI,CAAA,EAAG,MAAM,EAAE,CAAA;AAChE,EAAA,IAAI,OAAA,IAAW,MAAA,IAAU,KAAA,KAAU,MAAA,CAAO,KAAA;AACxC,IAAA,QAAA,CAAS,IAAA,CAAK,GAAG,IAAI,CAAA,UAAA,EAAa,KAAK,SAAA,CAAU,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,CAAA;AAClE,EAAA,IAAI,MAAA,CAAO,SAAS,MAAA,IAAa,CAAC,OAAO,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG;AAC7D,IAAA,QAAA,CAAS,IAAA;AAAA,MACP,CAAA,EAAG,IAAI,CAAA,iBAAA,EAAoB,MAAA,CAAO,KAAK,GAAA,CAAI,CAAC,CAAA,KAAM,IAAA,CAAK,UAAU,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,KACjF;AAAA,EACF;AACA,EAAA,IAAI,MAAA,CAAO,SAAS,MAAA,EAAW;AAC7B,IAAA,MAAM,KAAA,GAA2B,OAAO,MAAA,CAAO,IAAA,KAAS,WAAW,CAAC,MAAA,CAAO,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA;AAC1F,IAAA,IAAI,CAAC,MAAM,IAAA,CAAK,CAAC,MAAM,WAAA,CAAY,KAAA,EAAO,CAAC,CAAC,CAAA,EAAG;AAC7C,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,EAAG,IAAI,CAAA,UAAA,EAAa,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,CAAA,KAAA,EAAQ,MAAA,CAAO,KAAK,CAAC,CAAA,CAAE,CAAA;AAC3E,MAAA,OAAO,QAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,IAAS,MAAA,CAAO,WAAW,CAAA,QAAA,CAAA,EAAY;AACtE,IAAA,QAAA,CAAS,IAAA,CAAK,GAAG,IAAI,CAAA,mBAAA,EAAsB,OAAO,MAAA,CAAO,OAAO,CAAC,CAAA,CAAE,CAAA;AAAA,EACrE;AACA,EAAA,IAAI,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxE,IAAA,MAAM,MAAA,GAAS,KAAA;AACf,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,QAAA,IAAY,EAAC,EAAG;AACvC,MAAA,IAAI,EAAE,OAAO,MAAA,CAAA,EAAS,QAAA,CAAS,KAAK,CAAA,EAAG,IAAI,CAAA,cAAA,EAAiB,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,IACpE;AACA,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,GAAG,CAAA,IAAK,MAAA,CAAO,QAAQ,MAAA,CAAO,UAAA,IAAc,EAAE,CAAA,EAAG;AAChE,MAAA,IAAI,OAAO,MAAA,EAAQ,QAAA,CAAS,IAAA,CAAK,GAAG,SAAS,MAAA,CAAO,GAAG,CAAA,EAAG,GAAA,EAAK,MAAM,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,GAAG,EAAE,CAAC,CAAA;AAAA,IACtF;AAAA,EACF;AACA,EAAA,IAAI,MAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,MAAA,CAAO,UAAU,MAAA,EAAW;AACtD,IAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AACrB,IAAA,KAAA,CAAM,QAAQ,CAAC,IAAA,EAAM,MAAM,QAAA,CAAS,IAAA,CAAK,GAAG,QAAA,CAAS,IAAA,EAAM,OAAO,IAAA,EAAM,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,MAAA,CAAO,CAAC,CAAC,CAAA,CAAE,CAAC,CAAC,CAAA;AAAA,EAChG;AACA,EAAA,IAAI,MAAA,CAAO,UAAU,MAAA,EAAW;AAC9B,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,QAAA,CAAS,KAAA,EAAO,CAAA,EAAG,IAAA,EAAM,EAAE,CAAC,CAAA;AACpE,IAAA,MAAM,OAAA,GAAU,QAAQ,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,CAAC,CAAA,CAAE,MAAA;AACtD,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAA,QAAA,CAAS,IAAA;AAAA,QACP,CAAA,EAAG,IAAI,CAAA,4BAAA,EAA+B,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAC,CAAA,iBAAA,EAAoB,MAAA,CAAO,OAAO,CAAC,CAAA;AAAA,OACjG;AAGA,MAAA,QAAA,CAAS,IAAA,CAAK,GAAG,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAO,CAAA,CAAE,MAAA,GAAS,CAAA,CAAE,MAAA,GAAS,CAAA,GAAI,CAAE,CAAC,CAAA;AAAA,IAC1E;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT;AAiBO,SAAS,YAAY,IAAA,EAA+B;AACzD,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI;AACF,IAAA,KAAA,GAAQ,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,UAAU,yCAAyC,CAAA;AAAA,EAC/D;AACA,EAAA,MAAM,UAAW,KAAA,EAA8C,aAAA;AAC/D,EAAA,IAAI,YAAY,cAAA,EAAgB;AAC9B,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,kCAAkC,MAAA,CAAO,OAAO,CAAC,CAAA,gCAAA,EAAmC,MAAA,CAAO,cAAc,CAAC,CAAA,CAAA;AAAA,KAC5G;AAAA,EACF;AACA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,KAAA,EAAO,cAAc,CAAA;AAC/C,EAAA,IAAI,SAAS,MAAA,GAAS,CAAA;AACpB,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA;AAAA,EAAwD,QAAA,CAAS,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,KAC7E;AACF,EAAA,OAAO,KAAA;AACT;ACviBO,IAAM,UAAA,GAAN,cAAyB,KAAA,CAAM;AAAA,EAClB,IAAA,GAAO,YAAA;AAC3B;AAOuB,OAAO,MAAA,CAAO;AAAA,EACnC,OAAO,aAAA,CAAc,IAAI,IAAI,mBAAA,EAAqB,MAAA,CAAA,IAAA,CAAY,GAAG,CAAC,CAAA;AAAA,EAClE,UAAU,aAAA,CAAc,IAAI,IAAI,oBAAA,EAAsB,MAAA,CAAA,IAAA,CAAY,GAAG,CAAC;AACxE,CAAC","file":"index.mjs","sourcesContent":["/**\n * The benchmark constants, frozen.\n *\n * Every value here is fixed by the performance standard,\n * `documentation/decisions/0005-the-performance-standard.md` in the `config` repo, which says for\n * each one whether it was measured or chosen, and on what basis. The warmup rule is not here: it\n * is `PERF_CONTRACT` in `@cosyte/test-utils/perf` (ADR 0001 section 2), which `measure.ts` imports\n * and the build bundles, so the scaling gate and the benchmarks warm up by one rule kept in one\n * place.\n *\n * They are not tuning knobs and cannot be overridden per package. A comparison's false alarm rate\n * was measured with these values; change one and the measurement no longer describes the tool.\n *\n * @packageDocumentation\n */\n\n/** The benchmark constants. See {@link BENCH_CONTRACT}. */\nexport interface BenchContractShape {\n /** Timed samples per case per process. Each sample is whole passes over the corpus. */\n readonly SAMPLES: number;\n /** Minimum timed length of one sample, in milliseconds. A sample runs whole passes until it is reached. */\n readonly SAMPLE_MIN_MS: number;\n /** Rounds in one base against head comparison. Each round runs both sides once, in fresh processes. */\n readonly ROUNDS: number;\n /** One-sided error allowed at each end of the comparison's interval on the median ratio. */\n readonly ALPHA: number;\n /** The smallest slowdown a comparison will call a regression, as a fraction: 0.03 is 3%. */\n readonly THRESHOLD: number;\n /** Time budget for the latency calls of one case, in milliseconds. */\n readonly LATENCY_BUDGET_MS: number;\n /** Most latency calls taken for one case. */\n readonly LATENCY_MAX_CALLS: number;\n /** Fewest latency calls taken for one case, whatever the budget says. */\n readonly LATENCY_MIN_CALLS: number;\n /** A percentile q is reported only when at least this many calls lie above it: n(1 - q) >= 10. */\n readonly LATENCY_TAIL_CALLS: number;\n /** The median call must be at least this many timer reads long, or latency is skipped. */\n readonly LATENCY_TIMER_MULTIPLE: number;\n /** Most operations in the first allocation window. Halved while a garbage collection lands in a window. */\n readonly ALLOC_WINDOW_OPS: number;\n /** The first allocation window is cut to about this many milliseconds of operations, from the warmup's pace. */\n readonly ALLOC_WINDOW_MS: number;\n /** The young generation size the allocation process runs with, in MiB, so a window rarely collects. */\n readonly ALLOC_SEMI_SPACE_MIB: number;\n /** Calls measured one by one for the peak heap. */\n readonly PEAK_CALLS: number;\n /**\n * An input under this many bytes, on average, is budgeted on its median call's total peak above\n * the empty-call floor's median, rather than its highest raw peak. On a GitHub-hosted runner the\n * peak probe costs about 3.3 KiB on a typical empty call and 28 KiB on its highest (one call, the\n * first), so the highest of a small input's calls reads the probe more than the code: 20 times a\n * 1 KiB input is spent before the code runs. At 64 KiB even the probe's highest call is about 2%\n * of a 20 times budget, so above it the raw highest peak is read as before.\n */\n readonly FLOOR_INPUT_BYTES: number;\n /** Rounds in the long run that checks the heap stays flat. */\n readonly LONG_RUN_ROUNDS: number;\n /** Rounds at the start of the long run left out of the slope, while caches and compiled code settle. */\n readonly LONG_RUN_SETTLE_ROUNDS: number;\n /** Minimum length of one long run round, in milliseconds, fixed as a number of operations by the first. */\n readonly LONG_RUN_ROUND_MS: number;\n /** A long run whose fitted growth over the measured rounds reaches this many bytes is reported as growing. */\n readonly LONG_RUN_GROWTH_BYTES: number;\n /** A garbage collection round that reclaims less than this, in bytes, counts as settled (ADR 0001 section 8). */\n readonly GC_SETTLE_BYTES: number;\n /** Consecutive settled rounds needed (ADR 0001 section 8). */\n readonly GC_SETTLE_ROUNDS: number;\n /** Most garbage collection rounds before giving up (ADR 0001 section 8). */\n readonly GC_MAX_ROUNDS: number;\n /** Fresh processes timed for the cold import, for the target and for an empty module each. */\n readonly IMPORT_RUNS: number;\n /**\n * A case whose last three warmup batches each took at least this many milliseconds a pass runs on\n * the large-case plan: every batch is one pass, so the warmup rule's aggregation cannot happen.\n */\n readonly LARGE_PASS_MS: number;\n /** Passes in each of the two windows whose medians the large-case warmup compares. */\n readonly LARGE_WARMUP_WINDOW: number;\n /** Hard cap on the large-case warmup, in milliseconds, from the start of the warmup. */\n readonly LARGE_WARMUP_MAX_MS: number;\n /**\n * The long run also runs on a benchmark's largest size whose rounds fit in this many milliseconds,\n * a round being at least one pass.\n */\n readonly LONG_RUN_MAX_MS: number;\n /** The timing comparison may become a required check when A/A false alarms are under this rate. */\n readonly REQUIRED_FALSE_ALARM_RATE: number;\n /** The confidence that the false alarm rate is under the line, for the rule above. */\n readonly REQUIRED_CONFIDENCE: number;\n}\n\n/**\n * The frozen benchmark constants.\n *\n * @example\n * ```ts\n * import { BENCH_CONTRACT } from \"@cosyte/bench\";\n *\n * BENCH_CONTRACT.ROUNDS; // => 12\n * BENCH_CONTRACT.THRESHOLD; // => 0.03\n * ```\n */\nexport const BENCH_CONTRACT: BenchContractShape = Object.freeze({\n SAMPLES: 5,\n SAMPLE_MIN_MS: 50,\n ROUNDS: 12,\n ALPHA: 0.05,\n THRESHOLD: 0.03,\n LATENCY_BUDGET_MS: 2_000,\n LATENCY_MAX_CALLS: 5_000,\n LATENCY_MIN_CALLS: 20,\n LATENCY_TAIL_CALLS: 10,\n LATENCY_TIMER_MULTIPLE: 20,\n ALLOC_WINDOW_OPS: 256,\n ALLOC_WINDOW_MS: 100,\n ALLOC_SEMI_SPACE_MIB: 64,\n PEAK_CALLS: 20,\n FLOOR_INPUT_BYTES: 64 * 1024,\n LONG_RUN_ROUNDS: 30,\n LONG_RUN_SETTLE_ROUNDS: 5,\n LONG_RUN_ROUND_MS: 100,\n LONG_RUN_GROWTH_BYTES: 64 * 1024,\n GC_SETTLE_BYTES: 64 * 1024,\n GC_SETTLE_ROUNDS: 2,\n GC_MAX_ROUNDS: 7,\n IMPORT_RUNS: 20,\n LARGE_PASS_MS: 50,\n LARGE_WARMUP_WINDOW: 5,\n LARGE_WARMUP_MAX_MS: 30_000,\n LONG_RUN_MAX_MS: 60_000,\n REQUIRED_FALSE_ALARM_RATE: 0.01,\n REQUIRED_CONFIDENCE: 0.95,\n});\n","/**\n * How a package describes its benchmarks.\n *\n * A suite is a module whose default export is {@link defineSuite}'s result. It is handed the module\n * under test (`bind`) rather than importing it, so one suite file measures the base build and the\n * head build of a comparison. Every corpus is built in code (`build`): the runner never takes a data\n * file, which is how its inputs stay synthetic by construction.\n *\n * @packageDocumentation\n */\n\n/** One corpus: the inputs one pass runs over, built outside every timed region. */\nexport interface BenchCorpus<TInput> {\n /** The inputs. One pass calls the measured function once on each. */\n readonly inputs: readonly TInput[];\n /** Bytes one pass handles, when it means something. Turns operations a second into bytes a second. */\n readonly bytes?: number;\n /** Called once the process has finished with this corpus, for example to remove a temporary directory. */\n readonly teardown?: () => void;\n}\n\n/** One input size of a benchmark: small, large, or anything between. */\nexport interface BenchSize<TInput> {\n /** How a reader names this size, for example `\"1 KiB\"`. Unique within its benchmark. */\n readonly label: string;\n /** Build the corpus. Called once per process, before anything is timed. */\n readonly build: () => BenchCorpus<TInput>;\n /**\n * Whether the base against head comparison runs this size. Defaults to `true`. Set `false` for a\n * size whose processes are too slow for a pull request's time budget; a reading still measures it.\n */\n readonly compare?: boolean;\n}\n\n/** One benchmark: a function measured at one or more input sizes. */\nexport interface Benchmark<TTarget, TInput, TResult> {\n /** Unique within the suite. */\n readonly name: string;\n /** The sizes, smallest first. Each size is measured on its own. */\n readonly sizes: readonly BenchSize<TInput>[];\n /** Take the imported module under test and return the function to measure. */\n readonly bind: (target: TTarget) => (input: TInput) => TResult;\n /**\n * Reduce a result to a number summed into a sink the runner checks, so the compiler cannot delete\n * the measured call. Defaults to 1 for each result that is not `null` or `undefined`; supply it\n * when the function returns nothing. The sum must grow over a measurement, so return a positive\n * number for at least some results; a run whose sink did not grow is refused.\n */\n readonly weigh?: (result: TResult) => number;\n /** The size label the long run (is the heap flat?) uses. Defaults to the first size. */\n readonly longRun?: string;\n}\n\n/** A benchmark with its input and result types erased, as a suite holds it. */\nexport type AnyBenchmark<TTarget> = Benchmark<TTarget, unknown, unknown>;\n\n/** A suite: the benchmarks of one package. */\nexport interface BenchSuite<TTarget> {\n /** Shown in every report, for example `\"@cosyte/script-utils phi-scan\"`. */\n readonly name: string;\n /** At least one. */\n readonly benchmarks: readonly AnyBenchmark<TTarget>[];\n}\n\n/**\n * Type one benchmark. Only a type helper: it returns its argument, with the input and result types\n * erased so benchmarks of different shapes can sit in one suite.\n *\n * @example\n * ```ts\n * import { defineBenchmark } from \"@cosyte/bench\";\n *\n * const b = defineBenchmark({\n * name: \"split\",\n * sizes: [{ label: \"1 KiB\", build: () => ({ inputs: [\"a|b\".repeat(341)] }) }],\n * bind: () => (s: string) => s.split(\"|\"),\n * });\n * b.name; // => \"split\"\n * ```\n */\nexport function defineBenchmark<TTarget, TInput, TResult>(\n benchmark: Benchmark<TTarget, TInput, TResult>,\n): AnyBenchmark<TTarget> {\n return benchmark as unknown as AnyBenchmark<TTarget>;\n}\n\n/**\n * Check a suite and freeze it. Throws a `TypeError` naming the problem: an empty name, no\n * benchmarks, a repeated name or label, a benchmark with no sizes, or a `longRun` naming no size.\n *\n * @example\n * ```ts\n * import { defineBenchmark, defineSuite } from \"@cosyte/bench\";\n *\n * export default defineSuite({\n * name: \"example\",\n * benchmarks: [\n * defineBenchmark({\n * name: \"split\",\n * sizes: [{ label: \"1 KiB\", build: () => ({ inputs: [\"a|b\".repeat(341)] }) }],\n * bind: () => (s: string) => s.split(\"|\"),\n * }),\n * ],\n * });\n * ```\n */\nexport function defineSuite<TTarget>(suite: BenchSuite<TTarget>): BenchSuite<TTarget> {\n return checkSuite(suite);\n}\n\nfunction fail(message: string): never {\n throw new TypeError(`@cosyte/bench: ${message}`);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction checkName(value: unknown, what: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") fail(`${what} must be a non-empty string`);\n return value;\n}\n\n/**\n * Check that a value is a suite, as a suite module's default export must be. The messages name\n * benchmarks and sizes, never an input.\n *\n * @internal\n */\nexport function checkSuite(value: unknown): BenchSuite<unknown> {\n if (!isRecord(value)) fail(\"a suite must be an object made by defineSuite\");\n const name = checkName(value[\"name\"], \"the suite name\");\n const benchmarks = value[\"benchmarks\"];\n if (!Array.isArray(benchmarks) || benchmarks.length === 0) {\n fail(`suite \"${name}\" must have at least one benchmark`);\n }\n const seen = new Set<string>();\n for (const b of benchmarks as unknown[]) {\n if (!isRecord(b)) fail(`suite \"${name}\" holds a benchmark that is not an object`);\n const bName = checkName(b[\"name\"], `a benchmark name in suite \"${name}\"`);\n if (seen.has(bName)) fail(`suite \"${name}\" has two benchmarks named \"${bName}\"`);\n seen.add(bName);\n if (typeof b[\"bind\"] !== \"function\") fail(`benchmark \"${bName}\" has no bind function`);\n if (b[\"weigh\"] !== undefined && typeof b[\"weigh\"] !== \"function\") {\n fail(`benchmark \"${bName}\" has a weigh that is not a function`);\n }\n const sizes = b[\"sizes\"];\n if (!Array.isArray(sizes) || sizes.length === 0) fail(`benchmark \"${bName}\" has no sizes`);\n const labels = new Set<string>();\n for (const s of sizes as unknown[]) {\n if (!isRecord(s)) fail(`benchmark \"${bName}\" holds a size that is not an object`);\n const label = checkName(s[\"label\"], `a size label in benchmark \"${bName}\"`);\n if (labels.has(label)) fail(`benchmark \"${bName}\" has two sizes labelled \"${label}\"`);\n labels.add(label);\n if (typeof s[\"build\"] !== \"function\")\n fail(`size \"${label}\" of \"${bName}\" has no build function`);\n if (s[\"compare\"] !== undefined && typeof s[\"compare\"] !== \"boolean\") {\n fail(`size \"${label}\" of \"${bName}\" has a compare that is not true or false`);\n }\n }\n if (b[\"longRun\"] !== undefined && !labels.has(b[\"longRun\"] as string)) {\n fail(`benchmark \"${bName}\" names a longRun size it does not have`);\n }\n }\n return Object.freeze(value) as unknown as BenchSuite<unknown>;\n}\n\n/** One case: a benchmark at one size. */\nexport interface CaseRef {\n readonly benchmark: string;\n readonly size: string;\n}\n\n/**\n * Every case in a suite, in suite order, optionally kept to the ones `only` names. A name in\n * `only` matches a whole benchmark (`\"scan\"`) or one case (`\"scan @ 1 KiB\"`). A name that matches\n * nothing throws, so a typo cannot quietly measure less than was asked. With `forComparison`, a\n * size marked `compare: false` is left out.\n *\n * @internal\n */\nexport function listCases(\n suite: BenchSuite<unknown>,\n only: readonly string[] = [],\n forComparison = false,\n): CaseRef[] {\n const all: CaseRef[] = [];\n for (const b of suite.benchmarks) {\n for (const s of b.sizes) {\n if (!forComparison || s.compare !== false) all.push({ benchmark: b.name, size: s.label });\n }\n }\n if (only.length === 0) return all;\n for (const o of only) {\n if (!all.some((c) => c.benchmark === o || caseId(c) === o)) {\n fail(`--only \"${o}\" names no benchmark or case in suite \"${suite.name}\"`);\n }\n }\n return all.filter((c) => only.includes(c.benchmark) || only.includes(caseId(c)));\n}\n\n/**\n * How a case is named in reports and in `--only`: `benchmark @ size`.\n *\n * @internal\n */\nexport function caseId(c: CaseRef): string {\n return `${c.benchmark} @ ${c.size}`;\n}\n\n/**\n * Find a case's benchmark and size in a suite. Throws when either is missing.\n *\n * @internal\n */\nexport function findCase(\n suite: BenchSuite<unknown>,\n c: CaseRef,\n): { benchmark: AnyBenchmark<unknown>; size: BenchSize<unknown> } {\n const benchmark = suite.benchmarks.find((b) => b.name === c.benchmark);\n const size = benchmark?.sizes.find((s) => s.label === c.size);\n // No benchmark means no size either, so one check covers both.\n if (size === undefined) fail(`suite \"${suite.name}\" has no case \"${caseId(c)}\"`);\n return { benchmark: benchmark as AnyBenchmark<unknown>, size };\n}\n","/**\n * The statistics the benchmarks report and judge by. Plain arithmetic with no dependencies, each\n * function small enough to check by hand against the worked numbers in its tests.\n *\n * @packageDocumentation\n */\n\n/**\n * A sorted copy, ascending. The input is not touched.\n *\n * @internal\n */\nexport function sortedCopy(xs: readonly number[]): number[] {\n return [...xs].sort((a, b) => a - b);\n}\n\n/**\n * The median: the middle value, or the mean of the two middle values. `NaN` for an empty vector,\n * so an empty measurement can never read as a fast one.\n *\n * @internal\n */\nexport function median(xs: readonly number[]): number {\n const s = sortedCopy(xs);\n const mid = Math.floor(s.length / 2);\n // An even length of at least 2 has both middle values. An empty vector takes the even branch and\n // reads two missing values, which is `NaN`: the empty answer, by arithmetic rather than a check.\n return s.length % 2 === 1\n ? (s[mid] as number)\n : ((s[mid - 1] as number) + (s[mid] as number)) / 2;\n}\n\n/**\n * A percentile by nearest rank: the smallest value with at least a fraction `q` of the values at or\n * below it. No interpolation, so every reported percentile is a value that was observed.\n *\n * @internal\n */\nexport function nearestRank(xs: readonly number[], q: number): number {\n if (xs.length === 0) return Number.NaN;\n const s = sortedCopy(xs);\n const rank = Math.max(1, Math.ceil(q * s.length));\n return s[rank - 1] as number;\n}\n\n/**\n * `P(X <= k)` for `X ~ Binomial(n, p)`, summed in log space so it stays finite for large `n`, for\n * `0 < p < 1` (every caller's range). A negative `k` sums nothing and is 0; a `k` past `n` sums all\n * of it and is 1.\n *\n * @internal\n */\nexport function binomialCdf(k: number, n: number, p: number): number {\n const logP = Math.log(p);\n const logQ = Math.log1p(-p);\n let logCoef = 0;\n let total = 0;\n for (let i = 0; i <= Math.min(k, n); i++) {\n if (i > 0) logCoef += Math.log(n - i + 1) - Math.log(i);\n total += Math.exp(logCoef + i * logP + (n - i) * logQ);\n }\n return Math.min(1, total);\n}\n\n/**\n * The sign test's rank for an interval on the median of `n` values: the largest `k` such that the\n * `k`-th smallest value lies below the true median with probability at most `alpha`, that is\n * `P(Binomial(n, 1/2) <= k - 1) <= alpha`. `0` when no rank qualifies (too few values).\n *\n * @internal\n */\nexport function signTestRank(n: number, alpha: number): number {\n let k = 0;\n while (k < n && binomialCdf(k, n, 0.5) <= alpha) k++;\n return k;\n}\n\n/** An interval on a median, from the sign test. */\nexport interface MedianInterval {\n /** The `rank`-th smallest value. */\n readonly low: number;\n /** The `rank`-th largest value. */\n readonly high: number;\n /** Which order statistics bound it. */\n readonly rank: number;\n /** The chance that each end alone misses the median, exactly: `P(Binomial(n, 1/2) <= rank - 1)`. */\n readonly tail: number;\n}\n\n/**\n * The distribution-free interval on the median of independent values: the `k`-th smallest and the\n * `k`-th largest, with `k` from {@link signTestRank}. It assumes nothing about the shape of the\n * noise, which on a shared runner is one-sided and heavy-tailed. `null` when there are too few\n * values for any rank to meet `alpha`.\n *\n * @internal\n */\nexport function signTestInterval(xs: readonly number[], alpha: number): MedianInterval | null {\n const rank = signTestRank(xs.length, alpha);\n if (rank === 0) return null;\n const s = sortedCopy(xs);\n // `rank` is at least 1 and at most `n`, so both order statistics exist.\n return {\n low: s[rank - 1] as number,\n high: s[s.length - rank] as number,\n rank,\n tail: binomialCdf(rank - 1, xs.length, 0.5),\n };\n}\n\n/**\n * The exact (Clopper-Pearson) one-sided upper confidence bound on a rate, from `k` events in `n`\n * trials: the largest rate `p` at which seeing `k` or fewer is still at least `1 - confidence`\n * likely. With no events this is `1 - (1 - confidence)^(1/n)`, about `3 / n` at 95%.\n *\n * @internal\n */\nexport function rateUpperBound(k: number, n: number, confidence: number): number {\n // No trials say nothing. Every trial an event bisects to 1 on its own.\n if (n === 0) return 1;\n const alpha = 1 - confidence;\n let lo = k / n;\n let hi = 1;\n // Stryker disable next-line EqualityOperator: a 101st halving moves the bound by under 2^-100, below a double's resolution.\n for (let i = 0; i < 100; i++) {\n const mid = (lo + hi) / 2;\n // Stryker disable next-line EqualityOperator: `>` and `>=` differ only where the tail equals alpha exactly, which bisection over doubles does not land on.\n if (binomialCdf(k, n, mid) > alpha) lo = mid;\n else hi = mid;\n }\n return hi;\n}\n\n/**\n * The fewest trials, all without an event, whose {@link rateUpperBound} is under `rate`: how long an\n * A/A calibration has to run before a clean record can show a false alarm rate under `rate`.\n *\n * @internal\n */\nexport function trialsForZeroEvents(rate: number, confidence: number): number {\n return Math.ceil(Math.log(1 - confidence) / Math.log1p(-rate));\n}\n\n/**\n * The Theil-Sen slope: the median of the slopes between every pair of points with distinct `x`.\n * One wild point moves it little, which matters for a heap reading that a late collection can\n * spike. `NaN` with fewer than two distinct `x`.\n *\n * @internal\n */\nexport function theilSenSlope(xs: readonly number[], ys: readonly number[]): number {\n // Pairs only exist where both vectors do; a caller passes vectors of one length.\n const slopes: number[] = [];\n xs.forEach((xi, i) => {\n for (let j = i + 1; j < xs.length; j++) {\n const dx = (xs[j] as number) - xi;\n if (dx !== 0) slopes.push(((ys[j] as number) - (ys[i] as number)) / dx);\n }\n });\n return median(slopes);\n}\n\n/**\n * Round to a number of decimal places, for readable reports. The full values stay in\n * the sample vectors.\n *\n * @internal\n */\nexport function round(n: number, places = 4): number {\n const f = 10 ** places;\n return Math.round(n * f) / f;\n}\n","/**\n * The base against head comparison, and its calibration.\n *\n * A comparison is `ROUNDS` rounds. In each round every case runs once on the base build and once on\n * the head build, each in a fresh process, back to back; even rounds run the base first, odd rounds\n * the head. A drift in the machine over the run lands on both sides of a round, seconds apart, and\n * cancels in their ratio; an order effect lands on the head in half the rounds and on the base in\n * the other half, so it widens the spread of the ratios instead of biasing their median.\n *\n * Each round gives one ratio per case, `head / base` of the two processes' fastest samples. The\n * fastest, not the median, because the noise on a shared machine is one-sided (another tenant only\n * ever slows a sample down), and ADR 0001 section 1 measured `min` as the one estimator that keeps a\n * stall on one side out of a ratio. The verdict reads the sign test's interval on the median of the\n * per-round ratios, which assumes nothing about the shape of the noise, only that rounds are\n * independent.\n *\n * @packageDocumentation\n */\n\nimport type { AnyJobResult, Job } from \"./child.js\";\nimport { BENCH_CONTRACT } from \"./contract.js\";\nimport type { WarmupPlan } from \"./measure.js\";\nimport {\n median,\n rateUpperBound,\n round,\n signTestInterval,\n trialsForZeroEvents,\n type MedianInterval,\n} from \"./stats.js\";\nimport { caseId, listCases, type BenchSuite, type CaseRef } from \"./suite.js\";\n\n/** What a comparison says about one case. */\nexport type Verdict = \"regression\" | \"improvement\" | \"no-regression\" | \"inconclusive\" | \"skipped\";\n\n/**\n * Judge one case from its per-round ratios.\n *\n * - `regression`: the whole interval is above `1 + THRESHOLD`. The head is slower by more than the\n * threshold, with each end of the interval wrong at most `ALPHA` of the time.\n * - `improvement`: the whole interval is below `1 / (1 + THRESHOLD)`.\n * - `no-regression`: the whole interval is at or below `1 + THRESHOLD`: a slowdown larger than\n * the threshold is ruled out.\n * - `inconclusive`: the interval straddles `1 + THRESHOLD`, or there are too few rounds for one.\n * Not a pass and not a failure: the rounds disagreed too much to tell.\n *\n * @example\n * ```ts\n * import { judge } from \"@cosyte/bench\";\n *\n * judge([1.1, 1.12, 1.09, 1.11, 1.1, 1.13, 1.1, 1.08, 1.12, 1.1, 1.11, 1.09]).verdict; // => \"regression\"\n * judge([1, 1.01, 0.99, 1, 1.02, 0.98, 1, 1.01, 0.99, 1, 1, 1.01]).verdict; // => \"no-regression\"\n * ```\n */\nexport function judge(\n ratios: readonly number[],\n threshold: number = BENCH_CONTRACT.THRESHOLD,\n alpha: number = BENCH_CONTRACT.ALPHA,\n): { verdict: Exclude<Verdict, \"skipped\">; interval: MedianInterval | null; medianRatio: number } {\n const interval = signTestInterval(ratios, alpha);\n const medianRatio = median(ratios);\n if (interval === null) return { verdict: \"inconclusive\", interval, medianRatio };\n const up = 1 + threshold;\n let verdict: Exclude<Verdict, \"skipped\">;\n if (interval.low > up) verdict = \"regression\";\n else if (interval.high < 1 / up) verdict = \"improvement\";\n else if (interval.high <= up) verdict = \"no-regression\";\n else verdict = \"inconclusive\";\n return { verdict, interval, medianRatio };\n}\n\n/** One case of a comparison. */\nexport interface CaseComparison extends CaseRef {\n /** Each round's base process: the fastest of its samples, milliseconds per operation. */\n readonly baseMsPerOp: readonly number[];\n /** Each round's head process: the fastest of its samples, milliseconds per operation. */\n readonly headMsPerOp: readonly number[];\n /** Each round's base samples, whole, so another estimator can be judged on the same runs. */\n readonly baseSamples: readonly (readonly number[])[];\n /** Each round's head samples, whole. */\n readonly headSamples: readonly (readonly number[])[];\n /**\n * Each round's base process: the warmup rule it ended on. Absent in a document written before it\n * was recorded.\n */\n readonly basePlans?: readonly WarmupPlan[];\n /** Each round's head process: the warmup rule it ended on. */\n readonly headPlans?: readonly WarmupPlan[];\n /** Each round's `head / base`. */\n readonly ratios: readonly number[];\n readonly medianRatio: number | null;\n readonly interval: MedianInterval | null;\n readonly verdict: Verdict;\n /** Why the case was skipped, when it was. A skip is not a pass. */\n readonly skipReason: string | null;\n}\n\n/** A whole comparison. */\nexport interface ComparisonBody {\n readonly rounds: number;\n readonly threshold: number;\n readonly alpha: number;\n /** The slowdown injected on the head side, for calibration. Zero in a real comparison. */\n readonly inject: number;\n readonly cases: readonly CaseComparison[];\n}\n\n/**\n * How the comparison runs one job. The real one starts a fresh process.\n *\n * @internal\n */\nexport type JobRunner = (job: Job) => Promise<AnyJobResult>;\n\n/** What {@link compareBuilds} needs. */\nexport interface CompareOptions {\n /** The suite file, an absolute path. */\n readonly suiteFile: string;\n /** The suite, already loaded from `suiteFile`, to list its cases. */\n readonly suite: BenchSuite<unknown>;\n /** The base build's entry, an absolute path. */\n readonly base: string;\n /** The head build's entry, an absolute path. */\n readonly head: string;\n /** Keep to these benchmarks or cases. */\n readonly only?: readonly string[];\n /** A known slowdown on the head side, for calibration only. */\n readonly inject?: number;\n /** Progress lines, for a person watching. */\n readonly onProgress?: (line: string) => void;\n /** Runs one measuring job: `runInChild` for real. */\n readonly runJob: JobRunner;\n}\n\n/**\n * Compare a base build against a head build, interleaved, `ROUNDS` rounds.\n *\n * @internal\n */\nexport async function compareBuilds(options: CompareOptions): Promise<ComparisonBody> {\n const runJob = options.runJob;\n const inject = options.inject ?? 0;\n const cases = listCases(options.suite, options.only, true);\n const state = cases.map((c) => ({\n c,\n rounds: [] as {\n base: readonly number[];\n head: readonly number[];\n basePlan: WarmupPlan;\n headPlan: WarmupPlan;\n }[],\n skip: null as string | null,\n }));\n for (let r = 0; r < BENCH_CONTRACT.ROUNDS; r++) {\n for (const s of state) {\n if (s.skip !== null) continue;\n const sides: (\"base\" | \"head\")[] = r % 2 === 0 ? [\"base\", \"head\"] : [\"head\", \"base\"];\n const got = new Map<\"base\" | \"head\", { samples: readonly number[]; plan: WarmupPlan }>();\n for (const side of sides) {\n const result = await runJob({\n mode: \"timing\",\n suite: options.suiteFile,\n target: side === \"base\" ? options.base : options.head,\n case: s.c,\n latency: false,\n inject: side === \"head\" ? inject : 0,\n });\n if (result.mode !== \"timing\")\n throw new Error(\"@cosyte/bench: a timing job returned another kind\");\n if (result.status === \"skipped\") {\n s.skip = `${side}: ${result.reason}`;\n break;\n }\n got.set(side, { samples: result.samplesMsPerOp, plan: result.warmup.plan });\n }\n const base = got.get(\"base\");\n const head = got.get(\"head\");\n // Both sides or neither: a round that skipped half way is not a round.\n if (base !== undefined && head !== undefined) {\n s.rounds.push({\n base: base.samples,\n head: head.samples,\n basePlan: base.plan,\n headPlan: head.plan,\n });\n }\n }\n options.onProgress?.(`round ${String(r + 1)} of ${String(BENCH_CONTRACT.ROUNDS)} done`);\n }\n return {\n rounds: BENCH_CONTRACT.ROUNDS,\n threshold: BENCH_CONTRACT.THRESHOLD,\n alpha: BENCH_CONTRACT.ALPHA,\n inject,\n cases: state.map(({ c, rounds, skip }) => {\n const recorded = {\n baseMsPerOp: rounds.map((p) => Math.min(...p.base)),\n headMsPerOp: rounds.map((p) => Math.min(...p.head)),\n baseSamples: rounds.map((p) => p.base),\n headSamples: rounds.map((p) => p.head),\n basePlans: rounds.map((p) => p.basePlan),\n headPlans: rounds.map((p) => p.headPlan),\n };\n if (skip !== null) {\n return {\n ...c,\n ...recorded,\n ratios: [],\n medianRatio: null,\n interval: null,\n verdict: \"skipped\",\n skipReason: skip,\n };\n }\n const ratios = rounds.map((p) => round(Math.min(...p.head) / Math.min(...p.base), 6));\n const j = judge(ratios);\n return {\n ...c,\n ...recorded,\n ratios,\n medianRatio: round(j.medianRatio, 6),\n interval: j.interval,\n verdict: j.verdict,\n skipReason: null,\n };\n }),\n };\n}\n\n/**\n * The exit code a comparison earns: 1 when any case is a regression, else 2 when any case was\n * skipped (it could not be measured), else 0.\n *\n * @internal\n */\nexport function comparisonExitCode(body: ComparisonBody): number {\n if (body.cases.some((c) => c.verdict === \"regression\")) return 1;\n if (body.cases.some((c) => c.verdict === \"skipped\")) return 2;\n return 0;\n}\n\n/** A rate with its exact upper bound. */\nexport interface CountedRate {\n readonly events: number;\n readonly trials: number;\n readonly rate: number;\n /** The one-sided upper bound at `REQUIRED_CONFIDENCE` (Clopper-Pearson). */\n readonly upperBound: number;\n}\n\nfunction counted(events: number, trials: number): CountedRate {\n return {\n events,\n trials,\n rate: trials === 0 ? 0 : round(events / trials, 6),\n upperBound: round(rateUpperBound(events, trials, BENCH_CONTRACT.REQUIRED_CONFIDENCE), 6),\n };\n}\n\n/** The ratio one case was observed at over a whole calibration. */\nexport interface ObservedRatio extends CaseRef {\n /** Measured rounds, pooled over every comparison that measured the case. */\n readonly rounds: number;\n /**\n * The median of those rounds' `head / base` ratios, or `null` when no round measured. Under an\n * injected slowdown this is the slowdown the head side was actually measured at, which is not\n * `1 + inject` (see `sampleThroughput`): a detection rate is read against it.\n */\n readonly medianRatio: number | null;\n}\n\n/** A calibration: many comparisons of known outcome. */\nexport interface CalibrationBody {\n /** The slowdown injected on the head side. Zero is an A/A calibration: every regression is a false alarm. */\n readonly inject: number;\n readonly comparisons: number;\n /** Each comparison's cases, kept whole so a different rule can be judged against the same runs. */\n readonly runs: readonly (readonly CaseComparison[])[];\n /** Case verdicts of `regression`, out of the case verdicts that measured. A skipped case is not a trial. */\n readonly perCase: CountedRate;\n /**\n * Comparisons with at least one `regression`, out of the comparisons whose outcome is known: every\n * case measured, or a regression was called. A comparison that skipped a case and called no\n * regression is neither a pass nor a trial.\n */\n readonly perComparison: CountedRate;\n /** Comparisons with at least one skipped case, out of all comparisons. A required check is red on each (exit 2). */\n readonly skippedComparisons: CountedRate;\n /** Case verdicts by kind. */\n readonly verdicts: Readonly<Record<Verdict, number>>;\n /** Each case's observed ratio, in the order the cases first appear. */\n readonly observed: readonly ObservedRatio[];\n /** For an A/A calibration: whether the timing comparison may now be a required check, and why. */\n readonly requiredRule: {\n readonly applies: boolean;\n readonly met: boolean;\n readonly reason: string;\n };\n}\n\n/**\n * Summarize calibration runs. With `inject` zero, every `regression` is a false alarm, and the rule\n * for making the comparison a required check is judged. A required check on an unchanged pull\n * request is wrongly red for either of two reasons, so the rule bounds both at\n * `REQUIRED_CONFIDENCE`: the per-comparison false alarm rate (over the comparisons whose outcome is\n * known) and the rate of comparisons that skipped a case (over all of them) must each have an upper\n * bound under `REQUIRED_FALSE_ALARM_RATE`. Comparisons that measured nothing count toward neither a\n * pass nor a trial, so they can never meet it. With `inject` above zero every `regression` is a\n * detection, and the rule does not apply.\n *\n * @internal\n */\nexport function summarizeCalibration(\n inject: number,\n runs: readonly (readonly CaseComparison[])[],\n): CalibrationBody {\n const verdicts: Record<Verdict, number> = {\n regression: 0,\n improvement: 0,\n \"no-regression\": 0,\n inconclusive: 0,\n skipped: 0,\n };\n const pooled = new Map<string, { ref: CaseRef; ratios: number[] }>();\n let caseTrials = 0;\n let caseEvents = 0;\n let compTrials = 0;\n let compEvents = 0;\n let compSkipped = 0;\n for (const run of runs) {\n let alarmed = false;\n let skipped = false;\n for (const c of run) {\n verdicts[c.verdict]++;\n const id = caseId(c);\n const pool = pooled.get(id) ?? { ref: { benchmark: c.benchmark, size: c.size }, ratios: [] };\n pooled.set(id, pool);\n if (c.verdict === \"skipped\") {\n skipped = true;\n continue;\n }\n pool.ratios.push(...c.ratios);\n caseTrials++;\n if (c.verdict === \"regression\") {\n caseEvents++;\n alarmed = true;\n }\n }\n if (skipped) compSkipped++;\n if (alarmed || !skipped) compTrials++;\n if (alarmed) compEvents++;\n }\n const perComparison = counted(compEvents, compTrials);\n const skippedComparisons = counted(compSkipped, runs.length);\n const line = BENCH_CONTRACT.REQUIRED_FALSE_ALARM_RATE;\n const needed = trialsForZeroEvents(line, BENCH_CONTRACT.REQUIRED_CONFIDENCE);\n // Four decimals of a percentage: a bound of 0.9969% must not print as \"1%\" beside \"under 1%\".\n const pct = (x: number) => `${String(round(x * 100, 4))}%`;\n // Stryker disable next-line EqualityOperator: an exact Clopper-Pearson bound of exactly 1% is not reached by any whole count of comparisons.\n const alarmsUnder = perComparison.upperBound < line;\n // Stryker disable next-line EqualityOperator: the same bound, on the same whole counts.\n const skipsUnder = skippedComparisons.upperBound < line;\n const alarms = `${String(compEvents)} false alarm(s) in ${String(compTrials)} A/A comparisons that measured every case or called a regression`;\n const skips = `${String(compSkipped)} of ${String(runs.length)} comparisons skipped a case`;\n let requiredRule: CalibrationBody[\"requiredRule\"];\n if (inject !== 0) {\n requiredRule = {\n applies: false,\n met: false,\n reason: \"a calibration with an injected slowdown measures detection, not false alarms\",\n };\n } else if (alarmsUnder && skipsUnder) {\n requiredRule = {\n applies: true,\n met: true,\n reason: `${alarms}, and ${skips}: both rates are under ${pct(line)} with ${pct(BENCH_CONTRACT.REQUIRED_CONFIDENCE)} confidence (upper bounds ${pct(perComparison.upperBound)} and ${pct(skippedComparisons.upperBound)})`,\n };\n } else {\n const why: string[] = [];\n if (!alarmsUnder) {\n why.push(\n `${alarms}: the upper bound ${pct(perComparison.upperBound)} is not under ${pct(line)}, and a clean record needs at least ${String(needed)} of them`,\n );\n }\n if (!skipsUnder) {\n why.push(\n `${skips}, and a required check is red on each: the upper bound ${pct(skippedComparisons.upperBound)} on that rate is not under ${pct(line)}`,\n );\n }\n requiredRule = { applies: true, met: false, reason: why.join(\"; \") };\n }\n return {\n inject,\n comparisons: runs.length,\n runs,\n perCase: counted(caseEvents, caseTrials),\n perComparison,\n skippedComparisons,\n verdicts,\n observed: [...pooled.values()].map(({ ref, ratios }) => ({\n ...ref,\n rounds: ratios.length,\n medianRatio: ratios.length === 0 ? null : round(median(ratios), 6),\n })),\n requiredRule,\n };\n}\n\n/** What {@link calibrateComparison} needs: a comparison's options and how many to run. */\nexport interface CalibrateOptions extends CompareOptions {\n readonly comparisons: number;\n}\n\n/**\n * Run `comparisons` full comparisons and summarize them. Base and head should be the same build (two\n * copies, ideally in two directories, so a difference in where the code sits is part of what is\n * calibrated); `inject` makes the head side slower by a known fraction.\n *\n * @internal\n */\nexport async function calibrateComparison(options: CalibrateOptions): Promise<CalibrationBody> {\n const runs: CaseComparison[][] = [];\n for (let k = 0; k < options.comparisons; k++) {\n const { onProgress: _quiet, ...quiet } = options;\n const body = await compareBuilds(quiet);\n runs.push([...body.cases]);\n const alarms = body.cases.filter((c) => c.verdict === \"regression\").map(caseId);\n options.onProgress?.(\n `comparison ${String(k + 1)} of ${String(options.comparisons)}: ` +\n (alarms.length === 0 ? \"no regression verdict\" : `regression on ${alarms.join(\", \")}`),\n );\n }\n return summarizeCalibration(options.inject ?? 0, runs);\n}\n","/**\n * A reading: how fast, how much memory and how heavy one build is, on this machine.\n *\n * A reading is not a guarantee and travels with its machine: it is what the results repository keeps\n * over time, compared only within one runner class.\n *\n * @packageDocumentation\n */\n\nimport type { AnyJob, AnyJobResult, MemoryResult, TimingResult } from \"./child.js\";\nimport type { ImportCostReport } from \"./import-cost.js\";\nimport { BENCH_CONTRACT } from \"./contract.js\";\nimport type { LatencyReport, WarmupPlan } from \"./measure.js\";\nimport {\n aboveFloor,\n type AllocationReport,\n type LongRunReport,\n type PeakReport,\n} from \"./memory.js\";\nimport { median, round } from \"./stats.js\";\nimport { findCase, listCases, type BenchSize, type BenchSuite, type CaseRef } from \"./suite.js\";\n\n/** Throughput of one case. */\nexport type Throughput =\n | {\n readonly status: \"measured\";\n /** One entry per process: its samples and their median, milliseconds per operation. */\n readonly processes: readonly {\n readonly samplesMsPerOp: readonly number[];\n readonly medianMsPerOp: number;\n /** The warmup rule the process ended on. Absent in a document written before it was recorded. */\n readonly plan?: WarmupPlan;\n }[];\n /** The median of the process medians. */\n readonly medianMsPerOp: number;\n readonly opsPerSecond: number;\n /** `null` when the suite gave no byte count for the corpus. */\n readonly bytesPerSecond: number | null;\n }\n | { readonly status: \"skipped\"; readonly reason: string };\n\n/**\n * A part of a run that could not be measured: a case's timing or memory, or the empty-call floor.\n * The message is the first line of what went wrong. Only the first line is kept, because the lines\n * after it may be a measuring process's stderr, which may quote an input; the command line prints\n * the whole of it to its own stderr.\n */\nexport interface RunFailure {\n readonly phase: \"timing\" | \"memory\" | \"floor\";\n /** The case, or `null` for the floor. */\n readonly benchmark: string | null;\n readonly size: string | null;\n readonly message: string;\n}\n\n/** Everything a reading measured for one case. */\nexport interface CaseReading extends CaseRef {\n readonly inputsPerPass: number;\n readonly bytesPerPass: number | null;\n readonly throughput: Throughput;\n readonly latency: LatencyReport | null;\n readonly allocations: AllocationReport | null;\n readonly peak: PeakReport | null;\n readonly longRun: LongRunReport | null;\n}\n\n/** A whole reading. */\nexport interface ReadingBody {\n /** Fresh processes timed per case. */\n readonly processes: number;\n readonly cases: readonly CaseReading[];\n /**\n * The peak of an empty call, measured once per run through the same probe as the cases: what the\n * probe itself costs. Each case's peak also gives its figures above this one. `null` when memory\n * was not measured or the floor could not be. Absent in a document written before it.\n */\n readonly memoryFloor?: PeakReport | null;\n /**\n * What could not be measured. A case whose timing failed is here and not in `cases`; one whose\n * memory failed is in both, with the memory it could not read as `null`. Every other case is\n * written as usual. Empty when everything measured; absent in a document written before it.\n */\n readonly failures?: readonly RunFailure[];\n /** `null` when import cost was not asked for. */\n readonly importCost: ImportCostReport | null;\n}\n\n/**\n * Runs one measuring job of a reading, the empty-call floor included: `runInChild` for real.\n *\n * @internal\n */\nexport type ReadRunner = (job: AnyJob) => Promise<AnyJobResult>;\n\n/** What {@link readBuild} needs. */\nexport interface ReadOptions {\n readonly suiteFile: string;\n readonly suite: BenchSuite<unknown>;\n /** The build's entry, an absolute path. */\n readonly target: string;\n readonly only?: readonly string[];\n /** Fresh timing processes per case. At least 1. */\n readonly processes?: number;\n /** Measure memory (two more processes per case). */\n readonly memory?: boolean;\n /** Measure cold import time and loaded bytes. */\n readonly importCost?: boolean;\n readonly onProgress?: (line: string) => void;\n /** Runs one measuring job: `runInChild` for real. */\n readonly runJob: ReadRunner;\n /** Measures cold import cost: `measureImportCost` for real. */\n readonly measureImport: (target: string) => Promise<ImportCostReport>;\n}\n\n/** A runner that answered a job with another kind of result: a defect in the runner, not a case. */\nclass WrongKind extends Error {}\n\nfunction expectTiming(result: AnyJobResult): TimingResult {\n if (result.mode !== \"timing\")\n throw new WrongKind(\"@cosyte/bench: a timing job returned another kind\");\n return result;\n}\n\nfunction expectMemory(result: AnyJobResult): MemoryResult {\n if (result.mode === \"timing\" || result.mode === \"floor\")\n throw new WrongKind(\"@cosyte/bench: a memory job returned another kind\");\n return result;\n}\n\nfunction expectFloor(result: AnyJobResult): PeakReport {\n if (result.mode !== \"floor\")\n throw new WrongKind(\"@cosyte/bench: a floor job returned another kind\");\n return result.peak;\n}\n\n/** The first line of an error's message: what failed, without any process output after it. */\nfunction firstLine(error: unknown): string {\n const text = error instanceof Error ? error.message : String(error);\n return text.split(\"\\n\")[0] as string;\n}\n\n/**\n * The peak, in bytes, a memory budget reads for a case, or `null` when the case has none. A case\n * whose inputs average under `FLOOR_INPUT_BYTES` (its bytes a pass over its inputs a pass) reads its\n * median call's total peak above the run's empty-call floor (`medianPeakTotalAboveFloorBytes`); any\n * other case, and one whose suite gave no byte count, reads the highest raw total peak (the V8 heap\n * and external memory together). The probe costs a few KiB on a typical call and tens of KiB on its\n * highest, which is most of the budget of a small input and nothing the code under test did, and a\n * rounding error on a large one. A document without a floor reads the raw highest.\n *\n * @example\n * ```ts\n * import { budgetPeakBytes } from \"@cosyte/bench\";\n *\n * // 300 bytes an input, and a floor whose median call is 3,400 bytes: the highest call read\n * // 30,000 bytes, most of it the probe, and the median call 6,000 above the floor.\n * budgetPeakBytes({\n * benchmark: \"parse\",\n * size: \"300 B\",\n * inputsPerPass: 1,\n * bytesPerPass: 300,\n * throughput: { status: \"skipped\", reason: \"warmup-unstable\" },\n * latency: null,\n * allocations: null,\n * peak: {\n * status: \"measured\",\n * calls: 20,\n * peakHeapBytes: 30_000,\n * medianPeakHeapBytes: 9_400,\n * peakTotalBytes: 30_000,\n * medianPeakTotalBytes: 9_400,\n * peakTotalAboveFloorBytes: 26_600,\n * medianPeakTotalAboveFloorBytes: 6_000,\n * },\n * longRun: null,\n * }); // => 6000\n * ```\n */\nexport function budgetPeakBytes(c: CaseReading): number | null {\n const p = c.peak;\n return p === null || p.status !== \"measured\" ? null : budgetOf(c, p);\n}\n\n/**\n * {@link budgetPeakBytes} for a case whose peak was measured.\n *\n * @internal\n */\nexport function budgetOf(\n c: Pick<CaseReading, \"bytesPerPass\" | \"inputsPerPass\">,\n p: Extract<PeakReport, { status: \"measured\" }>,\n): number {\n const raw = p.peakTotalBytes ?? p.peakHeapBytes;\n if (c.bytesPerPass === null) return raw;\n // A corpus is never empty (the measuring process refuses one); were it, the size reads infinite.\n const perInput = c.bytesPerPass / c.inputsPerPass;\n if (perInput >= BENCH_CONTRACT.FLOOR_INPUT_BYTES) return raw;\n return p.medianPeakTotalAboveFloorBytes ?? raw;\n}\n\ntype Measured = Extract<TimingResult, { status: \"measured\" }>;\n\n/**\n * Pool one case's timing processes: the median of their medians, or the first skip.\n *\n * @internal\n */\nexport function summarizeThroughput(\n first: TimingResult,\n rest: readonly TimingResult[],\n): Throughput {\n const measured: Measured[] = [];\n for (const t of [first, ...rest]) {\n if (t.status === \"skipped\") return { status: \"skipped\", reason: t.reason };\n measured.push(t);\n }\n const per = measured.map((t) => ({\n samplesMsPerOp: t.samplesMsPerOp,\n medianMsPerOp: t.medianMsPerOp,\n plan: t.warmup.plan,\n }));\n const mid = median(per.map((x) => x.medianMsPerOp));\n const perOpBytes = first.bytesPerPass === null ? null : first.bytesPerPass / first.inputsPerPass;\n return {\n status: \"measured\",\n processes: per,\n medianMsPerOp: mid,\n opsPerSecond: round(1000 / mid, 2),\n bytesPerSecond: perOpBytes === null ? null : round(perOpBytes * (1000 / mid), 0),\n };\n}\n\n/** One case's timing, before its memory is read. */\ninterface Timed {\n readonly c: CaseRef;\n readonly first: TimingResult;\n readonly throughput: Throughput;\n}\n\n/**\n * Whether a case's long run fits `LONG_RUN_MAX_MS`: `LONG_RUN_ROUNDS` rounds, each at least\n * `LONG_RUN_ROUND_MS` and at least one pass, at the pass time its throughput measured. A case whose\n * throughput was skipped has no pass time, so it does not fit.\n *\n * @internal\n */\nexport function longRunFits(throughput: Throughput, inputsPerPass: number): boolean {\n if (throughput.status !== \"measured\") return false;\n const roundMs = Math.max(\n BENCH_CONTRACT.LONG_RUN_ROUND_MS,\n throughput.medianMsPerOp * inputsPerPass,\n );\n return BENCH_CONTRACT.LONG_RUN_ROUNDS * roundMs <= BENCH_CONTRACT.LONG_RUN_MAX_MS;\n}\n\n/**\n * Take a reading of one build, a benchmark at a time: each of its sizes timed in `processes` fresh\n * processes (latency from the first); then per size, allocations from one more process, and the\n * peak from another, which also runs the long run on the benchmark's long-run size (the one it\n * names, or its first) and on its largest size whose long run fits `LONG_RUN_MAX_MS`; then the\n * empty-call floor in one more process, which every peak is also given above; then the build's\n * cold import cost. Timing every size first is what lets the long run pick its largest size from\n * measured pass times.\n *\n * A measuring process that fails is a failure of its own case and phase, listed in `failures`: the\n * run goes on and every other case is written.\n *\n * @internal\n */\nexport async function readBuild(\n options: ReadOptions,\n): Promise<ReadingBody & { readonly failures: readonly RunFailure[] }> {\n const runJob = options.runJob;\n const processes = options.processes ?? 3;\n if (!Number.isInteger(processes) || processes < 1) {\n throw new TypeError(\"@cosyte/bench: processes must be a positive integer\");\n }\n const memory = options.memory ?? true;\n const base = { suite: options.suiteFile, target: options.target };\n const listed = listCases(options.suite, options.only);\n const cases: CaseReading[] = [];\n const failures: RunFailure[] = [];\n const fail = (phase: RunFailure[\"phase\"], c: CaseRef | null, error: unknown) => {\n if (error instanceof WrongKind) throw error;\n const message = firstLine(error);\n failures.push({ phase, benchmark: c?.benchmark ?? null, size: c?.size ?? null, message });\n const what = c === null ? \"the memory floor\" : `${phase} of ${c.benchmark} @ ${c.size}`;\n options.onProgress?.(\n `could not measure ${what}: ${error instanceof Error ? error.message : String(error)}`,\n );\n };\n for (const name of new Set(listed.map((c) => c.benchmark))) {\n const timed: Timed[] = [];\n for (const c of listed.filter((l) => l.benchmark === name)) {\n const timing = async (latency: boolean) =>\n expectTiming(await runJob({ mode: \"timing\", ...base, case: c, latency, inject: 0 }));\n try {\n const first = await timing(true);\n const rest: TimingResult[] = [];\n for (let p = 1; p < processes; p++) rest.push(await timing(false));\n timed.push({ c, first, throughput: summarizeThroughput(first, rest) });\n } catch (error) {\n fail(\"timing\", c, error);\n }\n }\n // Sizes run smallest first, so the last that fits is the largest.\n const largest = timed.filter((t) => longRunFits(t.throughput, t.first.inputsPerPass)).pop();\n for (const { c, first, throughput } of timed) {\n let allocations: AllocationReport | null = null;\n let peak: PeakReport | null = null;\n let longRun: LongRunReport | null = null;\n if (memory) {\n try {\n allocations = expectMemory(await runJob({ mode: \"alloc\", ...base, case: c })).allocations;\n const { benchmark } = findCase(options.suite, c);\n // A suite has at least one size per benchmark (checkSuite), so the first one exists.\n const longRunSize = benchmark.longRun ?? (benchmark.sizes[0] as BenchSize<unknown>).label;\n const heap = expectMemory(\n await runJob({\n mode: \"heap\",\n ...base,\n case: c,\n longRun: c.size === longRunSize || c.size === largest?.c.size,\n }),\n );\n peak = heap.peak;\n longRun = heap.longRun;\n } catch (error) {\n fail(\"memory\", c, error);\n }\n }\n cases.push({\n ...c,\n inputsPerPass: first.inputsPerPass,\n bytesPerPass: first.bytesPerPass,\n throughput,\n latency: first.status === \"measured\" ? first.latency : null,\n allocations,\n peak,\n longRun,\n });\n options.onProgress?.(`read ${c.benchmark} @ ${c.size}`);\n }\n }\n let memoryFloor: PeakReport | null = null;\n if (memory) {\n try {\n memoryFloor = expectFloor(await runJob({ mode: \"floor\" }));\n } catch (error) {\n fail(\"floor\", null, error);\n }\n }\n const importCost =\n (options.importCost ?? true) ? await options.measureImport(options.target) : null;\n return {\n processes,\n cases: cases.map((c) =>\n c.peak === null ? c : { ...c, peak: aboveFloor(c.peak, memoryFloor) },\n ),\n memoryFloor,\n failures,\n importCost,\n };\n}\n","/**\n * The results document: one JSON file per reading, comparison or calibration, in a versioned shape a\n * results repository can keep for years and read back.\n *\n * The shape is fixed by a JSON Schema ({@link RESULTS_SCHEMA}, also shipped as\n * `schema/results-v1.schema.json`). Version 1 only grows: a new field may appear, no field changes\n * meaning or goes away, and readers ignore fields they do not know. Anything else is version 2.\n *\n * @packageDocumentation\n */\n\nimport { createRequire } from \"node:module\";\n\nimport type { CalibrationBody, ComparisonBody } from \"./compare.js\";\nimport { BENCH_CONTRACT, type BenchContractShape } from \"./contract.js\";\nimport type { Environment } from \"./environment.js\";\nimport { WARMUP } from \"./measure.js\";\nimport type { ReadingBody } from \"./reading.js\";\n\n/** The version this package writes and reads. */\nexport const SCHEMA_VERSION = 1;\n\n/** Which build a document describes. */\nexport interface BuildRef {\n /** The entry measured, relative to the working directory. */\n readonly target: string;\n /** The commit it was built from, when known. */\n readonly commit: string | null;\n}\n\ninterface DocumentBase {\n readonly schemaVersion: 1;\n readonly tool: { readonly name: \"@cosyte/bench\"; readonly version: string };\n /** When the document was written, ISO 8601 UTC. */\n readonly createdAt: string;\n readonly suite: { readonly name: string; readonly file: string };\n readonly environment: Environment;\n /** The constants in force, so a document can be judged against the method that made it. */\n readonly contract: { readonly bench: BenchContractShape; readonly warmup: typeof WARMUP };\n}\n\n/** A results document. */\nexport type ResultsDocument = DocumentBase &\n (\n | { readonly kind: \"reading\"; readonly subject: BuildRef; readonly reading: ReadingBody }\n | {\n readonly kind: \"comparison\";\n readonly base: BuildRef;\n readonly head: BuildRef;\n readonly comparison: ComparisonBody;\n }\n | {\n readonly kind: \"calibration\";\n readonly base: BuildRef;\n readonly head: BuildRef;\n readonly calibration: CalibrationBody;\n }\n );\n\n/**\n * This package's version, read from its own manifest.\n *\n * @internal\n */\nexport function toolVersion(): string {\n const require = createRequire(import.meta.url);\n return (require(\"../package.json\") as { version: string }).version;\n}\n\n/**\n * The parts every document shares.\n *\n * @internal\n */\nexport function documentBase(\n suite: { name: string; file: string },\n environment: Environment,\n now: Date = new Date(),\n): DocumentBase {\n return {\n schemaVersion: SCHEMA_VERSION,\n tool: { name: \"@cosyte/bench\", version: toolVersion() },\n createdAt: now.toISOString(),\n suite,\n environment,\n contract: { bench: BENCH_CONTRACT, warmup: WARMUP },\n };\n}\n\n/** The JSON Schema keywords the results schema uses. */\ninterface Schema {\n readonly $ref?: string;\n readonly $defs?: Readonly<Record<string, Schema>>;\n readonly const?: unknown;\n readonly enum?: readonly unknown[];\n readonly type?: string | readonly string[];\n readonly minimum?: number;\n readonly required?: readonly string[];\n readonly properties?: Readonly<Record<string, Schema>>;\n readonly items?: Schema;\n readonly oneOf?: readonly Schema[];\n}\n\nconst num = { type: \"number\" } as const;\nconst int = { type: \"integer\", minimum: 0 } as const;\nconst str = { type: \"string\" } as const;\nconst strOrNull = { type: [\"string\", \"null\"] } as const;\nconst numOrNull = { type: [\"number\", \"null\"] } as const;\nconst nums = { type: \"array\", items: num } as const;\nconst skipped = {\n type: \"object\",\n required: [\"status\", \"reason\"],\n properties: { status: { const: \"skipped\" }, reason: str },\n} as const;\nconst measuredOrSkipped = (measured: Schema): Schema => ({ oneOf: [measured, skipped] });\nconst plan = { enum: [\"standard\", \"large\"] } as const;\nconst orNull = (s: Schema): Schema => ({ oneOf: [{ type: \"null\" }, s] });\n\n/**\n * The results schema, version 1, as JSON Schema (draft 2020-12). It holds what every reader relies\n * on; a document may carry more.\n *\n * @example\n * ```ts\n * import { RESULTS_SCHEMA } from \"@cosyte/bench\";\n *\n * RESULTS_SCHEMA.$id; // => \"https://github.com/cosyte/config/blob/main/packages/bench/schema/results-v1.schema.json\"\n * ```\n */\nexport const RESULTS_SCHEMA = {\n $schema: \"https://json-schema.org/draft/2020-12/schema\",\n $id: \"https://github.com/cosyte/config/blob/main/packages/bench/schema/results-v1.schema.json\",\n title: \"@cosyte/bench results, version 1\",\n type: \"object\",\n required: [\"schemaVersion\", \"kind\", \"tool\", \"createdAt\", \"suite\", \"environment\", \"contract\"],\n properties: {\n schemaVersion: { const: 1 },\n kind: { enum: [\"reading\", \"comparison\", \"calibration\"] },\n tool: {\n type: \"object\",\n required: [\"name\", \"version\"],\n properties: { name: { const: \"@cosyte/bench\" }, version: str },\n },\n createdAt: str,\n suite: { type: \"object\", required: [\"name\", \"file\"], properties: { name: str, file: str } },\n environment: { $ref: \"#/$defs/environment\" },\n contract: {\n type: \"object\",\n required: [\"bench\", \"warmup\"],\n properties: { bench: { type: \"object\" }, warmup: { type: \"object\" } },\n },\n },\n oneOf: [\n {\n required: [\"subject\", \"reading\"],\n properties: {\n kind: { const: \"reading\" },\n subject: { $ref: \"#/$defs/build\" },\n reading: { $ref: \"#/$defs/reading\" },\n },\n },\n {\n required: [\"base\", \"head\", \"comparison\"],\n properties: {\n kind: { const: \"comparison\" },\n base: { $ref: \"#/$defs/build\" },\n head: { $ref: \"#/$defs/build\" },\n comparison: { $ref: \"#/$defs/comparison\" },\n },\n },\n {\n required: [\"base\", \"head\", \"calibration\"],\n properties: {\n kind: { const: \"calibration\" },\n base: { $ref: \"#/$defs/build\" },\n head: { $ref: \"#/$defs/build\" },\n calibration: { $ref: \"#/$defs/calibration\" },\n },\n },\n ],\n $defs: {\n build: {\n type: \"object\",\n required: [\"target\", \"commit\"],\n properties: { target: str, commit: strOrNull },\n },\n environment: {\n type: \"object\",\n required: [\n \"node\",\n \"v8\",\n \"platform\",\n \"arch\",\n \"osRelease\",\n \"cpuModel\",\n \"logicalCpus\",\n \"totalMemoryBytes\",\n \"cgroupCpuMax\",\n \"runner\",\n ],\n properties: {\n node: str,\n v8: str,\n platform: str,\n arch: str,\n osRelease: str,\n cpuModel: strOrNull,\n logicalCpus: int,\n totalMemoryBytes: int,\n cgroupCpuMax: strOrNull,\n // Added within version 1, so not required: a document written before it is still valid.\n loadAverage: nums,\n runner: {\n type: \"object\",\n required: [\"ci\", \"provider\", \"class\"],\n properties: { ci: { type: \"boolean\" }, provider: strOrNull, class: str },\n },\n },\n },\n interval: orNull({\n type: \"object\",\n required: [\"low\", \"high\", \"rank\", \"tail\"],\n properties: { low: num, high: num, rank: int, tail: num },\n }),\n caseComparison: {\n type: \"object\",\n required: [\n \"benchmark\",\n \"size\",\n \"baseMsPerOp\",\n \"headMsPerOp\",\n \"ratios\",\n \"medianRatio\",\n \"interval\",\n \"verdict\",\n \"skipReason\",\n ],\n properties: {\n benchmark: str,\n size: str,\n baseMsPerOp: nums,\n headMsPerOp: nums,\n ratios: nums,\n medianRatio: numOrNull,\n // Added within version 1, so not required: a document written before them is still valid.\n basePlans: { type: \"array\", items: plan },\n headPlans: { type: \"array\", items: plan },\n interval: { $ref: \"#/$defs/interval\" },\n verdict: {\n enum: [\"regression\", \"improvement\", \"no-regression\", \"inconclusive\", \"skipped\"],\n },\n skipReason: strOrNull,\n },\n },\n comparison: {\n type: \"object\",\n required: [\"rounds\", \"threshold\", \"alpha\", \"inject\", \"cases\"],\n properties: {\n rounds: int,\n threshold: num,\n alpha: num,\n inject: num,\n cases: { type: \"array\", items: { $ref: \"#/$defs/caseComparison\" } },\n },\n },\n rate: {\n type: \"object\",\n required: [\"events\", \"trials\", \"rate\", \"upperBound\"],\n properties: { events: int, trials: int, rate: num, upperBound: num },\n },\n calibration: {\n type: \"object\",\n required: [\n \"inject\",\n \"comparisons\",\n \"runs\",\n \"perCase\",\n \"perComparison\",\n \"verdicts\",\n \"requiredRule\",\n ],\n properties: {\n inject: num,\n comparisons: int,\n runs: {\n type: \"array\",\n items: { type: \"array\", items: { $ref: \"#/$defs/caseComparison\" } },\n },\n perCase: { $ref: \"#/$defs/rate\" },\n perComparison: { $ref: \"#/$defs/rate\" },\n // Added within version 1, so optional: documents written before it validate unchanged.\n skippedComparisons: { $ref: \"#/$defs/rate\" },\n verdicts: { type: \"object\" },\n observed: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"benchmark\", \"size\", \"rounds\", \"medianRatio\"],\n properties: { benchmark: str, size: str, rounds: int, medianRatio: numOrNull },\n },\n },\n requiredRule: {\n type: \"object\",\n required: [\"applies\", \"met\", \"reason\"],\n properties: { applies: { type: \"boolean\" }, met: { type: \"boolean\" }, reason: str },\n },\n },\n },\n peak: measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"calls\", \"peakHeapBytes\", \"medianPeakHeapBytes\"],\n properties: {\n status: { const: \"measured\" },\n calls: int,\n peakHeapBytes: num,\n medianPeakHeapBytes: num,\n // Added within version 1, so not required.\n peakExternalBytes: num,\n medianPeakExternalBytes: num,\n peakTotalBytes: num,\n medianPeakTotalBytes: num,\n peakHeapAboveFloorBytes: num,\n medianPeakHeapAboveFloorBytes: num,\n peakExternalAboveFloorBytes: num,\n medianPeakExternalAboveFloorBytes: num,\n peakTotalAboveFloorBytes: num,\n medianPeakTotalAboveFloorBytes: num,\n },\n }),\n failure: {\n type: \"object\",\n required: [\"phase\", \"benchmark\", \"size\", \"message\"],\n properties: {\n phase: { enum: [\"timing\", \"memory\", \"floor\"] },\n benchmark: strOrNull,\n size: strOrNull,\n message: str,\n },\n },\n reading: {\n type: \"object\",\n required: [\"processes\", \"cases\", \"importCost\"],\n properties: {\n processes: int,\n cases: { type: \"array\", items: { $ref: \"#/$defs/caseReading\" } },\n // Added within version 1, so not required.\n memoryFloor: orNull({ $ref: \"#/$defs/peak\" }),\n failures: { type: \"array\", items: { $ref: \"#/$defs/failure\" } },\n importCost: orNull({\n type: \"object\",\n required: [\n \"runs\",\n \"coldImportMs\",\n \"medianMs\",\n \"p95Ms\",\n \"emptyImportMs\",\n \"emptyMedianMs\",\n \"files\",\n \"totalBytes\",\n \"totalGzipBytes\",\n ],\n properties: {\n runs: int,\n coldImportMs: nums,\n medianMs: num,\n p95Ms: num,\n emptyImportMs: nums,\n emptyMedianMs: num,\n files: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"path\", \"bytes\", \"gzipBytes\"],\n properties: { path: str, bytes: int, gzipBytes: int },\n },\n },\n totalBytes: int,\n totalGzipBytes: int,\n },\n }),\n },\n },\n caseReading: {\n type: \"object\",\n required: [\n \"benchmark\",\n \"size\",\n \"inputsPerPass\",\n \"bytesPerPass\",\n \"throughput\",\n \"latency\",\n \"allocations\",\n \"peak\",\n \"longRun\",\n ],\n properties: {\n benchmark: str,\n size: str,\n inputsPerPass: int,\n bytesPerPass: numOrNull,\n throughput: measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"processes\", \"medianMsPerOp\", \"opsPerSecond\", \"bytesPerSecond\"],\n properties: {\n status: { const: \"measured\" },\n processes: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"samplesMsPerOp\", \"medianMsPerOp\"],\n // `plan` was added within version 1, so it is not required.\n properties: { samplesMsPerOp: nums, medianMsPerOp: num, plan },\n },\n },\n medianMsPerOp: num,\n opsPerSecond: num,\n bytesPerSecond: numOrNull,\n },\n }),\n latency: orNull({\n type: \"object\",\n required: [\"status\", \"calls\", \"timerOverheadNs\", \"p50Ns\"],\n properties: {\n status: { enum: [\"measured\", \"skipped\"] },\n calls: int,\n timerOverheadNs: num,\n p50Ns: numOrNull,\n p95Ns: numOrNull,\n p99Ns: numOrNull,\n maxNs: num,\n },\n }),\n allocations: orNull(\n measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"windowOps\", \"heapBytesPerOp\", \"externalBytesPerOp\"],\n properties: {\n status: { const: \"measured\" },\n // Added within version 1, so not required.\n method: { enum: [\"no-gc-window\", \"gc-accounted\"] },\n collections: int,\n windowOps: int,\n heapBytesPerOp: num,\n externalBytesPerOp: num,\n },\n }),\n ),\n peak: orNull({ $ref: \"#/$defs/peak\" }),\n longRun: orNull(\n measuredOrSkipped({\n type: \"object\",\n required: [\"status\", \"verdict\", \"ops\", \"opsPerRound\", \"settledHeap\", \"slopeBytesPerOp\"],\n properties: {\n status: { const: \"measured\" },\n verdict: { enum: [\"flat\", \"growing\"] },\n ops: int,\n opsPerRound: int,\n settledHeap: nums,\n slopeBytesPerOp: num,\n },\n }),\n ),\n },\n },\n },\n} as const;\n\nfunction typeOf(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n if (Number.isInteger(value)) return \"integer\";\n return typeof value;\n}\n\nfunction typeMatches(value: unknown, type: string): boolean {\n const actual = typeOf(value);\n return actual === type || (type === \"number\" && actual === \"integer\");\n}\n\nfunction resolveRef(ref: string, root: Schema): Schema {\n const target = root.$defs?.[ref.replace(\"#/$defs/\", \"\")];\n if (target === undefined) throw new Error(`schema has no definition ${ref}`);\n return target;\n}\n\n/**\n * Check a value against the subset of JSON Schema the results schema uses (`type`, `const`, `enum`,\n * `required`, `properties`, `items`, `minimum`, `oneOf`, `$ref` into `$defs`) and return every\n * problem, each with its JSON pointer. An empty list means the value conforms.\n *\n * @internal\n */\nexport function validate(value: unknown, schema: Schema, root: Schema = schema, at = \"\"): string[] {\n const problems: string[] = [];\n const here = at === \"\" ? \"/\" : at;\n if (typeof schema.$ref === \"string\")\n return validate(value, resolveRef(schema.$ref, root), root, at);\n if (\"const\" in schema && value !== schema.const)\n problems.push(`${here}: must be ${JSON.stringify(schema.const)}`);\n if (schema.enum !== undefined && !schema.enum.includes(value)) {\n problems.push(\n `${here}: must be one of ${schema.enum.map((e) => JSON.stringify(e)).join(\", \")}`,\n );\n }\n if (schema.type !== undefined) {\n const types: readonly string[] = typeof schema.type === \"string\" ? [schema.type] : schema.type;\n if (!types.some((t) => typeMatches(value, t))) {\n problems.push(`${here}: must be ${types.join(\" or \")}, is ${typeOf(value)}`);\n return problems;\n }\n }\n if (typeof value === \"number\" && value < (schema.minimum ?? -Infinity)) {\n problems.push(`${here}: must be at least ${String(schema.minimum)}`);\n }\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n const record = value as Record<string, unknown>;\n for (const key of schema.required ?? []) {\n if (!(key in record)) problems.push(`${here}: is missing \"${key}\"`);\n }\n for (const [key, sub] of Object.entries(schema.properties ?? {})) {\n if (key in record) problems.push(...validate(record[key], sub, root, `${at}/${key}`));\n }\n }\n if (Array.isArray(value) && schema.items !== undefined) {\n const items = schema.items;\n value.forEach((item, i) => problems.push(...validate(item, items, root, `${at}/${String(i)}`)));\n }\n if (schema.oneOf !== undefined) {\n const results = schema.oneOf.map((s) => validate(value, s, root, at));\n const passing = results.filter((r) => r.length === 0).length;\n if (passing !== 1) {\n problems.push(\n `${here}: must match exactly one of ${String(results.length)} shapes, matches ${String(passing)}`,\n );\n // The nearest shape's problems say what to fix. With two or more matching, the nearest has\n // none, so this adds nothing.\n problems.push(...results.reduce((a, b) => (b.length < a.length ? b : a)));\n }\n }\n return problems;\n}\n\n/**\n * Parse and check a results document. Throws a `TypeError` listing every problem when the text is\n * not JSON, is a version this package does not read, or does not match the schema.\n *\n * @example\n * ```ts\n * import { readResults } from \"@cosyte/bench\";\n *\n * try {\n * readResults('{\"schemaVersion\": 2}');\n * } catch (error) {\n * String(error).includes(\"version 2\"); // => true\n * }\n * ```\n */\nexport function readResults(text: string): ResultsDocument {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n throw new TypeError(\"@cosyte/bench: the results are not JSON\");\n }\n const version = (value as { schemaVersion?: unknown } | null)?.schemaVersion;\n if (version !== SCHEMA_VERSION) {\n throw new TypeError(\n `@cosyte/bench: results version ${String(version)} is not one this package reads (${String(SCHEMA_VERSION)})`,\n );\n }\n const problems = validate(value, RESULTS_SCHEMA);\n if (problems.length > 0)\n throw new TypeError(\n `@cosyte/bench: the results do not match the schema:\\n${problems.join(\"\\n\")}`,\n );\n return value as ResultsDocument;\n}\n","/**\n * Starting measuring processes and reading what they measured.\n *\n * @packageDocumentation\n */\n\nimport { spawn } from \"node:child_process\";\nimport { mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { AnyJob, AnyJobResult, FloorJob, FloorResult, Job, JobResult } from \"./child.js\";\nimport { BENCH_CONTRACT } from \"./contract.js\";\n\n/**\n * A run that could not produce a measurement. The command line reports it and exits 2.\n *\n * @example\n * ```ts\n * import { BenchError } from \"@cosyte/bench\";\n *\n * new BenchError(\"no result\").name; // => \"BenchError\"\n * ```\n */\nexport class BenchError extends Error {\n override readonly name = \"BenchError\";\n}\n\n/**\n * Where the built measuring processes live, beside this module in `dist`.\n *\n * @internal\n */\nexport const ENTRIES = Object.freeze({\n child: fileURLToPath(new URL(\"./child-entry.mjs\", import.meta.url)),\n importer: fileURLToPath(new URL(\"./import-entry.mjs\", import.meta.url)),\n});\n\n/**\n * How measuring processes are started. The defaults are the real ones; tests point `entry` at a\n * stand-in.\n *\n * @internal\n */\nexport interface SpawnOptions {\n /** The script the process runs. */\n readonly entry?: string;\n /** Flags for `node` before the script. */\n readonly execArgv?: readonly string[];\n /** Killed after this long, in milliseconds. */\n readonly timeoutMs?: number;\n}\n\n/**\n * The environment a measuring process gets: this one's, less what would change what it measures.\n * `NODE_V8_COVERAGE` would instrument it (a test runner sets it) and `NODE_COMPILE_CACHE` would\n * make a cold import warm.\n *\n * @internal\n */\nexport function childEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {\n const out = { ...env };\n delete out[\"NODE_V8_COVERAGE\"];\n delete out[\"NODE_COMPILE_CACHE\"];\n return out;\n}\n\n/** What a process said, raw. */\ninterface ProcessOutcome {\n readonly code: number | null;\n readonly signal: string | null;\n readonly stderr: string;\n readonly timedOut: boolean;\n}\n\nconst STDERR_LIMIT = 16 * 1024;\n\n/**\n * Run `node <execArgv> <entry> <args>` with stdout discarded (the code under test may print), and\n * keep the last 16 KiB of stderr for a diagnostic.\n *\n * @internal\n */\nexport function runNode(\n entry: string,\n args: readonly string[],\n execArgv: readonly string[],\n timeoutMs: number,\n execPath: string = process.execPath,\n): Promise<ProcessOutcome> {\n return new Promise((resolve, reject) => {\n const child = spawn(execPath, [...execArgv, entry, ...args], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n env: childEnv(),\n });\n let stderr = \"\";\n let timedOut = false;\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr = (stderr + String(chunk)).slice(-STDERR_LIMIT);\n });\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGKILL\");\n }, timeoutMs);\n child.on(\"error\", (error) => {\n // Stryker disable next-line all: an uncleared timer only keeps the event loop alive until it fires; killing a process that is gone does nothing.\n clearTimeout(timer);\n reject(error);\n });\n child.on(\"close\", (code, signal) => {\n // Stryker disable next-line all: as above, the timer's only effect after the process ended is to hold the event loop.\n clearTimeout(timer);\n resolve({ code, signal, stderr, timedOut });\n });\n });\n}\n\n/**\n * The flags a job's process starts with. Memory jobs need `gc()`, and free the ArrayBuffers a\n * collection finds dead within that collection rather than on a background thread after it, so the\n * external memory a collection reports before and after it is exact; the allocation job also gets a\n * young generation large enough that a window of operations rarely collects.\n *\n * @internal\n */\nexport function flagsFor(job: AnyJob): string[] {\n if (job.mode === \"timing\") return [];\n const memory = [\"--expose-gc\", \"--no-concurrent-array-buffer-sweeping\"];\n if (job.mode === \"heap\" || job.mode === \"floor\") return memory;\n const mib = String(BENCH_CONTRACT.ALLOC_SEMI_SPACE_MIB);\n return [...memory, `--min-semi-space-size=${mib}`, `--max-semi-space-size=${mib}`];\n}\n\nfunction describe(job: AnyJob): string {\n if (job.mode === \"floor\") return \"floor job\";\n return `${job.mode} job for \"${job.case.benchmark} @ ${job.case.size}\"`;\n}\n\n/**\n * Run one job in a fresh process and return what it measured. Throws {@link BenchError} when the\n * process timed out, died, or wrote no result, with the tail of its stderr.\n *\n * @internal\n */\nexport async function runInChild(job: FloorJob, options?: SpawnOptions): Promise<FloorResult>;\nexport async function runInChild(job: Job, options?: SpawnOptions): Promise<JobResult>;\nexport async function runInChild(job: AnyJob, options?: SpawnOptions): Promise<AnyJobResult>;\nexport async function runInChild(job: AnyJob, options: SpawnOptions = {}): Promise<AnyJobResult> {\n // Stryker disable next-line StringLiteral: the prefix only names the temporary directory.\n const dir = mkdtempSync(join(tmpdir(), \"cosyte-bench-\"));\n try {\n const jobFile = join(dir, \"job.json\");\n const outFile = join(dir, \"out.json\");\n writeFileSync(jobFile, JSON.stringify(job));\n const outcome = await runNode(\n options.entry ?? ENTRIES.child,\n [jobFile, outFile],\n options.execArgv ?? flagsFor(job),\n options.timeoutMs ?? 600_000,\n );\n if (outcome.timedOut) throw new BenchError(`the ${describe(job)} timed out`);\n let text: string;\n try {\n text = String(readFileSync(outFile));\n } catch {\n const how = outcome.signal === null ? `exit ${String(outcome.code)}` : outcome.signal;\n throw new BenchError(\n `the ${describe(job)} ended (${how}) without a result. Its stderr ends:\\n${outcome.stderr}`,\n );\n }\n const out = JSON.parse(text) as { ok: boolean; result?: AnyJobResult; error?: string };\n if (!out.ok || out.result === undefined) {\n throw new BenchError(`the ${describe(job)} could not measure: ${String(out.error)}`);\n }\n return out.result;\n } finally {\n rmSync(dir, { recursive: true });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosyte/bench",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Benchmarks for the @cosyte/* packages: throughput, latency percentiles, memory, cold import time and loaded bytes, as versioned JSON, and a base against head comparison interleaved on one machine.",
5
5
  "keywords": [
6
6
  "benchmark",
@@ -50,18 +50,18 @@
50
50
  },
51
51
  "prettier": "@cosyte/prettier-config",
52
52
  "devDependencies": {
53
- "@types/node": "^22.0.0",
53
+ "@types/node": "^22.20.5",
54
54
  "@vitest/coverage-v8": "5.1.0",
55
55
  "eslint": "10.14.0",
56
56
  "prettier": "3.9.10",
57
57
  "tsup": "9.0.0",
58
58
  "typescript": "5.9.3",
59
59
  "vitest": "5.1.0",
60
- "@cosyte/eslint-config": "^0.1.4",
61
- "@cosyte/prettier-config": "^0.1.2",
62
60
  "@cosyte/test-utils": "^0.1.4",
61
+ "@cosyte/prettier-config": "^0.1.2",
63
62
  "@cosyte/tsconfig": "^0.1.2",
64
- "@cosyte/tsup-config": "^0.1.3"
63
+ "@cosyte/tsup-config": "^0.1.3",
64
+ "@cosyte/eslint-config": "^0.1.4"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",