@mark1russell7/rigor-bench 0.2.6 → 0.2.8

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.
package/README.md CHANGED
@@ -54,6 +54,9 @@ v.comparison; // the cases that gain and dro
54
54
  | `childLine(stdout)` | From `/fresh`: the last JSON line of a child, with its time `ms`, its `hash` and the reason of a skip. |
55
55
  | `pairOrder(pairs, round, caseIndex)` | From `/fresh`: the order of the pairs of one case in one round, and the first arm of each pair. |
56
56
  | `freshRun(options)` | From `/fresh`: the records of each pair, with each arm in its own fresh child process. |
57
+ | `superviseCases(options)` | From `/supervise`: each case in a child process, held to a time budget, with a hard stop and a resume. |
58
+ | `caseProgress(file)` | From `/supervise`: the writer of the progress file of a child. |
59
+ | `startSupervisedProcess(argv, options)` | From `/supervise`: a child process that `superviseCases` can stop with its tree. |
57
60
 
58
61
  ## A claim with a limit
59
62
 
@@ -90,6 +93,50 @@ v.cases; // the medians, the interval,
90
93
  - The level 0.95 needs 6 rounds or more in each arm. With fewer rounds, the case is `cannot decide`.
91
94
  - A case is `within` when the high end of the interval of its median is at most the limit. A case is `beyond` when the low end is above the limit.
92
95
 
96
+ ## A claim at each size: the ladder
97
+
98
+ A ladder is the scale plan of a product, as data. It has rungs at sizes that grow, for example 1, 10 and 100 tenants. Each rung has budgets, and it names the test that holds it. A number in a document is not a rung: a rung is held only when a record of its test shows each budget `within`.
99
+
100
+ ```ts
101
+ const ladder: Ladder = {
102
+ format: LADDER_FORMAT,
103
+ subject: "reference service",
104
+ dimensions: [{ name: "tenants", describe: "one customer" }],
105
+ rungs: [{
106
+ name: "1,000 tenants",
107
+ at: { tenants: 1000 },
108
+ test: "load-1000",
109
+ locked: true,
110
+ budgets: [
111
+ { kind: "median", measure: "read", unit: "ms", limit: 50, maxDifference: 20 },
112
+ { kind: "rate", measure: "failed requests", limit: 0.0001 },
113
+ { kind: "worst", measure: "lost writes", unit: "writes", limit: 0, minDrills: 3 },
114
+ ],
115
+ }],
116
+ };
117
+
118
+ const v = ladderVerdict(ladder, records, { now: new Date().toISOString(), maxAgeDays: 8 });
119
+
120
+ v.held; // the highest rung with each rung up to it within
121
+ v.next; // the first rung that is not within
122
+ v.failed; // true when a locked rung is not within
123
+ ```
124
+
125
+ There are three kinds of budget:
126
+
127
+ | Kind | Evidence | The verdict |
128
+ | --- | --- | --- |
129
+ | `median` | Rounds and an A/A arm | `budgetVerdict`, so the rules of a budget apply. |
130
+ | `rate` | Failures and trials | The Wilson interval at 95 % against the limit. |
131
+ | `worst` | The value of each drill | The largest value against the limit. One bad drill is `beyond`. |
132
+
133
+ - A failed rate of 0 of 38,500 holds a limit of 0.01 %. A failed rate of 0 of 30,000 is `cannot decide`, because the Wilson bound of that run is 0.0128 %.
134
+ - A locked rung fails when it is `beyond` and when it is `cannot decide`. A skipped test is not a pass.
135
+ - The verdict reads the newest record of each rung, so an old pass does not hide a new failure. With `maxAgeDays`, an older record gives `cannot decide`.
136
+ - A high rung does not count over a gap: `held` stops at the first rung that is not `within`.
137
+ - A record states what was real and what was synthetic. A record with no entry in `real` gives `cannot decide`.
138
+ - `checkLadder` gives each problem of a ladder as data. `ladderVerdict` throws a `RangeError` for a ladder with problems.
139
+
93
140
  ## Time the rounds
94
141
 
95
142
  `timeRounds` times one case in one process. Start a fresh process for each case and each pair, and give the index of the case as `offset`:
@@ -139,6 +186,35 @@ A run of many jobs sets `roundOffset` to `job * rounds`. Then the merged records
139
186
 
140
187
  A rotation of N arms turns the order of two arms only when they are N/2 apart in the rotation. For 4 arms, two arms 1 apart come in the order 3:1 over 4 rounds. Thus each pair of `freshRun` has its own two children, and the order of each pair turns each round.
141
188
 
189
+ ## Run cases with a time budget
190
+
191
+ The entry `@mark1russell7/rigor-bench/supervise` starts child processes, so it is for Node only.
192
+
193
+ The function `superviseCases` holds each case to a time budget. Code that does not return cannot stop itself, so the cases run in a child process and a hard stop ends the child.
194
+
195
+ ```ts
196
+ import { startSupervisedProcess, superviseCases } from "@mark1russell7/rigor-bench/supervise";
197
+
198
+ const result = await superviseCases({
199
+ cases: [{ id: "a", budgetMs: 300_000, groups: { "": "router-1" } }],
200
+ progressFile: "out/progress.ndjson",
201
+ start: (left) => startSupervisedProcess([process.execPath, "run-cases.js", ...left]),
202
+ onEnd: (end) => writeRecord(end),
203
+ });
204
+ ```
205
+
206
+ The child calls `caseProgress(file).start(id, stage)` before each stage, and `end(id)` after the result is on the disk.
207
+
208
+ - A stage over its budget gives `over-budget`. This time is a lower bound, so a statistic treats it as censored, not as a value. The next child runs the cases that are left.
209
+ - After 2 overruns in one group (the option `overruns`), each other case of the group gives `not-run`, with the ids of the 2 overruns. An overrun is measured, and a skip is not. Keep these tags different.
210
+ - An overrun counts for the group of its stage only. A slow router thus does not count against the packer of its cases.
211
+ - A case in `done` does not run, so a resume runs no case two times.
212
+ - The option `earlier` takes the ends of a stopped run. Its overruns count for their groups, so a stop and a resume give the same ends as a run with no stop. Keep each case in `cases`, and put the ids of the finished cases in `done`.
213
+ - Only an overrun starts a new child, so the other cases keep the process and the caches of a normal run.
214
+ - A case can end between a read of the file and the stop. That case has its result, so it does not get a tag.
215
+
216
+ The callback `onEnd` writes the record of each tag. The callback `start` can also repair a file that a stop cut.
217
+
142
218
  ## The pooled verdict
143
219
 
144
220
  `pooledVerdict` gives the verdict of the records of all jobs together:
@@ -1 +1 @@
1
- {"version":3,"file":"fresh.d.ts","names":[],"sources":["../src/fresh.ts"],"mappings":";;iBASiB;WACN;WACA;WACA;WACA;WACA;;WAEA;;;wBAIK,YAAY;;YAQhB,YAAY,YAAY;;iBAGnB;WACN;;WAEA;WACA;;;iBAIM;;WAEN;WACA;WACA;;;;;;wBAOK,UAAU,iBAAiB;;iBAwB1B;WACN;WACA,OAAO;;;;;;;;wBASF,UAAU,eAAe,eAAe,oBAAoB;;iBAY3D;WACN;WACA;;;iBAIM;;WAEN;;WAEA;;;iBAIM;WACN;WACA;;WAEA;;WAEA;;WAEA;;;YAIC,cAAc,yBAAyB,SAAS,iBAAiB,QAAQ;;iBAGpE;;WAEN,MAAM;;WAEN,qBAAqB;;WAErB;;WAEA;;;;;;WAMA;;WAEA;;WAEA,QAAQ;;WAER;;WAEA;;WAEA;;WAEA,MAAM;;WAEN,QAAQ;;;iBAIF,oBAAoB;WAC1B;WACA,KAAK;;;iBAIC;WACN;WACA;;WAEA;WACA;;;iBAIM;WACN,KAAK;WACL;;WAEA,OAAO,eAAe;WACtB,SAAS;;;;;;;;;;;wBAkBE,SAAS,SAAS,eAAe,QAAQ"}
1
+ {"version":3,"file":"fresh.d.ts","names":[],"sources":["../src/fresh.ts"],"mappings":";;iBAUiB;WACN;WACA;WACA;WACA;WACA;;WAEA;;;wBAIK,YAAY;;YAQhB,YAAY,YAAY;;iBAGnB;WACN;;WAEA;WACA;;;iBAIM;;WAEN;WACA;WACA;;;;;;wBAOK,UAAU,iBAAiB;;iBAwB1B;WACN;WACA,OAAO;;;;;;;;wBASF,UAAU,eAAe,eAAe,oBAAoB;;iBAY3D;WACN;WACA;;;iBAIM;;WAEN;;WAEA;;;iBAIM;WACN;WACA;;WAEA;;WAEA;;WAEA;;;YAIC,cAAc,yBAAyB,SAAS,iBAAiB,QAAQ;;iBAGpE;;WAEN,MAAM;;WAEN,qBAAqB;;WAErB;;WAEA;;;;;;WAMA;;WAEA;;WAEA,QAAQ;;WAER;;WAEA;;WAEA;;WAEA,MAAM;;WAEN,QAAQ;;;iBAIF,oBAAoB;WAC1B;WACA,KAAK;;;iBAIC;WACN;WACA;;WAEA;WACA;;;iBAIM;WACN,KAAK;WACL;;WAEA,OAAO,eAAe;WACtB,SAAS;;;;;;;;;;;wBAkBE,SAAS,SAAS,eAAe,QAAQ"}
package/build/fresh.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { r as firstArm } from "./paired.js";
2
+ import { t as stopTree } from "./tree.js";
2
3
  import { spawn } from "node:child_process";
3
4
  import { arch, cpus, platform } from "node:os";
4
5
  // src/fresh.ts
@@ -234,27 +235,7 @@ function startProcess(argv, options) {
234
235
  let stopping = false;
235
236
  let error;
236
237
  let grace;
237
- const signal = (name) => {
238
- const pid = proc.pid;
239
- if (pid === void 0) return;
240
- if (windows) {
241
- spawn("taskkill", [
242
- "/pid",
243
- String(pid),
244
- "/T",
245
- "/F"
246
- ], {
247
- stdio: "ignore",
248
- windowsHide: true
249
- }).on("error", () => proc.kill());
250
- return;
251
- }
252
- try {
253
- process.kill(-pid, name);
254
- } catch {
255
- proc.kill(name);
256
- }
257
- };
238
+ const signal = (name) => stopTree(proc, name);
258
239
  const finish = (code) => {
259
240
  if (settled) return;
260
241
  settled = true;
@@ -1 +1 @@
1
- {"version":3,"file":"fresh.js","names":[],"sources":["../src/fresh.ts"],"sourcesContent":["/**\n * The fresh-process runner of a bench, for Node only. Each arm of each pair, case and round is one fresh child process.\n * The child prints one JSON line with its time. The runner keeps the order of `pairOrder`, and gives `PairedTime` records.\n */\nimport { spawn } from \"node:child_process\";\nimport { arch, cpus, platform } from \"node:os\";\nimport { type Arm, type PairedTime, firstArm } from \"./paired.ts\";\n\n/** The computer of a run. The `cpu` is the model of the first CPU. */\nexport interface BenchEnv {\n readonly cpu: string;\n readonly cores: number;\n readonly os: string;\n readonly arch: string;\n readonly node: string;\n /** The name of the runner, from `BENCH_RUNNER` or else `RUNNER_NAME`. An empty value is absent. */\n readonly runner?: string;\n}\n\n/** This function gives the computer of this process. The runner comes from `BENCH_RUNNER`, or else from `RUNNER_NAME`. */\nexport function benchEnv(): BenchEnv {\n const all = cpus();\n const env = { cpu: all[0]?.model.trim() || \"unknown\", cores: all.length, os: platform(), arch: arch(), node: process.version };\n const runner = process.env[\"BENCH_RUNNER\"] || process.env[\"RUNNER_NAME\"];\n return runner === undefined || runner === \"\" ? env : { ...env, runner };\n}\n\n/** The JSON line of a child: its time in milliseconds, the hash of its output, and the reason of a skip. */\nexport type ChildLine = ChildTime | ChildSkip;\n\n/** The result line of a child that timed the arm. */\nexport interface ChildTime {\n readonly ms: number;\n /** The hash of the output of the arm. Two different hashes of one arm on one case are an error. */\n readonly hash?: string;\n readonly skipped?: undefined;\n}\n\n/** The result line of a child that skipped the case. A skip line does not need a time. */\nexport interface ChildSkip {\n /** The reason of the skip. The runner removes the case from each pair. */\n readonly skipped: string;\n readonly ms?: number;\n readonly hash?: string;\n}\n\n/**\n * This function reads the last line of `stdout` that starts with `{`. The time `ms` is a finite number above 0.\n * A skip line has the reason `skipped` and does not need a time. No such line, bad JSON or a bad field is an error.\n */\nexport function childLine(stdout: string): ChildLine {\n const line = stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .findLast((l) => l.startsWith(\"{\"));\n if (line === undefined) throw new Error(\"childLine: the output has no line that starts with {.\");\n let value: unknown;\n try {\n value = JSON.parse(line);\n } catch {\n throw new Error(`childLine: the last line that starts with { is not JSON: ${line.slice(0, 200)}`);\n }\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new Error(`childLine: the last JSON line is not an object: ${line.slice(0, 200)}`);\n const { ms, hash, skipped } = value as Record<string, unknown>;\n if (hash !== undefined && typeof hash !== \"string\") throw new Error(`childLine: the hash is a string, not ${JSON.stringify(hash)}.`);\n if (skipped !== undefined) {\n if (!(typeof skipped === \"string\" && skipped !== \"\")) throw new Error(`childLine: the reason of a skip is a string that is not empty, not ${JSON.stringify(skipped)}.`);\n return { skipped, ...(typeof ms === \"number\" ? { ms } : {}), ...(hash === undefined ? {} : { hash }) };\n }\n if (!(typeof ms === \"number\" && Number.isFinite(ms) && ms > 0)) throw new Error(`childLine: the time ms is a finite number above 0, not ${JSON.stringify(ms)}.`);\n return { ms, ...(hash === undefined ? {} : { hash }) };\n}\n\n/** One slot of the order of a round: the index of the pair, and the arm of that pair that starts first. */\nexport interface PairSlot {\n readonly pair: number;\n readonly first: Arm;\n}\n\n/**\n * This function gives the order of the pairs of one case in one round. `round` is the global round.\n * - Pair `p` starts with `firstArm(round, caseIndex + p)`.\n * - The pairs rotate by `(Math.floor(round / 2) + caseIndex) % pairs`. Thus the pair that starts first turns each second round.\n * - For 2 pairs, the order is the order of `timeArmOrder` of the joint bench.\n */\nexport function pairOrder(pairs: number, round: number, caseIndex: number): PairSlot[] {\n if (!Number.isSafeInteger(pairs) || pairs < 1) throw new RangeError(`pairOrder: the count of the pairs is a whole number of 1 or more, not ${pairs}.`);\n if (!Number.isSafeInteger(round) || round < 0) throw new RangeError(`pairOrder: the round is a whole number of 0 or more, not ${round}.`);\n if (!Number.isSafeInteger(caseIndex) || caseIndex < 0) throw new RangeError(`pairOrder: the index of the case is a whole number of 0 or more, not ${caseIndex}.`);\n const shift = (Math.floor(round / 2) + caseIndex) % pairs;\n return Array.from({ length: pairs }, (_, i) => {\n const pair = (shift + i) % pairs;\n return { pair, first: firstArm(round, caseIndex + pair) };\n });\n}\n\n/** One arm of a run: its id and the argv of its child. The first item of `command` is the program. */\nexport interface FreshArm {\n readonly id: string;\n readonly command: readonly string[];\n}\n\n/** The limits of one child. */\nexport interface StartOptions {\n /** The longest time of the child in milliseconds. */\n readonly timeoutMs: number;\n /** The most bytes of `stdout` or `stderr`. */\n readonly maxBuffer: number;\n}\n\n/** The end of one child. */\nexport interface ChildExit {\n readonly stdout: string;\n readonly stderr: string;\n /** The exit code, or `null` when a signal stopped the child. */\n readonly code: number | null;\n /** The value is `true` when the child took more time than the limit. */\n readonly timedOut?: boolean;\n /** The reason of a fault outside the child, for example a program that does not exist or too much output. */\n readonly error?: string;\n}\n\n/** The start of one child: it gives the end of the child. The default starts a process with `spawn`, without a shell. */\nexport type StartChild = (argv: readonly string[], options: StartOptions) => Promise<ChildExit>;\n\n/** The options of a fresh run. */\nexport interface FreshOptions {\n /** The base arm. It is arm `a` of each pair. */\n readonly base: FreshArm;\n /** The candidate arms. Each candidate is arm `b` of its own pair, and the id of the pair is its id. */\n readonly candidates: readonly FreshArm[];\n /** The ids of the cases. */\n readonly cases: readonly string[];\n /** The count of the rounds of this job. */\n readonly rounds: number;\n /**\n * The global round of the first round of this job. The default is 0.\n * A run of many jobs sets it to `job * rounds`, so each round is global over the jobs.\n * Without it, each job repeats the first orders, and the merge removes more records.\n */\n readonly roundOffset?: number;\n /** The value `true` adds the A/A pair (base, base) with the id `${base.id}~aa`. The default is `true`. */\n readonly aa?: boolean;\n /** The arguments of a case after the command of an arm. The default is `[\"--case\", id]`. */\n readonly args?: (caseId: string) => readonly string[];\n /** The longest time of one child in milliseconds. The default is 600000 (10 minutes). */\n readonly timeoutMs?: number;\n /** The most bytes of the output of one child. The default is 16 MiB. */\n readonly maxBuffer?: number;\n /** The label of the job. The default is `\"0\"`. */\n readonly job?: string;\n /** The computer of the run. The default is `benchEnv()`. */\n readonly env?: BenchEnv;\n /** The start of a child. The default starts a process. A test gives its own. */\n readonly start?: StartChild;\n}\n\n/** One record of a fresh run: a `PairedTime` record with the job and the computer of its job. */\nexport interface FreshRecord extends PairedTime {\n readonly job: string;\n readonly env: BenchEnv;\n}\n\n/** A case that an arm skipped. The case is in no pair. */\nexport interface FreshSkip {\n readonly arm: string;\n readonly case: string;\n /** The global round of the skip. A skip is in the first round of its job. */\n readonly round: number;\n readonly reason: string;\n}\n\n/** The result of a fresh run. */\nexport interface FreshRun {\n readonly env: BenchEnv;\n readonly job: string;\n /** The records of each pair, by the id of the pair. */\n readonly pairs: Record<string, FreshRecord[]>;\n readonly skipped: FreshSkip[];\n}\n\n/** The suffix of the id of the A/A pair. */\nconst AA_SUFFIX = \"~aa\";\n\n/** The longest tail of `stderr` in an error. */\nconst TAIL = 2000;\n\n/**\n * This function starts each arm of each pair, case and round in a fresh child, and gives the records of each pair.\n * - Each candidate is a pair (base, candidate) with its own base child. The A/A pair is (base, base).\n * - In each round and case, the pairs come in the order of `pairOrder`. The first arm of a pair starts first.\n * - A child that exits with an error, takes too long or prints no JSON line is an error. Only a skip line skips a case.\n * - A case that an arm skips in the first round of the job leaves each pair, and the runner starts no more children for it.\n * - A skip after the first round is an error. It shows a child that is not deterministic, and it drops paid data.\n * - Two different hashes of one arm on one case are an error.\n */\nexport async function freshRun(options: FreshOptions): Promise<FreshRun> {\n const { base, candidates, cases, rounds } = options;\n const roundOffset = options.roundOffset ?? 0;\n const job = options.job ?? \"0\";\n const timeoutMs = options.timeoutMs ?? 600_000;\n const maxBuffer = options.maxBuffer ?? 1 << 24;\n const args = options.args ?? ((id: string) => [\"--case\", id]);\n const start = options.start ?? startProcess;\n if (!Number.isSafeInteger(rounds) || rounds < 1) throw new RangeError(`freshRun: the rounds are a whole number of 1 or more, not ${rounds}.`);\n if (!Number.isSafeInteger(roundOffset) || roundOffset < 0) throw new RangeError(`freshRun: the round offset is a whole number of 0 or more, not ${roundOffset}.`);\n if (!(timeoutMs > 0) || !(maxBuffer > 0)) throw new RangeError(\"freshRun: the time limit and the output limit are above 0.\");\n if (cases.length === 0 || new Set(cases).size !== cases.length || cases.some((c) => typeof c !== \"string\" || c === \"\")) {\n throw new RangeError(\"freshRun: the cases are 1 or more different strings that are not empty.\");\n }\n const pairs: { id: string; a: FreshArm; b: FreshArm }[] = candidates.map((c) => ({ id: c.id, a: base, b: c }));\n if (options.aa ?? true) pairs.push({ id: `${base.id}${AA_SUFFIX}`, a: base, b: base });\n if (pairs.length === 0) throw new RangeError(\"freshRun: the run has no pairs. Give a candidate, or keep the A/A pair.\");\n for (const arm of [base, ...candidates]) {\n if (typeof arm.id !== \"string\" || arm.id === \"\") throw new RangeError(\"freshRun: the id of an arm is a string that is not empty.\");\n if (arm.command.length === 0 || arm.command.some((s) => typeof s !== \"string\")) throw new RangeError(`freshRun: the command of the arm ${JSON.stringify(arm.id)} is 1 or more strings.`);\n }\n const ids = new Set([base.id]);\n for (const { id } of pairs) {\n if (ids.has(id)) throw new RangeError(`freshRun: two arms or pairs have the id ${JSON.stringify(id)}.`);\n ids.add(id);\n }\n\n const env = options.env ?? benchEnv();\n const out: Record<string, FreshRecord[]> = Object.fromEntries(pairs.map((p) => [p.id, []]));\n const skipped: FreshSkip[] = [];\n const gone = new Set<string>();\n const hashes = new Map<string, string>();\n\n for (let r = 0; r < rounds; r++) {\n const round = roundOffset + r;\n for (let i = 0; i < cases.length; i++) {\n const id = cases[i]!;\n if (gone.has(id)) continue;\n const records: { pair: string; record: FreshRecord }[] = [];\n let skip: FreshSkip | undefined;\n for (const slot of pairOrder(pairs.length, round, i)) {\n const pair = pairs[slot.pair]!;\n const times: Partial<Record<Arm, number>> = {};\n for (const arm of [slot.first, slot.first === \"a\" ? \"b\" : \"a\"] as const) {\n const who = pair[arm];\n const where = `the arm ${JSON.stringify(who.id)} (pair ${JSON.stringify(pair.id)}, arm ${arm}) on the case ${JSON.stringify(id)}, round ${round}`;\n const line = await child(start, [...who.command, ...args(id)], { timeoutMs, maxBuffer }, where);\n if (line.skipped !== undefined) {\n if (r > 0) throw new Error(`freshRun: ${where} skipped the case after the first round of the job (${JSON.stringify(line.skipped)}). A child that skips only in a later round is not deterministic.`);\n skip = { arm: who.id, case: id, round, reason: line.skipped };\n break;\n }\n if (line.hash !== undefined) {\n const key = `${who.id}\\u0000${id}`;\n const known = hashes.get(key);\n if (known !== undefined && known !== line.hash) throw new Error(`freshRun: ${where} gave the hash ${line.hash}, and an earlier child of the arm gave ${known}.`);\n hashes.set(key, line.hash);\n }\n times[arm] = line.ms;\n }\n if (skip !== undefined) break;\n records.push({ pair: pair.id, record: { case: id, round, first: slot.first, a: times.a!, b: times.b!, job, env } });\n }\n if (skip !== undefined) {\n skipped.push(skip);\n gone.add(id);\n continue;\n }\n for (const { pair, record } of records) out[pair]!.push(record);\n }\n }\n return { env, job, pairs: out, skipped };\n}\n\n/** This function starts one child and reads its JSON line. A fault gives an error that names the arm, the case, the round and the tail of `stderr`. */\nasync function child(start: StartChild, argv: readonly string[], limits: StartOptions, where: string): Promise<ChildLine> {\n let exit: ChildExit;\n try {\n exit = await start(argv, limits);\n } catch (error) {\n throw new Error(`freshRun: ${where} did not start: ${error instanceof Error ? error.message : String(error)}`, { cause: error });\n }\n const fail = (why: string): Error => {\n const tail = exit.stderr.length > TAIL ? `...${exit.stderr.slice(-TAIL)}` : exit.stderr;\n return new Error(`freshRun: ${where} ${why}. The tail of stderr: ${tail.trim() === \"\" ? \"(empty)\" : tail.trimEnd()}`);\n };\n if (exit.error !== undefined) throw fail(`failed: ${exit.error}`);\n if (exit.timedOut === true) throw fail(`took more than ${limits.timeoutMs} ms`);\n if (exit.code !== 0) throw fail(exit.code === null ? \"stopped on a signal\" : `exited with the code ${exit.code}`);\n try {\n return childLine(exit.stdout);\n } catch (error) {\n throw fail(`printed no good JSON line (${error instanceof Error ? error.message : String(error)})`);\n }\n}\n\n/** The wait, in milliseconds, for the pipes of a child after its exit, and for the exit of a child after a stop. */\nconst GRACE_MS = 1000;\n\n/**\n * This function starts a process with `spawn` and no shell. It stops the process and its tree at the time limit or at the output limit.\n *\n * - The promise settles at the close of the pipes, or a grace time after the exit of the child. A grandchild can keep a pipe open.\n * - A stop sends SIGTERM to the process group, and SIGKILL after the grace time. On Windows, `taskkill /T /F` stops the tree.\n */\nfunction startProcess(argv: readonly string[], options: StartOptions): Promise<ChildExit> {\n return new Promise((resolve) => {\n const [program, ...rest] = argv;\n const windows = process.platform === \"win32\";\n // On POSIX, the child leads its own process group, so a signal to the group reaches the grandchildren.\n const proc = spawn(program!, rest, { stdio: [\"ignore\", \"pipe\", \"pipe\"], windowsHide: true, detached: !windows });\n const out: Buffer[] = [];\n const err: Buffer[] = [];\n let timedOut = false;\n let settled = false;\n let stopping = false;\n let error: string | undefined;\n let grace: ReturnType<typeof setTimeout> | undefined;\n const signal = (name: NodeJS.Signals): void => {\n const pid = proc.pid;\n if (pid === undefined) return;\n if (windows) {\n // TerminateProcess stops only the direct child. The flag /T of taskkill also stops its tree.\n spawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true }).on(\"error\", () => proc.kill());\n return;\n }\n try {\n process.kill(-pid, name);\n } catch {\n proc.kill(name);\n }\n };\n const finish = (code: number | null): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearTimeout(grace);\n proc.stdout.destroy();\n proc.stderr.destroy();\n const exit: ChildExit = { stdout: Buffer.concat(out).toString(\"utf8\"), stderr: Buffer.concat(err).toString(\"utf8\"), code, timedOut };\n resolve(error === undefined ? exit : { ...exit, error });\n };\n const stop = (): void => {\n if (stopping) return;\n stopping = true;\n signal(\"SIGTERM\");\n clearTimeout(grace);\n grace = setTimeout(() => {\n if (!windows) signal(\"SIGKILL\");\n finish(null);\n }, GRACE_MS);\n };\n const timer = setTimeout(() => {\n timedOut = true;\n stop();\n }, options.timeoutMs);\n const take = (list: Buffer[]) => {\n let bytes = 0;\n return (chunk: Buffer): void => {\n bytes += chunk.length;\n if (bytes > options.maxBuffer) {\n error ??= `an output is more than ${options.maxBuffer} bytes`;\n stop();\n return;\n }\n list.push(chunk);\n };\n };\n proc.stdout.on(\"data\", take(out));\n proc.stderr.on(\"data\", take(err));\n proc.on(\"error\", (e) => {\n error ??= e.message;\n // A program that does not start gives no event close.\n if (proc.pid === undefined) finish(null);\n });\n proc.on(\"exit\", (code) => {\n clearTimeout(timer);\n if (stopping) return;\n // A grandchild that holds a pipe keeps the event close away. After the grace time, the runner stops the group.\n grace = setTimeout(() => {\n if (!windows) signal(\"SIGKILL\");\n finish(code);\n }, GRACE_MS);\n });\n proc.on(\"close\", (code) => finish(code));\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fresh.js","names":[],"sources":["../src/fresh.ts"],"sourcesContent":["/**\n * The fresh-process runner of a bench, for Node only. Each arm of each pair, case and round is one fresh child process.\n * The child prints one JSON line with its time. The runner keeps the order of `pairOrder`, and gives `PairedTime` records.\n */\nimport { spawn } from \"node:child_process\";\nimport { arch, cpus, platform } from \"node:os\";\nimport { type Arm, type PairedTime, firstArm } from \"./paired.ts\";\nimport { stopTree } from \"./tree.ts\";\n\n/** The computer of a run. The `cpu` is the model of the first CPU. */\nexport interface BenchEnv {\n readonly cpu: string;\n readonly cores: number;\n readonly os: string;\n readonly arch: string;\n readonly node: string;\n /** The name of the runner, from `BENCH_RUNNER` or else `RUNNER_NAME`. An empty value is absent. */\n readonly runner?: string;\n}\n\n/** This function gives the computer of this process. The runner comes from `BENCH_RUNNER`, or else from `RUNNER_NAME`. */\nexport function benchEnv(): BenchEnv {\n const all = cpus();\n const env = { cpu: all[0]?.model.trim() || \"unknown\", cores: all.length, os: platform(), arch: arch(), node: process.version };\n const runner = process.env[\"BENCH_RUNNER\"] || process.env[\"RUNNER_NAME\"];\n return runner === undefined || runner === \"\" ? env : { ...env, runner };\n}\n\n/** The JSON line of a child: its time in milliseconds, the hash of its output, and the reason of a skip. */\nexport type ChildLine = ChildTime | ChildSkip;\n\n/** The result line of a child that timed the arm. */\nexport interface ChildTime {\n readonly ms: number;\n /** The hash of the output of the arm. Two different hashes of one arm on one case are an error. */\n readonly hash?: string;\n readonly skipped?: undefined;\n}\n\n/** The result line of a child that skipped the case. A skip line does not need a time. */\nexport interface ChildSkip {\n /** The reason of the skip. The runner removes the case from each pair. */\n readonly skipped: string;\n readonly ms?: number;\n readonly hash?: string;\n}\n\n/**\n * This function reads the last line of `stdout` that starts with `{`. The time `ms` is a finite number above 0.\n * A skip line has the reason `skipped` and does not need a time. No such line, bad JSON or a bad field is an error.\n */\nexport function childLine(stdout: string): ChildLine {\n const line = stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .findLast((l) => l.startsWith(\"{\"));\n if (line === undefined) throw new Error(\"childLine: the output has no line that starts with {.\");\n let value: unknown;\n try {\n value = JSON.parse(line);\n } catch {\n throw new Error(`childLine: the last line that starts with { is not JSON: ${line.slice(0, 200)}`);\n }\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new Error(`childLine: the last JSON line is not an object: ${line.slice(0, 200)}`);\n const { ms, hash, skipped } = value as Record<string, unknown>;\n if (hash !== undefined && typeof hash !== \"string\") throw new Error(`childLine: the hash is a string, not ${JSON.stringify(hash)}.`);\n if (skipped !== undefined) {\n if (!(typeof skipped === \"string\" && skipped !== \"\")) throw new Error(`childLine: the reason of a skip is a string that is not empty, not ${JSON.stringify(skipped)}.`);\n return { skipped, ...(typeof ms === \"number\" ? { ms } : {}), ...(hash === undefined ? {} : { hash }) };\n }\n if (!(typeof ms === \"number\" && Number.isFinite(ms) && ms > 0)) throw new Error(`childLine: the time ms is a finite number above 0, not ${JSON.stringify(ms)}.`);\n return { ms, ...(hash === undefined ? {} : { hash }) };\n}\n\n/** One slot of the order of a round: the index of the pair, and the arm of that pair that starts first. */\nexport interface PairSlot {\n readonly pair: number;\n readonly first: Arm;\n}\n\n/**\n * This function gives the order of the pairs of one case in one round. `round` is the global round.\n * - Pair `p` starts with `firstArm(round, caseIndex + p)`.\n * - The pairs rotate by `(Math.floor(round / 2) + caseIndex) % pairs`. Thus the pair that starts first turns each second round.\n * - For 2 pairs, the order is the order of `timeArmOrder` of the joint bench.\n */\nexport function pairOrder(pairs: number, round: number, caseIndex: number): PairSlot[] {\n if (!Number.isSafeInteger(pairs) || pairs < 1) throw new RangeError(`pairOrder: the count of the pairs is a whole number of 1 or more, not ${pairs}.`);\n if (!Number.isSafeInteger(round) || round < 0) throw new RangeError(`pairOrder: the round is a whole number of 0 or more, not ${round}.`);\n if (!Number.isSafeInteger(caseIndex) || caseIndex < 0) throw new RangeError(`pairOrder: the index of the case is a whole number of 0 or more, not ${caseIndex}.`);\n const shift = (Math.floor(round / 2) + caseIndex) % pairs;\n return Array.from({ length: pairs }, (_, i) => {\n const pair = (shift + i) % pairs;\n return { pair, first: firstArm(round, caseIndex + pair) };\n });\n}\n\n/** One arm of a run: its id and the argv of its child. The first item of `command` is the program. */\nexport interface FreshArm {\n readonly id: string;\n readonly command: readonly string[];\n}\n\n/** The limits of one child. */\nexport interface StartOptions {\n /** The longest time of the child in milliseconds. */\n readonly timeoutMs: number;\n /** The most bytes of `stdout` or `stderr`. */\n readonly maxBuffer: number;\n}\n\n/** The end of one child. */\nexport interface ChildExit {\n readonly stdout: string;\n readonly stderr: string;\n /** The exit code, or `null` when a signal stopped the child. */\n readonly code: number | null;\n /** The value is `true` when the child took more time than the limit. */\n readonly timedOut?: boolean;\n /** The reason of a fault outside the child, for example a program that does not exist or too much output. */\n readonly error?: string;\n}\n\n/** The start of one child: it gives the end of the child. The default starts a process with `spawn`, without a shell. */\nexport type StartChild = (argv: readonly string[], options: StartOptions) => Promise<ChildExit>;\n\n/** The options of a fresh run. */\nexport interface FreshOptions {\n /** The base arm. It is arm `a` of each pair. */\n readonly base: FreshArm;\n /** The candidate arms. Each candidate is arm `b` of its own pair, and the id of the pair is its id. */\n readonly candidates: readonly FreshArm[];\n /** The ids of the cases. */\n readonly cases: readonly string[];\n /** The count of the rounds of this job. */\n readonly rounds: number;\n /**\n * The global round of the first round of this job. The default is 0.\n * A run of many jobs sets it to `job * rounds`, so each round is global over the jobs.\n * Without it, each job repeats the first orders, and the merge removes more records.\n */\n readonly roundOffset?: number;\n /** The value `true` adds the A/A pair (base, base) with the id `${base.id}~aa`. The default is `true`. */\n readonly aa?: boolean;\n /** The arguments of a case after the command of an arm. The default is `[\"--case\", id]`. */\n readonly args?: (caseId: string) => readonly string[];\n /** The longest time of one child in milliseconds. The default is 600000 (10 minutes). */\n readonly timeoutMs?: number;\n /** The most bytes of the output of one child. The default is 16 MiB. */\n readonly maxBuffer?: number;\n /** The label of the job. The default is `\"0\"`. */\n readonly job?: string;\n /** The computer of the run. The default is `benchEnv()`. */\n readonly env?: BenchEnv;\n /** The start of a child. The default starts a process. A test gives its own. */\n readonly start?: StartChild;\n}\n\n/** One record of a fresh run: a `PairedTime` record with the job and the computer of its job. */\nexport interface FreshRecord extends PairedTime {\n readonly job: string;\n readonly env: BenchEnv;\n}\n\n/** A case that an arm skipped. The case is in no pair. */\nexport interface FreshSkip {\n readonly arm: string;\n readonly case: string;\n /** The global round of the skip. A skip is in the first round of its job. */\n readonly round: number;\n readonly reason: string;\n}\n\n/** The result of a fresh run. */\nexport interface FreshRun {\n readonly env: BenchEnv;\n readonly job: string;\n /** The records of each pair, by the id of the pair. */\n readonly pairs: Record<string, FreshRecord[]>;\n readonly skipped: FreshSkip[];\n}\n\n/** The suffix of the id of the A/A pair. */\nconst AA_SUFFIX = \"~aa\";\n\n/** The longest tail of `stderr` in an error. */\nconst TAIL = 2000;\n\n/**\n * This function starts each arm of each pair, case and round in a fresh child, and gives the records of each pair.\n * - Each candidate is a pair (base, candidate) with its own base child. The A/A pair is (base, base).\n * - In each round and case, the pairs come in the order of `pairOrder`. The first arm of a pair starts first.\n * - A child that exits with an error, takes too long or prints no JSON line is an error. Only a skip line skips a case.\n * - A case that an arm skips in the first round of the job leaves each pair, and the runner starts no more children for it.\n * - A skip after the first round is an error. It shows a child that is not deterministic, and it drops paid data.\n * - Two different hashes of one arm on one case are an error.\n */\nexport async function freshRun(options: FreshOptions): Promise<FreshRun> {\n const { base, candidates, cases, rounds } = options;\n const roundOffset = options.roundOffset ?? 0;\n const job = options.job ?? \"0\";\n const timeoutMs = options.timeoutMs ?? 600_000;\n const maxBuffer = options.maxBuffer ?? 1 << 24;\n const args = options.args ?? ((id: string) => [\"--case\", id]);\n const start = options.start ?? startProcess;\n if (!Number.isSafeInteger(rounds) || rounds < 1) throw new RangeError(`freshRun: the rounds are a whole number of 1 or more, not ${rounds}.`);\n if (!Number.isSafeInteger(roundOffset) || roundOffset < 0) throw new RangeError(`freshRun: the round offset is a whole number of 0 or more, not ${roundOffset}.`);\n if (!(timeoutMs > 0) || !(maxBuffer > 0)) throw new RangeError(\"freshRun: the time limit and the output limit are above 0.\");\n if (cases.length === 0 || new Set(cases).size !== cases.length || cases.some((c) => typeof c !== \"string\" || c === \"\")) {\n throw new RangeError(\"freshRun: the cases are 1 or more different strings that are not empty.\");\n }\n const pairs: { id: string; a: FreshArm; b: FreshArm }[] = candidates.map((c) => ({ id: c.id, a: base, b: c }));\n if (options.aa ?? true) pairs.push({ id: `${base.id}${AA_SUFFIX}`, a: base, b: base });\n if (pairs.length === 0) throw new RangeError(\"freshRun: the run has no pairs. Give a candidate, or keep the A/A pair.\");\n for (const arm of [base, ...candidates]) {\n if (typeof arm.id !== \"string\" || arm.id === \"\") throw new RangeError(\"freshRun: the id of an arm is a string that is not empty.\");\n if (arm.command.length === 0 || arm.command.some((s) => typeof s !== \"string\")) throw new RangeError(`freshRun: the command of the arm ${JSON.stringify(arm.id)} is 1 or more strings.`);\n }\n const ids = new Set([base.id]);\n for (const { id } of pairs) {\n if (ids.has(id)) throw new RangeError(`freshRun: two arms or pairs have the id ${JSON.stringify(id)}.`);\n ids.add(id);\n }\n\n const env = options.env ?? benchEnv();\n const out: Record<string, FreshRecord[]> = Object.fromEntries(pairs.map((p) => [p.id, []]));\n const skipped: FreshSkip[] = [];\n const gone = new Set<string>();\n const hashes = new Map<string, string>();\n\n for (let r = 0; r < rounds; r++) {\n const round = roundOffset + r;\n for (let i = 0; i < cases.length; i++) {\n const id = cases[i]!;\n if (gone.has(id)) continue;\n const records: { pair: string; record: FreshRecord }[] = [];\n let skip: FreshSkip | undefined;\n for (const slot of pairOrder(pairs.length, round, i)) {\n const pair = pairs[slot.pair]!;\n const times: Partial<Record<Arm, number>> = {};\n for (const arm of [slot.first, slot.first === \"a\" ? \"b\" : \"a\"] as const) {\n const who = pair[arm];\n const where = `the arm ${JSON.stringify(who.id)} (pair ${JSON.stringify(pair.id)}, arm ${arm}) on the case ${JSON.stringify(id)}, round ${round}`;\n const line = await child(start, [...who.command, ...args(id)], { timeoutMs, maxBuffer }, where);\n if (line.skipped !== undefined) {\n if (r > 0) throw new Error(`freshRun: ${where} skipped the case after the first round of the job (${JSON.stringify(line.skipped)}). A child that skips only in a later round is not deterministic.`);\n skip = { arm: who.id, case: id, round, reason: line.skipped };\n break;\n }\n if (line.hash !== undefined) {\n const key = `${who.id}\\u0000${id}`;\n const known = hashes.get(key);\n if (known !== undefined && known !== line.hash) throw new Error(`freshRun: ${where} gave the hash ${line.hash}, and an earlier child of the arm gave ${known}.`);\n hashes.set(key, line.hash);\n }\n times[arm] = line.ms;\n }\n if (skip !== undefined) break;\n records.push({ pair: pair.id, record: { case: id, round, first: slot.first, a: times.a!, b: times.b!, job, env } });\n }\n if (skip !== undefined) {\n skipped.push(skip);\n gone.add(id);\n continue;\n }\n for (const { pair, record } of records) out[pair]!.push(record);\n }\n }\n return { env, job, pairs: out, skipped };\n}\n\n/** This function starts one child and reads its JSON line. A fault gives an error that names the arm, the case, the round and the tail of `stderr`. */\nasync function child(start: StartChild, argv: readonly string[], limits: StartOptions, where: string): Promise<ChildLine> {\n let exit: ChildExit;\n try {\n exit = await start(argv, limits);\n } catch (error) {\n throw new Error(`freshRun: ${where} did not start: ${error instanceof Error ? error.message : String(error)}`, { cause: error });\n }\n const fail = (why: string): Error => {\n const tail = exit.stderr.length > TAIL ? `...${exit.stderr.slice(-TAIL)}` : exit.stderr;\n return new Error(`freshRun: ${where} ${why}. The tail of stderr: ${tail.trim() === \"\" ? \"(empty)\" : tail.trimEnd()}`);\n };\n if (exit.error !== undefined) throw fail(`failed: ${exit.error}`);\n if (exit.timedOut === true) throw fail(`took more than ${limits.timeoutMs} ms`);\n if (exit.code !== 0) throw fail(exit.code === null ? \"stopped on a signal\" : `exited with the code ${exit.code}`);\n try {\n return childLine(exit.stdout);\n } catch (error) {\n throw fail(`printed no good JSON line (${error instanceof Error ? error.message : String(error)})`);\n }\n}\n\n/** The wait, in milliseconds, for the pipes of a child after its exit, and for the exit of a child after a stop. */\nconst GRACE_MS = 1000;\n\n/**\n * This function starts a process with `spawn` and no shell. It stops the process and its tree at the time limit or at the output limit.\n *\n * - The promise settles at the close of the pipes, or a grace time after the exit of the child. A grandchild can keep a pipe open.\n * - A stop sends SIGTERM to the process group, and SIGKILL after the grace time. On Windows, `taskkill /T /F` stops the tree.\n */\nfunction startProcess(argv: readonly string[], options: StartOptions): Promise<ChildExit> {\n return new Promise((resolve) => {\n const [program, ...rest] = argv;\n const windows = process.platform === \"win32\";\n // On POSIX, the child leads its own process group, so a signal to the group reaches the grandchildren.\n const proc = spawn(program!, rest, { stdio: [\"ignore\", \"pipe\", \"pipe\"], windowsHide: true, detached: !windows });\n const out: Buffer[] = [];\n const err: Buffer[] = [];\n let timedOut = false;\n let settled = false;\n let stopping = false;\n let error: string | undefined;\n let grace: ReturnType<typeof setTimeout> | undefined;\n const signal = (name: NodeJS.Signals): void => stopTree(proc, name);\n const finish = (code: number | null): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearTimeout(grace);\n proc.stdout.destroy();\n proc.stderr.destroy();\n const exit: ChildExit = { stdout: Buffer.concat(out).toString(\"utf8\"), stderr: Buffer.concat(err).toString(\"utf8\"), code, timedOut };\n resolve(error === undefined ? exit : { ...exit, error });\n };\n const stop = (): void => {\n if (stopping) return;\n stopping = true;\n signal(\"SIGTERM\");\n clearTimeout(grace);\n grace = setTimeout(() => {\n if (!windows) signal(\"SIGKILL\");\n finish(null);\n }, GRACE_MS);\n };\n const timer = setTimeout(() => {\n timedOut = true;\n stop();\n }, options.timeoutMs);\n const take = (list: Buffer[]) => {\n let bytes = 0;\n return (chunk: Buffer): void => {\n bytes += chunk.length;\n if (bytes > options.maxBuffer) {\n error ??= `an output is more than ${options.maxBuffer} bytes`;\n stop();\n return;\n }\n list.push(chunk);\n };\n };\n proc.stdout.on(\"data\", take(out));\n proc.stderr.on(\"data\", take(err));\n proc.on(\"error\", (e) => {\n error ??= e.message;\n // A program that does not start gives no event close.\n if (proc.pid === undefined) finish(null);\n });\n proc.on(\"exit\", (code) => {\n clearTimeout(timer);\n if (stopping) return;\n // A grandchild that holds a pipe keeps the event close away. After the grace time, the runner stops the group.\n grace = setTimeout(() => {\n if (!windows) signal(\"SIGKILL\");\n finish(code);\n }, GRACE_MS);\n });\n proc.on(\"close\", (code) => finish(code));\n });\n}\n"],"mappings":"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"}
package/build/index.d.ts CHANGED
@@ -61,6 +61,151 @@ interface BudgetVerdict {
61
61
  * - Each other verdict is `cannot decide`, and so is a verdict with no cases.
62
62
  */
63
63
  export declare function budgetVerdict(rounds: readonly BudgetRound[], aa: readonly BudgetRound[], options: BudgetOptions): BudgetVerdict;
64
+ /** The format name of a ladder. It changes when a reader of an older form cannot read the data. */
65
+ export declare const LADDER_FORMAT = "rigor-ladder/1";
66
+ /** One dimension of a ladder, for example tenants or nodes. */
67
+ interface LadderDimension {
68
+ readonly name: string;
69
+ /** What one unit of the dimension is, in plain words. */
70
+ readonly describe: string;
71
+ }
72
+ /** A budget on the median of each case, for example the time of a read. A record gives rounds and an A/A arm. */
73
+ interface MedianBudget {
74
+ readonly kind: "median";
75
+ readonly measure: string;
76
+ /** The unit of the limit and of the values. */
77
+ readonly unit: string;
78
+ /** The largest median that the budget permits. */
79
+ readonly limit: number;
80
+ /** The largest difference between the medians of the two arms. A larger difference makes the run too noisy. */
81
+ readonly maxDifference: number;
82
+ /** The level of the interval of each median. The default is 0.95. */
83
+ readonly caseLevel?: number;
84
+ }
85
+ /** A budget on the share of failed trials, for example failed requests. A record gives failures and trials. */
86
+ interface RateBudget {
87
+ readonly kind: "rate";
88
+ readonly measure: string;
89
+ /** The largest share of failures that the budget permits, from 0 to 1. */
90
+ readonly limit: number;
91
+ }
92
+ /** A budget on the largest value of a set of drills, for example the writes that a restore lost. One bad drill is `beyond`. */
93
+ interface WorstBudget {
94
+ readonly kind: "worst";
95
+ readonly measure: string;
96
+ readonly unit: string;
97
+ /** The largest value that the budget permits in any drill. */
98
+ readonly limit: number;
99
+ /** The number of drills that a verdict needs, 1 or more. With fewer, the budget is `cannot decide`. */
100
+ readonly minDrills: number;
101
+ }
102
+ /** One budget of a rung. */
103
+ type RungBudget = MedianBudget | RateBudget | WorstBudget;
104
+ /** One rung of a ladder. */
105
+ interface Rung {
106
+ /** The name of the rung, unique in the ladder. */
107
+ readonly name: string;
108
+ /** The size of the rung in each dimension that it names. */
109
+ readonly at: Readonly<Record<string, number>>;
110
+ readonly budgets: readonly RungBudget[];
111
+ /** The name of the automatic test that holds the rung. */
112
+ readonly test: string;
113
+ /** True when a gate holds the rung: the verdict fails when the rung is not `within`. */
114
+ readonly locked?: boolean;
115
+ }
116
+ /** The ladder of one subject. The rungs go from the smallest to the largest. */
117
+ interface Ladder {
118
+ readonly format: typeof LADDER_FORMAT;
119
+ /** The product or the service that the ladder is for. */
120
+ readonly subject: string;
121
+ readonly dimensions: readonly LadderDimension[];
122
+ readonly rungs: readonly Rung[];
123
+ }
124
+ /** The measured evidence for one budget. Its `kind` is the kind of the budget. */
125
+ type RungEvidence = {
126
+ readonly kind: "median";
127
+ readonly measure: string;
128
+ readonly rounds: readonly BudgetRound[];
129
+ readonly aa: readonly BudgetRound[];
130
+ } | {
131
+ readonly kind: "rate";
132
+ readonly measure: string;
133
+ readonly failures: number;
134
+ readonly trials: number;
135
+ } | {
136
+ readonly kind: "worst";
137
+ readonly measure: string;
138
+ readonly values: readonly number[];
139
+ };
140
+ /** One run of the test of a rung. */
141
+ interface RungRecord {
142
+ /** The name of the rung. */
143
+ readonly rung: string;
144
+ /** The commit that the run measured. */
145
+ readonly commit: string;
146
+ /** The time of the run, as an ISO 8601 string. */
147
+ readonly at: string;
148
+ /** What the run did for real, for example "2 processes" or "a restore from a real backup". */
149
+ readonly real: readonly string[];
150
+ /** What the run made up, for example "90 % of the tenants as data at rest". An empty list states that nothing was synthetic. */
151
+ readonly synthetic: readonly string[];
152
+ readonly evidence: readonly RungEvidence[];
153
+ }
154
+ /** The verdict of one budget of a rung. */
155
+ interface BudgetResult {
156
+ readonly measure: string;
157
+ readonly outcome: BudgetOutcome;
158
+ /** The reason of the outcome, in one sentence, with the numbers. */
159
+ readonly reason: string;
160
+ }
161
+ /** The verdict of one rung. */
162
+ interface RungVerdict {
163
+ readonly rung: string;
164
+ readonly test: string;
165
+ readonly locked: boolean;
166
+ readonly outcome: BudgetOutcome;
167
+ /** The reason of the outcome, in one sentence. */
168
+ readonly reason: string;
169
+ readonly budgets: readonly BudgetResult[];
170
+ /** The commit of the record, or undefined when the rung has no record. */
171
+ readonly commit: string | undefined;
172
+ readonly real: readonly string[];
173
+ readonly synthetic: readonly string[];
174
+ }
175
+ /** The verdict of a ladder. */
176
+ interface LadderVerdict {
177
+ readonly subject: string;
178
+ /** The highest rung with each rung up to it `within`, or undefined when the first rung is not `within`. */
179
+ readonly held: string | undefined;
180
+ /** The first rung that is not `within`, or undefined when each rung is `within`. */
181
+ readonly next: string | undefined;
182
+ /** True when a locked rung is not `within`. A rung with no result is not held, so `cannot decide` fails too. */
183
+ readonly failed: boolean;
184
+ /** The reason of `failed`, in one sentence. */
185
+ readonly reason: string;
186
+ readonly rungs: readonly RungVerdict[];
187
+ }
188
+ /** The options of a ladder verdict. */
189
+ interface LadderOptions {
190
+ /** The time of the verdict, as an ISO 8601 string. It is an input, so that a verdict is the same at each read. */
191
+ readonly now: string;
192
+ /** The largest age of a record, in days. An older record gives `cannot decide`. The default accepts any age. */
193
+ readonly maxAgeDays?: number;
194
+ }
195
+ /**
196
+ * This function gives each problem of a ladder as data, or an empty list. A ladder needs a subject, one dimension or more,
197
+ * and one rung or more. Names are unique. Each rung names a test and has budgets. From one rung to the next, each dimension
198
+ * that the two rungs name grows.
199
+ */
200
+ export declare function checkLadder(ladder: Ladder): readonly string[];
201
+ /** This function gives the verdict of one rung from its record. With no record, the rung is `cannot decide`. */
202
+ export declare function rungVerdict(rung: Rung, record: RungRecord | undefined, options: LadderOptions): RungVerdict;
203
+ /**
204
+ * This function gives the verdict of a ladder from the records of its rungs. It uses the newest record of each rung, so an
205
+ * older pass does not hide a newer failure. A rung high up does not count when a rung below it is not `within`. It throws
206
+ * a `RangeError` for a ladder with problems, because a verdict on a bad ladder has no meaning. See `checkLadder`.
207
+ */
208
+ export declare function ladderVerdict(ladder: Ladder, records: readonly RungRecord[], options: LadderOptions): LadderVerdict;
64
209
  /**
65
210
  * The two kinds of an A/A band.
66
211
  * - The band `case` holds the ratio of one A/A case. It is wide, so it finds only a large change.
@@ -464,5 +609,5 @@ interface Rounds {
464
609
  * keeps the work.
465
610
  */
466
611
  export declare function timeRounds(arms: Readonly<Record<Arm, () => number>>, options: RoundsOptions): Rounds;
467
- export { type Arm, type Balanced, type Band, type BandKind, type BandOptions, type BudgetOptions, type BudgetOutcome, type BudgetRound, type BudgetVerdict, type CaseBudget, type CaseInterval, type CaseLimit, type CaseRatio, type JobLimitVerdict, type JobRecords, type JobVerdict, type LimitOptions, type LimitOutcome, type LimitVerdict, type NoiseCheck, type PairedTime, type PoolFacts, type PoolOptions, type PooledJob, type PooledLimitVerdict, type PooledRecords, type PooledVerdict, type Power, type PowerOptions, type Rounds, type RoundsOptions, type RunPairs, type TimeOutcome, type TimeVerdict, type VerdictOptions, type VerdictRule, caseRatios, checkPairedTime, firstArm };
612
+ export { type Arm, type Balanced, type Band, type BandKind, type BandOptions, type BudgetOptions, type BudgetOutcome, type BudgetResult, type BudgetRound, type BudgetVerdict, type CaseBudget, type CaseInterval, type CaseLimit, type CaseRatio, type JobLimitVerdict, type JobRecords, type JobVerdict, type Ladder, type LadderDimension, type LadderOptions, type LadderVerdict, type LimitOptions, type LimitOutcome, type LimitVerdict, type MedianBudget, type NoiseCheck, type PairedTime, type PoolFacts, type PoolOptions, type PooledJob, type PooledLimitVerdict, type PooledRecords, type PooledVerdict, type Power, type PowerOptions, type RateBudget, type Rounds, type RoundsOptions, type RunPairs, type Rung, type RungBudget, type RungEvidence, type RungRecord, type RungVerdict, type TimeOutcome, type TimeVerdict, type VerdictOptions, type VerdictRule, type WorstBudget, caseRatios, checkPairedTime, firstArm };
468
613
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/budget.ts","../src/verdict.ts","../src/limit.ts","../src/merge.ts","../src/power.ts","../src/rounds.ts"],"mappings":";;;UAQiB;;WAEN;;WAEA;WACA;;;KAIC;;UAGK;;WAEN;;WAEA;;WAEA;;;UAIM;WACN;;WAEA;;WAEA;WACA;;WAEA;;WAEA;;WAEA;WACA;;WAEA;WACA,SAAS;;WAET;;;UAIM;WACN,SAAS;;WAET;WACA;WACA;WACA;WACA,gBAAgB;;;;;;;;;;;;wBAyDX,cAAc,iBAAiB,eAAe,aAAa,eAAe,SAAS,gBAAgB;;;;;;KCvGvG;;UAGK;;WAEN,OAAO;;WAEP;;WAEA;;WAEA,YAAY;;;UAIN;;WAEN;WACA,MAAM;;;;;;WAMN;;WAEA;WACA;;WAEA;;;wBAIK,OAAO,aAAa,cAAc,UAAS,cAAmB;;;;;wBAoB9D,SAAS,aAAa,cAAc;;UAWnC;;WAEN,kBAAkB;;WAElB;;WAEA;;;;;WAKA,OAAO,QAAQ;;;UAIT;;WAEN;;WAEA;;WAEA;WACA;;WAEA;;;;;;;;wBASK,WAAW,MAAM,aAAa,iBAAc;;KAmBhD;;;;;;;KAQA;;UAGK,uBAAuB;;WAE7B,OAAO;;;;;WAKP;;;;;;WAMA;;WAEA;;;;;WAKA,OAAO;;;UAID;;WAEN;WACA;;WAEA;;WAEA;;;;;WAKA;;;UAIM;WACN,SAAS;;WAET;WACA,gBAAgB;WAChB,MAAM;;;;;;;WAON,UAAU;;WAEV,MAAM;;;;;WAKN,YAAY;;WAEZ,OAAO;;WAEP,QAAQ;;;;;;;;;;;;;;wBAeH,YAAY,oBAAoB,cAAc,aAAa,cAAc,UAAS,iBAAsB;;;;;;;wBA+CxG,cAAc,UAAS;;UChQtB,qBAAqB;;WAE3B;WACA;;WAEA;;;;;;wBAOK,cAAc,kBAAkB,cAAc,iBAAe;;KAcjE;;UAGK,qBAAqB;;WAE3B;;WAEA;;;;;WAKA;;;UAIM,kBAAkB;;WAExB;;WAEA;WACA,SAAS;;;UAIH;WACN,SAAS;;WAET;WACA;WACA,gBAAgB;;WAEhB,MAAM;;WAEN;;WAEA;;;;;;;;;;wBAWK,aAAa,oBAAoB,cAAc,aAAa,cAAc,SAAS,eAAe;;UCzEjG,SAAS,UAAU;WACzB,SAAS;;WAET;;;;;;;wBAQK,cAAc,UAAU,YAAY,kBAAkB,MAAM,SAAS;;UAkBpE;;WAEN;WACA,oBAAoB;WACpB,aAAa;;WAEb;aAA8B;;;;UAIxB;WACN;WACA,WAAW;WACX,IAAI;;;UAIE;;WAEN,WAAW;;WAEX,IAAI;;WAEJ,eAAe;;WAEf;;WAEA;;;;;;;;wBASK,YAAY,eAAe,eAAe;;UAsEzC;WACN;WACA,SAAS;;;UAIH;;WAEN,SAAS;;WAET,QAAQ;;WAER,eAAe;;WAEf;;WAEA;;WAEA;;WAEA;;;;;;;;wBAWK,cAAc,eAAe,cAAc,UAAS,iBAAsB;;UASzE;WACN;WACA,SAAS;;;UAIH;;WAEN,SAAS;;WAET,QAAQ;;WAER,eAAe;;WAEf;;WAEA;;WAEA;;WAEA;;;;;;;;wBAWK,mBAAmB,eAAe,cAAc,SAAS,eAAe;;UASvE;WACN,OAAO,SAAS,wBAAwB;;;;;;wBAOnC,YAAY,KAAK,UAAU,gBAAgB,UAAS,iBAAsB,eAAe;;UC/NxF;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA,UAAU;;;UAIJ;WACN;WACA,UAAU,SAAS,OAAO;;;;;;WAM1B;;;;;;wBAOK,aAAa,SAAS,eAAe;;UClCpC;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;UAIM;WACN,SAAS;;WAET;;WAEA;;;;;;;wBAQK,WAAW,MAAM,SAAS,OAAO,qBAAqB,SAAS,gBAAgB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/budget.ts","../src/ladder.ts","../src/verdict.ts","../src/limit.ts","../src/merge.ts","../src/power.ts","../src/rounds.ts"],"mappings":"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"}