@cosyte/bench 0.1.2 → 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 +26 -0
- package/README.md +42 -14
- package/dist/child-entry.mjs +8 -0
- package/dist/child-entry.mjs.map +1 -1
- package/dist/cli.mjs +168 -47
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.ts +111 -2
- package/dist/index.mjs +50 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/schema/results-v1.schema.json +100 -45
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,15 @@ interface BenchContractShape {
|
|
|
43
43
|
readonly ALLOC_SEMI_SPACE_MIB: number;
|
|
44
44
|
/** Calls measured one by one for the peak heap. */
|
|
45
45
|
readonly PEAK_CALLS: number;
|
|
46
|
+
/**
|
|
47
|
+
* An input under this many bytes, on average, is budgeted on its median call's total peak above
|
|
48
|
+
* the empty-call floor's median, rather than its highest raw peak. On a GitHub-hosted runner the
|
|
49
|
+
* peak probe costs about 3.3 KiB on a typical empty call and 28 KiB on its highest (one call, the
|
|
50
|
+
* first), so the highest of a small input's calls reads the probe more than the code: 20 times a
|
|
51
|
+
* 1 KiB input is spent before the code runs. At 64 KiB even the probe's highest call is about 2%
|
|
52
|
+
* of a 20 times budget, so above it the raw highest peak is read as before.
|
|
53
|
+
*/
|
|
54
|
+
readonly FLOOR_INPUT_BYTES: number;
|
|
46
55
|
/** Rounds in the long run that checks the heap stays flat. */
|
|
47
56
|
readonly LONG_RUN_ROUNDS: number;
|
|
48
57
|
/** Rounds at the start of the long run left out of the slope, while caches and compiled code settle. */
|
|
@@ -319,6 +328,17 @@ type PeakReport = {
|
|
|
319
328
|
readonly peakTotalBytes?: number;
|
|
320
329
|
/** The median call's peak of the two together. */
|
|
321
330
|
readonly medianPeakTotalBytes?: number;
|
|
331
|
+
/**
|
|
332
|
+
* Each figure above, less the same figure of the run's empty-call floor (its median), and
|
|
333
|
+
* never below zero. Absent when the run measured no floor, and in a document written before it.
|
|
334
|
+
* See {@link aboveFloor}.
|
|
335
|
+
*/
|
|
336
|
+
readonly peakHeapAboveFloorBytes?: number;
|
|
337
|
+
readonly medianPeakHeapAboveFloorBytes?: number;
|
|
338
|
+
readonly peakExternalAboveFloorBytes?: number;
|
|
339
|
+
readonly medianPeakExternalAboveFloorBytes?: number;
|
|
340
|
+
readonly peakTotalAboveFloorBytes?: number;
|
|
341
|
+
readonly medianPeakTotalAboveFloorBytes?: number;
|
|
322
342
|
} | {
|
|
323
343
|
readonly status: "skipped";
|
|
324
344
|
readonly reason: MemorySkipReason;
|
|
@@ -549,6 +569,19 @@ type Throughput = {
|
|
|
549
569
|
readonly status: "skipped";
|
|
550
570
|
readonly reason: string;
|
|
551
571
|
};
|
|
572
|
+
/**
|
|
573
|
+
* A part of a run that could not be measured: a case's timing or memory, or the empty-call floor.
|
|
574
|
+
* The message is the first line of what went wrong. Only the first line is kept, because the lines
|
|
575
|
+
* after it may be a measuring process's stderr, which may quote an input; the command line prints
|
|
576
|
+
* the whole of it to its own stderr.
|
|
577
|
+
*/
|
|
578
|
+
interface RunFailure {
|
|
579
|
+
readonly phase: "timing" | "memory" | "floor";
|
|
580
|
+
/** The case, or `null` for the floor. */
|
|
581
|
+
readonly benchmark: string | null;
|
|
582
|
+
readonly size: string | null;
|
|
583
|
+
readonly message: string;
|
|
584
|
+
}
|
|
552
585
|
/** Everything a reading measured for one case. */
|
|
553
586
|
interface CaseReading extends CaseRef {
|
|
554
587
|
readonly inputsPerPass: number;
|
|
@@ -564,9 +597,59 @@ interface ReadingBody {
|
|
|
564
597
|
/** Fresh processes timed per case. */
|
|
565
598
|
readonly processes: number;
|
|
566
599
|
readonly cases: readonly CaseReading[];
|
|
600
|
+
/**
|
|
601
|
+
* The peak of an empty call, measured once per run through the same probe as the cases: what the
|
|
602
|
+
* probe itself costs. Each case's peak also gives its figures above this one. `null` when memory
|
|
603
|
+
* was not measured or the floor could not be. Absent in a document written before it.
|
|
604
|
+
*/
|
|
605
|
+
readonly memoryFloor?: PeakReport | null;
|
|
606
|
+
/**
|
|
607
|
+
* What could not be measured. A case whose timing failed is here and not in `cases`; one whose
|
|
608
|
+
* memory failed is in both, with the memory it could not read as `null`. Every other case is
|
|
609
|
+
* written as usual. Empty when everything measured; absent in a document written before it.
|
|
610
|
+
*/
|
|
611
|
+
readonly failures?: readonly RunFailure[];
|
|
567
612
|
/** `null` when import cost was not asked for. */
|
|
568
613
|
readonly importCost: ImportCostReport | null;
|
|
569
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* The peak, in bytes, a memory budget reads for a case, or `null` when the case has none. A case
|
|
617
|
+
* whose inputs average under `FLOOR_INPUT_BYTES` (its bytes a pass over its inputs a pass) reads its
|
|
618
|
+
* median call's total peak above the run's empty-call floor (`medianPeakTotalAboveFloorBytes`); any
|
|
619
|
+
* other case, and one whose suite gave no byte count, reads the highest raw total peak (the V8 heap
|
|
620
|
+
* and external memory together). The probe costs a few KiB on a typical call and tens of KiB on its
|
|
621
|
+
* highest, which is most of the budget of a small input and nothing the code under test did, and a
|
|
622
|
+
* rounding error on a large one. A document without a floor reads the raw highest.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```ts
|
|
626
|
+
* import { budgetPeakBytes } from "@cosyte/bench";
|
|
627
|
+
*
|
|
628
|
+
* // 300 bytes an input, and a floor whose median call is 3,400 bytes: the highest call read
|
|
629
|
+
* // 30,000 bytes, most of it the probe, and the median call 6,000 above the floor.
|
|
630
|
+
* budgetPeakBytes({
|
|
631
|
+
* benchmark: "parse",
|
|
632
|
+
* size: "300 B",
|
|
633
|
+
* inputsPerPass: 1,
|
|
634
|
+
* bytesPerPass: 300,
|
|
635
|
+
* throughput: { status: "skipped", reason: "warmup-unstable" },
|
|
636
|
+
* latency: null,
|
|
637
|
+
* allocations: null,
|
|
638
|
+
* peak: {
|
|
639
|
+
* status: "measured",
|
|
640
|
+
* calls: 20,
|
|
641
|
+
* peakHeapBytes: 30_000,
|
|
642
|
+
* medianPeakHeapBytes: 9_400,
|
|
643
|
+
* peakTotalBytes: 30_000,
|
|
644
|
+
* medianPeakTotalBytes: 9_400,
|
|
645
|
+
* peakTotalAboveFloorBytes: 26_600,
|
|
646
|
+
* medianPeakTotalAboveFloorBytes: 6_000,
|
|
647
|
+
* },
|
|
648
|
+
* longRun: null,
|
|
649
|
+
* }); // => 6000
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
declare function budgetPeakBytes(c: CaseReading): number | null;
|
|
570
653
|
|
|
571
654
|
/**
|
|
572
655
|
* The machine a measurement was taken on. Every results document carries it, because a number
|
|
@@ -1036,6 +1119,25 @@ declare const RESULTS_SCHEMA: {
|
|
|
1036
1119
|
};
|
|
1037
1120
|
};
|
|
1038
1121
|
};
|
|
1122
|
+
readonly peak: Schema;
|
|
1123
|
+
readonly failure: {
|
|
1124
|
+
readonly type: "object";
|
|
1125
|
+
readonly required: readonly ["phase", "benchmark", "size", "message"];
|
|
1126
|
+
readonly properties: {
|
|
1127
|
+
readonly phase: {
|
|
1128
|
+
readonly enum: readonly ["timing", "memory", "floor"];
|
|
1129
|
+
};
|
|
1130
|
+
readonly benchmark: {
|
|
1131
|
+
readonly type: readonly ["string", "null"];
|
|
1132
|
+
};
|
|
1133
|
+
readonly size: {
|
|
1134
|
+
readonly type: readonly ["string", "null"];
|
|
1135
|
+
};
|
|
1136
|
+
readonly message: {
|
|
1137
|
+
readonly type: "string";
|
|
1138
|
+
};
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
1039
1141
|
readonly reading: {
|
|
1040
1142
|
readonly type: "object";
|
|
1041
1143
|
readonly required: readonly ["processes", "cases", "importCost"];
|
|
@@ -1050,6 +1152,13 @@ declare const RESULTS_SCHEMA: {
|
|
|
1050
1152
|
readonly $ref: "#/$defs/caseReading";
|
|
1051
1153
|
};
|
|
1052
1154
|
};
|
|
1155
|
+
readonly memoryFloor: Schema;
|
|
1156
|
+
readonly failures: {
|
|
1157
|
+
readonly type: "array";
|
|
1158
|
+
readonly items: {
|
|
1159
|
+
readonly $ref: "#/$defs/failure";
|
|
1160
|
+
};
|
|
1161
|
+
};
|
|
1053
1162
|
readonly importCost: Schema;
|
|
1054
1163
|
};
|
|
1055
1164
|
};
|
|
@@ -1116,5 +1225,5 @@ declare class BenchError extends Error {
|
|
|
1116
1225
|
readonly name = "BenchError";
|
|
1117
1226
|
}
|
|
1118
1227
|
|
|
1119
|
-
export { BENCH_CONTRACT, BenchError, RESULTS_SCHEMA, SCHEMA_VERSION, defineBenchmark, defineSuite, judge, readResults };
|
|
1120
|
-
export type { AllocationReport, AnyBenchmark, BenchContractShape, BenchCorpus, BenchSize, BenchSuite, Benchmark, BuildRef, CalibrationBody, CaseComparison, CaseReading, CaseRef, ComparisonBody, CountedRate, Environment, ImportCostReport, LatencyReport, LoadedFile, LongRunReport, MemorySkipReason, PeakReport, ReadingBody, ResultsDocument, Runner, Throughput, Verdict, WarmupReport };
|
|
1228
|
+
export { BENCH_CONTRACT, BenchError, RESULTS_SCHEMA, SCHEMA_VERSION, budgetPeakBytes, defineBenchmark, defineSuite, judge, readResults };
|
|
1229
|
+
export type { AllocationReport, AnyBenchmark, BenchContractShape, BenchCorpus, BenchSize, BenchSuite, Benchmark, BuildRef, CalibrationBody, CaseComparison, CaseReading, CaseRef, ComparisonBody, CountedRate, Environment, ImportCostReport, LatencyReport, LoadedFile, LongRunReport, MemorySkipReason, PeakReport, ReadingBody, ResultsDocument, RunFailure, Runner, Throughput, Verdict, WarmupReport };
|
package/dist/index.mjs
CHANGED
|
@@ -21,6 +21,7 @@ var BENCH_CONTRACT = Object.freeze({
|
|
|
21
21
|
ALLOC_WINDOW_MS: 100,
|
|
22
22
|
ALLOC_SEMI_SPACE_MIB: 64,
|
|
23
23
|
PEAK_CALLS: 20,
|
|
24
|
+
FLOOR_INPUT_BYTES: 64 * 1024,
|
|
24
25
|
LONG_RUN_ROUNDS: 30,
|
|
25
26
|
LONG_RUN_SETTLE_ROUNDS: 5,
|
|
26
27
|
LONG_RUN_ROUND_MS: 100,
|
|
@@ -142,6 +143,19 @@ function judge(ratios, threshold = BENCH_CONTRACT.THRESHOLD, alpha = BENCH_CONTR
|
|
|
142
143
|
else verdict = "inconclusive";
|
|
143
144
|
return { verdict, interval, medianRatio };
|
|
144
145
|
}
|
|
146
|
+
|
|
147
|
+
// src/reading.ts
|
|
148
|
+
function budgetPeakBytes(c) {
|
|
149
|
+
const p = c.peak;
|
|
150
|
+
return p === null || p.status !== "measured" ? null : budgetOf(c, p);
|
|
151
|
+
}
|
|
152
|
+
function budgetOf(c, p) {
|
|
153
|
+
const raw = p.peakTotalBytes ?? p.peakHeapBytes;
|
|
154
|
+
if (c.bytesPerPass === null) return raw;
|
|
155
|
+
const perInput = c.bytesPerPass / c.inputsPerPass;
|
|
156
|
+
if (perInput >= BENCH_CONTRACT.FLOOR_INPUT_BYTES) return raw;
|
|
157
|
+
return p.medianPeakTotalAboveFloorBytes ?? raw;
|
|
158
|
+
}
|
|
145
159
|
var SCHEMA_VERSION = 1;
|
|
146
160
|
var num = { type: "number" };
|
|
147
161
|
var int = { type: "integer", minimum: 0 };
|
|
@@ -336,12 +350,46 @@ var RESULTS_SCHEMA = {
|
|
|
336
350
|
}
|
|
337
351
|
}
|
|
338
352
|
},
|
|
353
|
+
peak: measuredOrSkipped({
|
|
354
|
+
type: "object",
|
|
355
|
+
required: ["status", "calls", "peakHeapBytes", "medianPeakHeapBytes"],
|
|
356
|
+
properties: {
|
|
357
|
+
status: { const: "measured" },
|
|
358
|
+
calls: int,
|
|
359
|
+
peakHeapBytes: num,
|
|
360
|
+
medianPeakHeapBytes: num,
|
|
361
|
+
// Added within version 1, so not required.
|
|
362
|
+
peakExternalBytes: num,
|
|
363
|
+
medianPeakExternalBytes: num,
|
|
364
|
+
peakTotalBytes: num,
|
|
365
|
+
medianPeakTotalBytes: num,
|
|
366
|
+
peakHeapAboveFloorBytes: num,
|
|
367
|
+
medianPeakHeapAboveFloorBytes: num,
|
|
368
|
+
peakExternalAboveFloorBytes: num,
|
|
369
|
+
medianPeakExternalAboveFloorBytes: num,
|
|
370
|
+
peakTotalAboveFloorBytes: num,
|
|
371
|
+
medianPeakTotalAboveFloorBytes: num
|
|
372
|
+
}
|
|
373
|
+
}),
|
|
374
|
+
failure: {
|
|
375
|
+
type: "object",
|
|
376
|
+
required: ["phase", "benchmark", "size", "message"],
|
|
377
|
+
properties: {
|
|
378
|
+
phase: { enum: ["timing", "memory", "floor"] },
|
|
379
|
+
benchmark: strOrNull,
|
|
380
|
+
size: strOrNull,
|
|
381
|
+
message: str
|
|
382
|
+
}
|
|
383
|
+
},
|
|
339
384
|
reading: {
|
|
340
385
|
type: "object",
|
|
341
386
|
required: ["processes", "cases", "importCost"],
|
|
342
387
|
properties: {
|
|
343
388
|
processes: int,
|
|
344
389
|
cases: { type: "array", items: { $ref: "#/$defs/caseReading" } },
|
|
390
|
+
// Added within version 1, so not required.
|
|
391
|
+
memoryFloor: orNull({ $ref: "#/$defs/peak" }),
|
|
392
|
+
failures: { type: "array", items: { $ref: "#/$defs/failure" } },
|
|
345
393
|
importCost: orNull({
|
|
346
394
|
type: "object",
|
|
347
395
|
required: [
|
|
@@ -441,23 +489,7 @@ var RESULTS_SCHEMA = {
|
|
|
441
489
|
}
|
|
442
490
|
})
|
|
443
491
|
),
|
|
444
|
-
peak: orNull(
|
|
445
|
-
measuredOrSkipped({
|
|
446
|
-
type: "object",
|
|
447
|
-
required: ["status", "calls", "peakHeapBytes", "medianPeakHeapBytes"],
|
|
448
|
-
properties: {
|
|
449
|
-
status: { const: "measured" },
|
|
450
|
-
calls: int,
|
|
451
|
-
peakHeapBytes: num,
|
|
452
|
-
medianPeakHeapBytes: num,
|
|
453
|
-
// Added within version 1, so not required.
|
|
454
|
-
peakExternalBytes: num,
|
|
455
|
-
medianPeakExternalBytes: num,
|
|
456
|
-
peakTotalBytes: num,
|
|
457
|
-
medianPeakTotalBytes: num
|
|
458
|
-
}
|
|
459
|
-
})
|
|
460
|
-
),
|
|
492
|
+
peak: orNull({ $ref: "#/$defs/peak" }),
|
|
461
493
|
longRun: orNull(
|
|
462
494
|
measuredOrSkipped({
|
|
463
495
|
type: "object",
|
|
@@ -567,6 +599,6 @@ Object.freeze({
|
|
|
567
599
|
importer: fileURLToPath(new URL("./import-entry.mjs", import.meta.url))
|
|
568
600
|
});
|
|
569
601
|
|
|
570
|
-
export { BENCH_CONTRACT, BenchError, RESULTS_SCHEMA, SCHEMA_VERSION, defineBenchmark, defineSuite, judge, readResults };
|
|
602
|
+
export { BENCH_CONTRACT, BenchError, RESULTS_SCHEMA, SCHEMA_VERSION, budgetPeakBytes, defineBenchmark, defineSuite, judge, readResults };
|
|
571
603
|
//# sourceMappingURL=index.mjs.map
|
|
572
604
|
//# sourceMappingURL=index.mjs.map
|