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}