code wiki / (root) / nx_gpuguard.nx

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}