nx_gpuguard.nx source
↩ module page · 470 lines · 22844 B
1// nx_gpuguard.nx -- GPU SERVICE RESIDENCY GUARD: the DECISION, natively.
2//
3// nx_gpuguard decide <held_mb> <util_pct> <listening> <idle_ticks> [conf]
4// nx_gpuguard -- self-gate (teeth incl. the liar-killers)
5//
6// WHY IT EXISTS, MEASURED 2026-08-14: sd-server was running WITHOUT --offload-to-cpu, so its weights
7// were PINNED in VRAM for the process lifetime -- 302 MiB free of 16,303 on the 5080 -- and the gen
8// lane had been dead for writes for hours. The launcher carrying that flag already existed, written
9// to the operator's 2026-08-05 directive. NOTHING CHECKED THAT THE RUNNING PROCESS WAS THE ONE THE
10// LAUNCHER STARTS. That is an adoption gap, and an adoption gap needs a guard, not another launcher.
11//
12// ★IT CHECKS THE CONDITION, NOT THE COMMAND LINE. A cmdline check answers "was it started right?";
13// this answers "is it behaving right?" -- which catches a bad start however it happened, survives a
14// flag being renamed, and is measurable from outside the process. Behaviour over configuration.
15//
16// ★THE JUDGEMENT IS HERE, IN THE ORGAN. The laptop-side wire may only MEASURE (footprint, utilisation,
17// is-the-port-bound) and ACT on the verdict. It decides nothing. The .ps1 keeper this replaces decided
18// "wedged vs still loading" in shell, and the .ps1 sentinel classified "is a game present" in shell;
19// both are exactly the shape the estate forbids -- shell doing work rather than carrying it.
20//
21// ★held_mb IS THE SERVICE'S OWN FOOTPRINT, NOT THE CARD'S. Judging on card-wide usage would read a
22// game's VRAM as our service being resident and restart an innocent, healthy engine.
23//
24// Every threshold lives in knowledge/gpu_guard.conf. This organ carries NONE.
25// exit/verdict: 0 OK | 1 START | 2 RESTART-RESIDENT | 3 RESTART-WEDGED | 4 BUSY | 5 WAIT | 6 REFUSE
26// license_tier: ORIGINAL. No hw writes (Rule 26).
27import "nx_syscalls.nx"
28
29const GG_OK: i64 = 0
30const GG_START: i64 = 1
31const GG_RESTART_RESIDENT: i64 = 2
32const GG_RESTART_WEDGED: i64 = 3
33const GG_BUSY: i64 = 4
34const GG_WAIT: i64 = 5
35const GG_REFUSE: i64 = 6
36
37const GG_CFG_N: i64 = 4
38const GG_CFG_HOLD: i64 = 0
39const GG_CFG_BUSY: i64 = 1
40const GG_CFG_IDLE: i64 = 2
41const GG_CFG_WEDGE: i64 = 3
42const GG_CONF_DEFAULT: *u8 = "knowledge/gpu_guard.conf"
43
44func gg_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
45func gg_puts(s: *u8) -> i64 { sys_write(1, s, gg_len(s)); return 0 }
46func gg_putn(v: i64) -> i64 {
47 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
48 var m: i64 = v
49 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
50 let t: *u8 = sys_mmap(32)
51 var k: i64 = 0
52 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
53 let o: *u8 = sys_mmap(32)
54 var i: i64 = 0
55 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
56 sys_write(1, o, k)
57 return 0
58}
59func gg_atoi(s: *u8) -> i64 {
60 var v: i64 = 0
61 var i: i64 = 0
62 var sg: i64 = 1
63 if s[0] == (45 as u8) { sg = 0 - 1; i = 1 }
64 while s[i] != (0 as u8) {
65 let c: i64 = s[i] as i64
66 if c >= 48 { if c <= 57 { v = v*10 + (c - 48) } }
67 i = i + 1
68 }
69 return v*sg
70}
71func gg_keyis(b: *u8, p: i64, len: i64, key: *u8) -> i64 {
72 var i: i64 = 0
73 while key[i] != (0 as u8) {
74 if p + i >= len { return 0 }
75 if b[p+i] != key[i] { return 0 }
76 i = i + 1
77 }
78 return 1
79}
80func gg_slot(b: *u8, p: i64, len: i64) -> i64 {
81 if gg_keyis(b, p, len, "idle_hold_max_mb" as *u8) == 1 { return GG_CFG_HOLD }
82 if gg_keyis(b, p, len, "busy_util_pct" as *u8) == 1 { return GG_CFG_BUSY }
83 if gg_keyis(b, p, len, "idle_min_ticks" as *u8) == 1 { return GG_CFG_IDLE }
84 if gg_keyis(b, p, len, "wedge_ticks" as *u8) == 1 { return GG_CFG_WEDGE }
85 return 0 - 1
86}
87// buffer int reader; skips leading non-digits. Safe to call at a key's start BECAUSE EVERY KEY IS
88// DIGIT-FREE -- stated because a future key like 'busy2_pct' would silently misparse.
89func gg_rdint(b: *u8, pos: *i64, end: i64) -> i64 {
90 var i: i64 = pos[0]
91 var go: i64 = 1
92 while go == 1 {
93 if i >= end { go = 0 } else {
94 let c: i64 = b[i] as i64
95 if c == 45 { go = 0 } else { if c >= 48 { if c <= 57 { go = 0 } else { i = i+1 } } else { i = i+1 } }
96 }
97 }
98 var sg: i64 = 1
99 if i < end { if (b[i] as i64) == 45 { sg = 0 - 1; i = i + 1 } }
100 var v: i64 = 0
101 var g2: i64 = 1
102 while g2 == 1 {
103 if i >= end { g2 = 0 } else {
104 let c2: i64 = b[i] as i64
105 if c2 >= 48 { if c2 <= 57 { v = v*10 + (c2-48); i = i+1 } else { g2 = 0 } } else { g2 = 0 }
106 }
107 }
108 pos[0] = i
109 return v*sg
110}
111// parse 'G <key> <value>' rows. '#' comments cannot trigger it (the tag is read at line start only).
112// Returns slots FILLED, or -1 on an unknown key -- REFUSED, never ignored.
113func gg_parse(b: *u8, len: i64, cfg: *i64, seen: *i64) -> i64 {
114 let pos: *i64 = sys_mmap(16) as *i64
115 var i: i64 = 0
116 var bol: i64 = 1
117 var bad: i64 = 0
118 var n: i64 = 0
119 while i < len {
120 if bol == 1 {
121 if (b[i] as i64) == 71 {
122 var p: i64 = i + 1
123 var sk: i64 = 1
124 while sk == 1 {
125 if p >= len { sk = 0 } else { if (b[p] as i64) == 32 { p = p + 1 } else { sk = 0 } }
126 }
127 let s: i64 = gg_slot(b, p, len)
128 if s < 0 { bad = 1 } else {
129 pos[0] = p
130 cfg[s] = gg_rdint(b, pos, len)
131 if seen[s] == 0 { seen[s] = 1; n = n + 1 }
132 i = pos[0]
133 }
134 }
135 }
136 if (b[i] as i64) == 10 { bol = 1 } else { bol = 0 }
137 i = i + 1
138 }
139 if bad == 1 { return 0 - 1 }
140 return n
141}
142
143// ---- THE PURE DECISION. No I/O, no globals: every input is an argument, so the gate can drive it
144// directly and a tooth can prove one field changes the verdict. ----
145// ★BUSY DOMINATES EVERY OTHER RULE. Reclaiming memory from a card that is rendering is worse than
146// the memory: it destroys work in progress, and the footprint it objects to is the render itself.
147func gg_decide(held_mb: i64, util_pct: i64, listening: i64, idle_ticks: i64, cfg: *i64) -> i64 {
148 if util_pct > cfg[GG_CFG_BUSY] { return GG_BUSY }
149 if listening == 0 {
150 // down and holding nothing: it is simply absent -- launch it
151 if held_mb <= cfg[GG_CFG_HOLD] { return GG_START }
152 // down but still holding VRAM: loading, or wedged. Only TIME separates those two, which is
153 // why a tick count is required and one sample is never enough.
154 if idle_ticks >= cfg[GG_CFG_WEDGE] { return GG_RESTART_WEDGED }
155 return GG_WAIT
156 }
157 if held_mb <= cfg[GG_CFG_HOLD] { return GG_OK }
158 // up, idle, and holding more than an idle service should: the weights are resident rather than
159 // offloaded. Still requires sustained idleness -- a service that JUST finished a render holds
160 // its working set for a moment and is not misconfigured.
161 if idle_ticks >= cfg[GG_CFG_IDLE] { return GG_RESTART_RESIDENT }
162 return GG_WAIT
163}
164func gg_name(v: i64) -> *u8 {
165 if v == GG_OK { return "OK" as *u8 }
166 if v == GG_START { return "START" as *u8 }
167 if v == GG_RESTART_RESIDENT { return "RESTART-RESIDENT" as *u8 }
168 if v == GG_RESTART_WEDGED { return "RESTART-WEDGED" as *u8 }
169 if v == GG_BUSY { return "BUSY" as *u8 }
170 if v == GG_WAIT { return "WAIT" as *u8 }
171 return "REFUSE" as *u8
172}
173func gg_load(path: *u8, cfg: *i64) -> i64 {
174 let seen: *i64 = sys_mmap(GG_CFG_N*8) as *i64
175 let ln: *i64 = sys_mmap(16) as *i64
176 let b: *u8 = sys_read_file(path, ln)
177 if (b as i64) == 0 { return 0 - 1 }
178 if ln[0] <= 0 { return 0 - 1 }
179 let n: i64 = gg_parse(b, ln[0], cfg, seen)
180 if n < 0 { return 0 - 2 }
181 if n != GG_CFG_N { return 0 - 3 }
182 return 0
183}
184
185// ---- NATIVE REACHABILITY PROBE ----
186// ★THE 'IS IT UP?' QUESTION IS ANSWERED HERE, NOT IN SHELL. The .ps1 keeper asked it with
187// Invoke-WebRequest; a service being up is a fact this estate can measure with a socket, and every
188// fact measured outside an organ is a fact no gate can reach.
189// sockaddr_in is built BYTE-WISE and split out as a PURE function precisely so the gate can assert
190// the bytes -- a port silently written in host order connects to the wrong service and looks like
191// 'the service is down', which is the most expensive possible way to be wrong here.
192func gg_fill_sa(sa: *u8, ip: *u8, port: i64) -> i64 {
193 var i: i64 = 0
194 while i < 16 { sa[i] = 0 as u8; i = i + 1 }
195 sa[0] = 2 as u8 // AF_INET, low byte of a little-endian u16
196 sa[1] = 0 as u8
197 sa[2] = ((port / 256) % 256) as u8 // sin_port is NETWORK order (big-endian): high byte first
198 sa[3] = (port % 256) as u8
199 var p: i64 = 0
200 var oct: i64 = 0
201 var idx: i64 = 4
202 var run: i64 = 1
203 while run == 1 {
204 let c: i64 = ip[p] as i64
205 if c == 0 {
206 if idx < 8 { sa[idx] = oct as u8; idx = idx + 1 }
207 run = 0
208 } else {
209 if c == 46 {
210 if idx < 8 { sa[idx] = oct as u8; idx = idx + 1 }
211 oct = 0
212 p = p + 1
213 } else {
214 if c >= 48 { if c <= 57 { oct = oct*10 + (c - 48) } }
215 p = p + 1
216 }
217 }
218 }
219 if idx != 8 { return 0 - 2 }
220 return 0
221}
222// 1 = accepting, 0 = refused, -1 = could not look (socket/parse failure).
223// ★THE THIRD STATE IS DELIBERATE: 'I could not look' is not 'it is down', and collapsing them would
224// make a broken probe indistinguishable from a dead service -- then the guard restarts a healthy
225// engine every tick because it cannot see it.
226func gg_probe(ip: *u8, port: i64, tmo: i64) -> i64 {
227 let sa: *u8 = sys_mmap(32)
228 if gg_fill_sa(sa, ip, port) != 0 { return 0 - 1 }
229 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
230 if fd < 0 { return 0 - 1 }
231 sys_set_socket_timeout(fd, tmo)
232 let rc: i64 = sys_connect(fd, sa, 16)
233 sys_close(fd)
234 if rc == 0 { return 1 }
235 return 0
236}
237// ---- IDLE-TICK STATE ----
238// The tick counter is the organ's memory of how long a condition has held. It lives here rather than
239// in the caller because a counter owned by the wire is a counter no gate can prove, and 'sustained'
240// is the whole difference between a misconfigured service and one that just finished a render.
241func gg_readticks(path: *u8) -> i64 {
242 let ln: *i64 = sys_mmap(16) as *i64
243 let b: *u8 = sys_read_file(path, ln)
244 if (b as i64) == 0 { return 0 }
245 if ln[0] <= 0 { return 0 }
246 let pos: *i64 = sys_mmap(16) as *i64
247 pos[0] = 0
248 return gg_rdint(b, pos, ln[0])
249}
250func gg_writeticks(path: *u8, v: i64) -> i64 {
251 let o: *u8 = sys_mmap(32)
252 var m: i64 = v
253 var k: i64 = 0
254 if m == 0 { o[0] = 48 as u8; k = 1 } else {
255 let t: *u8 = sys_mmap(32)
256 var j: i64 = 0
257 while m > 0 { t[j] = (48 + (m % 10)) as u8; m = m / 10; j = j + 1 }
258 while j > 0 { j = j - 1; o[k] = t[j]; k = k + 1 }
259 }
260 o[k] = 10 as u8
261 let fd: i64 = sys_openat_wr(path, 0x1a4)
262 if fd < 0 { return 0 - 1 }
263 sys_write(fd, o, k + 1)
264 sys_close(fd)
265 return 0
266}
267
268// ---- SELF-GATE ----
269// ★COUNTERS ARE PASSED, NOT GLOBAL. The estate has measured module-level statics failing silently on
270// this toolchain, and a gate whose counter is the thing that broke reports a number it did not earn.
271// ctr[0]=passed ctr[1]=total, incremented in ONE place so declared can never drift from executed.
272func gg_check(ctr: *i64, name: *u8, got: i64, want: i64) -> i64 {
273 ctr[1] = ctr[1] + 1
274 if got == want {
275 ctr[0] = ctr[0] + 1
276 gg_puts(" PASS " as *u8); gg_puts(name)
277 gg_puts(" got=" as *u8); gg_puts(gg_name(got)); gg_puts("\n" as *u8)
278 return 1
279 }
280 gg_puts(" FAIL " as *u8); gg_puts(name)
281 gg_puts(" got=" as *u8); gg_puts(gg_name(got))
282 gg_puts(" want=" as *u8); gg_puts(gg_name(want)); gg_puts("\n" as *u8)
283 return 0
284}
285func gg_checkn(ctr: *i64, name: *u8, got: i64, want: i64) -> i64 {
286 ctr[1] = ctr[1] + 1
287 if got == want {
288 ctr[0] = ctr[0] + 1
289 gg_puts(" PASS " as *u8); gg_puts(name); gg_puts("\n" as *u8)
290 return 1
291 }
292 gg_puts(" FAIL " as *u8); gg_puts(name)
293 gg_puts(" got=" as *u8); gg_putn(got)
294 gg_puts(" want=" as *u8); gg_putn(want); gg_puts("\n" as *u8)
295 return 0
296}
297func gg_gate() -> i64 {
298 gg_puts("== nx_gpuguard self-gate ==\n" as *u8)
299 let ctr: *i64 = sys_mmap(32) as *i64
300 ctr[0] = 0
301 ctr[1] = 0
302 let cfg: *i64 = sys_mmap(GG_CFG_N*8) as *i64
303 // the gate builds its OWN fixture rather than reading the production conf: a gate that shares a
304 // fixture with a production beat measures the beat, and a conf edit would silently move the bar.
305 cfg[GG_CFG_HOLD] = 512
306 cfg[GG_CFG_BUSY] = 5
307 cfg[GG_CFG_IDLE] = 3
308 cfg[GG_CFG_WEDGE] = 2
309
310 gg_check(ctr, "t1-healthy-idle-small-footprint" as *u8, gg_decide(0, 0, 1, 9, cfg), GG_OK)
311 gg_check(ctr, "t2-absent-launch-it" as *u8, gg_decide(0, 0, 0, 9, cfg), GG_START)
312 // THE DEFECT THIS ORGAN WAS BUILT FOR: up, idle, weights pinned (measured 11,123 MiB)
313 gg_check(ctr, "t3-resident-weights-while-idle" as *u8, gg_decide(11123, 0, 1, 9, cfg), GG_RESTART_RESIDENT)
314 gg_check(ctr, "t4-wedged-holding-but-not-listening" as *u8, gg_decide(8000, 0, 0, 9, cfg), GG_RESTART_WEDGED)
315 // ★SAFETY: identical to t3 EXCEPT utilisation. If these two agree, the busy check is decorative.
316 gg_check(ctr, "t5-busy-dominates-never-kill-a-render" as *u8, gg_decide(11123, 97, 1, 9, cfg), GG_BUSY)
317 // ★EVIDENCE: identical to t3 EXCEPT idle_ticks. One sample must never be enough.
318 gg_check(ctr, "t6-wait-one-sample-is-not-evidence" as *u8, gg_decide(11123, 0, 1, 1, cfg), GG_WAIT)
319 gg_check(ctr, "t7-wait-down-and-holding-but-may-be-loading" as *u8, gg_decide(8000, 0, 0, 1, cfg), GG_WAIT)
320 // boundary: AT the budget is still healthy, one over is not (an off-by-one here restarts a good service)
321 gg_check(ctr, "t8-at-budget-is-healthy" as *u8, gg_decide(512, 0, 1, 9, cfg), GG_OK)
322 gg_check(ctr, "t9-one-over-budget-is-resident" as *u8, gg_decide(513, 0, 1, 9, cfg), GG_RESTART_RESIDENT)
323 // BUSY boundary: at the threshold is idle, one over is busy
324 gg_check(ctr, "t10-at-busy-threshold-is-still-idle" as *u8, gg_decide(11123, 5, 1, 9, cfg), GG_RESTART_RESIDENT)
325 gg_check(ctr, "t11-one-over-busy-threshold-is-busy" as *u8, gg_decide(11123, 6, 1, 9, cfg), GG_BUSY)
326
327 // ---- NEGATIVE CONTROLS: a decider that answers one thing for everything must fail here ----
328 // neg-control-always-ok : if gg_decide returned OK unconditionally, t2/t3/t4 above would fail.
329 // neg-control-always-act: if it returned RESTART unconditionally, t1/t5/t6/t8 above would fail.
330 // Both are asserted by CONSTRUCTION above; this tooth proves the pair is DISCRIMINATING at all by
331 // requiring two inputs differing in ONE field to disagree -- the anti-vacuity check.
332 let a: i64 = gg_decide(11123, 0, 1, 9, cfg)
333 let b: i64 = gg_decide(11123, 97, 1, 9, cfg)
334 ctr[1] = ctr[1] + 1
335 if a != b {
336 ctr[0] = ctr[0] + 1
337 gg_puts(" PASS neg-control-util-alone-changes-the-verdict\n" as *u8)
338 } else {
339 gg_puts(" FAIL neg-control-util-alone-changes-the-verdict -- the busy check is DECORATIVE\n" as *u8)
340 }
341 // a conf that declares nothing must fill NOTHING, so the caller REFUSES rather than falling back
342 // to built-in numbers. This is the tooth that stops the organ growing a silent default.
343 let empty: *i64 = sys_mmap(GG_CFG_N*8) as *i64
344 let seen2: *i64 = sys_mmap(GG_CFG_N*8) as *i64
345 let nfilled: i64 = gg_parse("# only a comment, no G rows\n" as *u8, 28, empty, seen2)
346 ctr[1] = ctr[1] + 1
347 if nfilled == 0 {
348 ctr[0] = ctr[0] + 1
349 gg_puts(" PASS neg-control-empty-conf-fills-nothing\n" as *u8)
350 } else { gg_puts(" FAIL neg-control-empty-conf-fills-nothing\n" as *u8) }
351 // an UNKNOWN key must be refused, not skipped -- a typo'd threshold silently left at zero would
352 // make the guard act constantly or never, and both read as 'the guard is broken'
353 let bad: *i64 = sys_mmap(GG_CFG_N*8) as *i64
354 let seen3: *i64 = sys_mmap(GG_CFG_N*8) as *i64
355 let nbad: i64 = gg_parse("G idle_hold_max_mb 512\nG typo_key 7\n" as *u8, 36, bad, seen3)
356 ctr[1] = ctr[1] + 1
357 if nbad < 0 {
358 ctr[0] = ctr[0] + 1
359 gg_puts(" PASS neg-control-unknown-key-refused\n" as *u8)
360 } else { gg_puts(" FAIL neg-control-unknown-key-refused -- a typo would guard on a silent zero\n" as *u8) }
361
362 // ---- sockaddr construction: the bytes, asserted ----
363 let sa: *u8 = sys_mmap(32)
364 gg_checkn(ctr, "t12-sockaddr-ok-for-127.0.0.1:7861" as *u8, gg_fill_sa(sa, "127.0.0.1" as *u8, 7861), 0)
365 gg_checkn(ctr, "t13-family-af-inet" as *u8, sa[0] as i64, 2)
366 // 7861 = 30*256 + 181. NETWORK order puts the HIGH byte first.
367 gg_checkn(ctr, "t14-port-high-byte-first" as *u8, sa[2] as i64, 30)
368 gg_checkn(ctr, "t15-port-low-byte-second" as *u8, sa[3] as i64, 181)
369 gg_checkn(ctr, "t16-addr-octet-0" as *u8, sa[4] as i64, 127)
370 gg_checkn(ctr, "t17-addr-octet-3" as *u8, sa[7] as i64, 1)
371 // ★ANTI-VACUITY: host order would put 181 first. If this ever passes, the probe is dialling the
372 // wrong port and every verdict downstream is about a service that was never contacted.
373 ctr[1] = ctr[1] + 1
374 if (sa[2] as i64) != 181 {
375 ctr[0] = ctr[0] + 1
376 gg_puts(" PASS neg-control-port-is-not-host-order\n" as *u8)
377 } else { gg_puts(" FAIL neg-control-port-is-not-host-order -- probe would dial the wrong port\n" as *u8) }
378 // a malformed address must REFUSE, not connect to whatever the partial bytes happen to spell
379 gg_checkn(ctr, "neg-control-malformed-ip-refused" as *u8, gg_fill_sa(sa, "1.2.3" as *u8, 80), 0 - 2)
380
381 gg_puts("passed " as *u8); gg_putn(ctr[0])
382 gg_puts("/" as *u8); gg_putn(ctr[1]); gg_puts("\n" as *u8)
383 if ctr[0] == ctr[1] { gg_puts("verdict=GREEN\n" as *u8); return 0 }
384 gg_puts("verdict=RED\n" as *u8)
385 return 1
386}
387
388func main(argc: i64, argv: *i64) -> i64 {
389 if argc < 2 { let g: i64 = gg_gate(); sys_exit(g); return g }
390 let v: *u8 = argv[1] as *u8
391 // ---- tick: PROBE + REMEMBER + DECIDE, all here. The wire supplies only the two facts a Linux
392 // organ genuinely cannot read on a Windows host: the service's own VRAM footprint and the card's
393 // utilisation. Everything else -- is it up, how long has this held, what should happen -- is ours.
394 if v[0] == (116 as u8) {
395 if argc < 6 {
396 gg_puts("GPUGUARD REFUSE: tick needs <ip> <port> <held_mb> <util_pct> [statefile] [conf]\n" as *u8)
397 sys_exit(GG_REFUSE)
398 return GG_REFUSE
399 }
400 var statep: *u8 = "knowledge/status/gpuguard.ticks" as *u8
401 if argc > 6 { statep = argv[6] as *u8 }
402 var confp2: *u8 = GG_CONF_DEFAULT
403 if argc > 7 { confp2 = argv[7] as *u8 }
404 let cfg2: *i64 = sys_mmap(GG_CFG_N*8) as *i64
405 let rc2: i64 = gg_load(confp2, cfg2)
406 if rc2 != 0 { gg_puts("GPUGUARD REFUSE: policy conf unreadable/unknown-key/incomplete -- refusing to guard on built-in numbers\n" as *u8); sys_exit(GG_REFUSE); return GG_REFUSE }
407 let ip: *u8 = argv[2] as *u8
408 let port: i64 = gg_atoi(argv[3] as *u8)
409 let held2: i64 = gg_atoi(argv[4] as *u8)
410 let util2: i64 = gg_atoi(argv[5] as *u8)
411 let lis2: i64 = gg_probe(ip, port, 3)
412 if lis2 < 0 {
413 // ★I COULD NOT LOOK is not IT IS DOWN. Acting on a blind probe would restart a healthy
414 // engine every tick, which is worse than the condition the guard exists to fix.
415 gg_puts("GPUGUARD probe=UNREADABLE ip=" as *u8); gg_puts(ip)
416 gg_puts(" port=" as *u8); gg_putn(port)
417 gg_puts(" action=REFUSE (could not look; NOT treated as down)\n" as *u8)
418 sys_exit(GG_REFUSE)
419 return GG_REFUSE
420 }
421 var ticks: i64 = gg_readticks(statep)
422 if util2 > cfg2[GG_CFG_BUSY] { ticks = 0 } else { ticks = ticks + 1 }
423 gg_writeticks(statep, ticks)
424 let d2: i64 = gg_decide(held2, util2, lis2, ticks, cfg2)
425 gg_puts("GPUGUARD ip=" as *u8); gg_puts(ip)
426 gg_puts(" port=" as *u8); gg_putn(port)
427 gg_puts(" listening=" as *u8); gg_putn(lis2)
428 gg_puts(" held_mb=" as *u8); gg_putn(held2)
429 gg_puts(" util_pct=" as *u8); gg_putn(util2)
430 gg_puts(" idle_ticks=" as *u8); gg_putn(ticks)
431 gg_puts(" hold_max=" as *u8); gg_putn(cfg2[GG_CFG_HOLD])
432 gg_puts(" busy_pct=" as *u8); gg_putn(cfg2[GG_CFG_BUSY])
433 gg_puts(" action=" as *u8); gg_puts(gg_name(d2)); gg_puts("\n" as *u8)
434 sys_exit(d2)
435 return d2
436 }
437 if v[0] != (100 as u8) {
438 gg_puts("usage: nx_gpuguard tick <ip> <port> <held_mb> <util_pct> [statefile] [conf] | nx_gpuguard decide <held_mb> <util_pct> <listening> <idle_ticks> [conf] | nx_gpuguard (self-gate)\n" as *u8)
439 sys_exit(GG_REFUSE)
440 return GG_REFUSE
441 }
442 if argc < 6 {
443 gg_puts("GPUGUARD REFUSE: decide needs <held_mb> <util_pct> <listening> <idle_ticks> -- a missing measurement is not a zero\n" as *u8)
444 sys_exit(GG_REFUSE)
445 return GG_REFUSE
446 }
447 var confp: *u8 = GG_CONF_DEFAULT
448 if argc > 6 { confp = argv[6] as *u8 }
449 let cfg: *i64 = sys_mmap(GG_CFG_N*8) as *i64
450 let rc: i64 = gg_load(confp, cfg)
451 if rc == 0 - 1 { gg_puts("GPUGUARD REFUSE: cannot read the policy conf -- refusing to guard with built-in numbers\n" as *u8); sys_exit(GG_REFUSE); return GG_REFUSE }
452 if rc == 0 - 2 { gg_puts("GPUGUARD REFUSE: the policy conf carries an UNKNOWN key -- refusing rather than guarding on a silent zero\n" as *u8); sys_exit(GG_REFUSE); return GG_REFUSE }
453 if rc == 0 - 3 { gg_puts("GPUGUARD REFUSE: the policy conf is INCOMPLETE -- every threshold must be declared, a missing one is not a default\n" as *u8); sys_exit(GG_REFUSE); return GG_REFUSE }
454 let held: i64 = gg_atoi(argv[2] as *u8)
455 let util: i64 = gg_atoi(argv[3] as *u8)
456 let lis: i64 = gg_atoi(argv[4] as *u8)
457 let tick: i64 = gg_atoi(argv[5] as *u8)
458 let d: i64 = gg_decide(held, util, lis, tick, cfg)
459 gg_puts("GPUGUARD held_mb=" as *u8); gg_putn(held)
460 gg_puts(" util_pct=" as *u8); gg_putn(util)
461 gg_puts(" listening=" as *u8); gg_putn(lis)
462 gg_puts(" idle_ticks=" as *u8); gg_putn(tick)
463 gg_puts(" hold_max=" as *u8); gg_putn(cfg[GG_CFG_HOLD])
464 gg_puts(" busy_pct=" as *u8); gg_putn(cfg[GG_CFG_BUSY])
465 gg_puts(" idle_min=" as *u8); gg_putn(cfg[GG_CFG_IDLE])
466 gg_puts(" wedge=" as *u8); gg_putn(cfg[GG_CFG_WEDGE])
467 gg_puts(" action=" as *u8); gg_puts(gg_name(d)); gg_puts("\n" as *u8)
468 sys_exit(d)
469 return d
470}