code wiki / _hdl_build / nx_dsup_lib.nx

nx_dsup_lib.nx source

↩ module page · 118 lines · 6046 B

1// nx_dsup_lib.nx -- the fleet supervisor's PURE helpers, extracted 2026-09-02 so a gate can drive them IN-PROCESS 2// (nx_daemon_supervisor_gate). Nothing here touches a file, a socket or a clock: decimal emission, bounded append, 3// the conf-key parser, the /proc/<pid>/stat parser the status child uses for its PARENT PROBE, and the progress 4// trailer the supervisor publishes after every row. 5// 6// WHY THIS FILE EXISTS: the conf-key parser that lived inside nx_daemon_supervisor exited its inner loop by writing 7// 99 into the cursor (`k = 99`), which then read key[99] -- 87 bytes PAST the 12-byte "life_cycles=" literal -- and 8// looped forever wherever that rodata byte was non-zero. Every supervisor generation on the NAS spun at 100% CPU 9// before its first probe (parent state=R wchan=0, measured by the status child's own /proc read) while the same 10// binary ticked every 15 s on a laptop that had NO conf. nx_srclint's CURSOR-SENTINEL idiom, inside the organ that 11// supervises the fleet, invisible for as long as the predicate had no gate. Now it has one. 12// license_tier: ORIGINAL No hw writes (Rule 26). 13 14// write signed decimal v into out, return len. Temp-free (counts digits, then writes in place) so it can be 15// called many times per cycle without a single allocation. 16func ds_decw(v: i64, out: *u8) -> i64 { 17 var o: i64 = 0 18 var m: i64 = v 19 if m < 0 { out[0] = 45 as u8; o = 1; m = 0 - m } 20 if m == 0 { out[o] = 48 as u8; return o + 1 } 21 var nd: i64 = 1 22 var mm: i64 = m 23 while mm >= 10 { nd = nd + 1; mm = mm / 10 } 24 var i: i64 = nd - 1 25 while m > 0 { out[o + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 26 return o + nd 27} 28 29func ds_sappend(dst: *u8, w: i64, s: *u8) -> i64 { 30 var i: i64 = 0 31 var o: i64 = w 32 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 } 33 return o 34} 35 36func ds_clamp0(v: i64) -> i64 { if v < 0 { return 0 } return v } 37 38// unsigned decimal at buf[q0..n): stops at the first non-digit; 0 when none 39func ds_atoi_at(buf: *u8, n: i64, q0: i64) -> i64 { 40 var v: i64 = 0 41 var q: i64 = q0 42 while q < n { let c: i64 = buf[q] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); q = q + 1 } else { q = n } } else { q = n } } 43 return v 44} 45 46// 1 when key (NUL-terminated) matches b at i, bounded by n. The cursor is NEVER written as an exit -- every 47// exit is a return, which is the whole difference between this and the loop it replaces. 48func ds_key_at(b: *u8, n: i64, i: i64, key: *u8) -> i64 { 49 var k: i64 = 0 50 while key[k] != (0 as u8) { 51 if i + k >= n { return 0 } 52 if b[i + k] != key[k] { return 0 } 53 k = k + 1 54 } 55 return 1 56} 57 58func ds_klen(key: *u8) -> i64 { var k: i64 = 0; while key[k] != (0 as u8) { k = k + 1 } return k } 59 60// the integer after the FIRST occurrence of key AT A LINE START (offset 0 or right after a newline); 0 when absent. 61// Line-anchored so a comment that MENTIONS the key ("... (life_cycles=0) = never recycle") cannot shadow the row. 62func ds_conf_int(b: *u8, n: i64, key: *u8) -> i64 { 63 let kl: i64 = ds_klen(key) 64 var i: i64 = 0 65 while i < n { 66 var at_line: i64 = 0 67 if i == 0 { at_line = 1 } 68 if i > 0 { if b[i - 1] == (10 as u8) { at_line = 1 } } 69 if at_line == 1 { if ds_key_at(b, n, i, key) == 1 { return ds_atoi_at(b, n, i + kl) } } 70 i = i + 1 71 } 72 return 0 73} 74 75// index of the state char in a /proc/<pid>/stat line: the first non-space after the LAST ')' (comm may hold 76// spaces and parens); -1 when the line has no ')' 77func ds_stat_state_at(buf: *u8, n: i64) -> i64 { 78 var rp: i64 = 0 - 1 79 var i: i64 = 0 80 while i < n { if buf[i] == (41 as u8) { rp = i } i = i + 1 } 81 if rp < 0 { return 0 - 1 } 82 var p: i64 = rp + 1 83 while p < n { if buf[p] == (32 as u8) { p = p + 1 } else { return p } } 84 return 0 - 1 85} 86 87// ppid from a /proc/<pid>/stat line: the digits after the state char and its trailing spaces; -1 when unparseable 88func ds_stat_ppid(buf: *u8, n: i64) -> i64 { 89 let sp: i64 = ds_stat_state_at(buf, n) 90 if sp < 0 { return 0 - 1 } 91 var q: i64 = sp + 1 92 while q < n { if buf[q] == (32 as u8) { q = q + 1 } else { return ds_atoi_at(buf, n, q) } } 93 return 0 - 1 94} 95 96// PROGRESS TRAILER: tick= is the MONOTONIC publish counter -- it advances on EVERY row, so a reader (hostctl's 97// stale guard, a seat) can tell a slow cycle from a wedged one; a cycle counter alone cannot (measured 2026-09-02: 98// cycle=1 constant for 14 min under an I/O storm while hostctl's bound was derived from a 200 s worst case). 99// row= rows completed this cycle; last= the row just finished (or stage:<name> for a pre-loop stage); 100// ms_sweep/ms_probe/ms_after its stage costs; partial=1 mid-cycle, partial=0 at cycle end with cycle_ms=. 101// Self-delimiting (leads with a newline) so a mid-line publish still starts the trailer on its own line. 102// Bounded: name <= 64 + 8 numbers + labels < 256 bytes, which is DSL_TRAILER_MAX; the gate pins it. 103const DSL_TRAILER_MAX: i64 = 256 104func ds_trailer(tb: *u8, tick: i64, row: i64, nm: *u8, a: i64, b: i64, c: i64, partial: i64, cms: i64) -> i64 { 105 var w: i64 = 0 106 tb[0] = 10 as u8; w = 1 107 w = ds_sappend(tb, w, "tick=" as *u8); w = w + ds_decw(tick, (((tb as i64) + w)) as *u8) 108 w = ds_sappend(tb, w, " row=" as *u8); w = w + ds_decw(row, (((tb as i64) + w)) as *u8) 109 w = ds_sappend(tb, w, " last=" as *u8); w = ds_sappend(tb, w, nm) 110 w = ds_sappend(tb, w, " ms_sweep=" as *u8); w = w + ds_decw(ds_clamp0(a), (((tb as i64) + w)) as *u8) 111 w = ds_sappend(tb, w, " ms_probe=" as *u8); w = w + ds_decw(ds_clamp0(b), (((tb as i64) + w)) as *u8) 112 w = ds_sappend(tb, w, " ms_after=" as *u8); w = w + ds_decw(ds_clamp0(c), (((tb as i64) + w)) as *u8) 113 w = ds_sappend(tb, w, " partial=" as *u8); w = w + ds_decw(partial, (((tb as i64) + w)) as *u8) 114 if partial == 0 { w = ds_sappend(tb, w, " cycle_ms=" as *u8); w = w + ds_decw(ds_clamp0(cms), (((tb as i64) + w)) as *u8) } 115 tb[w] = 10 as u8; w = w + 1 116 tb[w] = 0 as u8 117 return w 118}