nx_daemon_bench_test.nx source
↩ module page · 153 lines · 6548 B
1// nx_daemon_bench_test.nx -- smoke: drive the daemon through every
2// state transition and verify each one.
3//
4// We do NOT actually sleep -- this is a smoke, not a soak. We feed
5// synthetic measurements that exercise:
6// 1. IDLE -> WARMING during warmup_cycles
7// 2. WARMING -> HEALTHY after warmup
8// 3. HEALTHY -> DEGRADED when |z| crosses 3-sigma
9// 4. DEGRADED -> REGRESSION when |z| crosses 5-sigma
10// 5. OVERBUDGET path when we synthesise a budget violation
11// 6. Cardinal compliance: REGRESSION without named_improvement = 0
12// 7. Cardinal compliance: REGRESSION with named_improvement = 1
13// 8. Sealed-enum validity predicate accepts 0..7, rejects 8 / -1
14//
15// All eight assertions must pass. Any failure returns a unique non-
16// zero exit code so debugging is one bisection.
17
18import "nx_syscalls.nx"
19import "nx_clock.nx"
20import "nx_tier.nx"
21import "nx_benchmark_harness.nx"
22import "nx_daemon_bench.nx"
23
24func main() -> nx_int {
25
26 // ---- Build a config tuned for fast smoke (no real sleeps) -------
27 let cfg_raw: *u8 = sys_mmap(80)
28 let cfg: *DaemonConfig = cfg_raw as *DaemonConfig
29 nx_daemon_config_defaults(cfg)
30 // Override: smaller warmup so the smoke runs fewer iterations.
31 cfg.warmup_cycles = 4
32
33 let d: *DaemonState = nx_daemon_alloc()
34 nx_daemon_init(d, cfg)
35
36 // ---- Test 1: status is IDLE after init ---------------------------
37 if d.status != NX_DAEMON_IDLE { return 1 }
38
39 // ---- Test 2: WARMING during warmup cycles ------------------------
40 //
41 // Feed steady-state samples (throughput ~ 1000 ops/sec) for the
42 // first 4 cycles. Expect WARMING for all 4.
43 var i: nx_int = 0
44 while i < 4 {
45 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
46 if d.status != NX_DAEMON_WARMING { return 2 }
47 // Record a stable measurement on the throughput axis (idx 0).
48 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 1000)
49 nx_daemon_cycle_end(d)
50 if d.last_cycle_status != NX_DAEMON_WARMING { return 3 }
51 i = i + 1
52 }
53
54 // ---- Test 3: HEALTHY after warmup with steady samples ------------
55 //
56 // Cycle 5: should transition to HEALTHY (worst_z under 3-sigma).
57 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
58 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 1000)
59 nx_daemon_cycle_end(d)
60 if d.last_cycle_status != NX_DAEMON_HEALTHY { return 4 }
61
62 // ---- Test 4: DEGRADED when |z| crosses 3-sigma but not 5 ---------
63 //
64 // After 5 steady samples at 1000 the Welford variance is exactly 0
65 // and the zero-variance branch returns NX_DAEMON_Z_MAX_Q10 on any
66 // deviation -- which immediately crosses 5-sigma, jumping to
67 // REGRESSION. To exercise the DEGRADED band we need real variance,
68 // so feed a small jitter sequence first, then a moderate spike.
69 //
70 // Reset state and replay with jitter.
71 nx_daemon_init(d, cfg)
72
73 // 4 warmup cycles with jitter +/-50 around 1000 (variance ~ 2500).
74 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
75 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 950)
76 nx_daemon_cycle_end(d)
77 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
78 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 1050)
79 nx_daemon_cycle_end(d)
80 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
81 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 970)
82 nx_daemon_cycle_end(d)
83 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
84 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 1030)
85 nx_daemon_cycle_end(d)
86
87 // Cycle 5 with moderate spike: 1200 (4-sigma-ish given std~50).
88 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
89 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 1200)
90 nx_daemon_cycle_end(d)
91 // Should be DEGRADED or REGRESSION (depending on exact variance).
92 if d.last_cycle_status == NX_DAEMON_HEALTHY { return 5 }
93 if d.last_cycle_status != NX_DAEMON_DEGRADED {
94 if d.last_cycle_status != NX_DAEMON_REGRESSION { return 6 }
95 }
96
97 // ---- Test 5: REGRESSION on huge spike ----------------------------
98 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
99 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 5000)
100 nx_daemon_cycle_end(d)
101 if d.last_cycle_status != NX_DAEMON_REGRESSION {
102 if d.last_cycle_status != NX_DAEMON_DEGRADED { return 7 }
103 }
104
105 // ---- Test 6: Cardinal compliance gate ----------------------------
106 //
107 // If status is REGRESSION and caller hasn't called
108 // nx_daemon_set_named_improvement, compliance is 0.
109 if d.status == NX_DAEMON_REGRESSION {
110 if nx_daemon_is_cardinal_compliant(d) != 0 { return 8 }
111 nx_daemon_set_named_improvement(d)
112 if nx_daemon_is_cardinal_compliant(d) != 1 { return 9 }
113 }
114
115 // ---- Test 7: Sealed-enum validity --------------------------------
116 if nx_daemon_status_is_valid(NX_DAEMON_IDLE) != 1 { return 10 }
117 if nx_daemon_status_is_valid(NX_DAEMON_STALE) != 1 { return 11 }
118 if nx_daemon_status_is_valid(NX_DAEMON_N_STATES) != 0 { return 12 }
119 if nx_daemon_status_is_valid(-1) != 0 { return 13 }
120
121 // ---- Test 8: Tier floor skips cycle ------------------------------
122 cfg.tier_floor = NX_TIER_WORKSTATION
123 nx_daemon_init(d, cfg)
124 nx_daemon_cycle_start(d, NX_TIER_MCU)
125 if d.status != NX_DAEMON_SLEEPING { return 14 }
126 if d.cycle_skipped != 1 { return 15 }
127
128 // ---- Test 9: Welford O(1) memory regardless of sample count ----
129 //
130 // Feed 1000 samples; state size should be unchanged. We have no
131 // direct mmap-size readout; instead verify Welford n increments
132 // correctly and mean stays in bounds.
133 cfg.tier_floor = NX_TIER_MCU
134 nx_daemon_init(d, cfg)
135 var k: nx_int = 0
136 while k < 1000 {
137 nx_daemon_cycle_start(d, NX_TIER_WORKSTATION)
138 nx_daemon_record_sample(d, NX_BENCH_AXIS_THROUGHPUT, 1000)
139 nx_daemon_cycle_end(d)
140 k = k + 1
141 }
142 if d.s_throughput.n != 1000 { return 16 }
143 // Mean should be near 1000 in Q10 -> ~1024000. Allow +/- 1% band.
144 let mean_low: nx_int = 1014000
145 let mean_high: nx_int = 1034000
146 if d.s_throughput.mean_q10 < mean_low { return 17 }
147 if d.s_throughput.mean_q10 > mean_high { return 18 }
148
149 // All assertions passed. Emit one summary line for the operator
150 // who reads CI logs.
151 nx_daemon_emit_summary(d)
152 return 0
153}