code wiki / (root) / nx_vsz_watchdog_core.nx

nx_vsz_watchdog_core.nx source

↩ module page · 158 lines · 7776 B

1// nx_vsz_watchdog_core.nx -- importable CORE of the VSZ watchdog (the permanent fix for outage MODE 2: 2// mmap-per-request daemons never munmap -> VSZ balloons (mgmt hit ~160GB) -> fork() fails -> child-exec 3// SILENTLY EMPTY while /api/health stays 200; see reference-mgmt-api-outage-tmp-log-rootcause-2026-07-12). 4// The watchdog DECIDES DEATH ONLY: it kills a conf-listed daemon whose VSZ crossed its threshold; RESPAWN 5// stays 100% the nx_hostctl guard's job (single responsibility, no dueling supervisors). FAIL-SAFE BY 6// CONSTRUCTION: no conf file -> INERT; unreadable /proc -> skip; cooldown suppresses kill-storms; pid<=300 7// and self are never killed. Pure decision funcs here (gate-locked); the /proc walk + kill live in the CLI. 8// license_tier: ORIGINAL 9import "nx_syscalls.nx" 10 11const VW_PROC_PATH_CAP: i64 = 256 // /proc/<pid>/status path buffer 12const VW_STATUS_BUF: i64 = 8192 // /proc status read buffer 13const VW_PTR_CELL: i64 = 16 // 2-i64 scratch cell (vw_num_at end-pointer out-param) 14 15func vw_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 16 17// substring containment (hostctl's hc_contains idiom; needle has no NUL so cmdline NUL separators are safe). 18func vw_contains(hay: *u8, hn: i64, needle: *u8, nn: i64) -> i64 { 19 if nn == 0 { return 0 } 20 var i: i64 = 0 21 while i + nn <= hn { 22 var k: i64 = 0 23 var ok: i64 = 1 24 while k < nn { if hay[i+k] != needle[k] { ok = 0; k = nn } k = k + 1 } 25 if ok == 1 { return 1 } 26 i = i + 1 27 } 28 return 0 29} 30 31// parse leading unsigned decimal from s[off..n): value, or -1 if no digit at off. end offset in endp[0]. 32func vw_num_at(s: *u8, n: i64, off: i64, endp: *i64) -> i64 { 33 var v: i64 = 0 34 var any: i64 = 0 35 var i: i64 = off 36 var go: i64 = 1 37 while go == 1 { 38 go = 0 39 if i < n { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); any = 1; i = i + 1; go = 1 } } } 40 } 41 endp[0] = i 42 if any == 0 { return 0 - 1 } 43 return v 44} 45 46// parse one conf line buf[ls..le): "<needle> <max_gb>". Writes NUL after the needle IN PLACE, returns gb 47// (>=1) with needle start in outp[0], or -1 for comment/blank/malformed (row dropped, fail-safe). 48func vw_parse_row(buf: *u8, ls: i64, le: i64, outp: *i64) -> i64 { 49 var i: i64 = ls 50 var go: i64 = 1 51 while go == 1 { go = 0; if i < le { let c: i64 = buf[i] as i64; if c == 32 { i = i + 1; go = 1 } else { if c == 9 { i = i + 1; go = 1 } } } } 52 if i >= le { return 0 - 1 } 53 if buf[i] == (35 as u8) { return 0 - 1 } // '#' comment 54 let nstart: i64 = i 55 go = 1 56 while go == 1 { go = 0; if i < le { let c: i64 = buf[i] as i64; if c != 32 { if c != 9 { i = i + 1; go = 1 } } } } 57 if i >= le { return 0 - 1 } // no separator -> malformed 58 let nend: i64 = i 59 let ep: *i64 = sys_mmap(VW_PTR_CELL) as *i64 60 var j: i64 = i 61 go = 1 62 while go == 1 { go = 0; if j < le { let c: i64 = buf[j] as i64; if c == 32 { j = j + 1; go = 1 } else { if c == 9 { j = j + 1; go = 1 } } } } 63 let gb: i64 = vw_num_at(buf, le, j, ep) 64 sys_munmap(ep as *u8, VW_PTR_CELL) // leak-free: ep (the end-ptr out-param) was leaked per row (the ep-out-param class my leak-checker flagged) 65 if gb < 1 { return 0 - 1 } // gb<1 -> inert row (never a 0-threshold kill-everything) 66 buf[nend] = 0 as u8 // NUL-terminate the needle in place 67 outp[0] = nstart 68 return gb 69} 70 71// parse the kB value of an arbitrary "<Label>:" row out of a /proc status text. -1 absent/unreadable. 72// Generalized so VmSize (address space) and VmRSS (resident -- the heap-leak meter VSZ can hide) share ONE 73// parser (DRY; NEVER kill on parse failure). 74func vw_status_kb(buf: *u8, n: i64, pat: *u8) -> i64 { 75 let pl: i64 = vw_slen(pat) 76 var i: i64 = 0 77 while i + pl <= n { 78 var k: i64 = 0 79 var ok: i64 = 1 80 while k < pl { if buf[i+k] != pat[k] { ok = 0; k = pl } k = k + 1 } 81 if ok == 1 { 82 var j: i64 = i + pl 83 var go: i64 = 1 84 while go == 1 { go = 0; if j < n { let c: i64 = buf[j] as i64; if c == 32 { j = j + 1; go = 1 } else { if c == 9 { j = j + 1; go = 1 } } } } 85 let ep: *i64 = sys_mmap(VW_PTR_CELL) as *i64 86 let r: i64 = vw_num_at(buf, n, j, ep) 87 sys_munmap(ep as *u8, VW_PTR_CELL) // leak-free: ep was mmap'd-and-leaked per call (the ep-out-param leak class) 88 return r 89 } 90 i = i + 1 91 } 92 return 0 - 1 93} 94 95// THE kill decision. 1 only when: threshold sane (gb>=1) AND vsz known (kb>0) AND over threshold AND the 96// per-row cooldown expired. Everything else -> 0 (fail-safe). 97func vw_should_kill(vsz_kb: i64, max_gb: i64, last_kill_s: i64, now_s: i64, cooldown_s: i64) -> i64 { 98 if max_gb < 1 { return 0 } 99 if vsz_kb <= 0 { return 0 } 100 if vsz_kb <= max_gb * 1048576 { return 0 } 101 if now_s - last_kill_s < cooldown_s { return 0 } 102 return 1 103} 104 105// bounded whole-file read. -1 absent. 106func vw_read(path: *u8, buf: *u8, cap: i64) -> i64 { 107 let fd: i64 = sys_openat_rd(path) 108 if fd < 0 { return 0 - 1 } 109 var tot: i64 = 0 110 var n: i64 = sys_read(fd, buf, cap) 111 while n > 0 { tot = tot + n; if tot >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot) } } 112 sys_close(fd) 113 return tot 114} 115 116// monotonic seconds (persists across one-shot runs within a boot -- exactly the cooldown scope we want). 117func vw_now_s() -> i64 { let ts: *i64 = sys_mmap(16) as *i64; sys_clock_gettime_mono(ts); return ts[0] } 118 119// self pid via /proc/self/stat leading digits (no getpid syscall-number risk). 120func vw_selfpid() -> i64 { 121 let b: *u8 = sys_mmap(VW_PROC_PATH_CAP) 122 let n: i64 = vw_read("/proc/self/stat" as *u8, b, VW_PROC_PATH_CAP - 1) 123 if n <= 0 { sys_munmap(b, VW_PROC_PATH_CAP); return 0 - 1 } 124 let ep: *i64 = sys_mmap(VW_PTR_CELL) as *i64 125 let r: i64 = vw_num_at(b, n, 0, ep) 126 sys_munmap(b, VW_PROC_PATH_CAP); sys_munmap(ep, VW_PTR_CELL) // leak-free (b + ep were leaked per call) 127 return r 128} 129 130// back-compat: the VmSize row (a sibling gate + vw_status_kb_of call this by name) 131func vw_vmsize_parse(buf: *u8, n: i64) -> i64 { return vw_status_kb(buf, n, "VmSize:" as *u8) } 132// kB of an arbitrary "<label>:" status row for a /proc entry named by DIRECTORY STRING (pid or "self"). 133// -1 unreadable. LEAK-FREE (munmaps path+b on every return). Generalized so VmSize (address space) and 134// VmRSS (resident heap -- the leak class an arena hides from VSZ) share ONE reader. 135func vw_status_kb_of(dirname: *u8, label: *u8) -> i64 { 136 let path: *u8 = sys_mmap(VW_PROC_PATH_CAP) 137 var o: i64 = 0 138 let pre: *u8 = "/proc/" as *u8 139 var a: i64 = 0 140 while pre[a] != (0 as u8) { path[o] = pre[a]; o = o + 1; a = a + 1 } 141 a = 0 142 while dirname[a] != (0 as u8) { path[o] = dirname[a]; o = o + 1; a = a + 1 } 143 let suf: *u8 = "/status" as *u8 144 a = 0 145 while suf[a] != (0 as u8) { path[o] = suf[a]; o = o + 1; a = a + 1 } 146 path[o] = 0 as u8 147 let b: *u8 = sys_mmap(VW_STATUS_BUF) 148 let n: i64 = vw_read(path, b, VW_STATUS_BUF - 1) 149 if n <= 0 { sys_munmap(path, VW_PROC_PATH_CAP); sys_munmap(b, VW_STATUS_BUF); return 0 - 1 } 150 let r: i64 = vw_status_kb(b, n, label) 151 sys_munmap(path, VW_PROC_PATH_CAP) 152 sys_munmap(b, VW_STATUS_BUF) 153 return r 154} 155// VmSize kB (back-compat; leak_check's memory meter). -1 unreadable. 156func vw_vmsize_kb_of(dirname: *u8) -> i64 { return vw_status_kb_of(dirname, "VmSize:" as *u8) } 157// VmRSS kB (resident set -- the heap-leak meter VmSize can hide in an arena). 158func vw_rss_kb_of(dirname: *u8) -> i64 { return vw_status_kb_of(dirname, "VmRSS:" as *u8) }