nx_sweep_daemon_lib.nx source
↩ module page · 423 lines · 20675 B
1// nx_sweep_daemon_lib.nx -- STANDING SWEEP, library half: parse the data-driven check registry, run every
2// check under its deadline (via nx_sweep_core -- timeout-kill so the sweep never hangs), hold results in
3// memory, and render a PLAIN-ENGLISH status page (per the dashboards-speak-plain-English doctrine:
4// questions + YES/NOT-YET/TIMED-OUT chips, never ledger keys). Data-driven: a check = one registry row,
5// never code. Callable by the gate (run_all in-process) and the daemon (serve loop). license_tier: ORIGINAL
6import "nx_sweep_core.nx"
7import "nx_vsz_watchdog_core.nx" // vw_read -- bounded file read (no new primitive)
8import "_hdl_build/nx_metrics_ring.nx" // mr_append (leak-safe write) + mr_hash + MR_* -- the sovereign TSDB
9
10const SD_MAX_CHECKS: i64 = 64 // registry capacity
11const SD_REG_CAP: i64 = 131072 // registry file read cap
12const SD_LINE_CAP: i64 = 4096 // one row
13const SD_FIELD_CAP: i64 = 2048 // one field
14const SD_ARGV_MAX: i64 = 24 // argv slots per check (matches the compiler's arg cap)
15const SD_OUT_CAP: i64 = 65536 // captured stdout per check
16const SD_PIPE: i64 = 124 // '|'
17const SD_NL: i64 = 10 // '\n'
18const SD_HASH: i64 = 35 // '#'
19const SD_SPACE: i64 = 32 // ' '
20const SD_TAB: i64 = 9 // '\t'
21const SD_ASCII_0: i64 = 48 // '0'
22const SD_ASCII_9: i64 = 57 // '9'
23const SD_DEC: i64 = 10
24// registry columns (0-based, '|'-delimited): name|question|cwd|argv|timeout_ms|expect_exit
25const SD_COL_NAME: i64 = 0
26const SD_COL_Q: i64 = 1
27const SD_COL_CWD: i64 = 2
28const SD_COL_ARGV: i64 = 3
29const SD_COL_TMO: i64 = 4
30const SD_COL_EXPECT: i64 = 5
31
32// ---- per-check state lives in a mmap'd CONTEXT block (NishiLang has no mutable module globals; state is
33// threaded explicitly). ctx = *i64 of SC_SLOTS: [0]=count, [1..10]=base pointers to SD_MAX_CHECKS arrays.
34const SC_N: i64 = 0 // check count
35const SC_NAME: i64 = 1 // -> name string ptrs
36const SC_Q: i64 = 2 // -> question string ptrs
37const SC_CWD: i64 = 3 // -> cwd string ptrs
38const SC_ARGV: i64 = 4 // -> argv vector ptrs
39const SC_TMO: i64 = 5 // deadline ms
40const SC_EXPECT: i64 = 6 // expected exit
41const SC_VERDICT: i64 = 7 // last verdict
42const SC_EXIT: i64 = 8 // last raw exit/sentinel
43const SC_MS: i64 = 9 // last duration ms
44const SC_RED: i64 = 10 // consecutive non-PASS (alert dedup)
45const SC_HASH: i64 = 11 // -> per-check FNV-1a name hash (the metrics-ring key)
46const SC_UPTIME: i64 = 12 // -> per-check uptime permille over the window (-1 = no history)
47const SC_ACC_UP: i64 = 13 // -> scratch: per-check "up" sample count this refresh
48const SC_ACC_TOT: i64 = 14 // -> scratch: per-check total sample count this refresh
49const SC_ARRAYS: i64 = 15 // slots 0..14 = count + 14 per-check arrays (the sd_new loop bound)
50// ---- hoisted scratch (allocated ONCE in sd_new; the serve loop + per-request render do ZERO sys_mmap,
51// killing the ~24kB-per-request + 64kB-per-cycle leaks a forever-daemon otherwise accrues) ----
52const SC_OUT: i64 = 15 // -> capture buffer (SD_OUT_CAP)
53const SC_OLEN: i64 = 16 // -> outlen cell (one i64)
54const SC_OP: i64 = 17 // -> render/http offset cell (one i64)
55const SC_CORE: i64 = 18 // -> core scratch for sw_run_deadline (fds/pf/stp)
56const SC_STAT: i64 = 19 // -> statbuf/ring-read scratch (SD_STATBUF bytes: mtime probe + ring hdr/rec)
57const SC_MTIME: i64 = 20 // VALUE slot: registry mtime at last successful load (hot-reload gate)
58const SC_TOTAL: i64 = 21 // ctx cell count
59const SD_STATBUF: i64 = 256 // struct stat scratch (also holds ring hdr 64 + rec 32)
60const SD_STAT_MT: i64 = 11 // st_mtim.tv_sec = i64[11] of struct stat (the ccz_mtime idiom)
61const SC_WORD: i64 = 8 // bytes per i64
62const SD_PTR1: i64 = 8 // single-i64 scratch cell
63const SD_CORE_SCR: i64 = 64 // sw_run_deadline scratch: fds(16)+pf(8)+stp(16), rounded up
64const SD_UPWINDOW_S: i64 = 86400 // uptime window = 24h (a threshold -> named, rule 11)
65const SD_PERMILLE: i64 = 1000 // permille scale
66const SD_NODATA: i64 = 0 - 1 // uptime sentinel: no samples in window
67// nx_metrics_ring layout offsets (mirror the ring's own header/record fields, i64-aligned)
68const MR_H_WIDX: i64 = 24 // header: write index
69const MR_H_WRAP: i64 = 32 // header: wrapped flag
70const MR_R_HASH: i64 = 8 // record: name-hash field
71const MR_R_VERD: i64 = 16 // record: verdict field
72const MR_WRAPPED: i64 = 1 // wrapped-flag value
73
74func sd_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
75// column array base for slot k
76func sc_arr(ctx: *i64, k: i64) -> *i64 { return ctx[k] as *i64 }
77func sc_n(ctx: *i64) -> i64 { return ctx[SC_N] }
78
79// allocate a fresh sweep context (count 0, all arrays zeroed, scratch buffers allocated ONCE)
80func sd_new() -> *i64 {
81 let ctx: *i64 = sys_mmap(SC_TOTAL * SC_WORD) as *i64
82 ctx[SC_N] = 0
83 var k: i64 = SC_NAME
84 while k < SC_ARRAYS {
85 let a: *i64 = sys_mmap(SD_MAX_CHECKS * SC_WORD) as *i64
86 var i: i64 = 0
87 while i < SD_MAX_CHECKS { a[i] = 0; i = i + 1 }
88 ctx[k] = a as i64
89 k = k + 1
90 }
91 ctx[SC_OUT] = sys_mmap(SD_OUT_CAP) as i64
92 ctx[SC_OLEN] = sys_mmap(SD_PTR1) as i64
93 ctx[SC_OP] = sys_mmap(SD_PTR1) as i64
94 ctx[SC_CORE] = sys_mmap(SD_CORE_SCR) as i64
95 ctx[SC_STAT] = sys_mmap(SD_STATBUF) as i64
96 ctx[SC_MTIME] = 0
97 return ctx
98}
99// registry mtime (epoch sec) via the hoisted statbuf; -1 absent. The HOT-RELOAD gate: the serve loop
100// re-parses ONLY when this changes, so steady-state cycles do ZERO allocation (parse mmaps are bounded
101// by the human edit rate, not the cycle rate -- the leak class stays dead).
102func sd_mtime(ctx: *i64, path: *u8) -> i64 {
103 let sb: *u8 = ctx[SC_STAT] as *u8
104 if sys_fstatat(path, sb) < 0 { return 0 - 1 }
105 let sw: *i64 = sb as *i64
106 return sw[SD_STAT_MT]
107}
108// LEAK-FREE wall-clock epoch seconds via the hoisted SC_STAT scratch (sys_now_realtime_sec mmaps a page
109// per call WITHOUT freeing -- a per-cycle leak in a forever-daemon; this reuses hoisted scratch, zero alloc).
110func sd_now_sec(ctx: *i64) -> i64 {
111 let ts: *i64 = ctx[SC_STAT] as *i64
112 sys_clock_gettime_real(ts)
113 return ts[0]
114}
115// reload iff the registry file changed since the last successful load. Returns 1=reloaded, 0=unchanged/kept.
116func sd_reload_if_changed(ctx: *i64, reg_path: *u8) -> i64 {
117 let mt: i64 = sd_mtime(ctx, reg_path)
118 if mt < 0 { return 0 } // absent/unreadable -> keep last-good
119 if mt == ctx[SC_MTIME] { return 0 } // unchanged -> keep (zero allocations this cycle)
120 if sd_load(ctx, reg_path) < 0 { return 0 }
121 ctx[SC_MTIME] = mt
122 return 1
123}
124// is byte c an ASCII space or tab?
125func sd_ws(c: i64) -> i64 { if c == SD_SPACE { return 1 } if c == SD_TAB { return 1 } return 0 }
126// copy field k ('|'-delimited) of line[0..ll) into a fresh buffer, trimming ASCII spaces/tabs; returns ptr.
127func sd_field(line: *u8, ll: i64, k: i64) -> *u8 {
128 // locate field start: skip past k pipes
129 var cur: i64 = 0
130 var st: i64 = 0
131 var i: i64 = 0
132 var findgo: i64 = 1
133 while findgo == 1 {
134 if cur >= k { findgo = 0 } else {
135 if i >= ll { st = ll; cur = k } else {
136 if line[i] == (SD_PIPE as u8) { cur = cur + 1; st = i + 1 }
137 i = i + 1
138 }
139 }
140 }
141 // field end: next pipe or line end
142 var e: i64 = st
143 var endgo: i64 = 1
144 while endgo == 1 {
145 if e >= ll { endgo = 0 } else {
146 if line[e] == (SD_PIPE as u8) { endgo = 0 } else { e = e + 1 }
147 }
148 }
149 // trim leading whitespace
150 var s: i64 = st
151 var lgo: i64 = 1
152 while lgo == 1 { if s < e { if sd_ws(line[s] as i64) == 1 { s = s + 1 } else { lgo = 0 } } else { lgo = 0 } }
153 // trim trailing whitespace
154 var te: i64 = e
155 var tgo: i64 = 1
156 while tgo == 1 { if te > s { if sd_ws(line[te-1] as i64) == 1 { te = te - 1 } else { tgo = 0 } } else { tgo = 0 } }
157 let out: *u8 = sys_mmap(SD_FIELD_CAP)
158 var o: i64 = 0
159 var p: i64 = s
160 while p < te { if o < SD_FIELD_CAP - 1 { out[o] = line[p]; o = o + 1 } p = p + 1 }
161 out[o] = 0 as u8
162 return out
163}
164func sd_atoi(s: *u8) -> i64 {
165 var v: i64 = 0
166 var i: i64 = 0
167 while s[i] != (0 as u8) {
168 let c: i64 = s[i] as i64
169 if c < SD_ASCII_0 { return v }
170 if c > SD_ASCII_9 { return v }
171 v = v * SD_DEC + (c - SD_ASCII_0)
172 i = i + 1
173 }
174 return v
175}
176// split argstr on spaces into a fresh argv vector (spaces -> NUL in a copy); returns the *i64 vector.
177func sd_build_argv(argstr: *u8) -> *i64 {
178 let copy: *u8 = sys_mmap(SD_FIELD_CAP)
179 var n: i64 = 0
180 while argstr[n] != (0 as u8) { copy[n] = argstr[n]; n = n + 1 }
181 copy[n] = 0 as u8
182 let av: *i64 = sys_mmap(SD_ARGV_MAX * SC_WORD) as *i64
183 var ac: i64 = 0
184 var i: i64 = 0
185 var intok: i64 = 0
186 while i < n {
187 if copy[i] == (SD_SPACE as u8) { copy[i] = 0 as u8; intok = 0 } else {
188 if intok == 0 { if ac < SD_ARGV_MAX - 1 { av[ac] = ((copy as i64) + i) as i64; ac = ac + 1 } intok = 1 }
189 }
190 i = i + 1
191 }
192 av[ac] = 0
193 return av
194}
195// parse the registry file into ctx. Returns check count, or -1 on read failure.
196func sd_load(ctx: *i64, reg_path: *u8) -> i64 {
197 let a_name: *i64 = sc_arr(ctx, SC_NAME)
198 let a_q: *i64 = sc_arr(ctx, SC_Q)
199 let a_cwd: *i64 = sc_arr(ctx, SC_CWD)
200 let a_argv: *i64 = sc_arr(ctx, SC_ARGV)
201 let a_tmo: *i64 = sc_arr(ctx, SC_TMO)
202 let a_exp: *i64 = sc_arr(ctx, SC_EXPECT)
203 let a_v: *i64 = sc_arr(ctx, SC_VERDICT)
204 let a_hash: *i64 = sc_arr(ctx, SC_HASH)
205 let a_up: *i64 = sc_arr(ctx, SC_UPTIME)
206 let buf: *u8 = sys_mmap(SD_REG_CAP)
207 let n: i64 = vw_read(reg_path, buf, SD_REG_CAP - 1)
208 if n <= 0 { return 0 - 1 } // read fail -> PRESERVE the last-good loaded set (never clear to empty)
209 var cnt: i64 = 0
210 var ls: i64 = 0
211 var i: i64 = 0
212 while i <= n {
213 var eol: i64 = 0
214 if i == n { eol = 1 } else { if buf[i] == (SD_NL as u8) { eol = 1 } }
215 if eol == 1 {
216 if i > ls { if buf[ls] != (SD_HASH as u8) {
217 if cnt < SD_MAX_CHECKS {
218 let lp: *u8 = ((buf as i64) + ls) as *u8
219 let ll: i64 = i - ls
220 let nm: *u8 = sd_field(lp, ll, SD_COL_NAME)
221 if nm[0] != (0 as u8) {
222 a_name[cnt] = nm as i64
223 a_q[cnt] = sd_field(lp, ll, SD_COL_Q) as i64
224 a_cwd[cnt] = sd_field(lp, ll, SD_COL_CWD) as i64
225 a_argv[cnt] = sd_build_argv(sd_field(lp, ll, SD_COL_ARGV)) as i64
226 a_tmo[cnt] = sd_atoi(sd_field(lp, ll, SD_COL_TMO))
227 a_exp[cnt] = sd_atoi(sd_field(lp, ll, SD_COL_EXPECT))
228 a_v[cnt] = SW_V_ERROR
229 a_hash[cnt] = mr_hash(nm) // stable ring key for this check's name
230 a_up[cnt] = SD_NODATA // no history until sd_sample runs (gate render skips it)
231 cnt = cnt + 1
232 }
233 }
234 } }
235 ls = i + 1
236 }
237 i = i + 1
238 }
239 ctx[SC_N] = cnt
240 return cnt
241}
242// run every loaded check once, updating g_verdict/g_exit/g_ms and the consecutive-red counter.
243// Returns the count of non-PASS verdicts this cycle.
244func sd_run_all(ctx: *i64) -> i64 {
245 let a_argv: *i64 = sc_arr(ctx, SC_ARGV)
246 let a_cwd: *i64 = sc_arr(ctx, SC_CWD)
247 let a_tmo: *i64 = sc_arr(ctx, SC_TMO)
248 let a_exp: *i64 = sc_arr(ctx, SC_EXPECT)
249 let a_v: *i64 = sc_arr(ctx, SC_VERDICT)
250 let a_exit: *i64 = sc_arr(ctx, SC_EXIT)
251 let a_ms: *i64 = sc_arr(ctx, SC_MS)
252 let a_red: *i64 = sc_arr(ctx, SC_RED)
253 let n: i64 = ctx[SC_N]
254 let out: *u8 = ctx[SC_OUT] as *u8 // hoisted scratch -- no per-cycle mmap
255 let olen: *i64 = ctx[SC_OLEN] as *i64
256 let scr: *u8 = ctx[SC_CORE] as *u8
257 var bad: i64 = 0
258 var i: i64 = 0
259 while i < n {
260 let av: *i64 = a_argv[i] as *i64
261 let path: *u8 = av[0] as *u8 // argv[0]
262 let cwd: *u8 = a_cwd[i] as *u8
263 let t0: i64 = sw_mono_ms()
264 let rc: i64 = sw_run_deadline(path, av, cwd, out, SD_OUT_CAP, olen, a_tmo[i], scr)
265 let dt: i64 = sw_mono_ms() - t0
266 let v: i64 = sw_verdict(rc, a_exp[i])
267 a_v[i] = v
268 a_exit[i] = rc
269 a_ms[i] = dt
270 if v == SW_V_PASS { a_red[i] = 0 } else { a_red[i] = a_red[i] + 1; bad = bad + 1 }
271 i = i + 1
272 }
273 return bad
274}
275// SINGLE-PASS ring scan (leak-free: reuses the hoisted SC_STAT scratch for hdr+rec; only an fd is opened/
276// closed). Buckets samples in [now-window, now] by name-hash into the per-check ACC_UP/ACC_TOT arrays. One
277// scan services ALL checks (vs mr_uptime's per-name full scan) and never per-call mmaps -- the forever-daemon
278// never-leak discipline. Ring absent -> no-op (uptime stays SD_NODATA).
279func sd_ring_scan(ctx: *i64, ring: *u8, now: i64, window: i64) -> i64 {
280 let a_hash: *i64 = sc_arr(ctx, SC_HASH)
281 let a_accu: *i64 = sc_arr(ctx, SC_ACC_UP)
282 let a_acct: *i64 = sc_arr(ctx, SC_ACC_TOT)
283 let n: i64 = ctx[SC_N]
284 let scratch: *u8 = ctx[SC_STAT] as *u8
285 let hdr: *u8 = scratch // bytes 0..64
286 let rec: *u8 = ((scratch as i64) + MR_HDR) as *u8 // bytes 64..96
287 let fd: i64 = sys_openat_rd(ring)
288 if fd < 0 { return 0 } // no ring yet -> no history
289 if mr_pread(fd, 0, hdr, MR_HDR) != MR_HDR { sys_close(fd); return 0 }
290 if mr_geti(hdr, 0) != MR_MAGIC { sys_close(fd); return 0 }
291 var cnt: i64 = mr_geti(hdr, MR_H_WIDX)
292 if mr_geti(hdr, MR_H_WRAP) == MR_WRAPPED { cnt = MR_CAP }
293 let lo: i64 = now - window
294 var r: i64 = 0
295 while r < cnt {
296 if mr_pread(fd, MR_HDR + r * MR_REC, rec, MR_REC) == MR_REC {
297 let ts: i64 = mr_geti(rec, 0)
298 if ts >= lo { if ts <= now {
299 let h: i64 = mr_geti(rec, MR_R_HASH)
300 let vv: i64 = mr_geti(rec, MR_R_VERD)
301 var i: i64 = 0
302 while i < n {
303 if a_hash[i] == h {
304 a_acct[i] = a_acct[i] + 1
305 if vv == MR_V_SERVING { a_accu[i] = a_accu[i] + 1 }
306 i = n
307 } else { i = i + 1 }
308 }
309 } }
310 }
311 r = r + 1
312 }
313 sys_close(fd)
314 return 0
315}
316// SAMPLE (the producer writes its OWN data inline -- the anti-sprawl law): append one ring record per check
317// (verdict PASS->SERVING else REFUSED, fails=red-count), then refresh per-check uptime permille over the
318// window via one leak-free scan. Called by the serve loop after each sweep; the gate never calls it (uptime
319// stays SD_NODATA so the gate's render is unchanged).
320func sd_sample(ctx: *i64, ring: *u8, now: i64, window: i64) -> i64 {
321 let a_hash: *i64 = sc_arr(ctx, SC_HASH)
322 let a_v: *i64 = sc_arr(ctx, SC_VERDICT)
323 let a_red: *i64 = sc_arr(ctx, SC_RED)
324 let a_up: *i64 = sc_arr(ctx, SC_UPTIME)
325 let a_accu: *i64 = sc_arr(ctx, SC_ACC_UP)
326 let a_acct: *i64 = sc_arr(ctx, SC_ACC_TOT)
327 let n: i64 = ctx[SC_N]
328 var i: i64 = 0
329 while i < n {
330 var v: i64 = MR_V_REFUSED
331 if a_v[i] == SW_V_PASS { v = MR_V_SERVING }
332 mr_append(ring, now, a_hash[i], v, a_red[i]) // mr_append munmaps its own scratch (leak-safe)
333 a_accu[i] = 0
334 a_acct[i] = 0
335 i = i + 1
336 }
337 sd_ring_scan(ctx, ring, now, window)
338 i = 0
339 while i < n {
340 if a_acct[i] > 0 { a_up[i] = a_accu[i] * SD_PERMILLE / a_acct[i] } else { a_up[i] = SD_NODATA }
341 i = i + 1
342 }
343 return 0
344}
345// append NUL-term src to buf at *op (bounded by cap)
346func sd_app(buf: *u8, op: *i64, cap: i64, src: *u8) -> i64 {
347 var i: i64 = 0
348 var o: i64 = op[0]
349 while src[i] != (0 as u8) { if o < cap - 1 { buf[o] = src[i]; o = o + 1 } i = i + 1 }
350 buf[o] = 0 as u8
351 op[0] = o
352 return 0
353}
354// ALLOCATION-FREE decimal render (no per-call mmap -- this runs on the per-request serve path; a scratch
355// mmap here leaked ~1 page every /status hit). Digits are emitted MSB-first by repeated divide, O(digits^2)
356// but digits<=19, so it is cheaper than a syscall.
357func sd_appn(buf: *u8, op: *i64, cap: i64, v: i64) -> i64 {
358 if v == 0 { return sd_app(buf, op, cap, "0" as *u8) }
359 var m: i64 = v
360 if m < 0 { sd_app(buf, op, cap, "-" as *u8); m = 0 - m }
361 var nd: i64 = 0
362 var t: i64 = m
363 while t > 0 { nd = nd + 1; t = t / SD_DEC }
364 var o: i64 = op[0]
365 var p: i64 = nd
366 while p > 0 {
367 var div: i64 = 1
368 var q: i64 = 1
369 while q < p { div = div * SD_DEC; q = q + 1 }
370 let digit: i64 = (m / div) % SD_DEC
371 if o < cap - 1 { buf[o] = (SD_ASCII_0 + digit) as u8; o = o + 1 }
372 p = p - 1
373 }
374 buf[o] = 0 as u8
375 op[0] = o
376 return 0
377}
378// PLAIN-ENGLISH status page: each check = its question + a YES/NOT-YET/TIMED-OUT/ERROR chip. Renders the
379// whole-system headline first (all green? / N need attention). buf gets the HTML; returns byte length.
380func sd_render_status(ctx: *i64, buf: *u8, cap: i64) -> i64 {
381 let a_q: *i64 = sc_arr(ctx, SC_Q)
382 let a_name: *i64 = sc_arr(ctx, SC_NAME)
383 let a_v: *i64 = sc_arr(ctx, SC_VERDICT)
384 let a_ms: *i64 = sc_arr(ctx, SC_MS)
385 let a_red: *i64 = sc_arr(ctx, SC_RED)
386 let a_up: *i64 = sc_arr(ctx, SC_UPTIME)
387 let n: i64 = ctx[SC_N]
388 let op: *i64 = ctx[SC_OP] as *i64 // hoisted -- no per-request mmap
389 op[0] = 0
390 var bad: i64 = 0
391 var i: i64 = 0
392 while i < n { if a_v[i] != SW_V_PASS { bad = bad + 1 } i = i + 1 }
393 sd_app(buf, op, cap, "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Standing Sweep</title><style>:root{--bg:#0b0e14;--card:#141a26;--ink:#e8edf6;--dim:#9aa7bd;--ok:#4fd1a5;--bad:#e8734f;--warn:#e8b34f}@media(prefers-color-scheme:light){:root{--bg:#f5f7fb;--card:#fff;--ink:#16202e;--dim:#5a6a82}}*{margin:0;box-sizing:border-box}body{background:var(--bg);color:var(--ink);font:16px/1.6 system-ui,sans-serif;padding:clamp(16px,4vw,40px)}main{max-width:760px;margin:0 auto}h1{font-size:clamp(1.4rem,3.5vw,2rem)}.h{padding:.8rem 1rem;border-radius:12px;margin:1rem 0;font-weight:600}.g{background:var(--ok);color:#04140e}.r{background:var(--bad);color:#1a0600}.card{background:var(--card);border-radius:12px;padding:.9rem 1.1rem;margin:.6rem 0;display:flex;justify-content:space-between;align-items:center;gap:1rem}.chip{padding:.15rem .7rem;border-radius:999px;font-weight:700;font-size:.85rem;white-space:nowrap}.yes{background:var(--ok);color:#04140e}.no{background:var(--bad);color:#1a0600}.to{background:var(--warn);color:#1a1400}.q{color:var(--ink)}.m{color:var(--dim);font-size:.8rem}</style></head><body><main><h1>Nishi Standing Sweep</h1>" as *u8)
394 if bad == 0 {
395 sd_app(buf, op, cap, "<div class=\"h g\">All checks passing \xe2\x9c\x93</div>" as *u8)
396 } else {
397 sd_app(buf, op, cap, "<div class=\"h r\">" as *u8)
398 sd_appn(buf, op, cap, bad)
399 sd_app(buf, op, cap, " check(s) need attention</div>" as *u8)
400 }
401 i = 0
402 while i < n {
403 sd_app(buf, op, cap, "<div class=card><div><div class=q>" as *u8)
404 sd_app(buf, op, cap, a_q[i] as *u8)
405 sd_app(buf, op, cap, "</div><div class=m>" as *u8)
406 sd_app(buf, op, cap, a_name[i] as *u8)
407 sd_app(buf, op, cap, " · " as *u8)
408 sd_appn(buf, op, cap, a_ms[i])
409 sd_app(buf, op, cap, "ms" as *u8)
410 if a_red[i] > 1 { sd_app(buf, op, cap, " · red x" as *u8); sd_appn(buf, op, cap, a_red[i]) }
411 if a_up[i] >= 0 { sd_app(buf, op, cap, " · up " as *u8); sd_appn(buf, op, cap, a_up[i]); sd_app(buf, op, cap, "permille/24h" as *u8) }
412 sd_app(buf, op, cap, "</div></div>" as *u8)
413 let v: i64 = a_v[i]
414 if v == SW_V_PASS { sd_app(buf, op, cap, "<span class=\"chip yes\">YES</span>" as *u8) } else {
415 if v == SW_V_TIMEOUT { sd_app(buf, op, cap, "<span class=\"chip to\">TIMED OUT</span>" as *u8) } else {
416 if v == SW_V_ERROR { sd_app(buf, op, cap, "<span class=\"chip to\">CANT RUN</span>" as *u8) } else {
417 sd_app(buf, op, cap, "<span class=\"chip no\">NOT YET</span>" as *u8) } } }
418 sd_app(buf, op, cap, "</div>" as *u8)
419 i = i + 1
420 }
421 sd_app(buf, op, cap, "</main></body></html>" as *u8)
422 return op[0]
423}