@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.
- package/CHANGELOG.md +52 -0
- package/README.md +59 -17
- package/dist/child-entry.mjs +108 -26
- package/dist/child-entry.mjs.map +1 -1
- package/dist/cli.mjs +233 -63
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.ts +195 -9
- package/dist/index.mjs +63 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/schema/results-v1.schema.json +122 -33
package/dist/cli.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import 'perf_hooks';
|
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
8
|
import { gzipSync } from 'zlib';
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
|
+
import 'v8';
|
|
10
11
|
|
|
11
12
|
function readCgroupCpuMax(path = "/sys/fs/cgroup/cpu.max") {
|
|
12
13
|
try {
|
|
@@ -59,6 +60,7 @@ var BENCH_CONTRACT = Object.freeze({
|
|
|
59
60
|
ALLOC_WINDOW_MS: 100,
|
|
60
61
|
ALLOC_SEMI_SPACE_MIB: 64,
|
|
61
62
|
PEAK_CALLS: 20,
|
|
63
|
+
FLOOR_INPUT_BYTES: 64 * 1024,
|
|
62
64
|
LONG_RUN_ROUNDS: 30,
|
|
63
65
|
LONG_RUN_SETTLE_ROUNDS: 5,
|
|
64
66
|
LONG_RUN_ROUND_MS: 100,
|
|
@@ -67,6 +69,10 @@ var BENCH_CONTRACT = Object.freeze({
|
|
|
67
69
|
GC_SETTLE_ROUNDS: 2,
|
|
68
70
|
GC_MAX_ROUNDS: 7,
|
|
69
71
|
IMPORT_RUNS: 20,
|
|
72
|
+
LARGE_PASS_MS: 50,
|
|
73
|
+
LARGE_WARMUP_WINDOW: 5,
|
|
74
|
+
LARGE_WARMUP_MAX_MS: 3e4,
|
|
75
|
+
LONG_RUN_MAX_MS: 6e4,
|
|
70
76
|
REQUIRED_FALSE_ALARM_RATE: 0.01,
|
|
71
77
|
REQUIRED_CONFIDENCE: 0.95
|
|
72
78
|
});
|
|
@@ -110,11 +116,13 @@ function runNode(entry, args, execArgv, timeoutMs, execPath = process.execPath)
|
|
|
110
116
|
}
|
|
111
117
|
function flagsFor(job) {
|
|
112
118
|
if (job.mode === "timing") return [];
|
|
113
|
-
|
|
119
|
+
const memory = ["--expose-gc", "--no-concurrent-array-buffer-sweeping"];
|
|
120
|
+
if (job.mode === "heap" || job.mode === "floor") return memory;
|
|
114
121
|
const mib = String(BENCH_CONTRACT.ALLOC_SEMI_SPACE_MIB);
|
|
115
|
-
return [
|
|
122
|
+
return [...memory, `--min-semi-space-size=${mib}`, `--max-semi-space-size=${mib}`];
|
|
116
123
|
}
|
|
117
124
|
function describe(job) {
|
|
125
|
+
if (job.mode === "floor") return "floor job";
|
|
118
126
|
return `${job.mode} job for "${job.case.benchmark} @ ${job.case.size}"`;
|
|
119
127
|
}
|
|
120
128
|
async function runInChild(job, options = {}) {
|
|
@@ -380,11 +388,18 @@ async function compareBuilds(options) {
|
|
|
380
388
|
s.skip = `${side}: ${result.reason}`;
|
|
381
389
|
break;
|
|
382
390
|
}
|
|
383
|
-
got.set(side, result.samplesMsPerOp);
|
|
391
|
+
got.set(side, { samples: result.samplesMsPerOp, plan: result.warmup.plan });
|
|
384
392
|
}
|
|
385
393
|
const base = got.get("base");
|
|
386
394
|
const head = got.get("head");
|
|
387
|
-
if (base !== void 0 && head !== void 0)
|
|
395
|
+
if (base !== void 0 && head !== void 0) {
|
|
396
|
+
s.rounds.push({
|
|
397
|
+
base: base.samples,
|
|
398
|
+
head: head.samples,
|
|
399
|
+
basePlan: base.plan,
|
|
400
|
+
headPlan: head.plan
|
|
401
|
+
});
|
|
402
|
+
}
|
|
388
403
|
}
|
|
389
404
|
options.onProgress?.(`round ${String(r + 1)} of ${String(BENCH_CONTRACT.ROUNDS)} done`);
|
|
390
405
|
}
|
|
@@ -398,7 +413,9 @@ async function compareBuilds(options) {
|
|
|
398
413
|
baseMsPerOp: rounds.map((p) => Math.min(...p.base)),
|
|
399
414
|
headMsPerOp: rounds.map((p) => Math.min(...p.head)),
|
|
400
415
|
baseSamples: rounds.map((p) => p.base),
|
|
401
|
-
headSamples: rounds.map((p) => p.head)
|
|
416
|
+
headSamples: rounds.map((p) => p.head),
|
|
417
|
+
basePlans: rounds.map((p) => p.basePlan),
|
|
418
|
+
headPlans: rounds.map((p) => p.headPlan)
|
|
402
419
|
};
|
|
403
420
|
if (skip !== null) {
|
|
404
421
|
return {
|
|
@@ -540,18 +557,55 @@ async function calibrateComparison(options) {
|
|
|
540
557
|
}
|
|
541
558
|
return summarizeCalibration(options.inject ?? 0, runs);
|
|
542
559
|
}
|
|
560
|
+
function aboveFloor(peak, floor) {
|
|
561
|
+
if (peak.status !== "measured" || floor === null || floor.status !== "measured") return peak;
|
|
562
|
+
const less = (value, base) => value === void 0 || base === void 0 ? void 0 : Math.max(0, value - base);
|
|
563
|
+
const out = {
|
|
564
|
+
...peak,
|
|
565
|
+
peakHeapAboveFloorBytes: less(peak.peakHeapBytes, floor.medianPeakHeapBytes),
|
|
566
|
+
medianPeakHeapAboveFloorBytes: less(peak.medianPeakHeapBytes, floor.medianPeakHeapBytes),
|
|
567
|
+
peakExternalAboveFloorBytes: less(peak.peakExternalBytes, floor.medianPeakExternalBytes),
|
|
568
|
+
medianPeakExternalAboveFloorBytes: less(
|
|
569
|
+
peak.medianPeakExternalBytes,
|
|
570
|
+
floor.medianPeakExternalBytes
|
|
571
|
+
),
|
|
572
|
+
peakTotalAboveFloorBytes: less(peak.peakTotalBytes, floor.medianPeakTotalBytes),
|
|
573
|
+
medianPeakTotalAboveFloorBytes: less(peak.medianPeakTotalBytes, floor.medianPeakTotalBytes)
|
|
574
|
+
};
|
|
575
|
+
return Object.fromEntries(
|
|
576
|
+
Object.entries(out).filter(([, v]) => v !== void 0)
|
|
577
|
+
);
|
|
578
|
+
}
|
|
543
579
|
|
|
544
580
|
// src/reading.ts
|
|
581
|
+
var WrongKind = class extends Error {
|
|
582
|
+
};
|
|
545
583
|
function expectTiming(result) {
|
|
546
584
|
if (result.mode !== "timing")
|
|
547
|
-
throw new
|
|
585
|
+
throw new WrongKind("@cosyte/bench: a timing job returned another kind");
|
|
548
586
|
return result;
|
|
549
587
|
}
|
|
550
588
|
function expectMemory(result) {
|
|
551
|
-
if (result.mode === "timing")
|
|
552
|
-
throw new
|
|
589
|
+
if (result.mode === "timing" || result.mode === "floor")
|
|
590
|
+
throw new WrongKind("@cosyte/bench: a memory job returned another kind");
|
|
553
591
|
return result;
|
|
554
592
|
}
|
|
593
|
+
function expectFloor(result) {
|
|
594
|
+
if (result.mode !== "floor")
|
|
595
|
+
throw new WrongKind("@cosyte/bench: a floor job returned another kind");
|
|
596
|
+
return result.peak;
|
|
597
|
+
}
|
|
598
|
+
function firstLine(error) {
|
|
599
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
600
|
+
return text.split("\n")[0];
|
|
601
|
+
}
|
|
602
|
+
function budgetOf(c, p) {
|
|
603
|
+
const raw = p.peakTotalBytes ?? p.peakHeapBytes;
|
|
604
|
+
if (c.bytesPerPass === null) return raw;
|
|
605
|
+
const perInput = c.bytesPerPass / c.inputsPerPass;
|
|
606
|
+
if (perInput >= BENCH_CONTRACT.FLOOR_INPUT_BYTES) return raw;
|
|
607
|
+
return p.medianPeakTotalAboveFloorBytes ?? raw;
|
|
608
|
+
}
|
|
555
609
|
function summarizeThroughput(first, rest) {
|
|
556
610
|
const measured = [];
|
|
557
611
|
for (const t of [first, ...rest]) {
|
|
@@ -560,7 +614,8 @@ function summarizeThroughput(first, rest) {
|
|
|
560
614
|
}
|
|
561
615
|
const per = measured.map((t) => ({
|
|
562
616
|
samplesMsPerOp: t.samplesMsPerOp,
|
|
563
|
-
medianMsPerOp: t.medianMsPerOp
|
|
617
|
+
medianMsPerOp: t.medianMsPerOp,
|
|
618
|
+
plan: t.warmup.plan
|
|
564
619
|
}));
|
|
565
620
|
const mid = median(per.map((x) => x.medianMsPerOp));
|
|
566
621
|
const perOpBytes = first.bytesPerPass === null ? null : first.bytesPerPass / first.inputsPerPass;
|
|
@@ -572,48 +627,102 @@ function summarizeThroughput(first, rest) {
|
|
|
572
627
|
bytesPerSecond: perOpBytes === null ? null : round(perOpBytes * (1e3 / mid), 0)
|
|
573
628
|
};
|
|
574
629
|
}
|
|
630
|
+
function longRunFits(throughput, inputsPerPass) {
|
|
631
|
+
if (throughput.status !== "measured") return false;
|
|
632
|
+
const roundMs = Math.max(
|
|
633
|
+
BENCH_CONTRACT.LONG_RUN_ROUND_MS,
|
|
634
|
+
throughput.medianMsPerOp * inputsPerPass
|
|
635
|
+
);
|
|
636
|
+
return BENCH_CONTRACT.LONG_RUN_ROUNDS * roundMs <= BENCH_CONTRACT.LONG_RUN_MAX_MS;
|
|
637
|
+
}
|
|
575
638
|
async function readBuild(options) {
|
|
576
639
|
const runJob = options.runJob;
|
|
577
640
|
const processes = options.processes ?? 3;
|
|
578
641
|
if (!Number.isInteger(processes) || processes < 1) {
|
|
579
642
|
throw new TypeError("@cosyte/bench: processes must be a positive integer");
|
|
580
643
|
}
|
|
644
|
+
const memory = options.memory ?? true;
|
|
581
645
|
const base = { suite: options.suiteFile, target: options.target };
|
|
646
|
+
const listed = listCases(options.suite, options.only);
|
|
582
647
|
const cases = [];
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
648
|
+
const failures = [];
|
|
649
|
+
const fail2 = (phase, c, error) => {
|
|
650
|
+
if (error instanceof WrongKind) throw error;
|
|
651
|
+
const message = firstLine(error);
|
|
652
|
+
failures.push({ phase, benchmark: c?.benchmark ?? null, size: c?.size ?? null, message });
|
|
653
|
+
const what = c === null ? "the memory floor" : `${phase} of ${c.benchmark} @ ${c.size}`;
|
|
654
|
+
options.onProgress?.(
|
|
655
|
+
`could not measure ${what}: ${error instanceof Error ? error.message : String(error)}`
|
|
656
|
+
);
|
|
657
|
+
};
|
|
658
|
+
for (const name of new Set(listed.map((c) => c.benchmark))) {
|
|
659
|
+
const timed = [];
|
|
660
|
+
for (const c of listed.filter((l) => l.benchmark === name)) {
|
|
661
|
+
const timing = async (latency) => expectTiming(await runJob({ mode: "timing", ...base, case: c, latency, inject: 0 }));
|
|
662
|
+
try {
|
|
663
|
+
const first = await timing(true);
|
|
664
|
+
const rest = [];
|
|
665
|
+
for (let p = 1; p < processes; p++) rest.push(await timing(false));
|
|
666
|
+
timed.push({ c, first, throughput: summarizeThroughput(first, rest) });
|
|
667
|
+
} catch (error) {
|
|
668
|
+
fail2("timing", c, error);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const largest = timed.filter((t) => longRunFits(t.throughput, t.first.inputsPerPass)).pop();
|
|
672
|
+
for (const { c, first, throughput } of timed) {
|
|
673
|
+
let allocations = null;
|
|
674
|
+
let peak = null;
|
|
675
|
+
let longRun = null;
|
|
676
|
+
if (memory) {
|
|
677
|
+
try {
|
|
678
|
+
allocations = expectMemory(await runJob({ mode: "alloc", ...base, case: c })).allocations;
|
|
679
|
+
const { benchmark } = findCase(options.suite, c);
|
|
680
|
+
const longRunSize = benchmark.longRun ?? benchmark.sizes[0].label;
|
|
681
|
+
const heap = expectMemory(
|
|
682
|
+
await runJob({
|
|
683
|
+
mode: "heap",
|
|
684
|
+
...base,
|
|
685
|
+
case: c,
|
|
686
|
+
longRun: c.size === longRunSize || c.size === largest?.c.size
|
|
687
|
+
})
|
|
688
|
+
);
|
|
689
|
+
peak = heap.peak;
|
|
690
|
+
longRun = heap.longRun;
|
|
691
|
+
} catch (error) {
|
|
692
|
+
fail2("memory", c, error);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
cases.push({
|
|
696
|
+
...c,
|
|
697
|
+
inputsPerPass: first.inputsPerPass,
|
|
698
|
+
bytesPerPass: first.bytesPerPass,
|
|
699
|
+
throughput,
|
|
700
|
+
latency: first.status === "measured" ? first.latency : null,
|
|
701
|
+
allocations,
|
|
702
|
+
peak,
|
|
703
|
+
longRun
|
|
704
|
+
});
|
|
705
|
+
options.onProgress?.(`read ${c.benchmark} @ ${c.size}`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
let memoryFloor = null;
|
|
709
|
+
if (memory) {
|
|
710
|
+
try {
|
|
711
|
+
memoryFloor = expectFloor(await runJob({ mode: "floor" }));
|
|
712
|
+
} catch (error) {
|
|
713
|
+
fail2("floor", null, error);
|
|
602
714
|
}
|
|
603
|
-
cases.push({
|
|
604
|
-
...c,
|
|
605
|
-
inputsPerPass: first.inputsPerPass,
|
|
606
|
-
bytesPerPass: first.bytesPerPass,
|
|
607
|
-
throughput,
|
|
608
|
-
latency,
|
|
609
|
-
allocations,
|
|
610
|
-
peak,
|
|
611
|
-
longRun
|
|
612
|
-
});
|
|
613
|
-
options.onProgress?.(`read ${c.benchmark} @ ${c.size}`);
|
|
614
715
|
}
|
|
615
716
|
const importCost = options.importCost ?? true ? await options.measureImport(options.target) : null;
|
|
616
|
-
return {
|
|
717
|
+
return {
|
|
718
|
+
processes,
|
|
719
|
+
cases: cases.map(
|
|
720
|
+
(c) => c.peak === null ? c : { ...c, peak: aboveFloor(c.peak, memoryFloor) }
|
|
721
|
+
),
|
|
722
|
+
memoryFloor,
|
|
723
|
+
failures,
|
|
724
|
+
importCost
|
|
725
|
+
};
|
|
617
726
|
}
|
|
618
727
|
|
|
619
728
|
// src/report.ts
|
|
@@ -698,26 +807,54 @@ function renderMarkdown(doc) {
|
|
|
698
807
|
const r = doc.reading;
|
|
699
808
|
lines.push(...header(doc, "Reading"));
|
|
700
809
|
lines.push(
|
|
701
|
-
`Throughput is the median of ${String(r.processes)} fresh processes. Latency is per call, nearest rank. Memory: allocations per operation, the peak
|
|
702
|
-
""
|
|
703
|
-
|
|
704
|
-
|
|
810
|
+
`Throughput is the median of ${String(r.processes)} fresh processes. "Large-case plan" marks a case whose calls each outlast a warmup batch. Latency is per call, nearest rank. Memory: allocations per operation ("collections counted" when every window collected), the peak of one call above what was in use before it (the V8 heap, external memory such as Buffers, and the two together), the total above the empty-call floor, the peak a memory budget reads (the median call's total above the floor for an input under ${bytes(BENCH_CONTRACT.FLOOR_INPUT_BYTES)}, the highest raw total for any other), and whether the settled heap stays flat over a long run.`,
|
|
811
|
+
""
|
|
812
|
+
);
|
|
813
|
+
const f = r.memoryFloor;
|
|
814
|
+
if (f !== void 0 && f !== null) {
|
|
815
|
+
lines.push(
|
|
816
|
+
f.status === "measured" ? `Empty-call floor, what the peak probe itself costs: ${bytes(f.medianPeakHeapBytes)} heap, ${f.medianPeakTotalBytes === void 0 ? "n/a" : bytes(f.medianPeakTotalBytes)} total (the median of ${String(f.calls)} calls).` : `Empty-call floor: skipped (${f.reason}).`,
|
|
817
|
+
""
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
lines.push(
|
|
821
|
+
"| Benchmark | Size | Per op | Ops/s | Bytes/s | p50 | p95 | p99 | Alloc/op | Peak heap | Peak external | Peak total | Above floor | Budget reads | Long run |",
|
|
822
|
+
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
|
|
705
823
|
);
|
|
706
824
|
for (const c of r.cases) {
|
|
707
825
|
const t = c.throughput;
|
|
708
|
-
const
|
|
826
|
+
const large = t.status === "measured" && t.processes.some((p2) => p2.plan === "large") ? " (large-case plan)" : "";
|
|
827
|
+
const perOp = t.status === "measured" ? `${time(t.medianMsPerOp)}${large}` : `skipped (${t.reason})`;
|
|
709
828
|
const ops = t.status === "measured" ? String(t.opsPerSecond) : "n/a";
|
|
710
829
|
const bps = t.status === "measured" && t.bytesPerSecond !== null ? `${bytes(t.bytesPerSecond)}/s` : "n/a";
|
|
711
830
|
const l = c.latency;
|
|
712
831
|
const lat = l === null ? ["n/a", "n/a", "n/a"] : l.status === "measured" ? [ns(l.p50Ns), ns(l.p95Ns), ns(l.p99Ns)] : [`skipped (${l.reason})`, "n/a", "n/a"];
|
|
713
832
|
const a = c.allocations;
|
|
714
|
-
const alloc = a === null ? "n/a" : a.status === "measured" ? bytes(a.heapBytesPerOp + a.externalBytesPerOp) : `skipped (${a.reason})`;
|
|
833
|
+
const alloc = a === null ? "n/a" : a.status === "measured" ? `${bytes(a.heapBytesPerOp + a.externalBytesPerOp)}${a.method === "gc-accounted" ? " (collections counted)" : ""}` : `skipped (${a.reason})`;
|
|
715
834
|
const p = c.peak;
|
|
716
|
-
const
|
|
835
|
+
const optional = (n) => n === void 0 ? "n/a" : bytes(n);
|
|
836
|
+
const peak = p === null ? ["n/a", "n/a", "n/a", "n/a", "n/a"] : p.status === "measured" ? [
|
|
837
|
+
bytes(p.peakHeapBytes),
|
|
838
|
+
optional(p.peakExternalBytes),
|
|
839
|
+
optional(p.peakTotalBytes),
|
|
840
|
+
optional(p.peakTotalAboveFloorBytes),
|
|
841
|
+
bytes(budgetOf(c, p))
|
|
842
|
+
] : Array.from({ length: 5 }, () => `skipped (${p.reason})`);
|
|
717
843
|
const lr = c.longRun;
|
|
718
844
|
const long = lr === null ? "" : lr.status === "measured" ? `${lr.verdict} (${bytes(lr.growthBytes)} over ${String(lr.ops)} ops, ${String(lr.slopeBytesPerOp)} B/op)` : `skipped (${lr.reason})`;
|
|
719
845
|
lines.push(
|
|
720
|
-
`| ${c.benchmark} | ${c.size} | ${perOp} | ${ops} | ${bps} | ${lat.join(" | ")} | ${alloc} | ${peak} | ${long} |`
|
|
846
|
+
`| ${c.benchmark} | ${c.size} | ${perOp} | ${ops} | ${bps} | ${lat.join(" | ")} | ${alloc} | ${peak.join(" | ")} | ${long} |`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
const failures = r.failures ?? [];
|
|
850
|
+
if (failures.length > 0) {
|
|
851
|
+
lines.push(
|
|
852
|
+
"",
|
|
853
|
+
`Could not measure ${String(failures.length)} part(s) of the run; every other case above was measured and written:`,
|
|
854
|
+
"",
|
|
855
|
+
...failures.map(
|
|
856
|
+
(x) => x.benchmark === null ? `- the empty-call floor: ${x.message}` : `- ${x.phase} of ${x.benchmark} @ ${String(x.size)}: ${x.message}`
|
|
857
|
+
)
|
|
721
858
|
);
|
|
722
859
|
}
|
|
723
860
|
if (r.importCost !== null) {
|
|
@@ -783,6 +920,7 @@ var skipped = {
|
|
|
783
920
|
properties: { status: { const: "skipped" }, reason: str }
|
|
784
921
|
};
|
|
785
922
|
var measuredOrSkipped = (measured) => ({ oneOf: [measured, skipped] });
|
|
923
|
+
var plan = { enum: ["standard", "large"] };
|
|
786
924
|
var orNull = (s) => ({ oneOf: [{ type: "null" }, s] });
|
|
787
925
|
var RESULTS_SCHEMA = {
|
|
788
926
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -899,6 +1037,9 @@ var RESULTS_SCHEMA = {
|
|
|
899
1037
|
headMsPerOp: nums,
|
|
900
1038
|
ratios: nums,
|
|
901
1039
|
medianRatio: numOrNull,
|
|
1040
|
+
// Added within version 1, so not required: a document written before them is still valid.
|
|
1041
|
+
basePlans: { type: "array", items: plan },
|
|
1042
|
+
headPlans: { type: "array", items: plan },
|
|
902
1043
|
interval: { $ref: "#/$defs/interval" },
|
|
903
1044
|
verdict: {
|
|
904
1045
|
enum: ["regression", "improvement", "no-regression", "inconclusive", "skipped"]
|
|
@@ -960,12 +1101,46 @@ var RESULTS_SCHEMA = {
|
|
|
960
1101
|
}
|
|
961
1102
|
}
|
|
962
1103
|
},
|
|
1104
|
+
peak: measuredOrSkipped({
|
|
1105
|
+
type: "object",
|
|
1106
|
+
required: ["status", "calls", "peakHeapBytes", "medianPeakHeapBytes"],
|
|
1107
|
+
properties: {
|
|
1108
|
+
status: { const: "measured" },
|
|
1109
|
+
calls: int,
|
|
1110
|
+
peakHeapBytes: num,
|
|
1111
|
+
medianPeakHeapBytes: num,
|
|
1112
|
+
// Added within version 1, so not required.
|
|
1113
|
+
peakExternalBytes: num,
|
|
1114
|
+
medianPeakExternalBytes: num,
|
|
1115
|
+
peakTotalBytes: num,
|
|
1116
|
+
medianPeakTotalBytes: num,
|
|
1117
|
+
peakHeapAboveFloorBytes: num,
|
|
1118
|
+
medianPeakHeapAboveFloorBytes: num,
|
|
1119
|
+
peakExternalAboveFloorBytes: num,
|
|
1120
|
+
medianPeakExternalAboveFloorBytes: num,
|
|
1121
|
+
peakTotalAboveFloorBytes: num,
|
|
1122
|
+
medianPeakTotalAboveFloorBytes: num
|
|
1123
|
+
}
|
|
1124
|
+
}),
|
|
1125
|
+
failure: {
|
|
1126
|
+
type: "object",
|
|
1127
|
+
required: ["phase", "benchmark", "size", "message"],
|
|
1128
|
+
properties: {
|
|
1129
|
+
phase: { enum: ["timing", "memory", "floor"] },
|
|
1130
|
+
benchmark: strOrNull,
|
|
1131
|
+
size: strOrNull,
|
|
1132
|
+
message: str
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
963
1135
|
reading: {
|
|
964
1136
|
type: "object",
|
|
965
1137
|
required: ["processes", "cases", "importCost"],
|
|
966
1138
|
properties: {
|
|
967
1139
|
processes: int,
|
|
968
1140
|
cases: { type: "array", items: { $ref: "#/$defs/caseReading" } },
|
|
1141
|
+
// Added within version 1, so not required.
|
|
1142
|
+
memoryFloor: orNull({ $ref: "#/$defs/peak" }),
|
|
1143
|
+
failures: { type: "array", items: { $ref: "#/$defs/failure" } },
|
|
969
1144
|
importCost: orNull({
|
|
970
1145
|
type: "object",
|
|
971
1146
|
required: [
|
|
@@ -1028,7 +1203,8 @@ var RESULTS_SCHEMA = {
|
|
|
1028
1203
|
items: {
|
|
1029
1204
|
type: "object",
|
|
1030
1205
|
required: ["samplesMsPerOp", "medianMsPerOp"],
|
|
1031
|
-
|
|
1206
|
+
// `plan` was added within version 1, so it is not required.
|
|
1207
|
+
properties: { samplesMsPerOp: nums, medianMsPerOp: num, plan }
|
|
1032
1208
|
}
|
|
1033
1209
|
},
|
|
1034
1210
|
medianMsPerOp: num,
|
|
@@ -1055,24 +1231,16 @@ var RESULTS_SCHEMA = {
|
|
|
1055
1231
|
required: ["status", "windowOps", "heapBytesPerOp", "externalBytesPerOp"],
|
|
1056
1232
|
properties: {
|
|
1057
1233
|
status: { const: "measured" },
|
|
1234
|
+
// Added within version 1, so not required.
|
|
1235
|
+
method: { enum: ["no-gc-window", "gc-accounted"] },
|
|
1236
|
+
collections: int,
|
|
1058
1237
|
windowOps: int,
|
|
1059
1238
|
heapBytesPerOp: num,
|
|
1060
1239
|
externalBytesPerOp: num
|
|
1061
1240
|
}
|
|
1062
1241
|
})
|
|
1063
1242
|
),
|
|
1064
|
-
peak: orNull(
|
|
1065
|
-
measuredOrSkipped({
|
|
1066
|
-
type: "object",
|
|
1067
|
-
required: ["status", "calls", "peakHeapBytes", "medianPeakHeapBytes"],
|
|
1068
|
-
properties: {
|
|
1069
|
-
status: { const: "measured" },
|
|
1070
|
-
calls: int,
|
|
1071
|
-
peakHeapBytes: num,
|
|
1072
|
-
medianPeakHeapBytes: num
|
|
1073
|
-
}
|
|
1074
|
-
})
|
|
1075
|
-
),
|
|
1243
|
+
peak: orNull({ $ref: "#/$defs/peak" }),
|
|
1076
1244
|
longRun: orNull(
|
|
1077
1245
|
measuredOrSkipped({
|
|
1078
1246
|
type: "object",
|
|
@@ -1186,7 +1354,8 @@ var USAGE = `Usage:
|
|
|
1186
1354
|
cosyte-bench check <results.json>
|
|
1187
1355
|
|
|
1188
1356
|
Exit codes: 0 clean, 1 a finding (a regression, or results that do not match the schema),
|
|
1189
|
-
2 could not measure or could not read its input.
|
|
1357
|
+
2 could not measure or could not read its input. A run that could not measure a case still
|
|
1358
|
+
writes every other case, lists what failed, and exits 2.
|
|
1190
1359
|
`;
|
|
1191
1360
|
var UsageError = class extends Error {
|
|
1192
1361
|
};
|
|
@@ -1374,7 +1543,8 @@ async function main(argv, io) {
|
|
|
1374
1543
|
subject: { target: rel(target), commit },
|
|
1375
1544
|
reading
|
|
1376
1545
|
},
|
|
1377
|
-
|
|
1546
|
+
// Whatever measured is written; a part that could not be is still exit 2.
|
|
1547
|
+
reading.failures.length > 0 ? 2 : 0
|
|
1378
1548
|
);
|
|
1379
1549
|
}
|
|
1380
1550
|
{
|