code wiki / (root) / nx_sweep_daemon_lib.nx

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, " &middot; " 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, " &middot; red x" as *u8); sd_appn(buf, op, cap, a_red[i]) } 411 if a_up[i] >= 0 { sd_app(buf, op, cap, " &middot; 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}