code wiki / _hdl_build / nx_procchurn_lib.nx

nx_procchurn_lib.nx source

↩ module page · 183 lines · 8958 B

1// nx_procchurn_lib.nx -- the PURE, GATEABLE core of nx_procchurn (perf lane, debt seq1317/seq1318). 2// 3// WHY THIS EXISTS: on 2026-07-30 the NAS measured 78.3 forks/sec SUSTAINED with kernel time at 231 permil 4// of an 8-core host against 98 permil userspace -- the kernel doing 2.36x the work of the application -- 5// while nx_health reported {overall:OK, degraded:0, down:0} and all 14 services read UP. nx_resmon owns the 6// MEMORY axis and nx_netobs owns the NETWORK axis; NOTHING owned the PROCESS-CHURN axis, so the single 7// largest CPU consumer on the platform had no instrument at all and had to be found by hand from /proc. 8// LAW: liveness is not performance -- a board that only asks \"is it up\" cannot see the machine burning. 9// 10// Split per rule 9 (single responsibility) + rule 15 (DRY): the organ owns the sampling and the printing, 11// this lib owns the four pure functions that encode the policy, so the gate exercises the SAME code the 12// organ runs rather than a reimplementation. Rule 15 again: the generic helpers (puts/num/read/conf) are 13// IMPORTED from nx_resmon_lib.nx, NOT copied -- D001/seq389 counts 9495 duplicate function bodies and the 14// emit family (gw x427, g_puts x325) is the worst group; a new organ must not add to it. 15// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). 16import "nx_resmon_lib.nx" 17 18// system:user sentinel for zero userspace time -- the kernel is infinitely dominant, not \"at parity\". 19// Deliberately ABOVE any plausible real ratio so it always trips red instead of reading as a quiet 0. 20const PC_RATIO_SAT: i64 = 1000000 21const PC_ASCII_0: i64 = 48 22const PC_ASCII_9: i64 = 57 23const PC_ASCII_NL: i64 = 10 24const PC_PERMIL: i64 = 1000 25 26// Parse up to `maxv` integers from the line that STARTS with `key`, into out[]. Returns how many integers 27// were FOUND on that line (may exceed maxv -- only the first maxv are stored), or -1 if NO line starts 28// with key. FAIL-CLOSED: -1 is unmistakable and can never be mistaken for a real measurement of 0. 29// PREFIX DISCIPLINE IS THE WHOLE POINT: the caller passes \"cpu \" WITH the trailing space. Without it the 30// scan would match \"cpu0\" and report ONE CORE's jiffies as the whole box -- a wrong answer that looks 31// entirely plausible. The gate proves this specific confusion cannot happen. 32func pc_line_ints(buf: *u8, n: i64, key: *u8, out: *i64, maxv: i64) -> i64 { 33 let kl: i64 = rm_slen(key) 34 var i: i64 = 0 35 var found: i64 = 0 - 1 36 while i + kl <= n { 37 if found < 0 { 38 var atline: i64 = 0 39 if i == 0 { atline = 1 } 40 if i > 0 { if buf[i - 1] == (PC_ASCII_NL as u8) { atline = 1 } } 41 if atline == 1 { 42 var j: i64 = 0 43 var ok: i64 = 1 44 while j < kl { if buf[i + j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 45 if ok == 1 { found = i + kl } 46 } 47 } 48 i = i + 1 49 } 50 if found < 0 { return 0 - 1 } 51 var p: i64 = found 52 var cnt: i64 = 0 53 var cur: i64 = 0 54 var seen: i64 = 0 55 var run: i64 = 1 56 while run == 1 { 57 var c: i64 = PC_ASCII_NL 58 if p < n { c = buf[p] as i64 } 59 var isdig: i64 = 0 60 if c >= PC_ASCII_0 { if c <= PC_ASCII_9 { isdig = 1 } } 61 if isdig == 1 { cur = cur * 10 + (c - PC_ASCII_0); seen = 1 } 62 if isdig == 0 { 63 if seen == 1 { 64 if cnt < maxv { out[cnt] = cur } 65 cnt = cnt + 1 66 cur = 0 67 seen = 0 68 } 69 } 70 if c == PC_ASCII_NL { run = 0 } 71 if p >= n { run = 0 } 72 p = p + 1 73 } 74 return cnt 75} 76 77// delta over an elapsed window -> per-second rate. FAIL-CLOSED on BOTH nonsense inputs: 78// elapsed<=0 two samples taken at the same instant (division by zero / meaningless window) 79// delta<0 the counter went BACKWARDS = a 32-bit wrap or a reboot between samples 80// Returning -1 instead of a number is precisely what stops a wrap being published as a plausible rate. 81func pc_rate(delta: i64, elapsed_ms: i64) -> i64 { 82 if elapsed_ms <= 0 { return 0 - 1 } 83 if delta < 0 { return 0 - 1 } 84 return (delta * PC_PERMIL) / elapsed_ms 85} 86 87// system jiffies : user jiffies, as permil. 1000 = parity. >1000 = the kernel is doing more work than 88// the application, which on a service host is the churn fingerprint. usr==0 -> PC_RATIO_SAT. 89func pc_ratio_permil(sys_j: i64, usr_j: i64) -> i64 { 90 if sys_j < 0 { return 0 - 1 } 91 if usr_j < 0 { return 0 - 1 } 92 if usr_j == 0 { return PC_RATIO_SAT } 93 return (sys_j * PC_PERMIL) / usr_j 94} 95 96// measurements -> severity. 0=GREEN 1=AMBER 2=RED. RED dominates AMBER, and ANY axis raises the verdict 97// on its own because each one independently indicates churn. Contains NO policy numbers of its own 98// (rule 11) -- every threshold is passed in, which is exactly what the gate mutates to prove it is not 99// vacuous. Callers MUST screen out negative (unmeasurable) inputs first: a -1 compares below every 100// threshold and would otherwise read as a confident GREEN, which is the partial-as-complete defect. 101func pc_verdict(forks_ps: i64, ratio_pm: i64, ctxsw_ps: i64, 102 f_amber: i64, f_red: i64, r_amber: i64, r_red: i64, 103 c_amber: i64, c_red: i64) -> i64 { 104 var sev: i64 = 0 105 if forks_ps >= f_amber { sev = 1 } 106 if ratio_pm >= r_amber { sev = 1 } 107 if ctxsw_ps >= c_amber { sev = 1 } 108 if forks_ps >= f_red { sev = 2 } 109 if ratio_pm >= r_red { sev = 2 } 110 if ctxsw_ps >= c_red { sev = 2 } 111 return sev 112} 113 114const PC_NUMSCRATCH: i64 = 32 115const PC_ASCII_MINUS: i64 = 45 116 117// ---- journal formatting: PURE, so the gate proves the ON-DISK frame without touching a filesystem ---- 118// Rule 15 note: cat/catn are re-implemented in nearly every organ (D001/seq389 measured gw x427, 119// g_puts x325 -- 9495 duplicate function bodies). They live HERE rather than inline in the organ so the 120// instrument and its gate share ONE copy, and the next organ on this plane imports them instead of 121// adding copy 428. 122func pc_cat(d: *u8, o: i64, s: *u8) -> i64 { 123 var i: i64 = 0 124 while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } 125 return o + i 126} 127func pc_catn(d: *u8, o: i64, v: i64) -> i64 { 128 let t: *u8 = sys_mmap(PC_NUMSCRATCH) 129 var m: i64 = v 130 var p: i64 = o 131 if m < 0 { d[p] = PC_ASCII_MINUS as u8; p = p + 1; m = 0 - m } 132 var k: i64 = 0 133 if m == 0 { t[0] = PC_ASCII_0 as u8; k = 1 } 134 while m > 0 { t[k] = (PC_ASCII_0 + (m - (m / 10) * 10)) as u8; m = m / 10; k = k + 1 } 135 var i: i64 = 0 136 while i < k { d[p + i] = t[k - 1 - i]; i = i + 1 } 137 sys_munmap(t, PC_NUMSCRATCH) 138 return p + k 139} 140 141// ONE append-only journal frame -- the trend line. PURE: caller owns d, returns the byte length. 142// WHY A JOURNAL AND NOT JUST A VERDICT: a single verdict is a snapshot, and the fork storm this organ 143// exists to catch is BURSTY. Measured the same hour on 2026-07-30: 85/s since-boot average over 13.5h, 144// 78/s over a 117s window, 18/s inside one 3s window -- ALL THREE HONEST, all different timescales. 145// One sample can therefore neither confirm nor refute the condition; the journal carries the movement, 146// exactly as nx_law_warden's does, and a fix is proven landed by the TREND, never by a single run. 147// QUEUE COMPOSITION appended (seq1555): running/blocked are the ONLY fields here that say WHAT a deep 148// queue is waiting on. loadavg counts D-state as well as runnable, and iowait is only accrued while a 149// CPU is otherwise IDLE -- so neither can distinguish an I/O-blocked queue from a compute one, and 150// blocked>>running settles it. They are FREE (already in /proc/stat) and must be in the FRAME, not just 151// on stdout, or the 200-char plan snippet truncates them: an instrument that cannot deliver its findings 152// has not delivered them. 153func pc_frame(d: *u8, ts: i64, win_ms: i64, forks_ps: i64, ctxsw_ps: i64, intr_ps: i64, 154 ku_pm: i64, usr_pm: i64, sys_pm: i64, busy_pm: i64, sev: i64, 155 running: i64, blocked: i64) -> i64 { 156 var o: i64 = 0 157 o = pc_cat(d, o, "ts=" as *u8) 158 o = pc_catn(d, o, ts) 159 o = pc_cat(d, o, " window_ms=" as *u8) 160 o = pc_catn(d, o, win_ms) 161 o = pc_cat(d, o, " forks_ps=" as *u8) 162 o = pc_catn(d, o, forks_ps) 163 o = pc_cat(d, o, " ctxsw_ps=" as *u8) 164 o = pc_catn(d, o, ctxsw_ps) 165 o = pc_cat(d, o, " intr_ps=" as *u8) 166 o = pc_catn(d, o, intr_ps) 167 o = pc_cat(d, o, " ku_permil=" as *u8) 168 o = pc_catn(d, o, ku_pm) 169 o = pc_cat(d, o, " usr_permil=" as *u8) 170 o = pc_catn(d, o, usr_pm) 171 o = pc_cat(d, o, " sys_permil=" as *u8) 172 o = pc_catn(d, o, sys_pm) 173 o = pc_cat(d, o, " busy_permil=" as *u8) 174 o = pc_catn(d, o, busy_pm) 175 o = pc_cat(d, o, " sev=" as *u8) 176 o = pc_catn(d, o, sev) 177 o = pc_cat(d, o, " running=" as *u8) 178 o = pc_catn(d, o, running) 179 o = pc_cat(d, o, " blocked=" as *u8) 180 o = pc_catn(d, o, blocked) 181 o = pc_cat(d, o, "\n" as *u8) 182 return o 183}