nx_bench_companion_tsv_parse.nx source
↩ module page · 224 lines · 7963 B
1// nx_bench_companion_tsv_parse.nx -- read bench TSV rows back from bytes.
2//
3// Sibling to [[nx_bench_companion_tsv]] (the emitter). Closes the
4// persistence loop: rows written by the emitter can be read back from
5// disk + paired-diffed without re-running any measurement.
6//
7// Format contract (must match the emitter):
8// label<TAB>n_actors<TAB>completed<TAB>...<TAB>verdict<LF>
9// 20 columns total: label + 19 counter fields.
10//
11// API:
12// nx_bc_parse_tsv_row(buf, buf_len,
13// out_label, out_label_cap, out_label_len,
14// out_report)
15// -> bytes_consumed (>=0; offset just past trailing LF) or
16// NEGATIVE structured verdict
17//
18// Substrate-honest: parser refuses malformed input (missing fields,
19// non-decimal chars, label overflow) at the boundary rather than
20// silently filling out_report with junk.
21
22import "nx_syscalls.nx"
23import "nx_tier.nx"
24import "nx_bench_companion.nx"
25import "nx_bench_companion_tsv.nx"
26
27// ===== Sealed parse verdict =====================================
28
29const NX_BCTP_OK: nx_int = 0
30const NX_BCTP_NULL_BUF: nx_int = 1
31const NX_BCTP_NULL_REPORT: nx_int = 2
32const NX_BCTP_MALFORMED_TAB: nx_int = 3
33const NX_BCTP_MALFORMED_LF: nx_int = 4
34const NX_BCTP_BAD_DECIMAL: nx_int = 5
35const NX_BCTP_LABEL_OVERFLOW: nx_int = 6
36const NX_BCTP_TOO_SHORT: nx_int = 7
37const NX_BCTP_INVALID: nx_int = 8
38const NX_BCTP_N: nx_int = 9
39
40func nx_bctp_v_is_valid(v: nx_int) -> nx_int {
41 if v < 0 { return 0 }
42 if v >= NX_BCTP_N { return 0 }
43 return 1
44}
45
46// ===== Byte predicates ===========================================
47
48func _bctp_is_digit(b: u8) -> nx_int {
49 if (b as nx_int) < 0x30 { return 0 }
50 if (b as nx_int) > 0x39 { return 0 }
51 return 1
52}
53
54// ===== Find next separator =======================================
55//
56// Returns the offset of `sep` at or after `off`, or NEGATIVE if not
57// found before `end`. Used to walk the row TAB by TAB.
58
59func _bctp_find(buf: *u8, off: nx_int, end: nx_int, sep: nx_int) -> nx_int {
60 var i: nx_int = off
61 while i < end {
62 if (buf[i] as nx_int) == sep { return i }
63 i = i + 1
64 }
65 return 0 - 1
66}
67
68// ===== Parse decimal ASCII span -> i64 ==========================
69//
70// Parses buf[off..end_excl] as decimal ASCII (optional leading '-').
71// Writes the parsed value to out_val[0]. Returns NX_BCTP_OK or
72// NX_BCTP_BAD_DECIMAL.
73
74func _bctp_parse_dec(buf: *u8, off: nx_int, end_excl: nx_int,
75 out_val: *i64) -> nx_int {
76 if end_excl <= off { return NX_BCTP_BAD_DECIMAL }
77 var i: nx_int = off
78 var neg: nx_int = 0
79 if (buf[i] as nx_int) == 0x2D {
80 neg = 1
81 i = i + 1
82 }
83 if i >= end_excl { return NX_BCTP_BAD_DECIMAL }
84 var acc: i64 = 0
85 while i < end_excl {
86 let b: u8 = buf[i]
87 if _bctp_is_digit(b) == 0 { return NX_BCTP_BAD_DECIMAL }
88 let d: i64 = (b as i64) - 0x30
89 acc = acc * 10 + d
90 i = i + 1
91 }
92 if neg == 1 { acc = 0 - acc }
93 out_val[0] = acc
94 return NX_BCTP_OK
95}
96
97// ===== Consume one TAB-terminated field as i64 ==================
98//
99// Reads decimal from buf[off..] until TAB, writes value, advances
100// offset to byte after TAB. Returns updated offset or NEGATIVE
101// verdict.
102
103func _bctp_consume_field_i64(buf: *u8, off: nx_int, end: nx_int,
104 out_val: *i64) -> nx_int {
105 let tab_at: nx_int = _bctp_find(buf, off, end, 0x09)
106 if tab_at < 0 { return 0 - NX_BCTP_MALFORMED_TAB }
107 let v: nx_int = _bctp_parse_dec(buf, off, tab_at, out_val)
108 if v != NX_BCTP_OK { return 0 - v }
109 return tab_at + 1
110}
111
112// ===== Public: parse one row ====================================
113
114func nx_bc_parse_tsv_row(buf: *u8, buf_len: nx_int,
115 out_label: *u8, out_label_cap: nx_int,
116 out_label_len: *i64,
117 out_report: *NxBenchReport) -> nx_int {
118 if (buf as i64) == 0 { return 0 - NX_BCTP_NULL_BUF }
119 if (out_report as i64) == 0 { return 0 - NX_BCTP_NULL_REPORT }
120 if buf_len <= 0 { return 0 - NX_BCTP_TOO_SHORT }
121
122 // Find label\t
123 let label_end: nx_int = _bctp_find(buf, 0, buf_len, 0x09)
124 if label_end < 0 { return 0 - NX_BCTP_MALFORMED_TAB }
125 let label_len_raw: nx_int = label_end - 0
126 if (out_label as i64) != 0 {
127 if label_len_raw > out_label_cap { return 0 - NX_BCTP_LABEL_OVERFLOW }
128 var li: nx_int = 0
129 while li < label_len_raw {
130 out_label[li] = buf[li]
131 li = li + 1
132 }
133 }
134 if (out_label_len as i64) != 0 { out_label_len[0] = label_len_raw as i64 }
135
136 // Scratch i64 for field reads
137 let val_buf: *u8 = sys_mmap(8)
138 let val_p: *i64 = val_buf as *i64
139
140 var off: nx_int = label_end + 1
141
142 // 19 counter fields, all TAB-terminated EXCEPT the last (LF-terminated).
143 // n_actors
144 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
145 if off < 0 { return off }
146 out_report.n_actors = val_p[0] as nx_int
147
148 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
149 if off < 0 { return off }
150 out_report.actors_completed = val_p[0] as nx_int
151
152 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
153 if off < 0 { return off }
154 out_report.actors_failed = val_p[0] as nx_int
155
156 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
157 if off < 0 { return off }
158 out_report.actors_running = val_p[0] as nx_int
159
160 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
161 if off < 0 { return off }
162 out_report.actors_waiting = val_p[0] as nx_int
163
164 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
165 if off < 0 { return off }
166 out_report.total_actor_steps = val_p[0] as nx_int
167
168 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
169 if off < 0 { return off }
170 out_report.cumulative_runtime_us = val_p[0] as nx_size
171
172 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
173 if off < 0 { return off }
174 out_report.n_subscriptions = val_p[0] as nx_int
175
176 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
177 if off < 0 { return off }
178 out_report.mux_pending = val_p[0] as nx_int
179
180 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
181 if off < 0 { return off }
182 out_report.arb_grants_full = val_p[0] as nx_int
183
184 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
185 if off < 0 { return off }
186 out_report.arb_grants_partial = val_p[0] as nx_int
187
188 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
189 if off < 0 { return off }
190 out_report.arb_denials = val_p[0] as nx_int
191
192 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
193 if off < 0 { return off }
194 out_report.arb_preemptions = val_p[0] as nx_int
195
196 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
197 if off < 0 { return off }
198 out_report.broker_grants = val_p[0] as nx_int
199
200 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
201 if off < 0 { return off }
202 out_report.broker_refusals = val_p[0] as nx_int
203
204 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
205 if off < 0 { return off }
206 out_report.broker_deadline_misses = val_p[0] as nx_int
207
208 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
209 if off < 0 { return off }
210 out_report.epoch_count = val_p[0] as nx_int
211
212 off = _bctp_consume_field_i64(buf, off, buf_len, val_p)
213 if off < 0 { return off }
214 out_report.captured_at_us = val_p[0] as nx_size
215
216 // Last field: verdict, LF-terminated
217 let lf_at: nx_int = _bctp_find(buf, off, buf_len, 0x0A)
218 if lf_at < 0 { return 0 - NX_BCTP_MALFORMED_LF }
219 let v_parse: nx_int = _bctp_parse_dec(buf, off, lf_at, val_p)
220 if v_parse != NX_BCTP_OK { return 0 - v_parse }
221 out_report.verdict = val_p[0] as nx_int
222
223 return lf_at + 1 // bytes consumed = offset just past the LF
224}