@zakkster/lite-perf-gate 1.3.0 → 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/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. Three signals:
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
- runGate(config) -- standalone report, same config as zgcSuite.
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 required. --max-semi-space-size=4 sharpens sensitivity.
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 comparison to
71
- verdict(); returns { name, pass, reasons, budgets: [{name, value, count,
72
- max, pass, reasons}] }. Never sets exit codes; CONT records (op 0x0F01) are
73
- never budget targets. No lite-scope import: coupled by SPP v1 protocol only.
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.0",
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",
@@ -30,8 +30,10 @@
30
30
  "prepublishOnly": "npm test"
31
31
  },
32
32
  "devDependencies": {
33
+ "@zakkster/lite-arena": "^1.9.0",
33
34
  "@zakkster/lite-gc-profiler": "^1.16.0",
34
- "@zakkster/lite-leak": "^1.10.0"
35
+ "@zakkster/lite-leak": "^1.10.0",
36
+ "@zakkster/lite-scope": "^1.2.0"
35
37
  },
36
38
  "keywords": [
37
39
  "benchmark",