@cosyte/bench 0.0.0-stage → 0.1.2

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.
@@ -0,0 +1,554 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { performance } from 'perf_hooks';
3
+ import { pathToFileURL } from 'url';
4
+ import { GCProfiler, getHeapStatistics } from 'v8';
5
+
6
+ // src/child.ts
7
+
8
+ // src/contract.ts
9
+ var BENCH_CONTRACT = Object.freeze({
10
+ SAMPLES: 5,
11
+ SAMPLE_MIN_MS: 50,
12
+ ROUNDS: 12,
13
+ ALPHA: 0.05,
14
+ THRESHOLD: 0.03,
15
+ LATENCY_BUDGET_MS: 2e3,
16
+ LATENCY_MAX_CALLS: 5e3,
17
+ LATENCY_MIN_CALLS: 20,
18
+ LATENCY_TAIL_CALLS: 10,
19
+ LATENCY_TIMER_MULTIPLE: 20,
20
+ ALLOC_WINDOW_OPS: 256,
21
+ ALLOC_WINDOW_MS: 100,
22
+ ALLOC_SEMI_SPACE_MIB: 64,
23
+ PEAK_CALLS: 20,
24
+ LONG_RUN_ROUNDS: 30,
25
+ LONG_RUN_SETTLE_ROUNDS: 5,
26
+ LONG_RUN_ROUND_MS: 100,
27
+ LONG_RUN_GROWTH_BYTES: 64 * 1024,
28
+ GC_SETTLE_BYTES: 64 * 1024,
29
+ GC_SETTLE_ROUNDS: 2,
30
+ GC_MAX_ROUNDS: 7,
31
+ IMPORT_RUNS: 20,
32
+ LARGE_PASS_MS: 50,
33
+ LARGE_WARMUP_WINDOW: 5,
34
+ LARGE_WARMUP_MAX_MS: 3e4,
35
+ LONG_RUN_MAX_MS: 6e4,
36
+ REQUIRED_FALSE_ALARM_RATE: 0.01,
37
+ REQUIRED_CONFIDENCE: 0.95
38
+ });
39
+
40
+ // src/stats.ts
41
+ function sortedCopy(xs) {
42
+ return [...xs].sort((a, b) => a - b);
43
+ }
44
+ function median(xs) {
45
+ const s = sortedCopy(xs);
46
+ const mid = Math.floor(s.length / 2);
47
+ return s.length % 2 === 1 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
48
+ }
49
+ function nearestRank(xs, q) {
50
+ if (xs.length === 0) return Number.NaN;
51
+ const s = sortedCopy(xs);
52
+ const rank = Math.max(1, Math.ceil(q * s.length));
53
+ return s[rank - 1];
54
+ }
55
+ function theilSenSlope(xs, ys) {
56
+ const slopes = [];
57
+ xs.forEach((xi, i) => {
58
+ for (let j = i + 1; j < xs.length; j++) {
59
+ const dx = xs[j] - xi;
60
+ if (dx !== 0) slopes.push((ys[j] - ys[i]) / dx);
61
+ }
62
+ });
63
+ return median(slopes);
64
+ }
65
+ function round(n, places = 4) {
66
+ const f = 10 ** places;
67
+ return Math.round(n * f) / f;
68
+ }
69
+
70
+ // src/memory.ts
71
+ function realProbe() {
72
+ const gc = globalThis.gc;
73
+ if (typeof gc !== "function") return null;
74
+ return {
75
+ gc: () => {
76
+ gc();
77
+ },
78
+ usedHeap: () => getHeapStatistics().used_heap_size,
79
+ external: () => process.memoryUsage().external,
80
+ profile: (fn) => {
81
+ const profiler = new GCProfiler();
82
+ profiler.start();
83
+ fn();
84
+ const stats = profiler.stop().statistics;
85
+ const report = {
86
+ gcs: stats.length,
87
+ peakBeforeGc: 0,
88
+ peakExternalBeforeGc: 0,
89
+ peakTotalBeforeGc: 0,
90
+ reclaimedHeap: 0,
91
+ reclaimedExternal: 0
92
+ };
93
+ for (const s of stats) {
94
+ const before = s.beforeGC.heapStatistics;
95
+ const after = s.afterGC.heapStatistics;
96
+ report.peakBeforeGc = Math.max(report.peakBeforeGc, before.usedHeapSize);
97
+ report.peakExternalBeforeGc = Math.max(report.peakExternalBeforeGc, before.externalMemory);
98
+ report.peakTotalBeforeGc = Math.max(
99
+ report.peakTotalBeforeGc,
100
+ before.usedHeapSize + before.externalMemory
101
+ );
102
+ report.reclaimedHeap += before.usedHeapSize - after.usedHeapSize;
103
+ report.reclaimedExternal += before.externalMemory - after.externalMemory;
104
+ }
105
+ return report;
106
+ }
107
+ };
108
+ }
109
+ function settle(probe) {
110
+ let quiet = 0;
111
+ let before = probe.usedHeap();
112
+ for (let round2 = 1; round2 <= BENCH_CONTRACT.GC_MAX_ROUNDS; round2++) {
113
+ probe.gc();
114
+ const after = probe.usedHeap();
115
+ quiet = before - after < BENCH_CONTRACT.GC_SETTLE_BYTES ? quiet + 1 : 0;
116
+ before = after;
117
+ if (quiet >= BENCH_CONTRACT.GC_SETTLE_ROUNDS)
118
+ return { settled: true, rounds: round2, heap: after };
119
+ }
120
+ return { settled: false, rounds: BENCH_CONTRACT.GC_MAX_ROUNDS, heap: before };
121
+ }
122
+ function measureAllocations(call, probe, msPerOp2) {
123
+ const window = (ops) => {
124
+ const s = settle(probe);
125
+ const h0 = probe.usedHeap();
126
+ const e0 = probe.external();
127
+ const p = probe.profile(() => {
128
+ for (let i = 0; i < ops; i++) call(i);
129
+ });
130
+ return {
131
+ settled: s.settled,
132
+ gcs: p.gcs,
133
+ heap: probe.usedHeap() - h0 + p.reclaimedHeap,
134
+ ext: probe.external() - e0 + p.reclaimedExternal
135
+ };
136
+ };
137
+ const perOp = (method, n, a2, b2) => ({
138
+ status: "measured",
139
+ method,
140
+ collections: a2.gcs + b2.gcs,
141
+ windowOps: n,
142
+ heapBytesPerOp: round((b2.heap - a2.heap) / n, 1),
143
+ externalBytesPerOp: round((b2.ext - a2.ext) / n, 1)
144
+ });
145
+ const paced = Math.floor(BENCH_CONTRACT.ALLOC_WINDOW_MS / msPerOp2);
146
+ const first = Math.max(1, Math.min(BENCH_CONTRACT.ALLOC_WINDOW_OPS, paced));
147
+ let largest = null;
148
+ for (let n = first; n >= 1; n = Math.floor(n / 2)) {
149
+ const a2 = window(n);
150
+ const b2 = window(2 * n);
151
+ if (!a2.settled || !b2.settled) return { status: "skipped", reason: "gc-unsettled" };
152
+ if (a2.gcs === 0 && b2.gcs === 0) return perOp("no-gc-window", n, a2, b2);
153
+ largest ??= [a2, b2];
154
+ }
155
+ const [a, b] = largest;
156
+ return perOp("gc-accounted", first, a, b);
157
+ }
158
+ function measurePeak(call, probe) {
159
+ const heap = [];
160
+ const external = [];
161
+ const total = [];
162
+ for (let i = 0; i < BENCH_CONTRACT.PEAK_CALLS; i++) {
163
+ const s = settle(probe);
164
+ if (!s.settled) return { status: "skipped", reason: "gc-unsettled" };
165
+ const e0 = probe.external();
166
+ const p = probe.profile(() => {
167
+ call(i);
168
+ });
169
+ const h1 = probe.usedHeap();
170
+ const e1 = probe.external();
171
+ heap.push(Math.max(p.peakBeforeGc, h1) - s.heap);
172
+ external.push(Math.max(p.peakExternalBeforeGc, e1) - e0);
173
+ total.push(Math.max(p.peakTotalBeforeGc, h1 + e1) - (s.heap + e0));
174
+ }
175
+ return {
176
+ status: "measured",
177
+ calls: heap.length,
178
+ peakHeapBytes: Math.max(...heap),
179
+ medianPeakHeapBytes: median(heap),
180
+ peakExternalBytes: Math.max(...external),
181
+ medianPeakExternalBytes: median(external),
182
+ peakTotalBytes: Math.max(...total),
183
+ medianPeakTotalBytes: median(total)
184
+ };
185
+ }
186
+ function measureLongRun(pass, opsPerPass, probe, clock) {
187
+ const t0 = clock();
188
+ let passesPerRound = 0;
189
+ do {
190
+ pass();
191
+ passesPerRound++;
192
+ } while (clock() - t0 < BENCH_CONTRACT.LONG_RUN_ROUND_MS);
193
+ const opsPerRound = passesPerRound * opsPerPass;
194
+ const rounds = [];
195
+ let ops = opsPerRound;
196
+ for (; ; ) {
197
+ const s = settle(probe);
198
+ if (!s.settled) return { status: "skipped", reason: "gc-unsettled" };
199
+ rounds.push({ ops, heap: s.heap });
200
+ if (rounds.length === BENCH_CONTRACT.LONG_RUN_ROUNDS) break;
201
+ for (let p = 0; p < passesPerRound; p++) pass();
202
+ ops += opsPerRound;
203
+ }
204
+ const skip = BENCH_CONTRACT.LONG_RUN_SETTLE_ROUNDS;
205
+ const fitted = rounds.slice(skip);
206
+ const slope = theilSenSlope(
207
+ fitted.map((r) => r.ops),
208
+ fitted.map((r) => r.heap)
209
+ );
210
+ const span = (BENCH_CONTRACT.LONG_RUN_ROUNDS - 1 - skip) * opsPerRound;
211
+ const growth = slope * span;
212
+ return {
213
+ status: "measured",
214
+ // Stryker disable next-line EqualityOperator: growth is a fitted slope times 24 rounds, so exactly 65,536 bytes is not reached in floating point (tried in memory.test.ts history).
215
+ verdict: growth >= BENCH_CONTRACT.LONG_RUN_GROWTH_BYTES ? "growing" : "flat",
216
+ ops: BENCH_CONTRACT.LONG_RUN_ROUNDS * opsPerRound,
217
+ opsPerRound,
218
+ settledHeap: rounds.map((r) => r.heap),
219
+ slopeBytesPerOp: round(slope, 4),
220
+ growthBytes: Math.round(growth),
221
+ detectableBytesPerOp: round(BENCH_CONTRACT.LONG_RUN_GROWTH_BYTES / span, 4)
222
+ };
223
+ }
224
+
225
+ // ../test-utils/src/perf/contract.ts
226
+ var PERF_CONTRACT = Object.freeze({
227
+ RATIO_CEILING: 8,
228
+ RATIO_FLOOR: 1.5,
229
+ REPS: 5,
230
+ SCALE_STEP: 4,
231
+ MIN_PHASE_MS: 4,
232
+ WARMUP_MIN_MS: 500,
233
+ WARMUP_BATCH_MS: 50,
234
+ WARMUP_MAX_MS: 5e3,
235
+ WARMUP_STABLE_TOL: 0.05,
236
+ WARMUP_STABLE_BATCHES: 3
237
+ });
238
+
239
+ // src/measure.ts
240
+ var WARMUP = Object.freeze({
241
+ MIN_MS: PERF_CONTRACT.WARMUP_MIN_MS,
242
+ BATCH_MS: PERF_CONTRACT.WARMUP_BATCH_MS,
243
+ MAX_MS: PERF_CONTRACT.WARMUP_MAX_MS,
244
+ STABLE_TOL: PERF_CONTRACT.WARMUP_STABLE_TOL,
245
+ STABLE_BATCHES: PERF_CONTRACT.WARMUP_STABLE_BATCHES
246
+ });
247
+ function batchesAreStable(batches) {
248
+ if (batches.length < WARMUP.STABLE_BATCHES) return false;
249
+ const window = batches.slice(-WARMUP.STABLE_BATCHES);
250
+ const mid = median(window);
251
+ return window.every((b) => Math.abs(b - mid) / mid <= WARMUP.STABLE_TOL);
252
+ }
253
+ function isLargeCase(batches) {
254
+ if (batches.length < WARMUP.STABLE_BATCHES) return false;
255
+ return batches.slice(-WARMUP.STABLE_BATCHES).every((b) => b >= BENCH_CONTRACT.LARGE_PASS_MS);
256
+ }
257
+ function windowsAreStable(passes) {
258
+ const w = BENCH_CONTRACT.LARGE_WARMUP_WINDOW;
259
+ if (passes.length < 2 * w) return false;
260
+ const last = median(passes.slice(-w));
261
+ const before = median(passes.slice(-2 * w, -w));
262
+ return Math.abs(last - before) / before <= WARMUP.STABLE_TOL;
263
+ }
264
+ function warmUp(pass, clock) {
265
+ const start = clock();
266
+ const batches = [];
267
+ let passes = 0;
268
+ for (; ; ) {
269
+ const b0 = clock();
270
+ let inBatch = 0;
271
+ let batchMs;
272
+ do {
273
+ pass();
274
+ inBatch++;
275
+ batchMs = clock() - b0;
276
+ } while (batchMs < WARMUP.BATCH_MS);
277
+ passes += inBatch;
278
+ batches.push(batchMs / inBatch);
279
+ const elapsedMs = clock() - start;
280
+ if (elapsedMs >= WARMUP.MIN_MS) {
281
+ if (batchesAreStable(batches)) {
282
+ return { stable: true, elapsedMs, passes, batches, plan: "standard" };
283
+ }
284
+ if (isLargeCase(batches)) return warmUpLarge(pass, clock, start, batches, passes);
285
+ }
286
+ if (elapsedMs >= WARMUP.MAX_MS) {
287
+ return { stable: false, elapsedMs, passes, batches, plan: "standard" };
288
+ }
289
+ }
290
+ }
291
+ function warmUpLarge(pass, clock, start, batches, passesSoFar) {
292
+ let passes = passesSoFar;
293
+ for (; ; ) {
294
+ const p0 = clock();
295
+ pass();
296
+ const t = clock();
297
+ passes++;
298
+ batches.push(t - p0);
299
+ const elapsedMs = t - start;
300
+ if (windowsAreStable(batches))
301
+ return { stable: true, elapsedMs, passes, batches, plan: "large" };
302
+ if (elapsedMs >= BENCH_CONTRACT.LARGE_WARMUP_MAX_MS) {
303
+ return { stable: false, elapsedMs, passes, batches, plan: "large" };
304
+ }
305
+ }
306
+ }
307
+ function sampleThroughput(pass, opsPerPass, clock, inject = 0) {
308
+ const samples = [];
309
+ for (let s = 0; s < BENCH_CONTRACT.SAMPLES; s++) {
310
+ const t0 = clock();
311
+ let t = t0;
312
+ let passes = 0;
313
+ do {
314
+ const p0 = t;
315
+ pass();
316
+ t = clock();
317
+ const until = t + inject * (t - p0);
318
+ while (t < until) t = clock();
319
+ passes++;
320
+ } while (t - t0 < BENCH_CONTRACT.SAMPLE_MIN_MS);
321
+ samples.push((t - t0) / (passes * opsPerPass));
322
+ }
323
+ return samples;
324
+ }
325
+ function timerOverheadNs(ns, reads = 1e3) {
326
+ const gaps = [];
327
+ for (let i = 0; i < reads; i++) {
328
+ const a = ns();
329
+ const b = ns();
330
+ gaps.push(Number(b - a));
331
+ }
332
+ return median(gaps);
333
+ }
334
+ function tailPercentile(times, q) {
335
+ return times.length * (1 - q) >= BENCH_CONTRACT.LATENCY_TAIL_CALLS ? nearestRank(times, q) : null;
336
+ }
337
+ function measureLatency(call, ns) {
338
+ const overhead = timerOverheadNs(ns);
339
+ const budgetNs = BigInt(BENCH_CONTRACT.LATENCY_BUDGET_MS) * 1000000n;
340
+ const times = [];
341
+ const start = ns();
342
+ let now = start;
343
+ while (times.length < BENCH_CONTRACT.LATENCY_MAX_CALLS && (times.length < BENCH_CONTRACT.LATENCY_MIN_CALLS || now - start < budgetNs)) {
344
+ const a = ns();
345
+ call(times.length);
346
+ now = ns();
347
+ times.push(Number(now - a));
348
+ }
349
+ const p50 = nearestRank(times, 0.5);
350
+ if (p50 < BENCH_CONTRACT.LATENCY_TIMER_MULTIPLE * overhead) {
351
+ return {
352
+ status: "skipped",
353
+ reason: "latency-below-timer-resolution",
354
+ calls: times.length,
355
+ timerOverheadNs: round(overhead, 1),
356
+ p50Ns: p50
357
+ };
358
+ }
359
+ return {
360
+ status: "measured",
361
+ calls: times.length,
362
+ timerOverheadNs: round(overhead, 1),
363
+ p50Ns: tailPercentile(times, 0.5),
364
+ p95Ns: tailPercentile(times, 0.95),
365
+ p99Ns: tailPercentile(times, 0.99),
366
+ maxNs: Math.max(...times)
367
+ };
368
+ }
369
+
370
+ // src/suite.ts
371
+ function fail(message) {
372
+ throw new TypeError(`@cosyte/bench: ${message}`);
373
+ }
374
+ function isRecord(value) {
375
+ return typeof value === "object" && value !== null;
376
+ }
377
+ function checkName(value, what) {
378
+ if (typeof value !== "string" || value.trim() === "") fail(`${what} must be a non-empty string`);
379
+ return value;
380
+ }
381
+ function checkSuite(value) {
382
+ if (!isRecord(value)) fail("a suite must be an object made by defineSuite");
383
+ const name = checkName(value["name"], "the suite name");
384
+ const benchmarks = value["benchmarks"];
385
+ if (!Array.isArray(benchmarks) || benchmarks.length === 0) {
386
+ fail(`suite "${name}" must have at least one benchmark`);
387
+ }
388
+ const seen = /* @__PURE__ */ new Set();
389
+ for (const b of benchmarks) {
390
+ if (!isRecord(b)) fail(`suite "${name}" holds a benchmark that is not an object`);
391
+ const bName = checkName(b["name"], `a benchmark name in suite "${name}"`);
392
+ if (seen.has(bName)) fail(`suite "${name}" has two benchmarks named "${bName}"`);
393
+ seen.add(bName);
394
+ if (typeof b["bind"] !== "function") fail(`benchmark "${bName}" has no bind function`);
395
+ if (b["weigh"] !== void 0 && typeof b["weigh"] !== "function") {
396
+ fail(`benchmark "${bName}" has a weigh that is not a function`);
397
+ }
398
+ const sizes = b["sizes"];
399
+ if (!Array.isArray(sizes) || sizes.length === 0) fail(`benchmark "${bName}" has no sizes`);
400
+ const labels = /* @__PURE__ */ new Set();
401
+ for (const s of sizes) {
402
+ if (!isRecord(s)) fail(`benchmark "${bName}" holds a size that is not an object`);
403
+ const label = checkName(s["label"], `a size label in benchmark "${bName}"`);
404
+ if (labels.has(label)) fail(`benchmark "${bName}" has two sizes labelled "${label}"`);
405
+ labels.add(label);
406
+ if (typeof s["build"] !== "function")
407
+ fail(`size "${label}" of "${bName}" has no build function`);
408
+ if (s["compare"] !== void 0 && typeof s["compare"] !== "boolean") {
409
+ fail(`size "${label}" of "${bName}" has a compare that is not true or false`);
410
+ }
411
+ }
412
+ if (b["longRun"] !== void 0 && !labels.has(b["longRun"])) {
413
+ fail(`benchmark "${bName}" names a longRun size it does not have`);
414
+ }
415
+ }
416
+ return Object.freeze(value);
417
+ }
418
+ function caseId(c) {
419
+ return `${c.benchmark} @ ${c.size}`;
420
+ }
421
+ function findCase(suite, c) {
422
+ const benchmark = suite.benchmarks.find((b) => b.name === c.benchmark);
423
+ const size = benchmark?.sizes.find((s) => s.label === c.size);
424
+ if (size === void 0) fail(`suite "${suite.name}" has no case "${caseId(c)}"`);
425
+ return { benchmark, size };
426
+ }
427
+
428
+ // src/child.ts
429
+ var realDeps = () => ({
430
+ clock: () => performance.now(),
431
+ ns: () => process.hrtime.bigint(),
432
+ probe: realProbe()
433
+ });
434
+ var defaultWeigh = (result) => result === void 0 || result === null ? 0 : 1;
435
+ var SKIPPED_NO_GC = { status: "skipped", reason: "gc-unavailable" };
436
+ function guard(c, phase, fn) {
437
+ try {
438
+ return fn();
439
+ } catch (error) {
440
+ const kind = error instanceof Error ? error.name : typeof error;
441
+ throw new Error(
442
+ `@cosyte/bench: case "${c.benchmark} @ ${c.size}": the suite's ${phase} threw (${kind}). Its message is not printed, because it may quote an input; run the case directly to read it.`,
443
+ { cause: error }
444
+ );
445
+ }
446
+ }
447
+ function msPerOp(warmup, shape) {
448
+ return (warmup.batches[warmup.batches.length - 1] ?? 0) / shape.inputsPerPass;
449
+ }
450
+ function measureJob(job, shape, warmup, pass, call, deps) {
451
+ const base = { case: job.case, ...shape };
452
+ if (job.mode === "timing") {
453
+ if (!warmup.stable) {
454
+ return { mode: "timing", ...base, warmup, status: "skipped", reason: "warmup-unstable" };
455
+ }
456
+ const samples = sampleThroughput(pass, shape.inputsPerPass, deps.clock, job.inject);
457
+ return {
458
+ mode: "timing",
459
+ ...base,
460
+ warmup,
461
+ status: "measured",
462
+ samplesMsPerOp: samples,
463
+ medianMsPerOp: median(samples),
464
+ latency: job.latency ? measureLatency(call, deps.ns) : null
465
+ };
466
+ }
467
+ const probe = deps.probe;
468
+ if (job.mode === "alloc") {
469
+ return {
470
+ mode: "alloc",
471
+ ...base,
472
+ allocations: probe === null ? SKIPPED_NO_GC : measureAllocations(call, probe, msPerOp(warmup, shape)),
473
+ peak: null,
474
+ longRun: null
475
+ };
476
+ }
477
+ let longRun = null;
478
+ if (job.longRun) {
479
+ longRun = probe === null ? SKIPPED_NO_GC : measureLongRun(pass, shape.inputsPerPass, probe, deps.clock);
480
+ }
481
+ return {
482
+ mode: "heap",
483
+ ...base,
484
+ allocations: null,
485
+ peak: probe === null ? SKIPPED_NO_GC : measurePeak(call, probe),
486
+ longRun
487
+ };
488
+ }
489
+ function measuredSomething(result) {
490
+ if (result.mode === "timing") return result.status === "measured";
491
+ return [result.allocations, result.peak, result.longRun].some((r) => r?.status === "measured");
492
+ }
493
+ async function runJob(job, deps = realDeps()) {
494
+ const suiteModule = await import(pathToFileURL(job.suite).href);
495
+ const suite = checkSuite(suiteModule.default);
496
+ const target = await import(pathToFileURL(job.target).href);
497
+ const { benchmark, size } = findCase(suite, job.case);
498
+ const corpus = guard(job.case, "build", () => size.build());
499
+ try {
500
+ const inputs = corpus.inputs;
501
+ if (!Array.isArray(inputs) || inputs.length === 0) {
502
+ throw new Error(
503
+ `@cosyte/bench: case "${job.case.benchmark} @ ${job.case.size}" built an empty corpus`
504
+ );
505
+ }
506
+ const fn = guard(job.case, "bind", () => benchmark.bind(target));
507
+ const weigh = benchmark.weigh ?? defaultWeigh;
508
+ const sink = { value: 0 };
509
+ const pass = () => {
510
+ let local = 0;
511
+ for (const input of inputs) local += weigh(fn(input));
512
+ sink.value += local;
513
+ };
514
+ const call = (i) => {
515
+ sink.value += weigh(fn(inputs[i % inputs.length]));
516
+ };
517
+ const shape = {
518
+ inputsPerPass: inputs.length,
519
+ bytesPerPass: typeof corpus.bytes === "number" ? corpus.bytes : null
520
+ };
521
+ const warmup = guard(job.case, "measured function", () => warmUp(pass, deps.clock));
522
+ const before = sink.value;
523
+ const result = guard(
524
+ job.case,
525
+ "measured function",
526
+ () => measureJob(job, shape, warmup, pass, call, deps)
527
+ );
528
+ if (!(sink.value > before) && measuredSomething(result)) {
529
+ throw new Error(
530
+ `@cosyte/bench: case "${job.case.benchmark} @ ${job.case.size}": the sink did not grow, so the measured loop may have been deleted by the compiler, or \`weigh\` gave nothing positive. Supply a \`weigh\` that returns a positive number for a result.`
531
+ );
532
+ }
533
+ return result;
534
+ } finally {
535
+ corpus.teardown?.();
536
+ }
537
+ }
538
+ async function childMain(jobFile, outFile, deps) {
539
+ try {
540
+ const job = JSON.parse(readFileSync(jobFile, "utf8"));
541
+ const result = await runJob(job, deps);
542
+ writeFileSync(outFile, JSON.stringify({ ok: true, result }));
543
+ return 0;
544
+ } catch (error) {
545
+ const message = error instanceof Error ? error.message : String(error);
546
+ writeFileSync(outFile, JSON.stringify({ ok: false, error: message }));
547
+ return 2;
548
+ }
549
+ }
550
+
551
+ // src/child-entry.ts
552
+ process.exitCode = await childMain(process.argv[2] ?? "", process.argv[3] ?? "");
553
+ //# sourceMappingURL=child-entry.mjs.map
554
+ //# sourceMappingURL=child-entry.mjs.map