@ac-bench/measure-resource 0.1.0
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/LICENSE +28 -0
- package/README.md +25 -0
- package/dist/_counters.d.ts +71 -0
- package/dist/_counters.d.ts.map +1 -0
- package/dist/_counters.js +134 -0
- package/dist/_counters.js.map +1 -0
- package/dist/_run-case.d.ts +9 -0
- package/dist/_run-case.d.ts.map +1 -0
- package/dist/_run-case.js +189 -0
- package/dist/_run-case.js.map +1 -0
- package/dist/_statistics.d.ts +15 -0
- package/dist/_statistics.d.ts.map +1 -0
- package/dist/_statistics.js +54 -0
- package/dist/_statistics.js.map +1 -0
- package/dist/_types.d.ts +91 -0
- package/dist/_types.d.ts.map +1 -0
- package/dist/_types.js +48 -0
- package/dist/_types.js.map +1 -0
- package/dist/_warnings.d.ts +23 -0
- package/dist/_warnings.d.ts.map +1 -0
- package/dist/_warnings.js +54 -0
- package/dist/_warnings.js.map +1 -0
- package/dist/decl.d.ts +8 -0
- package/dist/decl.d.ts.map +1 -0
- package/dist/decl.js +20 -0
- package/dist/decl.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +46 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +13 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +151 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Anthony Champagne and contributors
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @ac-bench/measure-resource
|
|
2
|
+
|
|
3
|
+
What an operation costs in bytes and CPU time, rather than in wall time.
|
|
4
|
+
|
|
5
|
+
Wall time alone cannot tell you why something is fast. An implementation that
|
|
6
|
+
wins by allocating freely looks identical to one that wins by doing less work —
|
|
7
|
+
until the garbage collector runs under load. This reports CPU time, peak heap,
|
|
8
|
+
bytes allocated, and garbage-collection count and pause time, so the two are
|
|
9
|
+
distinguishable.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { resourceCase, resourceCondition } from "@ac-bench/measure-resource";
|
|
13
|
+
|
|
14
|
+
resourceCondition("parse 10k records", () => {
|
|
15
|
+
resourceCase("streaming", () => parseStreaming(input));
|
|
16
|
+
resourceCase("buffer whole document", () => parseBuffered(input));
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Results are reported in their own units alongside a duration measure, never
|
|
21
|
+
converted into one.
|
|
22
|
+
|
|
23
|
+
Host-bound: it reads Node's resource-usage and GC instrumentation.
|
|
24
|
+
|
|
25
|
+
Run suites with the `measure` CLI from `@ac-bench/cli`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** One reading of the counters every resource figure is derived from. */
|
|
2
|
+
export type ResourceCounters = {
|
|
3
|
+
cpuUserUs: number;
|
|
4
|
+
cpuSystemUs: number;
|
|
5
|
+
heapUsedBytes: number;
|
|
6
|
+
externalBytes: number;
|
|
7
|
+
arrayBuffersBytes: number;
|
|
8
|
+
rssBytes: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Read twice per batch, so it allocates as little as the platform allows:
|
|
12
|
+
* `v8.getHeapStatistics()` is deliberately not called here — it builds the
|
|
13
|
+
* largest result of the three, and the only field wanted from it is a constant.
|
|
14
|
+
* See {@link readHeapLimitBytes}.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readResourceCounters(): ResourceCounters;
|
|
17
|
+
/** The heap ceiling this process was given, fixed for its lifetime. */
|
|
18
|
+
export declare function readHeapLimitBytes(): number;
|
|
19
|
+
/**
|
|
20
|
+
* Bytes a batch asked the allocator for, as the growth in everything the heap
|
|
21
|
+
* accounts for.
|
|
22
|
+
*
|
|
23
|
+
* Negative when a collection ran mid-batch and freed more than was allocated,
|
|
24
|
+
* which is why {@link forceCollection} is called before a batch rather than
|
|
25
|
+
* during it. Clamped at zero: a batch cannot allocate a negative amount, and
|
|
26
|
+
* reporting one would make a mean meaningless.
|
|
27
|
+
*/
|
|
28
|
+
export declare function allocatedBytes(before: ResourceCounters, after: ResourceCounters): number;
|
|
29
|
+
export declare function cpuTimeMs(before: ResourceCounters, after: ResourceCounters): number;
|
|
30
|
+
/** Whether the run was started with `--expose-gc`. */
|
|
31
|
+
export declare function hasExposedCollector(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Collects, so a batch starts from a known floor and its growth is what it
|
|
34
|
+
* allocated rather than what the collector happened not to have reclaimed yet.
|
|
35
|
+
*
|
|
36
|
+
* A no-op without `--expose-gc`; callers warn rather than fail, since the
|
|
37
|
+
* numbers are still comparable between cases in the same run.
|
|
38
|
+
*/
|
|
39
|
+
export declare function forceCollection(): void;
|
|
40
|
+
export type GcTotals = {
|
|
41
|
+
count: number;
|
|
42
|
+
pauseMs: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Whether this runtime actually delivers `"gc"` performance entries.
|
|
46
|
+
*
|
|
47
|
+
* `PerformanceObserver.supportedEntryTypes` lists `"gc"` on builds that never
|
|
48
|
+
* emit one — Node 26.8 does, and reports nothing even for a forced collection.
|
|
49
|
+
* Reporting the resulting zero as "no collections ran" would be a lie, so the
|
|
50
|
+
* capability is probed rather than assumed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function canObserveGc(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Running totals of the collections observed since construction, or `null`
|
|
55
|
+
* where {@link canObserveGc} says the runtime cannot report them.
|
|
56
|
+
*
|
|
57
|
+
* A class because it owns a subscription that outlives any one reading: the
|
|
58
|
+
* observer must stay attached across every batch, and callers difference two
|
|
59
|
+
* readings rather than restarting it.
|
|
60
|
+
*/
|
|
61
|
+
export declare class GcRecorder {
|
|
62
|
+
private readonly observer;
|
|
63
|
+
private count_;
|
|
64
|
+
private pauseMs_;
|
|
65
|
+
constructor(observable: boolean);
|
|
66
|
+
read(): GcTotals | null;
|
|
67
|
+
[Symbol.dispose](): void;
|
|
68
|
+
private absorb_;
|
|
69
|
+
}
|
|
70
|
+
export declare function gcSince(before: GcTotals | null, after: GcTotals | null): GcTotals | null;
|
|
71
|
+
//# sourceMappingURL=_counters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_counters.d.ts","sourceRoot":"","sources":["../src/_counters.ts"],"names":[],"mappings":"AAGA,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,gBAAgB,CAYvD;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,gBAAgB,GACrB,MAAM,CAQR;AAED,wBAAgB,SAAS,CACxB,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,gBAAgB,GACrB,MAAM,CAOR;AAED,sDAAsD;AACtD,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,MAAM,MAAM,QAAQ,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI,OAAO,CActC;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAK;IAErB,YAAY,UAAU,EAAE,OAAO,EAU9B;IAED,IAAI,IAAI,QAAQ,GAAG,IAAI,CAWtB;IAED,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAEvB;IAED,OAAO,CAAC,OAAO;CAMf;AAED,wBAAgB,OAAO,CACtB,MAAM,EAAE,QAAQ,GAAG,IAAI,EACvB,KAAK,EAAE,QAAQ,GAAG,IAAI,GACpB,QAAQ,GAAG,IAAI,CASjB"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { PerformanceObserver } from "node:perf_hooks";
|
|
2
|
+
import v8 from "node:v8";
|
|
3
|
+
/**
|
|
4
|
+
* Read twice per batch, so it allocates as little as the platform allows:
|
|
5
|
+
* `v8.getHeapStatistics()` is deliberately not called here — it builds the
|
|
6
|
+
* largest result of the three, and the only field wanted from it is a constant.
|
|
7
|
+
* See {@link readHeapLimitBytes}.
|
|
8
|
+
*/
|
|
9
|
+
export function readResourceCounters() {
|
|
10
|
+
const cpu = process.cpuUsage();
|
|
11
|
+
const memory = process.memoryUsage();
|
|
12
|
+
return {
|
|
13
|
+
cpuUserUs: cpu.user,
|
|
14
|
+
cpuSystemUs: cpu.system,
|
|
15
|
+
heapUsedBytes: memory.heapUsed,
|
|
16
|
+
externalBytes: memory.external,
|
|
17
|
+
arrayBuffersBytes: memory.arrayBuffers,
|
|
18
|
+
rssBytes: memory.rss,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** The heap ceiling this process was given, fixed for its lifetime. */
|
|
22
|
+
export function readHeapLimitBytes() {
|
|
23
|
+
return v8.getHeapStatistics().heap_size_limit;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Bytes a batch asked the allocator for, as the growth in everything the heap
|
|
27
|
+
* accounts for.
|
|
28
|
+
*
|
|
29
|
+
* Negative when a collection ran mid-batch and freed more than was allocated,
|
|
30
|
+
* which is why {@link forceCollection} is called before a batch rather than
|
|
31
|
+
* during it. Clamped at zero: a batch cannot allocate a negative amount, and
|
|
32
|
+
* reporting one would make a mean meaningless.
|
|
33
|
+
*/
|
|
34
|
+
export function allocatedBytes(before, after) {
|
|
35
|
+
const growth = after.heapUsedBytes -
|
|
36
|
+
before.heapUsedBytes +
|
|
37
|
+
(after.externalBytes - before.externalBytes) +
|
|
38
|
+
(after.arrayBuffersBytes - before.arrayBuffersBytes);
|
|
39
|
+
return Math.max(0, growth);
|
|
40
|
+
}
|
|
41
|
+
export function cpuTimeMs(before, after) {
|
|
42
|
+
return ((after.cpuUserUs -
|
|
43
|
+
before.cpuUserUs +
|
|
44
|
+
(after.cpuSystemUs - before.cpuSystemUs)) /
|
|
45
|
+
1000);
|
|
46
|
+
}
|
|
47
|
+
/** Whether the run was started with `--expose-gc`. */
|
|
48
|
+
export function hasExposedCollector() {
|
|
49
|
+
return typeof globalThis.gc === "function";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Collects, so a batch starts from a known floor and its growth is what it
|
|
53
|
+
* allocated rather than what the collector happened not to have reclaimed yet.
|
|
54
|
+
*
|
|
55
|
+
* A no-op without `--expose-gc`; callers warn rather than fail, since the
|
|
56
|
+
* numbers are still comparable between cases in the same run.
|
|
57
|
+
*/
|
|
58
|
+
export function forceCollection() {
|
|
59
|
+
globalThis.gc?.();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether this runtime actually delivers `"gc"` performance entries.
|
|
63
|
+
*
|
|
64
|
+
* `PerformanceObserver.supportedEntryTypes` lists `"gc"` on builds that never
|
|
65
|
+
* emit one — Node 26.8 does, and reports nothing even for a forced collection.
|
|
66
|
+
* Reporting the resulting zero as "no collections ran" would be a lie, so the
|
|
67
|
+
* capability is probed rather than assumed.
|
|
68
|
+
*/
|
|
69
|
+
export function canObserveGc() {
|
|
70
|
+
if (!PerformanceObserver.supportedEntryTypes.includes("gc")) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const probe = new PerformanceObserver(() => { });
|
|
74
|
+
try {
|
|
75
|
+
probe.observe({ entryTypes: ["gc"] });
|
|
76
|
+
forceCollection();
|
|
77
|
+
return probe.takeRecords().length > 0;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
probe.disconnect();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Running totals of the collections observed since construction, or `null`
|
|
85
|
+
* where {@link canObserveGc} says the runtime cannot report them.
|
|
86
|
+
*
|
|
87
|
+
* A class because it owns a subscription that outlives any one reading: the
|
|
88
|
+
* observer must stay attached across every batch, and callers difference two
|
|
89
|
+
* readings rather than restarting it.
|
|
90
|
+
*/
|
|
91
|
+
export class GcRecorder {
|
|
92
|
+
observer;
|
|
93
|
+
count_ = 0;
|
|
94
|
+
pauseMs_ = 0;
|
|
95
|
+
constructor(observable) {
|
|
96
|
+
if (!observable) {
|
|
97
|
+
this.observer = null;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.observer = new PerformanceObserver((list) => {
|
|
101
|
+
this.absorb_(list.getEntries());
|
|
102
|
+
});
|
|
103
|
+
this.observer.observe({ entryTypes: ["gc"] });
|
|
104
|
+
}
|
|
105
|
+
read() {
|
|
106
|
+
if (!this.observer) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
// The observer's callback runs on the microtask queue, and a synchronous
|
|
110
|
+
// case never yields to it — so without draining here, a batch that
|
|
111
|
+
// collected repeatedly would report no collections at all.
|
|
112
|
+
this.absorb_(this.observer.takeRecords());
|
|
113
|
+
return { count: this.count_, pauseMs: this.pauseMs_ };
|
|
114
|
+
}
|
|
115
|
+
[Symbol.dispose]() {
|
|
116
|
+
this.observer?.disconnect();
|
|
117
|
+
}
|
|
118
|
+
absorb_(entries) {
|
|
119
|
+
for (let index = 0; index < entries.length; index++) {
|
|
120
|
+
this.count_++;
|
|
121
|
+
this.pauseMs_ += entries[index].duration;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function gcSince(before, after) {
|
|
126
|
+
if (before === null || after === null) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
count: after.count - before.count,
|
|
131
|
+
pauseMs: after.pauseMs - before.pauseMs,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=_counters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_counters.js","sourceRoot":"","sources":["../src/_counters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BenchCaseRunContext } from "@ac-bench/core/runner";
|
|
2
|
+
import { MaybeAsyncCallable } from "@ac-kit/core";
|
|
3
|
+
import { ResourceCaseOptions, ResourceConditionOptions } from "./options.js";
|
|
4
|
+
export type ResourceCaseFn = MaybeAsyncCallable<[signal: AbortSignal]>;
|
|
5
|
+
export type RunResourceCaseOptions = ResourceCaseOptions & {
|
|
6
|
+
conditionOptions?: ResourceConditionOptions;
|
|
7
|
+
};
|
|
8
|
+
export declare function runResourceCase(title: string, fn: ResourceCaseFn, context: BenchCaseRunContext, options?: RunResourceCaseOptions): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=_run-case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_run-case.d.ts","sourceRoot":"","sources":["../src/_run-case.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,mBAAmB,EAKnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAIN,kBAAkB,EAClB,MAAM,cAAc,CAAC;AAgBtB,OAAO,EAEN,mBAAmB,EACnB,wBAAwB,EAExB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,cAAc,GAAG,kBAAkB,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAEvE,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG;IAC1D,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;CAC5C,CAAC;AA0DF,wBAAsB,eAAe,CACpC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Gf"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { allOfPolicies, createProjectedPolicy, createRelativeErrorPolicy, createStabilityPolicy, Sampler, } from "@ac-bench/core/runner";
|
|
54
|
+
import { defaults, formatError, isThenable, } from "@ac-kit/core";
|
|
55
|
+
import { allocatedBytes, canObserveGc, cpuTimeMs, forceCollection, GcRecorder, gcSince, hasExposedCollector, readHeapLimitBytes, readResourceCounters, } from "./_counters.js";
|
|
56
|
+
import { composeResourceBenchStatistics } from "./_statistics.js";
|
|
57
|
+
import { detectResourceBenchWarnings } from "./_warnings.js";
|
|
58
|
+
import { RESOURCE_SAMPLING_DEFAULTS, } from "./options.js";
|
|
59
|
+
/**
|
|
60
|
+
* Runs one batch and returns what it cost, per operation.
|
|
61
|
+
*
|
|
62
|
+
* Nothing inside the timed loop may allocate, or the measure reports its own
|
|
63
|
+
* bookkeeping as the case's: `process.memoryUsage()` builds a result object per
|
|
64
|
+
* call, so the peak is tracked through `memoryUsage.rss()`, which returns a
|
|
65
|
+
* number, and the heap counters are read at the boundaries only.
|
|
66
|
+
*
|
|
67
|
+
* The collection before the batch is deliberately outside the measured window:
|
|
68
|
+
* it establishes the floor the growth is measured from, and its own cost is not
|
|
69
|
+
* part of what the case allocated.
|
|
70
|
+
*/
|
|
71
|
+
async function measureBatch_(fn, iterations, gc, signal) {
|
|
72
|
+
forceCollection();
|
|
73
|
+
const before = readResourceCounters();
|
|
74
|
+
const gcBefore = gc.read();
|
|
75
|
+
let rssPeakBytes = before.rssBytes;
|
|
76
|
+
for (let index = 0; index < iterations; index++) {
|
|
77
|
+
signal.throwIfAborted();
|
|
78
|
+
// Awaiting unconditionally would allocate promise machinery per iteration
|
|
79
|
+
// for a synchronous case, which is allocation this measure would then
|
|
80
|
+
// attribute to the case.
|
|
81
|
+
const returned = fn(signal);
|
|
82
|
+
if (isThenable(returned)) {
|
|
83
|
+
await returned;
|
|
84
|
+
}
|
|
85
|
+
const rss = process.memoryUsage.rss();
|
|
86
|
+
if (rss > rssPeakBytes) {
|
|
87
|
+
rssPeakBytes = rss;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const after = readResourceCounters();
|
|
91
|
+
const gcDelta = gcSince(gcBefore, gc.read());
|
|
92
|
+
return {
|
|
93
|
+
operations: iterations,
|
|
94
|
+
cpuTimeMs: cpuTimeMs(before, after) / iterations,
|
|
95
|
+
allocatedBytes: allocatedBytes(before, after) / iterations,
|
|
96
|
+
heapPeakBytes: Math.max(before.heapUsedBytes, after.heapUsedBytes),
|
|
97
|
+
rssBytes: rssPeakBytes,
|
|
98
|
+
gcCount: gcDelta === null ? null : gcDelta.count / iterations,
|
|
99
|
+
gcPauseMs: gcDelta === null ? null : gcDelta.pauseMs / iterations,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export async function runResourceCase(title, fn, context, options) {
|
|
103
|
+
const { signal } = context;
|
|
104
|
+
signal.throwIfAborted();
|
|
105
|
+
const sampling = defaults(options?.conditionOptions?.sampling ?? {}, RESOURCE_SAMPLING_DEFAULTS);
|
|
106
|
+
const tags = options?.tags ?? {};
|
|
107
|
+
const forcedCollection = hasExposedCollector();
|
|
108
|
+
const samples = [];
|
|
109
|
+
const heapLimitBytes = readHeapLimitBytes();
|
|
110
|
+
try {
|
|
111
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
112
|
+
try {
|
|
113
|
+
await options?.setup?.(signal);
|
|
114
|
+
const gc = __addDisposableResource(env_1, new GcRecorder(canObserveGc()), false);
|
|
115
|
+
for (let iteration = 0; iteration < sampling.warmup; iteration++) {
|
|
116
|
+
signal.throwIfAborted();
|
|
117
|
+
await fn(signal);
|
|
118
|
+
}
|
|
119
|
+
const sampler = new Sampler(allOfPolicies([
|
|
120
|
+
createStabilityPolicy((sample) => [sample.allocatedBytes], {
|
|
121
|
+
window: sampling.stableBatches,
|
|
122
|
+
tolerance: sampling.allocationTolerance,
|
|
123
|
+
}),
|
|
124
|
+
createProjectedPolicy((sample) => sample.cpuTimeMs, createRelativeErrorPolicy({
|
|
125
|
+
minRuns: sampling.minRuns,
|
|
126
|
+
relativeError: sampling.relativeError,
|
|
127
|
+
confidenceLevel: sampling.confidenceLevel,
|
|
128
|
+
estimator: "median",
|
|
129
|
+
})),
|
|
130
|
+
]), {
|
|
131
|
+
bounds: {
|
|
132
|
+
minRuns: sampling.minRuns,
|
|
133
|
+
maxRuns: sampling.maxRuns,
|
|
134
|
+
minTimeMs: sampling.minTimeMs,
|
|
135
|
+
maxTimeMs: sampling.maxTimeMs,
|
|
136
|
+
},
|
|
137
|
+
signal,
|
|
138
|
+
// One sample is one batch, and the sampler sizes the next batch in
|
|
139
|
+
// iterations — so this is the cost of one operation, not of the batch
|
|
140
|
+
// the sample came from.
|
|
141
|
+
costOf: (sample) => sample.cpuTimeMs,
|
|
142
|
+
targetBatchMs: sampling.targetBatchMs,
|
|
143
|
+
});
|
|
144
|
+
for (let size = sampler.nextBatch(); size !== null; size = sampler.nextBatch()) {
|
|
145
|
+
const startedAt = performance.now();
|
|
146
|
+
const sample = await measureBatch_(fn, size, gc, signal);
|
|
147
|
+
samples.push(sample);
|
|
148
|
+
sampler.observe([sample], performance.now() - startedAt);
|
|
149
|
+
context.progress(samples.length);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (e_1) {
|
|
153
|
+
env_1.error = e_1;
|
|
154
|
+
env_1.hasError = true;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
__disposeResources(env_1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
let failureMessage;
|
|
162
|
+
try {
|
|
163
|
+
await options?.teardown?.(signal);
|
|
164
|
+
failureMessage = formatError(error);
|
|
165
|
+
}
|
|
166
|
+
catch (tearDownError) {
|
|
167
|
+
failureMessage = formatError(new SuppressedError(tearDownError, error, "teardown failed after case failure"));
|
|
168
|
+
}
|
|
169
|
+
context.setCaseResult({
|
|
170
|
+
name: title,
|
|
171
|
+
tags,
|
|
172
|
+
statistics: null,
|
|
173
|
+
forcedCollection,
|
|
174
|
+
warnings: [],
|
|
175
|
+
failure: failureMessage,
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
await options?.teardown?.(signal);
|
|
180
|
+
const statistics = composeResourceBenchStatistics(samples, heapLimitBytes);
|
|
181
|
+
context.setCaseResult({
|
|
182
|
+
name: title,
|
|
183
|
+
tags,
|
|
184
|
+
statistics,
|
|
185
|
+
forcedCollection,
|
|
186
|
+
warnings: detectResourceBenchWarnings(statistics, forcedCollection),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=_run-case.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_run-case.js","sourceRoot":"","sources":["../src/_run-case.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ResourceBenchSample, ResourceBenchStatistics } from "./_types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Summarise per-batch resource samples.
|
|
4
|
+
*
|
|
5
|
+
* Rates are medians because a batch that happened to absorb a collection is a
|
|
6
|
+
* heavy right tail, exactly as with timings. Peaks are maxima because the
|
|
7
|
+
* question they answer is what had to fit in memory, not what typically did.
|
|
8
|
+
*
|
|
9
|
+
* @param samples Per-operation costs, one entry per batch.
|
|
10
|
+
* @param heapLimitBytes The heap ceiling this process was given.
|
|
11
|
+
* @returns The computed statistics.
|
|
12
|
+
* @throws {RangeError} If `samples` is empty.
|
|
13
|
+
*/
|
|
14
|
+
export declare function composeResourceBenchStatistics(samples: readonly ResourceBenchSample[], heapLimitBytes: number): ResourceBenchStatistics;
|
|
15
|
+
//# sourceMappingURL=_statistics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_statistics.d.ts","sourceRoot":"","sources":["../src/_statistics.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAkB3E;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE,SAAS,mBAAmB,EAAE,EACvC,cAAc,EAAE,MAAM,GACpB,uBAAuB,CAwCzB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { maxBy } from "@ac-kit/algo";
|
|
2
|
+
import { median, sumBy } from "@ac-kit/math-stats";
|
|
3
|
+
function medianBy_(samples, valueOf) {
|
|
4
|
+
return median(Array.from(samples, valueOf));
|
|
5
|
+
}
|
|
6
|
+
function maxValueBy_(samples, valueOf) {
|
|
7
|
+
const peak = maxBy(samples, valueOf);
|
|
8
|
+
return peak === null ? 0 : valueOf(peak);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Summarise per-batch resource samples.
|
|
12
|
+
*
|
|
13
|
+
* Rates are medians because a batch that happened to absorb a collection is a
|
|
14
|
+
* heavy right tail, exactly as with timings. Peaks are maxima because the
|
|
15
|
+
* question they answer is what had to fit in memory, not what typically did.
|
|
16
|
+
*
|
|
17
|
+
* @param samples Per-operation costs, one entry per batch.
|
|
18
|
+
* @param heapLimitBytes The heap ceiling this process was given.
|
|
19
|
+
* @returns The computed statistics.
|
|
20
|
+
* @throws {RangeError} If `samples` is empty.
|
|
21
|
+
*/
|
|
22
|
+
export function composeResourceBenchStatistics(samples, heapLimitBytes) {
|
|
23
|
+
if (samples.length === 0) {
|
|
24
|
+
throw new RangeError("Cannot summarise an empty sample set");
|
|
25
|
+
}
|
|
26
|
+
const cpuTimeMs = medianBy_(samples, (sample) => sample.cpuTimeMs);
|
|
27
|
+
const heapPeakBytes = maxValueBy_(samples, (sample) => sample.heapPeakBytes);
|
|
28
|
+
// One `null` means the runtime never reports collections, so the whole
|
|
29
|
+
// series is absent rather than zero.
|
|
30
|
+
const observedGc = samples[0]?.gcPauseMs !== null;
|
|
31
|
+
const gcPauseMs = observedGc
|
|
32
|
+
? medianBy_(samples, (sample) => sample.gcPauseMs ?? 0)
|
|
33
|
+
: null;
|
|
34
|
+
const gcCount = observedGc
|
|
35
|
+
? medianBy_(samples, (sample) => sample.gcCount ?? 0)
|
|
36
|
+
: null;
|
|
37
|
+
// Ratios come from the totals rather than from the medians, so a collection
|
|
38
|
+
// that landed in one batch is not divided by another batch's CPU time.
|
|
39
|
+
const totalCpuTimeMs = sumBy(samples, (sample) => sample.cpuTimeMs);
|
|
40
|
+
const totalGcPauseMs = sumBy(samples, (sample) => sample.gcPauseMs ?? 0);
|
|
41
|
+
return {
|
|
42
|
+
samples: samples.length,
|
|
43
|
+
operations: sumBy(samples, (sample) => sample.operations),
|
|
44
|
+
cpuTimeMs,
|
|
45
|
+
allocatedBytes: medianBy_(samples, (sample) => sample.allocatedBytes),
|
|
46
|
+
gcPauseMs,
|
|
47
|
+
gcCount,
|
|
48
|
+
heapPeakBytes,
|
|
49
|
+
rssPeakBytes: maxValueBy_(samples, (sample) => sample.rssBytes),
|
|
50
|
+
heapLimitRatio: heapLimitBytes > 0 ? heapPeakBytes / heapLimitBytes : 0,
|
|
51
|
+
gcTimeRatio: observedGc && totalCpuTimeMs > 0 ? totalGcPauseMs / totalCpuTimeMs : null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=_statistics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_statistics.js","sourceRoot":"","sources":["../src/_statistics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAInD,SAAS,SAAS,CACjB,OAAuC,EACvC,OAAgD;IAEhD,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,WAAW,CACnB,OAAuC,EACvC,OAAgD;IAEhD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAErC,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,8BAA8B,CAC7C,OAAuC,EACvC,cAAsB;IAEtB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,UAAU,CAAC,sCAAsC,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnE,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAE7E,uEAAuE;IACvE,qCAAqC;IACrC,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IAElD,MAAM,SAAS,GAAG,UAAU;QAC3B,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;QACvD,CAAC,CAAC,IAAI,CAAC;IACR,MAAM,OAAO,GAAG,UAAU;QACzB,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;QACrD,CAAC,CAAC,IAAI,CAAC;IAER,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;IAEzE,OAAO;QACN,OAAO,EAAE,OAAO,CAAC,MAAM;QACvB,UAAU,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC;QAEzD,SAAS;QACT,cAAc,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC;QACrE,SAAS;QACT,OAAO;QAEP,aAAa;QACb,YAAY,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;QAE/D,cAAc,EAAE,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QACvE,WAAW,EACV,UAAU,IAAI,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI;KAC1E,CAAC;AACH,CAAC"}
|
package/dist/_types.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
/**
|
|
3
|
+
* One batch's cost, already divided by the operations it ran, so every sample
|
|
4
|
+
* is directly comparable regardless of how the sampler sized its batches.
|
|
5
|
+
*/
|
|
6
|
+
export declare const resourceBenchSampleSchema: z.ZodMiniObject<{
|
|
7
|
+
operations: z.ZodMiniNumber<number>;
|
|
8
|
+
cpuTimeMs: z.ZodMiniNumber<number>;
|
|
9
|
+
allocatedBytes: z.ZodMiniNumber<number>;
|
|
10
|
+
heapPeakBytes: z.ZodMiniNumber<number>;
|
|
11
|
+
rssBytes: z.ZodMiniNumber<number>;
|
|
12
|
+
gcCount: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
13
|
+
gcPauseMs: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type ResourceBenchSample = z.infer<typeof resourceBenchSampleSchema>;
|
|
16
|
+
declare const resourceBenchStatisticsSchema: z.ZodMiniObject<{
|
|
17
|
+
samples: z.ZodMiniNumber<number>;
|
|
18
|
+
operations: z.ZodMiniNumber<number>;
|
|
19
|
+
cpuTimeMs: z.ZodMiniNumber<number>;
|
|
20
|
+
allocatedBytes: z.ZodMiniNumber<number>;
|
|
21
|
+
gcPauseMs: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
22
|
+
gcCount: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
23
|
+
heapPeakBytes: z.ZodMiniNumber<number>;
|
|
24
|
+
rssPeakBytes: z.ZodMiniNumber<number>;
|
|
25
|
+
heapLimitRatio: z.ZodMiniNumber<number>;
|
|
26
|
+
gcTimeRatio: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export type ResourceBenchStatistics = z.infer<typeof resourceBenchStatisticsSchema>;
|
|
29
|
+
export declare const resourceBenchRunCaseResultSchema: z.ZodMiniObject<{
|
|
30
|
+
name: z.ZodMiniString<string>;
|
|
31
|
+
tags: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
|
|
32
|
+
statistics: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
33
|
+
samples: z.ZodMiniNumber<number>;
|
|
34
|
+
operations: z.ZodMiniNumber<number>;
|
|
35
|
+
cpuTimeMs: z.ZodMiniNumber<number>;
|
|
36
|
+
allocatedBytes: z.ZodMiniNumber<number>;
|
|
37
|
+
gcPauseMs: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
38
|
+
gcCount: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
39
|
+
heapPeakBytes: z.ZodMiniNumber<number>;
|
|
40
|
+
rssPeakBytes: z.ZodMiniNumber<number>;
|
|
41
|
+
heapLimitRatio: z.ZodMiniNumber<number>;
|
|
42
|
+
gcTimeRatio: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
43
|
+
}, z.core.$strip>>;
|
|
44
|
+
forcedCollection: z.ZodMiniBoolean<boolean>;
|
|
45
|
+
warnings: z.ZodMiniArray<z.ZodMiniObject<{
|
|
46
|
+
kind: z.ZodMiniEnum<{
|
|
47
|
+
"below-resolution": "below-resolution";
|
|
48
|
+
"gc-bound": "gc-bound";
|
|
49
|
+
"heap-pressure": "heap-pressure";
|
|
50
|
+
"no-forced-collection": "no-forced-collection";
|
|
51
|
+
"too-few-samples": "too-few-samples";
|
|
52
|
+
}>;
|
|
53
|
+
message: z.ZodMiniString<string>;
|
|
54
|
+
}, z.core.$strip>>;
|
|
55
|
+
failure: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export type ResourceBenchRunCaseResult = z.infer<typeof resourceBenchRunCaseResultSchema>;
|
|
58
|
+
export declare const resourceBenchRunConditionResultSchema: z.ZodMiniObject<{
|
|
59
|
+
title: z.ZodMiniString<string>;
|
|
60
|
+
results: z.ZodMiniArray<z.ZodMiniObject<{
|
|
61
|
+
name: z.ZodMiniString<string>;
|
|
62
|
+
tags: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
|
|
63
|
+
statistics: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
64
|
+
samples: z.ZodMiniNumber<number>;
|
|
65
|
+
operations: z.ZodMiniNumber<number>;
|
|
66
|
+
cpuTimeMs: z.ZodMiniNumber<number>;
|
|
67
|
+
allocatedBytes: z.ZodMiniNumber<number>;
|
|
68
|
+
gcPauseMs: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
69
|
+
gcCount: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
70
|
+
heapPeakBytes: z.ZodMiniNumber<number>;
|
|
71
|
+
rssPeakBytes: z.ZodMiniNumber<number>;
|
|
72
|
+
heapLimitRatio: z.ZodMiniNumber<number>;
|
|
73
|
+
gcTimeRatio: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
74
|
+
}, z.core.$strip>>;
|
|
75
|
+
forcedCollection: z.ZodMiniBoolean<boolean>;
|
|
76
|
+
warnings: z.ZodMiniArray<z.ZodMiniObject<{
|
|
77
|
+
kind: z.ZodMiniEnum<{
|
|
78
|
+
"below-resolution": "below-resolution";
|
|
79
|
+
"gc-bound": "gc-bound";
|
|
80
|
+
"heap-pressure": "heap-pressure";
|
|
81
|
+
"no-forced-collection": "no-forced-collection";
|
|
82
|
+
"too-few-samples": "too-few-samples";
|
|
83
|
+
}>;
|
|
84
|
+
message: z.ZodMiniString<string>;
|
|
85
|
+
}, z.core.$strip>>;
|
|
86
|
+
failure: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
87
|
+
}, z.core.$strip>>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
export type ResourceBenchRunConditionResult = z.infer<typeof resourceBenchRunConditionResultSchema>;
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=_types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_types.d.ts","sourceRoot":"","sources":["../src/_types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAI9B;;;GAGG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;iBASpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,QAAA,MAAM,6BAA6B;;;;;;;;;;;iBAmBjC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,6BAA6B,CACpC,CAAC;AAEF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAc3C,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAC/C,OAAO,gCAAgC,CACvC,CAAC;AAEF,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGhD,CAAC;AACH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CACpD,OAAO,qCAAqC,CAC5C,CAAC"}
|
package/dist/_types.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
import { resourceBenchWarningSchema } from "./_warnings.js";
|
|
3
|
+
/**
|
|
4
|
+
* One batch's cost, already divided by the operations it ran, so every sample
|
|
5
|
+
* is directly comparable regardless of how the sampler sized its batches.
|
|
6
|
+
*/
|
|
7
|
+
export const resourceBenchSampleSchema = z.object({
|
|
8
|
+
operations: z.number(),
|
|
9
|
+
cpuTimeMs: z.number(),
|
|
10
|
+
allocatedBytes: z.number(),
|
|
11
|
+
heapPeakBytes: z.number(),
|
|
12
|
+
rssBytes: z.number(),
|
|
13
|
+
/** `null` where the runtime does not report collections. */
|
|
14
|
+
gcCount: z.nullable(z.number()),
|
|
15
|
+
gcPauseMs: z.nullable(z.number()),
|
|
16
|
+
});
|
|
17
|
+
const resourceBenchStatisticsSchema = z.object({
|
|
18
|
+
samples: z.number(),
|
|
19
|
+
operations: z.number(),
|
|
20
|
+
/** Medians, per operation, except the peaks which are maxima over the run. */
|
|
21
|
+
cpuTimeMs: z.number(),
|
|
22
|
+
allocatedBytes: z.number(),
|
|
23
|
+
/** `null` where the runtime does not report collections. */
|
|
24
|
+
gcPauseMs: z.nullable(z.number()),
|
|
25
|
+
gcCount: z.nullable(z.number()),
|
|
26
|
+
heapPeakBytes: z.number(),
|
|
27
|
+
rssPeakBytes: z.number(),
|
|
28
|
+
/** Share of the heap limit the peak reached, in [0, 1]. */
|
|
29
|
+
heapLimitRatio: z.number(),
|
|
30
|
+
/** Fraction of the CPU time spent collecting, in [0, 1], or `null`. */
|
|
31
|
+
gcTimeRatio: z.nullable(z.number()),
|
|
32
|
+
});
|
|
33
|
+
export const resourceBenchRunCaseResultSchema = z.object({
|
|
34
|
+
name: z.string(),
|
|
35
|
+
tags: z.record(z.string(), z.string()),
|
|
36
|
+
/** `null` when the case failed. */
|
|
37
|
+
statistics: z.nullable(resourceBenchStatisticsSchema),
|
|
38
|
+
/** Whether `--expose-gc` was available, so the numbers can be read fairly. */
|
|
39
|
+
forcedCollection: z.boolean(),
|
|
40
|
+
warnings: z.array(resourceBenchWarningSchema),
|
|
41
|
+
/** Present when the case threw. */
|
|
42
|
+
failure: z.optional(z.string()),
|
|
43
|
+
});
|
|
44
|
+
export const resourceBenchRunConditionResultSchema = z.object({
|
|
45
|
+
title: z.string(),
|
|
46
|
+
results: z.array(resourceBenchRunCaseResultSchema),
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=_types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_types.js","sourceRoot":"","sources":["../src/_types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAE9B,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAE5D;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,4DAA4D;IAC5D,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CACjC,CAAC,CAAC;AAGH,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IAEtB,8EAA8E;IAC9E,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAC1B,4DAA4D;IAC5D,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAE/B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IAExB,2DAA2D;IAC3D,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAE1B,uEAAuE;IACvE,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CACnC,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC,MAAM,CAAC;IACxD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IAEtC,mCAAmC;IACnC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,6BAA6B,CAAC;IAErD,8EAA8E;IAC9E,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE;IAE7B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,0BAA0B,CAAC;IAE7C,mCAAmC;IACnC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC/B,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,gCAAgC,CAAC;CAClD,CAAC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
import { ResourceBenchStatistics } from "./_types.js";
|
|
3
|
+
export declare const resourceBenchWarningKindSchema: z.ZodMiniEnum<{
|
|
4
|
+
"below-resolution": "below-resolution";
|
|
5
|
+
"gc-bound": "gc-bound";
|
|
6
|
+
"heap-pressure": "heap-pressure";
|
|
7
|
+
"no-forced-collection": "no-forced-collection";
|
|
8
|
+
"too-few-samples": "too-few-samples";
|
|
9
|
+
}>;
|
|
10
|
+
export type ResourceBenchWarningKind = z.infer<typeof resourceBenchWarningKindSchema>;
|
|
11
|
+
export declare const resourceBenchWarningSchema: z.ZodMiniObject<{
|
|
12
|
+
kind: z.ZodMiniEnum<{
|
|
13
|
+
"below-resolution": "below-resolution";
|
|
14
|
+
"gc-bound": "gc-bound";
|
|
15
|
+
"heap-pressure": "heap-pressure";
|
|
16
|
+
"no-forced-collection": "no-forced-collection";
|
|
17
|
+
"too-few-samples": "too-few-samples";
|
|
18
|
+
}>;
|
|
19
|
+
message: z.ZodMiniString<string>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export type ResourceBenchWarning = z.infer<typeof resourceBenchWarningSchema>;
|
|
22
|
+
export declare function detectResourceBenchWarnings(statistics: ResourceBenchStatistics, forcedCollection: boolean): ResourceBenchWarning[];
|
|
23
|
+
//# sourceMappingURL=_warnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_warnings.d.ts","sourceRoot":"","sources":["../src/_warnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAE9B,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAWtD,eAAO,MAAM,8BAA8B;;;;;;EAMzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,8BAA8B,CACrC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;;;;iBAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,wBAAgB,2BAA2B,CAC1C,UAAU,EAAE,uBAAuB,EACnC,gBAAgB,EAAE,OAAO,GACvB,oBAAoB,EAAE,CA4CxB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
/** Share of the heap limit above which a peak is worth flagging. */
|
|
3
|
+
const HEAP_PRESSURE_RATIO_ = 0.8;
|
|
4
|
+
/** Share of CPU time spent collecting above which the case is GC-bound. */
|
|
5
|
+
const GC_BOUND_RATIO_ = 0.25;
|
|
6
|
+
/** Below this many bytes per operation, allocation is below what GC resolves. */
|
|
7
|
+
const ALLOCATION_RESOLUTION_BYTES_ = 8;
|
|
8
|
+
export const resourceBenchWarningKindSchema = z.enum([
|
|
9
|
+
"no-forced-collection",
|
|
10
|
+
"heap-pressure",
|
|
11
|
+
"gc-bound",
|
|
12
|
+
"below-resolution",
|
|
13
|
+
"too-few-samples",
|
|
14
|
+
]);
|
|
15
|
+
export const resourceBenchWarningSchema = z.object({
|
|
16
|
+
kind: resourceBenchWarningKindSchema,
|
|
17
|
+
message: z.string(),
|
|
18
|
+
});
|
|
19
|
+
export function detectResourceBenchWarnings(statistics, forcedCollection) {
|
|
20
|
+
const warnings = [];
|
|
21
|
+
if (!forcedCollection) {
|
|
22
|
+
warnings.push({
|
|
23
|
+
kind: "no-forced-collection",
|
|
24
|
+
message: "run without --expose-gc: allocation includes whatever the collector had not yet reclaimed, so compare cases within this run only",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (statistics.samples < 5) {
|
|
28
|
+
warnings.push({
|
|
29
|
+
kind: "too-few-samples",
|
|
30
|
+
message: `only ${statistics.samples} batches; the allocation figure has not been shown to settle`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (statistics.heapLimitRatio >= HEAP_PRESSURE_RATIO_) {
|
|
34
|
+
warnings.push({
|
|
35
|
+
kind: "heap-pressure",
|
|
36
|
+
message: `peak heap reached ${(statistics.heapLimitRatio * 100).toFixed(0)}% of the limit; the collector was working against the case and the timing reflects that`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (statistics.gcTimeRatio !== null &&
|
|
40
|
+
statistics.gcTimeRatio >= GC_BOUND_RATIO_) {
|
|
41
|
+
warnings.push({
|
|
42
|
+
kind: "gc-bound",
|
|
43
|
+
message: `${(statistics.gcTimeRatio * 100).toFixed(0)}% of CPU time was spent collecting; this case is bound by what it allocates, not by what it computes`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (statistics.allocatedBytes < ALLOCATION_RESOLUTION_BYTES_) {
|
|
47
|
+
warnings.push({
|
|
48
|
+
kind: "below-resolution",
|
|
49
|
+
message: "allocation per operation is at the limit of what the heap counters resolve; treat as zero",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return warnings;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=_warnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_warnings.js","sourceRoot":"","sources":["../src/_warnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAI9B,oEAAoE;AACpE,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAEjC,2EAA2E;AAC3E,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B,iFAAiF;AACjF,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAEvC,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,IAAI,CAAC;IACpD,sBAAsB;IACtB,eAAe;IACf,UAAU;IACV,kBAAkB;IAClB,iBAAiB;CACjB,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,IAAI,EAAE,8BAA8B;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAC;AAGH,MAAM,UAAU,2BAA2B,CAC1C,UAAmC,EACnC,gBAAyB;IAEzB,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAE5C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EACN,kIAAkI;SACnI,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,8DAA8D;SACjG,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,cAAc,IAAI,oBAAoB,EAAE,CAAC;QACvD,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,qBAAqB,CAAC,UAAU,CAAC,cAAc,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,yFAAyF;SACnK,CAAC,CAAC;IACJ,CAAC;IAED,IACC,UAAU,CAAC,WAAW,KAAK,IAAI;QAC/B,UAAU,CAAC,WAAW,IAAI,eAAe,EACxC,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,GAAG,CAAC,UAAU,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sGAAsG;SAC3J,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,cAAc,GAAG,4BAA4B,EAAE,CAAC;QAC9D,QAAQ,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,kBAAkB;YACxB,OAAO,EACN,2FAA2F;SAC5F,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC"}
|
package/dist/decl.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaybeAsyncCallableNoArgs } from "@ac-kit/core";
|
|
2
|
+
import { ResourceCaseFn } from "./_run-case.js";
|
|
3
|
+
import { ResourceCaseOptions, ResourceConditionOptions } from "./options.js";
|
|
4
|
+
export declare function resourceCondition(title: string, fn: MaybeAsyncCallableNoArgs): void;
|
|
5
|
+
export declare function resourceCondition(title: string, options: ResourceConditionOptions, fn: MaybeAsyncCallableNoArgs): void;
|
|
6
|
+
export declare function resourceCase(title: string, fn: ResourceCaseFn): void;
|
|
7
|
+
export declare function resourceCase(title: string, options: ResourceCaseOptions, fn: ResourceCaseFn): void;
|
|
8
|
+
//# sourceMappingURL=decl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decl.d.ts","sourceRoot":"","sources":["../src/decl.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,cAAc,EAAmB,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAE7E,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,wBAAwB,GAC1B,IAAI,CAAC;AACR,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,wBAAwB,EACjC,EAAE,EAAE,wBAAwB,GAC1B,IAAI,CAAC;AAgBR,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CAAC;AACtE,wBAAgB,YAAY,CAC3B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,mBAAmB,EAC5B,EAAE,EAAE,cAAc,GAChB,IAAI,CAAC"}
|
package/dist/decl.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { registerCase, registerCondition } from "@ac-bench/core/runner";
|
|
2
|
+
import { runResourceCase } from "./_run-case.js";
|
|
3
|
+
export function resourceCondition(title, optionsOrFn, fn) {
|
|
4
|
+
const [options, callback] = typeof optionsOrFn === "function"
|
|
5
|
+
? [undefined, optionsOrFn]
|
|
6
|
+
: [optionsOrFn, fn];
|
|
7
|
+
registerCondition("resource", title, callback, {
|
|
8
|
+
additionalRunArgs: [options],
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function resourceCase(title, optionsOrFn, fn) {
|
|
12
|
+
const isOptionsForm = typeof optionsOrFn !== "function";
|
|
13
|
+
const options = isOptionsForm ? optionsOrFn : undefined;
|
|
14
|
+
const caseFn = isOptionsForm ? fn : optionsOrFn;
|
|
15
|
+
registerCase("resource", title, (context, conditionOptions) => runResourceCase(title, caseFn, context, {
|
|
16
|
+
...options,
|
|
17
|
+
conditionOptions,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=decl.js.map
|
package/dist/decl.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decl.js","sourceRoot":"","sources":["../src/decl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGxE,OAAO,EAAkB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAYjE,MAAM,UAAU,iBAAiB,CAChC,KAAa,EACb,WAAgE,EAChE,EAA6B;IAE7B,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,GACxB,OAAO,WAAW,KAAK,UAAU;QAChC,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC;QAC1B,CAAC,CAAC,CAAC,WAAW,EAAE,EAAG,CAAC,CAAC;IAEvB,iBAAiB,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE;QAC9C,iBAAiB,EAAE,CAAC,OAAO,CAAC;KAC5B,CAAC,CAAC;AACJ,CAAC;AAQD,MAAM,UAAU,YAAY,CAC3B,KAAa,EACb,WAAiD,EACjD,EAAmB;IAEnB,MAAM,aAAa,GAAG,OAAO,WAAW,KAAK,UAAU,CAAC;IACxD,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,EAAG,CAAC,CAAC,CAAC,WAAW,CAAC;IAEjD,YAAY,CACX,UAAU,EACV,KAAK,EACL,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAC7B,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QACvC,GAAG,OAAO;QACV,gBAAgB;KAChB,CAAC,CACH,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,QAAQ,EACR,SAAS,EACT,SAAS,EACT,UAAU,GACV,MAAM,uBAAuB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,QAAQ,EACR,SAAS,EACT,SAAS,EACT,UAAU,GACV,MAAM,uBAAuB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CaseCommonOptions, SamplingBounds } from "@ac-bench/core/runner";
|
|
2
|
+
export type ResourceSampling = SamplingBounds & {
|
|
3
|
+
/** Batches run and discarded first, letting the JIT settle. Defaults to 2. */
|
|
4
|
+
warmup: number;
|
|
5
|
+
/**
|
|
6
|
+
* How tight the interval around CPU time must be before sampling stops.
|
|
7
|
+
*
|
|
8
|
+
* The byte counters are judged by settling instead, since a count that never
|
|
9
|
+
* varies has no interval worth tightening.
|
|
10
|
+
*/
|
|
11
|
+
relativeError: number | "auto" | "off";
|
|
12
|
+
/** Confidence level for the CPU-time interval. Defaults to 0.95. */
|
|
13
|
+
confidenceLevel: number;
|
|
14
|
+
/**
|
|
15
|
+
* Relative spread within which the byte counters count as settled. Defaults
|
|
16
|
+
* to 0.02 — a heap delta repeats to within a fraction of a percent, not to
|
|
17
|
+
* the byte.
|
|
18
|
+
*/
|
|
19
|
+
allocationTolerance: number;
|
|
20
|
+
/** Batches whose byte counters must agree before stopping. Defaults to 3. */
|
|
21
|
+
stableBatches: number;
|
|
22
|
+
/**
|
|
23
|
+
* Work one batch should amount to, in milliseconds. Defaults to 50 — far
|
|
24
|
+
* longer than a duration measure wants, because the counters are read once
|
|
25
|
+
* per batch and that cost is divided by the operations in it. A batch of one
|
|
26
|
+
* reports the measure's own bookkeeping as the case's allocation.
|
|
27
|
+
*/
|
|
28
|
+
targetBatchMs: number;
|
|
29
|
+
};
|
|
30
|
+
export declare const RESOURCE_SAMPLING_DEFAULTS: {
|
|
31
|
+
readonly warmup: 2;
|
|
32
|
+
readonly minRuns: 10;
|
|
33
|
+
readonly maxRuns: 2000;
|
|
34
|
+
readonly minTimeMs: 250;
|
|
35
|
+
readonly maxTimeMs: 10000;
|
|
36
|
+
readonly relativeError: "auto";
|
|
37
|
+
readonly confidenceLevel: 0.95;
|
|
38
|
+
readonly allocationTolerance: 0.02;
|
|
39
|
+
readonly stableBatches: 3;
|
|
40
|
+
readonly targetBatchMs: 50;
|
|
41
|
+
};
|
|
42
|
+
export type ResourceConditionOptions = {
|
|
43
|
+
sampling?: Partial<ResourceSampling>;
|
|
44
|
+
};
|
|
45
|
+
export type ResourceCaseOptions = CaseCommonOptions;
|
|
46
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAC/C,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IAEvC,oEAAoE;IACpE,eAAe,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,0BAA0B;qBAC9B,CAAC;sBACA,EAAE;sBACF,IAAI;wBACF,GAAG;wBACH,KAAM;4BACF,MAAM;8BACJ,IAAI;kCACA,IAAI;4BACV,CAAC;4BACD,EAAE;CACmB,CAAC;AAEtC,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const RESOURCE_SAMPLING_DEFAULTS = {
|
|
2
|
+
warmup: 2,
|
|
3
|
+
minRuns: 10,
|
|
4
|
+
maxRuns: 2000,
|
|
5
|
+
minTimeMs: 250,
|
|
6
|
+
maxTimeMs: 10000,
|
|
7
|
+
relativeError: "auto",
|
|
8
|
+
confidenceLevel: 0.95,
|
|
9
|
+
allocationTolerance: 0.02,
|
|
10
|
+
stableBatches: 3,
|
|
11
|
+
targetBatchMs: 50,
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACzC,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,GAAG;IACd,SAAS,EAAE,KAAM;IACjB,aAAa,EAAE,MAAM;IACrB,eAAe,EAAE,IAAI;IACrB,mBAAmB,EAAE,IAAI;IACzB,aAAa,EAAE,CAAC;IAChB,aAAa,EAAE,EAAE;CACmB,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":""}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { defineMeasurePlugin, MeasureResultValidationError, } from "@ac-bench/core/plugin";
|
|
2
|
+
import { BYTE, MILLISECOND } from "@ac-kit/model-dataset";
|
|
3
|
+
import * as z from "zod/mini";
|
|
4
|
+
import { resourceBenchRunCaseResultSchema, resourceBenchRunConditionResultSchema, } from "./_types.js";
|
|
5
|
+
const MEASURE_ID = "resource";
|
|
6
|
+
function parseCaseResult_(value) {
|
|
7
|
+
const result = resourceBenchRunCaseResultSchema.safeParse(value);
|
|
8
|
+
if (!result.success) {
|
|
9
|
+
throw new MeasureResultValidationError(MEASURE_ID, z.prettifyError(result.error));
|
|
10
|
+
}
|
|
11
|
+
return result.data;
|
|
12
|
+
}
|
|
13
|
+
function parseConditionResult_(value) {
|
|
14
|
+
const result = resourceBenchRunConditionResultSchema.safeParse(value);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw new MeasureResultValidationError(MEASURE_ID, z.prettifyError(result.error));
|
|
17
|
+
}
|
|
18
|
+
return result.data;
|
|
19
|
+
}
|
|
20
|
+
const BYTE_FORMAT = { maximumFractionDigits: 0 };
|
|
21
|
+
const MS_FORMAT = { minimumFractionDigits: 4, maximumFractionDigits: 4 };
|
|
22
|
+
const RATIO_FORMAT = { style: "percent", maximumFractionDigits: 1 };
|
|
23
|
+
const FIELDS = [
|
|
24
|
+
{ name: "case", kind: "nominal", title: "case" },
|
|
25
|
+
{
|
|
26
|
+
name: "allocatedBytes",
|
|
27
|
+
kind: "quantitative",
|
|
28
|
+
title: "alloc/op",
|
|
29
|
+
unit: BYTE,
|
|
30
|
+
direction: "lower-is-better",
|
|
31
|
+
format: BYTE_FORMAT,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "cpuTimeMs",
|
|
35
|
+
kind: "quantitative",
|
|
36
|
+
title: "cpu/op",
|
|
37
|
+
unit: MILLISECOND,
|
|
38
|
+
direction: "lower-is-better",
|
|
39
|
+
format: MS_FORMAT,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "heapPeakBytes",
|
|
43
|
+
kind: "quantitative",
|
|
44
|
+
title: "heap peak",
|
|
45
|
+
unit: BYTE,
|
|
46
|
+
direction: "lower-is-better",
|
|
47
|
+
format: BYTE_FORMAT,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "rssPeakBytes",
|
|
51
|
+
kind: "quantitative",
|
|
52
|
+
title: "rss peak",
|
|
53
|
+
unit: BYTE,
|
|
54
|
+
direction: "lower-is-better",
|
|
55
|
+
format: BYTE_FORMAT,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "gcPauseMs",
|
|
59
|
+
kind: "quantitative",
|
|
60
|
+
title: "gc/op",
|
|
61
|
+
unit: MILLISECOND,
|
|
62
|
+
direction: "lower-is-better",
|
|
63
|
+
format: MS_FORMAT,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "gcTimeRatio",
|
|
67
|
+
kind: "quantitative",
|
|
68
|
+
title: "gc share",
|
|
69
|
+
direction: "lower-is-better",
|
|
70
|
+
format: RATIO_FORMAT,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "samples",
|
|
74
|
+
kind: "quantitative",
|
|
75
|
+
title: "n",
|
|
76
|
+
format: { maximumFractionDigits: 0 },
|
|
77
|
+
},
|
|
78
|
+
{ name: "status", kind: "nominal", title: "status" },
|
|
79
|
+
];
|
|
80
|
+
function caseRow_(result) {
|
|
81
|
+
const stats = result.statistics;
|
|
82
|
+
if (!stats) {
|
|
83
|
+
return [
|
|
84
|
+
result.name,
|
|
85
|
+
null,
|
|
86
|
+
null,
|
|
87
|
+
null,
|
|
88
|
+
null,
|
|
89
|
+
null,
|
|
90
|
+
null,
|
|
91
|
+
null,
|
|
92
|
+
`FAILED: ${result.failure ?? "unknown"}`,
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
result.name,
|
|
97
|
+
stats.allocatedBytes,
|
|
98
|
+
stats.cpuTimeMs,
|
|
99
|
+
stats.heapPeakBytes,
|
|
100
|
+
stats.rssPeakBytes,
|
|
101
|
+
stats.gcPauseMs,
|
|
102
|
+
stats.gcTimeRatio,
|
|
103
|
+
stats.samples,
|
|
104
|
+
result.forcedCollection ? null : "no --expose-gc",
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
export default defineMeasurePlugin({
|
|
108
|
+
id: MEASURE_ID,
|
|
109
|
+
label: "Resource",
|
|
110
|
+
parseCaseResult: parseCaseResult_,
|
|
111
|
+
parseConditionResult: parseConditionResult_,
|
|
112
|
+
fields: FIELDS,
|
|
113
|
+
caseRow: caseRow_,
|
|
114
|
+
// Bytes allocated is the figure this measure exists to compare, and it is
|
|
115
|
+
// deterministic enough to order cases by without an interval.
|
|
116
|
+
comparison: { field: "allocatedBytes" },
|
|
117
|
+
warnings: (result) => {
|
|
118
|
+
const diagnostics = [];
|
|
119
|
+
if (result.failure) {
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
severity: "error",
|
|
122
|
+
code: "case-failed",
|
|
123
|
+
message: result.failure,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
for (const warning of result.warnings) {
|
|
127
|
+
diagnostics.push({
|
|
128
|
+
severity: "warning",
|
|
129
|
+
code: warning.kind,
|
|
130
|
+
message: warning.message,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return diagnostics;
|
|
134
|
+
},
|
|
135
|
+
// A shared process makes heap attribution impossible — case A's garbage is
|
|
136
|
+
// in case B's heap — so each case gets its own, and interleaving rounds
|
|
137
|
+
// would defeat the same thing at a coarser grain.
|
|
138
|
+
scheduling: {
|
|
139
|
+
rounds: "one",
|
|
140
|
+
grouping: "case",
|
|
141
|
+
execArgv: ["--expose-gc"],
|
|
142
|
+
},
|
|
143
|
+
// Each execution measured its own process's heap. Pooling them would average
|
|
144
|
+
// two heaps that never coexisted, so the first is reported as it stands.
|
|
145
|
+
pool: (executions) => executions[0].result,
|
|
146
|
+
toJsonCase: (result) => result,
|
|
147
|
+
toJsonCondition: (result) => ({
|
|
148
|
+
resourceConditions: [{ kind: "resource", ...result }],
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ac-bench/measure-resource",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Resource measure for @ac-bench: CPU time, peak heap, bytes allocated, and garbage-collection count and pause time.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"allocation",
|
|
7
|
+
"benchmark",
|
|
8
|
+
"cpu",
|
|
9
|
+
"gc",
|
|
10
|
+
"memory"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/anthochamp/node-essentials/tree/main/bench/measure-resource#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/anthochamp/node-essentials/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "BSD-3-Clause",
|
|
17
|
+
"author": "Anthony Champagne <dev@anthonychampagne.fr>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/anthochamp/node-essentials.git",
|
|
21
|
+
"directory": "bench/measure-resource"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./dist/index.js",
|
|
31
|
+
"./plugin": "./dist/plugin.js"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./dist/index.js",
|
|
37
|
+
"./plugin": "./dist/plugin.js"
|
|
38
|
+
},
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"compile": "tsc -p ./tsconfig.build.json --outDir dist",
|
|
43
|
+
"compile:check": "tsc -noEmit -p ./tsconfig.json",
|
|
44
|
+
"clean": "rimraf dist",
|
|
45
|
+
"build": "yarn clean && yarn compile",
|
|
46
|
+
"test": "yarn run -T vitest run",
|
|
47
|
+
"prepack": "yarn build"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@ac-bench/core": "^0.1.0",
|
|
51
|
+
"@ac-kit/algo": "^0.1.0",
|
|
52
|
+
"@ac-kit/app-report": "^0.1.0",
|
|
53
|
+
"@ac-kit/core": "^0.1.0",
|
|
54
|
+
"@ac-kit/math-stats": "^0.1.0",
|
|
55
|
+
"@ac-kit/model-dataset": "^0.1.0",
|
|
56
|
+
"zod": "^4.5.4"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@ac-kit/tsconfig": "0.1.0",
|
|
60
|
+
"@ac-kit/typedoc-config": "0.1.0",
|
|
61
|
+
"@ac-kit/vitest-config": "0.1.0",
|
|
62
|
+
"@types/node": "26.5.1",
|
|
63
|
+
"@typescript/native": "typescript@7.0.2",
|
|
64
|
+
"rimraf": "6.1.3",
|
|
65
|
+
"typedoc": "0.28.20",
|
|
66
|
+
"typescript": "@typescript/typescript6@6.0.2",
|
|
67
|
+
"vite": "8.3.0",
|
|
68
|
+
"vitest": "5.0.0"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=24"
|
|
72
|
+
}
|
|
73
|
+
}
|