nx_bench_companion_tsv.nx source
↩ module page · 304 lines · 11670 B
1// nx_bench_companion_tsv.nx -- emit NxBenchReport as TSV row to a
2// caller-provided byte buffer.
3//
4// Sibling to [[nx_bench_companion]]. Substrate-honest persistence:
5// the bench primitive captures counters; this primitive serializes
6// them so they survive process exit + cross-session comparison
7// becomes a `sort -t$'\t' -k4 -n` away.
8//
9// Format: tab-separated, label first, then 19 counter columns.
10// Header row emitted by nx_bc_emit_tsv_header(). Data row by
11// nx_bc_emit_tsv_row(). Both return byte-length written (>= 0)
12// or NEGATIVE on buffer overflow.
13//
14// Per CARDINAL [[feedback-no-strawman-perf-comparisons]]: persisting
15// the counters to disk lets future Nishi-vs-llama.cpp comparison
16// rigs read paired rows + emit honest deltas without re-running
17// every measurement.
18
19import "nx_syscalls.nx"
20import "nx_tier.nx"
21import "nx_bench_companion.nx"
22
23// ===== Sealed verdict ===========================================
24
25const NX_BCT_OK: nx_int = 0
26const NX_BCT_OVERFLOW: nx_int = 1
27const NX_BCT_NULL_BUF: nx_int = 2
28const NX_BCT_NULL_REPORT: nx_int = 3
29const NX_BCT_INVALID: nx_int = 4
30const NX_BCT_N: nx_int = 5
31
32func nx_bct_v_is_valid(v: nx_int) -> nx_int {
33 if v < 0 { return 0 }
34 if v >= NX_BCT_N { return 0 }
35 return 1
36}
37
38// ===== Decimal-bytes emitter (i64 -> caller buffer) =============
39//
40// Writes the decimal ASCII representation of n starting at buf[off].
41// Returns the new offset (off + bytes_written). Returns -1 on
42// overflow (would write past cap). Negative numbers get a leading
43// '-'; zero gets a single '0'.
44
45func _bct_emit_i64(buf: *u8, off: nx_int, cap: nx_int, n: i64) -> nx_int {
46 if off < 0 { return 0 - 1 }
47 if off >= cap { return 0 - 1 }
48
49 if n == 0 {
50 buf[off] = 0x30 as u8 // '0'
51 return off + 1
52 }
53
54 var v: i64 = n
55 var neg: nx_int = 0
56 if v < 0 {
57 neg = 1
58 v = 0 - v
59 }
60
61 // Reverse-fill into a scratch then copy forward. Worst case for
62 // i64 is 19 digits + 1 sign = 20 bytes; scratch of 24 is safe.
63 let scratch_buf: *u8 = sys_mmap(24)
64 var k: nx_int = 0
65 while v > 0 {
66 let digit: i64 = v - (v / 10) * 10
67 scratch_buf[k] = (0x30 + digit) as u8
68 v = v / 10
69 k = k + 1
70 }
71
72 var out: nx_int = off
73 if neg == 1 {
74 if out >= cap { return 0 - 1 }
75 buf[out] = 0x2D as u8 // '-'
76 out = out + 1
77 }
78 // Copy scratch (which holds digits LSB-first) into buf in reverse.
79 while k > 0 {
80 k = k - 1
81 if out >= cap { return 0 - 1 }
82 buf[out] = scratch_buf[k]
83 out = out + 1
84 }
85 return out
86}
87
88// ===== Byte / string emitter ====================================
89
90func _bct_emit_byte(buf: *u8, off: nx_int, cap: nx_int, b: nx_int) -> nx_int {
91 if off < 0 { return 0 - 1 }
92 if off >= cap { return 0 - 1 }
93 buf[off] = b as u8
94 return off + 1
95}
96
97func _bct_emit_str(buf: *u8, off: nx_int, cap: nx_int,
98 s: *u8, slen: nx_int) -> nx_int {
99 if off < 0 { return 0 - 1 }
100 if slen < 0 { return 0 - 1 }
101 if (s as i64) == 0 { return 0 - 1 }
102 var out: nx_int = off
103 var i: nx_int = 0
104 while i < slen {
105 if out >= cap { return 0 - 1 }
106 buf[out] = s[i]
107 out = out + 1
108 i = i + 1
109 }
110 return out
111}
112
113// ===== TSV header row ============================================
114//
115// Static column names matching the 19 NxBenchReport counters + a
116// leading "label" column. Total = 20 columns.
117
118func nx_bc_emit_tsv_header(buf: *u8, cap: nx_int) -> nx_int {
119 if (buf as i64) == 0 { return 0 - NX_BCT_NULL_BUF }
120 if cap < 0 { return 0 - NX_BCT_INVALID }
121
122 // "label\tn_actors\tactors_completed\tactors_failed\tactors_running\t"
123 // "actors_waiting\ttotal_actor_steps\tcumulative_runtime_us\t"
124 // "n_subscriptions\tmux_pending\tarb_grants_full\tarb_grants_partial\t"
125 // "arb_denials\tarb_preemptions\tbroker_grants\tbroker_refusals\t"
126 // "broker_deadline_misses\tepoch_count\tcaptured_at_us\tverdict\n"
127
128 let hdr: *u8 = sys_mmap(384)
129 // We literally write each column name + tab as bytes. Using
130 // explicit byte writes (no .nx string literal escape) keeps the
131 // emitter dependency-free.
132 var k: nx_int = 0
133 // "label"
134 hdr[k+0]=0x6C as u8; hdr[k+1]=0x61 as u8; hdr[k+2]=0x62 as u8
135 hdr[k+3]=0x65 as u8; hdr[k+4]=0x6C as u8; hdr[k+5]=0x09 as u8
136 k = k + 6
137 // "n_actors\t"
138 hdr[k+0]=0x6E as u8; hdr[k+1]=0x5F as u8; hdr[k+2]=0x61 as u8
139 hdr[k+3]=0x63 as u8; hdr[k+4]=0x74 as u8; hdr[k+5]=0x6F as u8
140 hdr[k+6]=0x72 as u8; hdr[k+7]=0x73 as u8; hdr[k+8]=0x09 as u8
141 k = k + 9
142 // "completed\t"
143 hdr[k+0]=0x63 as u8; hdr[k+1]=0x6F as u8; hdr[k+2]=0x6D as u8
144 hdr[k+3]=0x70 as u8; hdr[k+4]=0x6C as u8; hdr[k+5]=0x65 as u8
145 hdr[k+6]=0x74 as u8; hdr[k+7]=0x65 as u8; hdr[k+8]=0x64 as u8
146 hdr[k+9]=0x09 as u8
147 k = k + 10
148 // "failed\t"
149 hdr[k+0]=0x66 as u8; hdr[k+1]=0x61 as u8; hdr[k+2]=0x69 as u8
150 hdr[k+3]=0x6C as u8; hdr[k+4]=0x65 as u8; hdr[k+5]=0x64 as u8
151 hdr[k+6]=0x09 as u8
152 k = k + 7
153 // "running\t"
154 hdr[k+0]=0x72 as u8; hdr[k+1]=0x75 as u8; hdr[k+2]=0x6E as u8
155 hdr[k+3]=0x6E as u8; hdr[k+4]=0x69 as u8; hdr[k+5]=0x6E as u8
156 hdr[k+6]=0x67 as u8; hdr[k+7]=0x09 as u8
157 k = k + 8
158 // "waiting\t"
159 hdr[k+0]=0x77 as u8; hdr[k+1]=0x61 as u8; hdr[k+2]=0x69 as u8
160 hdr[k+3]=0x74 as u8; hdr[k+4]=0x69 as u8; hdr[k+5]=0x6E as u8
161 hdr[k+6]=0x67 as u8; hdr[k+7]=0x09 as u8
162 k = k + 8
163 // "steps\t"
164 hdr[k+0]=0x73 as u8; hdr[k+1]=0x74 as u8; hdr[k+2]=0x65 as u8
165 hdr[k+3]=0x70 as u8; hdr[k+4]=0x73 as u8; hdr[k+5]=0x09 as u8
166 k = k + 6
167 // "runtime_us\t"
168 hdr[k+0]=0x72 as u8; hdr[k+1]=0x75 as u8; hdr[k+2]=0x6E as u8
169 hdr[k+3]=0x74 as u8; hdr[k+4]=0x69 as u8; hdr[k+5]=0x6D as u8
170 hdr[k+6]=0x65 as u8; hdr[k+7]=0x5F as u8; hdr[k+8]=0x75 as u8
171 hdr[k+9]=0x73 as u8; hdr[k+10]=0x09 as u8
172 k = k + 11
173 // "subs\t"
174 hdr[k+0]=0x73 as u8; hdr[k+1]=0x75 as u8; hdr[k+2]=0x62 as u8
175 hdr[k+3]=0x73 as u8; hdr[k+4]=0x09 as u8
176 k = k + 5
177 // "mux_pend\t"
178 hdr[k+0]=0x6D as u8; hdr[k+1]=0x75 as u8; hdr[k+2]=0x78 as u8
179 hdr[k+3]=0x5F as u8; hdr[k+4]=0x70 as u8; hdr[k+5]=0x65 as u8
180 hdr[k+6]=0x6E as u8; hdr[k+7]=0x64 as u8; hdr[k+8]=0x09 as u8
181 k = k + 9
182 // "arb_gf\t" (grants_full)
183 hdr[k+0]=0x61 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x62 as u8
184 hdr[k+3]=0x5F as u8; hdr[k+4]=0x67 as u8; hdr[k+5]=0x66 as u8
185 hdr[k+6]=0x09 as u8
186 k = k + 7
187 // "arb_gp\t" (grants_partial)
188 hdr[k+0]=0x61 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x62 as u8
189 hdr[k+3]=0x5F as u8; hdr[k+4]=0x67 as u8; hdr[k+5]=0x70 as u8
190 hdr[k+6]=0x09 as u8
191 k = k + 7
192 // "arb_d\t" (denials)
193 hdr[k+0]=0x61 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x62 as u8
194 hdr[k+3]=0x5F as u8; hdr[k+4]=0x64 as u8; hdr[k+5]=0x09 as u8
195 k = k + 6
196 // "arb_pr\t" (preemptions)
197 hdr[k+0]=0x61 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x62 as u8
198 hdr[k+3]=0x5F as u8; hdr[k+4]=0x70 as u8; hdr[k+5]=0x72 as u8
199 hdr[k+6]=0x09 as u8
200 k = k + 7
201 // "br_g\t"
202 hdr[k+0]=0x62 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x5F as u8
203 hdr[k+3]=0x67 as u8; hdr[k+4]=0x09 as u8
204 k = k + 5
205 // "br_r\t"
206 hdr[k+0]=0x62 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x5F as u8
207 hdr[k+3]=0x72 as u8; hdr[k+4]=0x09 as u8
208 k = k + 5
209 // "br_dm\t"
210 hdr[k+0]=0x62 as u8; hdr[k+1]=0x72 as u8; hdr[k+2]=0x5F as u8
211 hdr[k+3]=0x64 as u8; hdr[k+4]=0x6D as u8; hdr[k+5]=0x09 as u8
212 k = k + 6
213 // "epoch\t"
214 hdr[k+0]=0x65 as u8; hdr[k+1]=0x70 as u8; hdr[k+2]=0x6F as u8
215 hdr[k+3]=0x63 as u8; hdr[k+4]=0x68 as u8; hdr[k+5]=0x09 as u8
216 k = k + 6
217 // "at_us\t"
218 hdr[k+0]=0x61 as u8; hdr[k+1]=0x74 as u8; hdr[k+2]=0x5F as u8
219 hdr[k+3]=0x75 as u8; hdr[k+4]=0x73 as u8; hdr[k+5]=0x09 as u8
220 k = k + 6
221 // "verdict\n"
222 hdr[k+0]=0x76 as u8; hdr[k+1]=0x65 as u8; hdr[k+2]=0x72 as u8
223 hdr[k+3]=0x64 as u8; hdr[k+4]=0x69 as u8; hdr[k+5]=0x63 as u8
224 hdr[k+6]=0x74 as u8; hdr[k+7]=0x0A as u8
225 k = k + 8
226
227 if k > cap { return 0 - NX_BCT_OVERFLOW }
228
229 // Copy hdr -> buf
230 var i: nx_int = 0
231 while i < k {
232 buf[i] = hdr[i]
233 i = i + 1
234 }
235 return k
236}
237
238// ===== TSV data row =============================================
239
240func _bct_emit_field(buf: *u8, off: nx_int, cap: nx_int, n: i64, sep: nx_int) -> nx_int {
241 let after_n: nx_int = _bct_emit_i64(buf, off, cap, n)
242 if after_n < 0 { return 0 - 1 }
243 return _bct_emit_byte(buf, after_n, cap, sep)
244}
245
246func nx_bc_emit_tsv_row(buf: *u8, cap: nx_int,
247 r: *NxBenchReport,
248 label: *u8, label_len: nx_int) -> nx_int {
249 if (buf as i64) == 0 { return 0 - NX_BCT_NULL_BUF }
250 if (r as i64) == 0 { return 0 - NX_BCT_NULL_REPORT }
251 if cap < 0 { return 0 - NX_BCT_INVALID }
252 if label_len < 0 { return 0 - NX_BCT_INVALID }
253
254 var off: nx_int = 0
255 if (label as i64) != 0 {
256 off = _bct_emit_str(buf, off, cap, label, label_len)
257 if off < 0 { return 0 - NX_BCT_OVERFLOW }
258 }
259 off = _bct_emit_byte(buf, off, cap, 0x09) // \t
260 if off < 0 { return 0 - NX_BCT_OVERFLOW }
261
262 // 19 fields, all tab-separated except the last which is \n
263 off = _bct_emit_field(buf, off, cap, r.n_actors as i64, 0x09)
264 if off < 0 { return 0 - NX_BCT_OVERFLOW }
265 off = _bct_emit_field(buf, off, cap, r.actors_completed as i64, 0x09)
266 if off < 0 { return 0 - NX_BCT_OVERFLOW }
267 off = _bct_emit_field(buf, off, cap, r.actors_failed as i64, 0x09)
268 if off < 0 { return 0 - NX_BCT_OVERFLOW }
269 off = _bct_emit_field(buf, off, cap, r.actors_running as i64, 0x09)
270 if off < 0 { return 0 - NX_BCT_OVERFLOW }
271 off = _bct_emit_field(buf, off, cap, r.actors_waiting as i64, 0x09)
272 if off < 0 { return 0 - NX_BCT_OVERFLOW }
273 off = _bct_emit_field(buf, off, cap, r.total_actor_steps as i64, 0x09)
274 if off < 0 { return 0 - NX_BCT_OVERFLOW }
275 off = _bct_emit_field(buf, off, cap, r.cumulative_runtime_us as i64, 0x09)
276 if off < 0 { return 0 - NX_BCT_OVERFLOW }
277 off = _bct_emit_field(buf, off, cap, r.n_subscriptions as i64, 0x09)
278 if off < 0 { return 0 - NX_BCT_OVERFLOW }
279 off = _bct_emit_field(buf, off, cap, r.mux_pending as i64, 0x09)
280 if off < 0 { return 0 - NX_BCT_OVERFLOW }
281 off = _bct_emit_field(buf, off, cap, r.arb_grants_full as i64, 0x09)
282 if off < 0 { return 0 - NX_BCT_OVERFLOW }
283 off = _bct_emit_field(buf, off, cap, r.arb_grants_partial as i64, 0x09)
284 if off < 0 { return 0 - NX_BCT_OVERFLOW }
285 off = _bct_emit_field(buf, off, cap, r.arb_denials as i64, 0x09)
286 if off < 0 { return 0 - NX_BCT_OVERFLOW }
287 off = _bct_emit_field(buf, off, cap, r.arb_preemptions as i64, 0x09)
288 if off < 0 { return 0 - NX_BCT_OVERFLOW }
289 off = _bct_emit_field(buf, off, cap, r.broker_grants as i64, 0x09)
290 if off < 0 { return 0 - NX_BCT_OVERFLOW }
291 off = _bct_emit_field(buf, off, cap, r.broker_refusals as i64, 0x09)
292 if off < 0 { return 0 - NX_BCT_OVERFLOW }
293 off = _bct_emit_field(buf, off, cap, r.broker_deadline_misses as i64, 0x09)
294 if off < 0 { return 0 - NX_BCT_OVERFLOW }
295 off = _bct_emit_field(buf, off, cap, r.epoch_count as i64, 0x09)
296 if off < 0 { return 0 - NX_BCT_OVERFLOW }
297 off = _bct_emit_field(buf, off, cap, r.captured_at_us as i64, 0x09)
298 if off < 0 { return 0 - NX_BCT_OVERFLOW }
299 off = _bct_emit_i64(buf, off, cap, r.verdict as i64)
300 if off < 0 { return 0 - NX_BCT_OVERFLOW }
301 off = _bct_emit_byte(buf, off, cap, 0x0A) // \n
302 if off < 0 { return 0 - NX_BCT_OVERFLOW }
303 return off
304}