@zakkster/lite-perf-gate 1.2.2 → 1.4.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/CHANGELOG.md +337 -0
- package/PerfGate.d.ts +86 -3
- package/PerfGate.js +666 -100
- package/llms.txt +69 -10
- package/package.json +8 -1
package/llms.txt
CHANGED
|
@@ -4,10 +4,20 @@ Zero-GC and performance regression gate for node:test.
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Proves a hot path allocates nothing, or names what did.
|
|
8
|
-
1. Scavenge count (perf_hooks GC minor) -- transient allocation.
|
|
7
|
+
Proves a hot path allocates nothing, or names what did. Five signals:
|
|
8
|
+
1. Scavenge count (perf_hooks GC minor) -- transient young-gen allocation.
|
|
9
9
|
2. Custom counters (user-supplied statsOf) -- exact engine internals.
|
|
10
10
|
3. Retained-heap delta (memoryUsage + gc) -- leak detector.
|
|
11
|
+
4. Old-gen activity (perf_hooks GC major + incremental) -- churn that
|
|
12
|
+
promotes or marks old space (PG-03a). GATE signal, default max 0.
|
|
13
|
+
5. External/arrayBuffers delta (memoryUsage().arrayBuffers + gc) --
|
|
14
|
+
backing-store memory heapUsed excludes (PG-03b). Default max 64 KB.
|
|
15
|
+
|
|
16
|
+
Signals 4 and 5 are GATE signals, not a profiler: when either trips, the
|
|
17
|
+
reason string names it and points at @zakkster/lite-gc-profiler for
|
|
18
|
+
diagnosis. New in v1.4.0; new defaults CAN fail a previously-green suite
|
|
19
|
+
exactly when a hot path fires an old-gen collection or grows external
|
|
20
|
+
memory. See decisions/0003 for the GC-kind census that chose them.
|
|
11
21
|
|
|
12
22
|
Scaling verdict: measure at N and k*N. Zero-alloc => ~0 scavenges at both.
|
|
13
23
|
Detector self-validation: positive control (must scavenge) + negative
|
|
@@ -22,10 +32,21 @@ zgcSuite(config) -- register node:test cases.
|
|
|
22
32
|
config.mustFail: Scenario[] -- must trip the gate (self-test).
|
|
23
33
|
config.N: number (default 200000), config.k: number (default 8).
|
|
24
34
|
config.flushMs: number (default 100, or PERF_GATE_FLUSH_MS env var).
|
|
35
|
+
config.allowEmpty: boolean -- controls-only detector smoke run (scenarios [] otherwise throws).
|
|
36
|
+
config.allowNoGc: boolean -- run without --expose-gc; retained is not gated, result.retainedReliable is false.
|
|
25
37
|
|
|
26
|
-
measure(scenario, {N, k, flushMs}) -- raw MeasureResult.
|
|
38
|
+
measure(scenario, {N, k, flushMs, allowNoGc}) -- raw MeasureResult.
|
|
39
|
+
N integer >=1, k integer >=2, flushMs finite >=0 (0 honored); bad values throw.
|
|
27
40
|
verdict(result, thresholds) -- { pass: boolean, reasons: string[] }.
|
|
28
|
-
|
|
41
|
+
Bad thresholds throw. thresholds.maxOldGen (default 0),
|
|
42
|
+
thresholds.maxArrayBuffersKB (default 64) join maxScavenges/maxRetainedKB.
|
|
43
|
+
Fail-closed reasons: '<signal>: not a number (fail closed)',
|
|
44
|
+
'<key>: no such counter measured (statsOf keys: ...)'. Over-budget:
|
|
45
|
+
'oldgen: N > 0 (old-gen GC activity -- diagnose with @zakkster/lite-gc-profiler)',
|
|
46
|
+
'arrayBuffers: NKB > 64KB (external memory -- diagnose with @zakkster/lite-gc-profiler)'.
|
|
47
|
+
arrayBuffers is skipped under allowNoGc; an explicit maxArrayBuffersKB with
|
|
48
|
+
allowNoGc throws (mirrors maxRetainedKB). oldgen gates even under allowNoGc.
|
|
49
|
+
runGate(config) -- standalone report; returns {passed, code, results}, code 0 pass / 1 scenario or must-fail failure / 2 detector validation failed; never sets process.exitCode.
|
|
29
50
|
|
|
30
51
|
## Scenario shape
|
|
31
52
|
|
|
@@ -38,7 +59,9 @@ statsOf returns numeric counters; deltas are computed automatically.
|
|
|
38
59
|
|
|
39
60
|
node --expose-gc --max-semi-space-size=4 --test your.test.mjs
|
|
40
61
|
|
|
41
|
-
--expose-gc is
|
|
62
|
+
--expose-gc is REQUIRED: measure() throws without it, with the run command in
|
|
63
|
+
the message. allowNoGc: true opts out and drops the retained signal.
|
|
64
|
+
--max-semi-space-size=4 sharpens sensitivity.
|
|
42
65
|
|
|
43
66
|
## CI tuning
|
|
44
67
|
|
|
@@ -67,10 +90,46 @@ node:test, node:assert/strict.
|
|
|
67
90
|
v1.1 suiteGate(config): SPP stream-fed budgets. config.source = Float64Array
|
|
68
91
|
slab or { forEach(cb(packed,t,a,b)) } (lite-scope memory sink). budgets[] =
|
|
69
92
|
{ name, packed? | stream?+op | op, slot: t|a|b (default a), reduce:
|
|
70
|
-
count|sum|max|mean|last (default max), max }. Delegates every
|
|
71
|
-
verdict(); returns { name, pass, reasons, budgets: [{name,
|
|
72
|
-
max, pass, reasons}] }. Never sets exit codes
|
|
73
|
-
|
|
93
|
+
count|sum|max|mean|last (default max), max, minCount? }. Delegates every
|
|
94
|
+
comparison to verdict(); returns { name, pass, reasons, budgets: [{name,
|
|
95
|
+
value, count, max, minCount, pass, reasons}] }. Never sets exit codes. No
|
|
96
|
+
lite-scope import: coupled by SPP v1 protocol only.
|
|
97
|
+
|
|
98
|
+
v1.4.0 suiteGate doors (decisions/0004; the package's ONLY hot body is visit()):
|
|
99
|
+
THROWS (data-integrity refusal, the caller handed us something we cannot read):
|
|
100
|
+
- a slab record whose packed header is not a u32 -- RangeError naming the
|
|
101
|
+
record index (slab lane) or the invocation index (forEach lane);
|
|
102
|
+
- a non-Float64Array typed-array source (Float32Array etc.) -- its forEach
|
|
103
|
+
binds (value, index, array) onto (packed, t, a, b); RangeError at dispatch;
|
|
104
|
+
- a forEach source whose ANY invocation is not four numbers (checked on
|
|
105
|
+
every record, naming the invocation index and the failing slot; a native
|
|
106
|
+
Array/TypedArray forEach binds (value, index, array); a non-number slot
|
|
107
|
+
like null/'3'/true would finitely coerce and pass silently otherwise).
|
|
108
|
+
A NaN slot is a number and passes this door; it fails closed at D-D only
|
|
109
|
+
when the reducer propagates it (sum/mean, or max/last at the extremum) --
|
|
110
|
+
under max/last/count a NaN coexisting with finite records is silently
|
|
111
|
+
excluded and the budget can pass (documented residue, decisions/0004);
|
|
112
|
+
- slot/reduce set to an inherited prototype key (slot 'toString', reduce
|
|
113
|
+
'constructor') -- null-proto lookup tables close this (PG-12);
|
|
114
|
+
- a budget targeting CONT (0x0F01), op form or packed low-16-bits form:
|
|
115
|
+
'CONT records are never budget targets (SPP v1)' (PG-13);
|
|
116
|
+
- a minCount that is not an integer >= 0.
|
|
117
|
+
FAILS a budget (a verdict, we read it and it is out of budget):
|
|
118
|
+
- a reduced value that is not finite (NaN, Infinity, -Infinity) ->
|
|
119
|
+
'<name>: not a number (fail closed)' (PG-06 / D-D);
|
|
120
|
+
- count < minCount -> '<name>: matched <count> < minCount <n>'.
|
|
121
|
+
Budget names may be any non-empty string, including 'toString', '__proto__',
|
|
122
|
+
'constructor' -- legal end to end through toNDJSON. Without minCount a
|
|
123
|
+
zero-match budget still reduces to 0, reports count 0, and PASSES (back-compat).
|
|
124
|
+
The D-A u32 door is measured within noise on a 1M-record slab (torture T4).
|
|
125
|
+
|
|
126
|
+
verdict()'s counter lane now fails closed on ANY non-finite measured value,
|
|
127
|
+
not only NaN: Infinity / -Infinity now read '<key>: not a number (fail
|
|
128
|
+
closed)' too (was '<key>: Infinity > <max>'). The other four lanes keep the
|
|
129
|
+
NaN-only predicate (decisions/0004 D-D). This is the reduce-time door suiteGate
|
|
130
|
+
delegates through, so there is one comparison authority for the string.
|
|
131
|
+
|
|
74
132
|
v1.2 toNDJSON(x, meta?): NDJSON for CI artifacts; accepts suiteGate result,
|
|
75
133
|
measure result, or array; budget lines then suite-gate summary; meta merged
|
|
76
|
-
per line; trailing newline.
|
|
134
|
+
per line; trailing newline. The budget-line shape is frozen: minCount is on
|
|
135
|
+
the in-process result object, NOT on the NDJSON line.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-perf-gate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
5
5
|
"description": "Zero-GC and performance regression gate for node:test. Scavenge-counting, scaling verdict, detector self-validation. Proves your hot path allocates nothing -- or names what did.",
|
|
6
6
|
"type": "module",
|
|
@@ -26,8 +26,15 @@
|
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
28
|
"test": "node --expose-gc --max-semi-space-size=4 --test test/self.test.mjs",
|
|
29
|
+
"torture": "node --expose-gc --max-semi-space-size=4 test/torture.mjs",
|
|
29
30
|
"prepublishOnly": "npm test"
|
|
30
31
|
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@zakkster/lite-arena": "^1.9.0",
|
|
34
|
+
"@zakkster/lite-gc-profiler": "^1.16.0",
|
|
35
|
+
"@zakkster/lite-leak": "^1.10.0",
|
|
36
|
+
"@zakkster/lite-scope": "^1.2.0"
|
|
37
|
+
},
|
|
31
38
|
"keywords": [
|
|
32
39
|
"benchmark",
|
|
33
40
|
"regression",
|