code wiki / _hdl_build / nx_resmon_lib.nx

nx_resmon_lib.nx source

↩ module page · 123 lines · 5071 B

1// nx_resmon_lib.nx -- the PURE, GATEABLE core of nx_resmon (debt seq1005). 2// 3// Split out per rule 9 (single responsibility) and rule 15 (DRY): the organ owns the /proc walk and 4// the printing, this lib owns the two predicates that actually encode the policy -- 5// rm_is_leaker : what counts as a leaking process 6// rm_verdict : how measurements become GREEN / AMBER / RED 7// Keeping them here means the gate exercises the SAME code the organ runs, not a reimplementation. 8// Everything here is a pure function of its arguments (rm_field/rm_conf parse a caller-owned buffer), 9// so the gate needs no /proc, no files and no fixtures on disk. 10// license_tier: ORIGINAL 11import "nx_syscalls.nx" 12 13func rm_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 14func rm_puts(s: *u8) -> i64 { sys_write(1, s, rm_slen(s)); return 0 } 15func rm_num(v: i64) -> i64 { 16 let b: *u8 = sys_mmap(32) 17 var x: i64 = v 18 var neg: i64 = 0 19 if x < 0 { neg = 1; x = 0 - x } 20 var i: i64 = 31 21 b[i] = 0 as u8 22 if x == 0 { i = i - 1; b[i] = 48 as u8 } 23 while x > 0 { i = i - 1; b[i] = (48 + (x - (x / 10) * 10)) as u8; x = x / 10 } 24 if neg == 1 { i = i - 1; b[i] = 45 as u8 } 25 rm_puts(((b as i64) + i) as *u8) 26 sys_munmap(b, 32) 27 return 0 28} 29// read a whole small file; returns byte count (-1 if unreadable). NUL-terminates. 30func rm_read(path: *u8, buf: *u8, cap: i64) -> i64 { 31 let fd: i64 = sys_openat_rd(path) 32 if fd < 0 { return 0 - 1 } 33 var tot: i64 = 0 34 var run: i64 = 1 35 while run == 1 { 36 let n: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot - 1) 37 if n <= 0 { run = 0 } else { 38 tot = tot + n 39 if tot >= cap - 1 { run = 0 } 40 } 41 } 42 sys_close(fd) 43 buf[tot] = 0 as u8 44 return tot 45} 46// first integer on the line that STARTS with `key`; -1 if absent. Serves BOTH 47// /proc/<pid>/status ("VmSize:\t 1234 kB") and conf rows ("leak-min-kb = 262144"). 48func rm_field(buf: *u8, n: i64, key: *u8) -> i64 { 49 let kl: i64 = rm_slen(key) 50 var i: i64 = 0 51 var found: i64 = 0 - 1 52 while i + kl <= n { 53 if found < 0 { 54 var atline: i64 = 0 55 if i == 0 { atline = 1 } else { if buf[i - 1] == (10 as u8) { atline = 1 } } 56 if atline == 1 { 57 var j: i64 = 0 58 var ok: i64 = 1 59 while j < kl { if buf[i + j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 60 if ok == 1 { found = i + kl } 61 } 62 } 63 i = i + 1 64 } 65 if found < 0 { return 0 - 1 } 66 var p: i64 = found 67 var v: i64 = 0 68 var seen: i64 = 0 69 var run: i64 = 1 70 while run == 1 { 71 if p >= n { run = 0 } else { 72 let c: i64 = buf[p] as i64 73 if c == 10 { run = 0 } else { 74 if c >= 48 { 75 if c <= 57 { v = v * 10 + (c - 48); seen = 1 } else { if seen == 1 { run = 0 } } 76 } else { if seen == 1 { run = 0 } } 77 p = p + 1 78 } 79 } 80 } 81 if seen == 0 { return 0 - 1 } 82 return v 83} 84func rm_conf(cbuf: *u8, cn: i64, key: *u8, dflt: i64) -> i64 { 85 if cn <= 0 { return dflt } 86 let v: i64 = rm_field(cbuf, cn, key) 87 if v < 0 { return dflt } 88 return v 89} 90 91// ---- PREDICATE 1: does this process look like a leaker? ------------------------------------------- 92// vsz==vpk => the address space has ONLY EVER GROWN (allocate-without-free fingerprint). 93// SIZE IS JUDGED ON COMMITTED MEMORY (rss+swap), NEVER ON RESERVED VmData. Measured 2026-07-25 on the 94// first live run: a VmData screen reported a worst case of 40.6 GiB on a 36.9 GB box -- larger than 95// physical RAM -- because VmData counts reservations a process never faulted in. Screening on rss+swap 96// took the census from 63 suspects to 23 and the worst case to 3.02 GiB. 97func rm_is_leaker(vsz: i64, vpk: i64, rss: i64, swap: i64, min_kb: i64) -> i64 { 98 if vsz <= 0 { return 0 } 99 if vpk <= 0 { return 0 } 100 if vsz != vpk { return 0 } 101 var touched: i64 = 0 102 if rss > 0 { touched = touched + rss } 103 if swap > 0 { touched = touched + swap } 104 if touched < min_kb { return 0 } 105 return 1 106} 107 108// ---- PREDICATE 2: measurements -> severity --------------------------------------------------------- 109// 0=GREEN 1=AMBER 2=RED. RED dominates AMBER; ANY axis can raise the verdict on its own, because each 110// one independently indicates the box is in trouble. Thresholds are ALWAYS passed in (rule 11) -- this 111// function contains no policy numbers of its own, which is exactly what the gate mutates to prove it. 112func rm_verdict(swused_pm: i64, avail_pm: i64, leakers: i64, 113 sw_amber: i64, sw_red: i64, av_amber: i64, av_red: i64, 114 leak_amber: i64, leak_red: i64) -> i64 { 115 var sev: i64 = 0 116 if swused_pm >= sw_amber { sev = 1 } 117 if avail_pm <= av_amber { sev = 1 } 118 if leakers >= leak_amber { sev = 1 } 119 if swused_pm >= sw_red { sev = 2 } 120 if avail_pm <= av_red { sev = 2 } 121 if leakers >= leak_red { sev = 2 } 122 return sev 123}