code wiki / (root) / nx_resgov.nx

nx_resgov.nx source

↩ module page · 396 lines · 21027 B

1// nx_resgov.nx -- THE UNIFIED RESOURCE GOVERNOR (one-shot, cron-invoked every minute). 2// 3// nx_resgov [conf] [dry] conf default knowledge/resgov.conf ; `dry` = decide + log, never signal 4// 5// Walks EVERY process, meters VmRSS + VmSwap (the resource that actually hurts), remembers each 6// process's footprint between runs to compute GROWTH VELOCITY, and escalates gracefully long before a 7// ceiling is reached. Default-covered: an unlisted process is governed by the default cap, not ignored. 8// Death is decided here; RESPAWN stays the guard's job (single responsibility -- no dueling supervisors). 9// 10// STATE (velocity memory): knowledge/status/resgov.state, rows `pid:starttime footprint_kb epoch level_ts`. 11// ★IDENTITY IS pid+starttime, NEVER pid ALONE -- Linux reuses pids, and a reused pid would make the 12// velocity of a brand-new process look like the growth of the dead one it replaced, which is how a 13// predictive breaker kills innocents. 14// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 15import "nx_syscalls.nx" 16import "nx_vsz_watchdog_core.nx" // proven /proc readers: vw_read, vw_status_kb_of, vw_num_at, vw_contains 17import "nx_resgov_core.nx" 18const RG_MAGIC_4096: i64 = 4096 19const RG_MAGIC_4095: i64 = 4095 20const RG_MAGIC_3600: i64 = 3600 21const RG_MAGIC_1024: i64 = 1024 22const RG_MAGIC_1048576: i64 = 1048576 23 24const RG_CONF_CAP: i64 = 16384 25const RG_STATE_CAP: i64 = 262144 26const RG_DIR_CAP: i64 = 65536 27const RG_CMD_CAP: i64 = 8192 28const RG_MAXROWS: i64 = 64 29const RG_MAXPROC: i64 = 2048 30const RG_STATE_PATH: *u8 = "knowledge/status/resgov.state" 31 32func g_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 33func g_putn(v: i64) -> i64 { 34 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 35 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 36 let d: *u8 = sys_mmap(24); var k: i64 = 0 37 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 38 let o: *u8 = sys_mmap(24); var i: i64 = 0 39 while i < k { o[i] = d[k-1-i]; i = i + 1 } 40 sys_write(1, o, k) 41 return 0 42} 43func g_streq(a: *u8, b: *u8) -> i64 { 44 var i: i64 = 0 45 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 46 if b[i] != (0 as u8) { return 0 } 47 return 1 48} 49func g_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 50// index of the first space in buf[from,to), or -1. ⚠Written as REAL scans returning a POSITION: the 51// first cut of this parser used the loop variable as a break FLAG (i = to + 1) and therefore threw the 52// position away -- it parsed 0 rows out of a perfectly good conf and every policy silently fell back to 53// the in-code defaults. ★A PARSER THAT LOSES THE POSITION REPORTS SUCCESS AND MEANS NOTHING. 54func g_find_sp(buf: *u8, from: i64, to: i64) -> i64 { 55 var i: i64 = from 56 var found: i64 = 0 - 1 57 while i < to { if buf[i] == (32 as u8) { found = i; i = to } else { i = i + 1 } } 58 return found 59} 60// index of the first non-space in buf[from,to), or -1 if only spaces remain. 61func g_skip_sp(buf: *u8, from: i64, to: i64) -> i64 { 62 var i: i64 = from 63 var found: i64 = 0 - 1 64 while i < to { if buf[i] != (32 as u8) { found = i; i = to } else { i = i + 1 } } 65 return found 66} 67func g_app(buf: *u8, p: i64, s: *u8) -> i64 { var i: i64 = 0; var q: i64 = p; while s[i] != (0 as u8) { buf[q] = s[i]; q = q + 1; i = i + 1 } return q } 68func g_appn(buf: *u8, p: i64, v: i64) -> i64 { 69 var q: i64 = p 70 if v == 0 { buf[q] = 48 as u8; return q + 1 } 71 var m: i64 = v 72 if m < 0 { buf[q] = 45 as u8; q = q + 1; m = 0 - m } 73 let t: *u8 = sys_mmap(32); var k: i64 = 0 74 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 75 var i: i64 = 0 76 while i < k { buf[q] = t[k-1-i]; q = q + 1; i = i + 1 } 77 return q 78} 79 80// process start-time (field 22 of /proc/<pid>/stat) -- the pid-reuse discriminator. 0 unreadable. 81// The comm field can contain spaces AND parens, so scanning starts after the LAST ')'. 82func g_starttime(dirname: *u8) -> i64 { 83 let path: *u8 = sys_mmap(256) 84 var o: i64 = g_app(path, 0, "/proc/" as *u8) 85 o = g_app(path, o, dirname) 86 o = g_app(path, o, "/stat" as *u8) 87 path[o] = 0 as u8 88 let b: *u8 = sys_mmap(RG_MAGIC_4096) 89 let n: i64 = vw_read(path, b, RG_MAGIC_4095) 90 if n <= 0 { return 0 } 91 var close: i64 = 0 - 1 92 var i: i64 = 0 93 while i < n { if b[i] == (41 as u8) { close = i } i = i + 1 } 94 if close < 0 { return 0 } 95 // fields after comm: state(3) ppid(4) ... starttime(22) -> the 19th token after ')' 96 var tok: i64 = 0 97 var j: i64 = close + 1 98 let ep: *i64 = sys_mmap(16) as *i64 99 var res: i64 = 0 100 var go: i64 = 1 101 while go == 1 { 102 go = 0 103 if j < n { 104 if b[j] == (32 as u8) { j = j + 1; go = 1 } else { 105 tok = tok + 1 106 if tok == 20 { res = vw_num_at(b, n, j, ep) } else { 107 while j < n { if b[j] == (32 as u8) { j = n + 1 } else { j = j + 1 } } 108 if j == n + 1 { j = j - 1 } 109 go = 1 110 } 111 } 112 } 113 } 114 return res 115} 116 117func main(argc: i64, argv: *i64) -> i64 { 118 var confp: *u8 = "knowledge/resgov.conf" as *u8 119 if argc >= 2 { confp = argv[1] as *u8 } 120 var dry: i64 = 0 121 if argc >= 3 { if g_streq(argv[2] as *u8, "dry" as *u8) == 1 { dry = 1 } } 122 123 // ---- config (data, not code -- rule 11) ---- 124 var default_cap_gb: i64 = 0 // 0 = INERT until the operator declares one (fail-safe) 125 var head_floor: i64 = 150 // permil of RAM free below which the system counts as scarce 126 var swap_ceil: i64 = 500 // permil of swap used above which the system counts as scarce 127 var eta_warn_s: i64 = RG_MAGIC_3600 128 var eta_act_s: i64 = 900 129 var cooldown_s: i64 = 600 130 let ex_off: *i64 = sys_mmap(RG_MAXROWS * 8) as *i64 131 var n_ex: i64 = 0 132 let cap_off: *i64 = sys_mmap(RG_MAXROWS * 8) as *i64 133 let cap_gb: *i64 = sys_mmap(RG_MAXROWS * 8) as *i64 134 var n_cap: i64 = 0 135 136 let conf: *u8 = sys_mmap(RG_CONF_CAP) 137 let cn: i64 = vw_read(confp, conf, RG_CONF_CAP - 1) 138 if cn <= 0 { g_puts("[resgov] INERT (no conf at " as *u8); g_puts(confp); g_puts(")\n" as *u8); return 0 } 139 let ep: *i64 = sys_mmap(16) as *i64 140 var ls: i64 = 0 141 while ls < cn { 142 var le: i64 = ls 143 var sc: i64 = 0 144 while sc == 0 { if le >= cn { sc = 1 } else { if conf[le] == (10 as u8) { sc = 1 } else { le = le + 1 } } } 145 if le > ls { if conf[ls] != (35 as u8) { 146 // key = first token, rest = args 147 let keyend: i64 = g_find_sp(conf, ls, le) 148 if keyend > ls { 149 var vstart: i64 = g_skip_sp(conf, keyend + 1, le) 150 if vstart < 0 { vstart = le } 151 conf[keyend] = 0 as u8 152 let key: *u8 = ((conf as i64) + ls) as *u8 153 if g_streq(key, "default_cap_gb" as *u8) == 1 { default_cap_gb = vw_num_at(conf, le, vstart, ep) } 154 if g_streq(key, "headroom_floor_permil" as *u8) == 1 { head_floor = vw_num_at(conf, le, vstart, ep) } 155 if g_streq(key, "swap_ceiling_permil" as *u8) == 1 { swap_ceil = vw_num_at(conf, le, vstart, ep) } 156 if g_streq(key, "eta_warn_s" as *u8) == 1 { eta_warn_s = vw_num_at(conf, le, vstart, ep) } 157 if g_streq(key, "eta_act_s" as *u8) == 1 { eta_act_s = vw_num_at(conf, le, vstart, ep) } 158 if g_streq(key, "cooldown_s" as *u8) == 1 { cooldown_s = vw_num_at(conf, le, vstart, ep) } 159 if g_streq(key, "exempt" as *u8) == 1 { if n_ex < RG_MAXROWS { conf[le] = 0 as u8; ex_off[n_ex] = (conf as i64) + vstart; n_ex = n_ex + 1 } } 160 if g_streq(key, "cap" as *u8) == 1 { 161 // cap <needle> <gb> 162 let nend: i64 = g_find_sp(conf, vstart, le) 163 if nend > vstart { 164 let gstart: i64 = g_skip_sp(conf, nend + 1, le) 165 if gstart > 0 { 166 let gb: i64 = vw_num_at(conf, le, gstart, ep) 167 if gb >= 1 { if n_cap < RG_MAXROWS { conf[nend] = 0 as u8; cap_off[n_cap] = (conf as i64) + vstart; cap_gb[n_cap] = gb; n_cap = n_cap + 1 } } 168 } 169 } 170 } 171 } 172 } } 173 ls = le + 1 174 } 175 176 // ---- system pressure (measured live, never assumed) ---- 177 let mi: *u8 = sys_mmap(RG_CONF_CAP) 178 let min_: i64 = vw_read("/proc/meminfo" as *u8, mi, RG_CONF_CAP - 1) 179 var headroom: i64 = 0 - 1 180 var swapper: i64 = 0 - 1 181 if min_ > 0 { 182 let mtot: i64 = rg_meminfo_kb(mi, min_, "MemTotal:" as *u8) 183 let mav: i64 = rg_meminfo_kb(mi, min_, "MemAvailable:" as *u8) 184 let stot: i64 = rg_meminfo_kb(mi, min_, "SwapTotal:" as *u8) 185 let sfree: i64 = rg_meminfo_kb(mi, min_, "SwapFree:" as *u8) 186 headroom = rg_headroom_permil(mav, mtot) 187 if stot > 0 { if sfree >= 0 { swapper = rg_swap_permil(stot - sfree, stot) } } 188 } 189 let pressured: i64 = rg_system_pressured(headroom, swapper, head_floor, swap_ceil) 190 191 // ---- prior samples (velocity memory) ---- 192 let st: *u8 = sys_mmap(RG_STATE_CAP) 193 let stn: i64 = vw_read(RG_STATE_PATH, st, RG_STATE_CAP - 1) 194 // parsed prior rows: key string offsets + footprint + epoch + last-action ts 195 let p_key: *i64 = sys_mmap(RG_MAXPROC * 8) as *i64 196 let p_kb: *i64 = sys_mmap(RG_MAXPROC * 8) as *i64 197 let p_t: *i64 = sys_mmap(RG_MAXPROC * 8) as *i64 198 let p_act: *i64 = sys_mmap(RG_MAXPROC * 8) as *i64 199 var n_prior: i64 = 0 200 if stn > 0 { 201 var s2: i64 = 0 202 while s2 < stn { 203 var e2: i64 = s2 204 var sc2: i64 = 0 205 while sc2 == 0 { if e2 >= stn { sc2 = 1 } else { if st[e2] == (10 as u8) { sc2 = 1 } else { e2 = e2 + 1 } } } 206 if e2 > s2 { if n_prior < RG_MAXPROC { 207 // key kb epoch act 208 let kend: i64 = g_find_sp(st, s2, e2) 209 if kend > s2 { 210 st[kend] = 0 as u8 211 p_key[n_prior] = (st as i64) + s2 212 var f: i64 = kend + 1 213 p_kb[n_prior] = vw_num_at(st, e2, f, ep) 214 f = ep[0] + 1 215 p_t[n_prior] = vw_num_at(st, e2, f, ep) 216 f = ep[0] + 1 217 let av: i64 = vw_num_at(st, e2, f, ep) 218 if av < 0 { p_act[n_prior] = 0 } else { p_act[n_prior] = av } 219 n_prior = n_prior + 1 220 } 221 } } 222 s2 = e2 + 1 223 } 224 } 225 226 let now: i64 = sys_now_realtime_sec() 227 let selfpid: i64 = vw_selfpid() 228 let fd: i64 = sys_openat_rd("/proc" as *u8) 229 if fd < 0 { g_puts("[resgov] cannot open /proc\n" as *u8); return 0 } 230 let dbuf: *u8 = sys_mmap(RG_DIR_CAP) 231 let clbuf: *u8 = sys_mmap(RG_CMD_CAP) 232 let out: *u8 = sys_mmap(RG_STATE_CAP) 233 var op: i64 = 0 234 var scanned: i64 = 0 235 var acted: i64 = 0 236 var warned: i64 = 0 237 var watched: i64 = 0 238 var run: i64 = 1 239 while run == 1 { 240 let n: i64 = sys_getdents64(fd, dbuf, RG_DIR_CAP) 241 if n <= 0 { run = 0 } else { 242 var off: i64 = 0 243 while off < n { 244 let rec: *u8 = ((dbuf as i64 + off) as *u8) 245 let reclen: i64 = dirent_reclen(rec) 246 if reclen <= 0 { off = n } else { 247 let name: *u8 = dirent_name(rec) 248 if name[0] >= (48 as u8) { if name[0] <= (57 as u8) { 249 let pid: i64 = vw_num_at(name, g_slen(name), 0, ep) 250 if pid > 300 { if pid != selfpid { 251 let rss: i64 = vw_rss_kb_of(name) 252 if rss > 0 { 253 scanned = scanned + 1 254 let swp: i64 = vw_status_kb_of(name, "VmSwap:" as *u8) 255 let foot: i64 = rg_footprint_kb(rss, swp) 256 // cmdline for policy matching 257 var o3: i64 = g_app(clbuf, 0, "/proc/" as *u8) 258 o3 = g_app(clbuf, o3, name) 259 o3 = g_app(clbuf, o3, "/cmdline" as *u8) 260 clbuf[o3] = 0 as u8 261 let cmdpath: *u8 = sys_mmap(256) 262 var cp: i64 = g_app(cmdpath, 0, "/proc/" as *u8) 263 cp = g_app(cmdpath, cp, name) 264 cp = g_app(cmdpath, cp, "/cmdline" as *u8) 265 cmdpath[cp] = 0 as u8 266 let cln: i64 = vw_read(cmdpath, clbuf, RG_CMD_CAP - 1) 267 // policy: exemption wins, then explicit cap, then the DEFAULT (coverage inversion) 268 var exempt: i64 = 0 269 var mycap: i64 = default_cap_gb 270 if cln > 0 { 271 var xi: i64 = 0 272 while xi < n_ex { 273 let nd: *u8 = ex_off[xi] as *u8 274 if vw_contains(clbuf, cln, nd, g_slen(nd)) == 1 { exempt = 1; xi = n_ex } 275 xi = xi + 1 276 } 277 var ci: i64 = 0 278 while ci < n_cap { 279 let nd2: *u8 = cap_off[ci] as *u8 280 if vw_contains(clbuf, cln, nd2, g_slen(nd2)) == 1 { mycap = cap_gb[ci]; ci = n_cap } 281 ci = ci + 1 282 } 283 } 284 // identity key = pid:starttime (pid reuse would poison the velocity) 285 let stt: i64 = g_starttime(name) 286 let keyb: *u8 = sys_mmap(64) 287 var kp: i64 = g_appn(keyb, 0, pid) 288 keyb[kp] = 58 as u8; kp = kp + 1 289 kp = g_appn(keyb, kp, stt) 290 keyb[kp] = 0 as u8 291 // find prior sample for this identity 292 var prev_kb: i64 = 0 293 var prev_t: i64 = 0 294 var last_act: i64 = 0 295 var pi: i64 = 0 296 while pi < n_prior { 297 if g_streq(p_key[pi] as *u8, keyb) == 1 { 298 prev_kb = p_kb[pi]; prev_t = p_t[pi]; last_act = p_act[pi]; pi = n_prior 299 } 300 pi = pi + 1 301 } 302 let vel: i64 = rg_velocity_kb_s(prev_kb, prev_t, foot, now) 303 var already_termed: i64 = 0 304 if last_act > 0 { if now - last_act < cooldown_s { already_termed = 1 } } 305 let lvl: i64 = rg_level(exempt, mycap, rss, swp, vel, pressured, already_termed, eta_warn_s, eta_act_s) 306 var act_ts: i64 = last_act 307 // ★EXEMPT FROM ACTION IS NOT EXEMPT FROM SCRUTINY. rg_level returns OK for an 308 // exempt process, which would make the estate's biggest growers INVISIBLE in 309 // the log -- the blind spot this whole regime exists to end. Recompute the 310 // level it WOULD have had and report that, while still acting on nothing. 311 if exempt == 1 { 312 let shadow: i64 = rg_level(0, mycap, rss, swp, vel, pressured, 0, eta_warn_s, eta_act_s) 313 if shadow != RG_OK { 314 watched = watched + 1 315 g_puts("[resgov] EXEMPT-" as *u8); g_puts(rg_level_name(shadow)) 316 g_puts(" pid=" as *u8); g_putn(pid) 317 g_puts(" foot_mb=" as *u8); g_putn(foot / RG_MAGIC_1024) 318 g_puts(" cap_gb=" as *u8); g_putn(mycap) 319 g_puts(" vel_kb_s=" as *u8); g_putn(vel) 320 g_puts(" (action withheld by an auditable exemption; scrutiny continues)\n" as *u8) 321 } 322 } 323 if lvl != RG_OK { 324 g_puts("[resgov] " as *u8); g_puts(rg_level_name(lvl)) 325 g_puts(" pid=" as *u8); g_putn(pid) 326 g_puts(" foot_mb=" as *u8); g_putn(foot / RG_MAGIC_1024) 327 g_puts(" cap_gb=" as *u8); g_putn(mycap) 328 g_puts(" vel_kb_s=" as *u8); g_putn(vel) 329 let eta: i64 = rg_eta_s(foot, mycap * RG_MAGIC_1048576, vel) 330 g_puts(" eta_s=" as *u8); g_putn(eta) 331 g_puts(" headroom=" as *u8); g_putn(headroom) 332 g_puts(" swap=" as *u8); g_putn(swapper) 333 if lvl == RG_WATCH { watched = watched + 1; g_puts(" (over cap but system calm -- big is not harmful)\n" as *u8) } 334 if lvl == RG_WARN { warned = warned + 1; g_puts(" (forecast breach -- early signal, no action)\n" as *u8) } 335 if lvl == RG_RENICE { 336 if dry == 1 { g_puts(" DRY renice\n" as *u8) } else { 337 sys_setpriority_of(pid, 15) 338 acted = acted + 1 339 g_puts(" DEPRIORITISED (slowed while headroom remains)\n" as *u8) 340 } 341 } 342 if rg_is_destructive(lvl) == 1 { 343 if rg_cooldown_ok(last_act, now, cooldown_s) == 1 { 344 if dry == 1 { g_puts(" DRY " as *u8); g_puts(rg_level_name(lvl)); g_puts("\n" as *u8) } else { 345 if lvl == RG_TERM { nx_kill(pid, 15) } else { nx_kill(pid, 9) } 346 act_ts = now 347 acted = acted + 1 348 g_puts(" SIGNALLED (guard respawns fresh)\n" as *u8) 349 } 350 } else { g_puts(" HELD (cooldown)\n" as *u8) } 351 } 352 } 353 // persist this sample (velocity memory for the next run) 354 if op < RG_STATE_CAP - 128 { 355 op = g_app(out, op, keyb) 356 out[op] = 32 as u8; op = op + 1 357 op = g_appn(out, op, foot) 358 out[op] = 32 as u8; op = op + 1 359 op = g_appn(out, op, now) 360 out[op] = 32 as u8; op = op + 1 361 op = g_appn(out, op, act_ts) 362 out[op] = 10 as u8; op = op + 1 363 } 364 } 365 } } 366 } } 367 off = off + reclen 368 } 369 } 370 } 371 } 372 sys_close(fd) 373 374 // atomic-ish state write (whole file, one open) 375 if op > 0 { 376 let sfd: i64 = sys_openat_wr(RG_STATE_PATH, 420) 377 if sfd >= 0 { 378 var w: i64 = 0 379 while w < op { let k: i64 = sys_write(sfd, ((out as i64) + w) as *u8, op - w); if k <= 0 { w = op } else { w = w + k } } 380 sys_close(sfd) 381 } 382 } 383 384 g_puts("[resgov] scanned=" as *u8); g_putn(scanned) 385 g_puts(" watch=" as *u8); g_putn(watched) 386 g_puts(" warn=" as *u8); g_putn(warned) 387 g_puts(" acted=" as *u8); g_putn(acted) 388 g_puts(" headroom_permil=" as *u8); g_putn(headroom) 389 g_puts(" swap_permil=" as *u8); g_putn(swapper) 390 g_puts(" pressured=" as *u8); g_putn(pressured) 391 g_puts(" default_cap_gb=" as *u8); g_putn(default_cap_gb) 392 g_puts(" exempt_rows=" as *u8); g_putn(n_ex) 393 g_puts(" cap_rows=" as *u8); g_putn(n_cap) 394 g_puts("\n" as *u8) 395 return 0 396}