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}