nx_bench_companion.nx source
↩ module page · 212 lines · 8374 B
1// nx_bench_companion.nx -- substrate-honest measurement primitive for
2// parallel-companion scenarios.
3//
4// Captures EVERY measurable axis from a running [[nx_session]] into
5// one structured report. Designed for substrate-internal counters
6// (deterministic + paired) so qemu-riscv64 produces meaningful
7// comparison numbers BEFORE the physical-hardware run lands.
8//
9// Per CARDINAL [[feedback-no-strawman-perf-comparisons]]: this
10// primitive emits ONLY measured counters from real substrate calls.
11// No throughput ratio is claimed without paired measurement of the
12// SAME workload on the SAME substrate (e.g. nx_actor_role_llm v1 vs
13// v2; or future Nishi vs llama.cpp on identical Llama-7B Q4_K).
14//
15// V1 ships the counter-tabulation primitive + the report struct.
16// V2 will add the run-N-iterations loop that drives a session
17// through multiple cycles and accumulates the deltas. V3 will add
18// TSV emission via nx_runtime fact stream for cross-session
19// comparison.
20
21import "nx_syscalls.nx"
22import "nx_tier.nx"
23import "nx_actor.nx"
24import "nx_message.nx"
25import "nx_stream_multiplexer.nx"
26import "nx_resource_arbiter.nx"
27import "nx_interrupt_broker.nx"
28import "nx_session.nx"
29
30// ===== Sealed enum: NxBenchVerdict =================================
31
32const NX_BC_V_OK: nx_int = 0
33const NX_BC_V_NULL_SESSION: nx_int = 1
34const NX_BC_V_NULL_REPORT: nx_int = 2
35const NX_BC_V_SESSION_NOT_READY: nx_int = 3
36const NX_BC_V_INVALID: nx_int = 4
37const NX_BC_V_N: nx_int = 5
38
39func nx_bc_v_is_valid(v: nx_int) -> nx_int {
40 if v < 0 { return 0 }
41 if v >= NX_BC_V_N { return 0 }
42 return 1
43}
44
45// ===== Struct: NxBenchReport =======================================
46//
47// Every field carries a SUBSTRATE-MEASURED counter, not an estimate.
48// Counters that combine multiple sub-substrates (e.g. "total messages
49// across all mailboxes") are aggregated by walking the relevant
50// primitive's accessor funcs.
51
52struct NxBenchReport {
53 n_actors: nx_int,
54 actors_completed: nx_int,
55 actors_failed: nx_int,
56 actors_running: nx_int,
57 actors_waiting: nx_int,
58 total_actor_steps: nx_int,
59 cumulative_runtime_us: nx_size,
60 n_subscriptions: nx_int,
61 mux_pending: nx_int,
62 arb_grants_full: nx_int,
63 arb_grants_partial: nx_int,
64 arb_denials: nx_int,
65 arb_preemptions: nx_int,
66 broker_grants: nx_int,
67 broker_refusals: nx_int,
68 broker_deadline_misses: nx_int,
69 epoch_count: nx_int,
70 captured_at_us: nx_size,
71 verdict: nx_int,
72}
73
74const NX_BC_REPORT_BYTES: nx_int = 152 // 19 fields * 8
75
76// ===== Constructor =================================================
77
78func nx_bc_report_new() -> *NxBenchReport {
79 let raw: *u8 = sys_mmap(NX_BC_REPORT_BYTES)
80 let r: *NxBenchReport = raw as *NxBenchReport
81 r.n_actors = 0
82 r.actors_completed = 0
83 r.actors_failed = 0
84 r.actors_running = 0
85 r.actors_waiting = 0
86 r.total_actor_steps = 0
87 r.cumulative_runtime_us = 0
88 r.n_subscriptions = 0
89 r.mux_pending = 0
90 r.arb_grants_full = 0
91 r.arb_grants_partial = 0
92 r.arb_denials = 0
93 r.arb_preemptions = 0
94 r.broker_grants = 0
95 r.broker_refusals = 0
96 r.broker_deadline_misses = 0
97 r.epoch_count = 0
98 r.captured_at_us = 0
99 r.verdict = NX_BC_V_OK
100 return r
101}
102
103// ===== Capture: walk session counters into report =================
104//
105// Single-shot: takes an already-driven session + a report, fills the
106// report with every counter the substrate exposes. Caller decides
107// when to capture (after one iteration / N iterations / at scene end).
108
109func nx_bc_capture(s: *NxSession,
110 r: *NxBenchReport,
111 n_subscription_kinds_to_count: nx_int,
112 now_us: nx_size) -> nx_int {
113 if (s as i64) == 0 { return NX_BC_V_NULL_SESSION }
114 if (r as i64) == 0 { return NX_BC_V_NULL_REPORT }
115 if nx_session_is_ready(s) != 1 { return NX_BC_V_SESSION_NOT_READY }
116 if n_subscription_kinds_to_count < 0 { return NX_BC_V_INVALID }
117
118 // ===== Scheduler counters =====
119 r.n_actors = nx_ac_actor_count(s.scheduler)
120 r.actors_completed = nx_ac_count_by_state(s.scheduler, NX_AC_STATE_COMPLETED)
121 r.actors_failed = nx_ac_count_by_state(s.scheduler, NX_AC_STATE_FAILED)
122 r.actors_running = nx_ac_count_by_state(s.scheduler, NX_AC_STATE_RUNNING)
123 r.actors_waiting = nx_ac_count_by_state(s.scheduler, NX_AC_STATE_WAITING)
124 r.total_actor_steps = s.scheduler.steps_executed_total
125 r.cumulative_runtime_us = nx_ac_total_runtime_us(s.scheduler)
126 r.epoch_count = s.scheduler.epoch_count
127
128 // ===== Message bus counters =====
129 // Aggregate subscription count across the first N message kinds
130 var subs: nx_int = 0
131 var k: nx_int = 0
132 while k < n_subscription_kinds_to_count {
133 subs = subs + nx_ms_subscription_count(s.bus, k)
134 k = k + 1
135 }
136 r.n_subscriptions = subs
137
138 // ===== Multiplexer counters =====
139 r.mux_pending = nx_sm_pending_count(s.multiplexer)
140
141 // ===== Arbiter counters =====
142 r.arb_grants_full = nx_ra_grants_full(s.arbiter)
143 r.arb_grants_partial = nx_ra_grants_partial(s.arbiter)
144 r.arb_denials = nx_ra_denials(s.arbiter)
145 r.arb_preemptions = nx_ra_preemptions(s.arbiter)
146
147 // ===== Interrupt broker counters =====
148 r.broker_grants = nx_ib_grants(s.broker)
149 r.broker_refusals = nx_ib_refusals(s.broker)
150 r.broker_deadline_misses = nx_ib_deadline_misses(s.broker)
151
152 r.captured_at_us = now_us
153 r.verdict = NX_BC_V_OK
154 return NX_BC_V_OK
155}
156
157// ===== Paired-comparison helper ===================================
158//
159// Given two reports captured under different conditions (e.g. v1 actor
160// vs v2 actor on same fixture), compute the substrate-internal delta
161// for honest comparison. Writes deltas into the OUTPUT report; original
162// reports unchanged.
163//
164// Per [[feedback-no-strawman-perf-comparisons]]: a delta is meaningful
165// ONLY if both inputs were captured on the SAME workload. This
166// primitive enforces nothing about that contract -- caller is
167// responsible for paired discipline.
168
169func nx_bc_diff(a: *NxBenchReport,
170 b: *NxBenchReport,
171 out: *NxBenchReport) -> nx_int {
172 if (a as i64) == 0 { return NX_BC_V_NULL_REPORT }
173 if (b as i64) == 0 { return NX_BC_V_NULL_REPORT }
174 if (out as i64) == 0 { return NX_BC_V_NULL_REPORT }
175 out.n_actors = b.n_actors - a.n_actors
176 out.actors_completed = b.actors_completed - a.actors_completed
177 out.actors_failed = b.actors_failed - a.actors_failed
178 out.actors_running = b.actors_running - a.actors_running
179 out.actors_waiting = b.actors_waiting - a.actors_waiting
180 out.total_actor_steps = b.total_actor_steps - a.total_actor_steps
181 out.cumulative_runtime_us = b.cumulative_runtime_us - a.cumulative_runtime_us
182 out.n_subscriptions = b.n_subscriptions - a.n_subscriptions
183 out.mux_pending = b.mux_pending - a.mux_pending
184 out.arb_grants_full = b.arb_grants_full - a.arb_grants_full
185 out.arb_grants_partial = b.arb_grants_partial - a.arb_grants_partial
186 out.arb_denials = b.arb_denials - a.arb_denials
187 out.arb_preemptions = b.arb_preemptions - a.arb_preemptions
188 out.broker_grants = b.broker_grants - a.broker_grants
189 out.broker_refusals = b.broker_refusals - a.broker_refusals
190 out.broker_deadline_misses = b.broker_deadline_misses - a.broker_deadline_misses
191 out.epoch_count = b.epoch_count - a.epoch_count
192 out.captured_at_us = b.captured_at_us - a.captured_at_us
193 out.verdict = NX_BC_V_OK
194 return NX_BC_V_OK
195}
196
197// ===== Accessors ==================================================
198
199func nx_bc_report_verdict(r: *NxBenchReport) -> nx_int {
200 if (r as i64) == 0 { return NX_BC_V_NULL_REPORT }
201 return r.verdict
202}
203
204func nx_bc_report_actors_completed(r: *NxBenchReport) -> nx_int {
205 if (r as i64) == 0 { return 0 }
206 return r.actors_completed
207}
208
209func nx_bc_report_total_steps(r: *NxBenchReport) -> nx_int {
210 if (r as i64) == 0 { return 0 }
211 return r.total_actor_steps
212}