code wiki / _hdl_build / nx_memvel.nx
nx_memvel.nx source
↩ module page · 206 lines · 9647 B
1// nx_memvel.nx -- WHICH process is EATING memory, ranked by SUSTAINED growth (seq1365 / seq1376).
2//
3// WHY IT EXISTS: nx_resmon ranks by MAGNITUDE, so a stable 3 GB tenant outranks a 50 MB process
4// doubling every minute and the real leaker hides. Measured 2026-07-30: swap 525->609 permil in 51 min
5// (~2.03 GB, ~40 MB/min) while the worst_committed process grew ~1 MB. LAW: a census that reports a
6// LEVEL cannot find a LEAK.
7//
8// ★★WHY IT IS N-SAMPLE (this organ's own v1 was WRONG -- seq1376): the FIRST version took ONE 5-second
9// window and I published its top row as a unified root cause. The very next run of the same organ put a
10// DIFFERENT process on top (3560 kB/s vs the previous winner's 1597) and demoted the old winner to #3.
11// The top slot ROTATES. A one-shot ranking of a noisy signal is a coin flip dressed as a finding -- the
12// exact ONE-WINDOW-IS-NOT-A-RATE defect (seq1340) committed by the author of that law, using the tool
13// built to prevent it. Knowing a discipline is not obeying it, so the discipline is now STRUCTURAL:
14// this organ CANNOT report a single-window ranking, because it no longer takes one.
15//
16// ★THE DECIDING COLUMN IS grew_in=k/N, NOT the rate. A real leak grows in EVERY window (k=N). A process
17// that tops one window and vanishes from the next is NOISE, and now says so on its own line. Rate alone
18// cannot distinguish them, which is precisely how v1 misled.
19// Rule 15: top-k, pid-join and rate arithmetic imported from nx_ctxtop_lib / nx_procchurn_lib.
20// Exit 0 = sampled; 3 = UNMEASURED (fail-closed).
21// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). expect_exit: 0
22import "nx_ctxtop_lib.nx"
23import "nx_proc_ctl.nx"
24
25const MV_MAXP: i64 = 2048
26const MV_TABLE_BYTES: i64 = 16384
27const MV_DIRBUF: i64 = 65536
28const MV_STATBUF: i64 = 4096
29const MV_TOPK: i64 = 8
30const MV_TOPK_BYTES: i64 = 128
31const MV_ROUNDS: i64 = 5
32const MV_WINDOW_MS: i64 = 1200
33const MV_US_PER_MS: i64 = 1000
34const MV_NAMEOFF: i64 = 6
35const MV_EXIT_UNMEASURED: i64 = 3
36const MV_EXIT_REFUSED: i64 = 4
37const MV_ADMIT_DEFAULT: i64 = 800
38const MV_CONFBUF: i64 = 4096
39const MV_LOADBUF: i64 = 128
40
41// committed = RSS + Swap. Judging on COMMITTED (never VmSize/VmData) is inherited from nx_resmon's own
42// correction: VmData counts reservations never faulted in and once reported 40.6 GiB on a 36.9 GB box.
43func mv_committed(sbuf: *u8, sn: i64) -> i64 {
44 let r: i64 = rm_field(sbuf, sn, "VmRSS:" as *u8)
45 let w: i64 = rm_field(sbuf, sn, "VmSwap:" as *u8)
46 if r < 0 { return 0 - 1 }
47 var t: i64 = r
48 if w > 0 { t = t + w }
49 return t
50}
51
52func mv_scan(pids: *i64, kbs: *i64, dbuf: *u8, path: *u8, sbuf: *u8) -> i64 {
53 var cnt: i64 = 0
54 let fd: i64 = sys_openat_rd("/proc" as *u8)
55 if fd < 0 { return 0 }
56 var run: i64 = 1
57 while run == 1 {
58 let n: i64 = sys_getdents64(fd, dbuf, MV_DIRBUF)
59 if n <= 0 { run = 0 } else {
60 var off: i64 = 0
61 while off < n {
62 let rec: *u8 = ((dbuf as i64) + off) as *u8
63 let reclen: i64 = dirent_reclen(rec)
64 if reclen <= 0 { off = n } else {
65 let nm: *u8 = dirent_name(rec)
66 if nm[0] >= (48 as u8) { if nm[0] <= (57 as u8) { if cnt < MV_MAXP {
67 var p: i64 = 0
68 let pre: *u8 = "/proc/" as *u8
69 var a: i64 = 0
70 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
71 var pidv: i64 = 0
72 a = 0
73 while nm[a] != (0 as u8) { path[p] = nm[a]; pidv = pidv * 10 + ((nm[a] as i64) - 48); p = p + 1; a = a + 1 }
74 let suf: *u8 = "/status" as *u8
75 a = 0
76 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
77 path[p] = 0 as u8
78 let sn: i64 = rm_read(path, sbuf, MV_STATBUF)
79 if sn > 0 {
80 let c: i64 = mv_committed(sbuf, sn)
81 if c >= 0 { pids[cnt] = pidv; kbs[cnt] = c; cnt = cnt + 1 }
82 }
83 } } }
84 off = off + reclen
85 }
86 }
87 }
88 }
89 sys_close(fd)
90 return cnt
91}
92
93func mv_putname(pid: i64, path: *u8, sbuf: *u8) -> i64 {
94 var p: i64 = 0
95 let pre: *u8 = "/proc/" as *u8
96 var a: i64 = 0
97 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
98 p = pc_catn(path, p, pid)
99 let suf: *u8 = "/status" as *u8
100 a = 0
101 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
102 path[p] = 0 as u8
103 let sn: i64 = rm_read(path, sbuf, MV_STATBUF)
104 if sn <= 0 { rm_puts("<gone>" as *u8); return 0 }
105 var z: i64 = MV_NAMEOFF
106 let out: *u8 = sys_mmap(64)
107 var w: i64 = 0
108 while z < sn { if sbuf[z] == (10 as u8) { z = sn } else { if w < 62 { out[w] = sbuf[z]; w = w + 1 } z = z + 1 } }
109 out[w] = 0 as u8
110 rm_puts(out)
111 sys_munmap(out, 64)
112 return 0
113}
114
115func main() -> i64 {
116 // ADMISSION CONTROL (seq341 / seq1389): 5 rounds x ~450 procs = ~2700 openat+read+close per run.
117 // That is a real load, and this organ exists to measure a box already under pressure -- exactly when
118 // adding to it is worst. ★LAW: A DIAGNOSTIC THAT CANNOT REFUSE TO RUN IS A LOAD GENERATOR WITH GOOD
119 // INTENTIONS. Fail-CLOSED: an unreadable loadavg REFUSES (ct_admit), because not knowing the load is
120 // not permission to add to it.
121 let cbuf: *u8 = sys_mmap(MV_CONFBUF)
122 let cn: i64 = rm_read("knowledge/status/procchurn.conf" as *u8, cbuf, MV_CONFBUF)
123 let maxload: i64 = rm_conf(cbuf, cn, "admit-max-load-centi" as *u8, MV_ADMIT_DEFAULT)
124 let lbuf: *u8 = sys_mmap(MV_LOADBUF)
125 let ln: i64 = rm_read("/proc/loadavg" as *u8, lbuf, MV_LOADBUF)
126 let loadc: i64 = ct_load_centi(lbuf, ln)
127 if ct_admit(loadc, maxload) == 0 {
128 rm_puts("NX-MEMVEL verdict=REFUSED load_centi=" as *u8); rm_num(loadc)
129 rm_puts(" max=" as *u8); rm_num(maxload)
130 rm_puts(" why=host already saturated; a /proc-walking diagnostic must not add to it (seq341)\n" as *u8)
131 return MV_EXIT_REFUSED
132 }
133 let pa: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // previous scan pids
134 let ka: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // previous scan committed kB
135 let pb: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // current scan pids
136 let kb: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // current scan committed kB
137 let ap: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // accumulator pids
138 let asum: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // accumulator summed positive delta (kB)
139 let acnt: *i64 = sys_mmap(MV_TABLE_BYTES) as *i64 // accumulator: in how many windows it GREW
140 let dbuf: *u8 = sys_mmap(MV_DIRBUF)
141 let path: *u8 = sys_mmap(256)
142 let sbuf: *u8 = sys_mmap(MV_STATBUF)
143
144 let t0: i64 = sys_now_us()
145 var na: i64 = mv_scan(pa, ka, dbuf, path, sbuf)
146 if na <= 0 { rm_puts("NX-MEMVEL verdict=UNMEASURED why=/proc walk empty\n" as *u8); return MV_EXIT_UNMEASURED }
147 var nacc: i64 = 0
148 var grew_total: i64 = 0
149 var r: i64 = 0
150 var nb: i64 = 0
151 while r < MV_ROUNDS {
152 sys_sleep_ms(MV_WINDOW_MS)
153 nb = mv_scan(pb, kb, dbuf, path, sbuf)
154 var i: i64 = 0
155 while i < nb {
156 let j: i64 = ct_find(pa, na, pb[i])
157 if j >= 0 {
158 let d: i64 = kb[i] - ka[j]
159 if d > 0 {
160 grew_total = grew_total + d
161 var s: i64 = ct_find(ap, nacc, pb[i])
162 if s < 0 { if nacc < MV_MAXP { ap[nacc] = pb[i]; asum[nacc] = 0; acnt[nacc] = 0; s = nacc; nacc = nacc + 1 } }
163 if s >= 0 { asum[s] = asum[s] + d; acnt[s] = acnt[s] + 1 }
164 }
165 }
166 i = i + 1
167 }
168 // current becomes previous (copy, so the next round diffs against THIS scan)
169 var c: i64 = 0
170 while c < nb { pa[c] = pb[c]; ka[c] = kb[c]; c = c + 1 }
171 na = nb
172 r = r + 1
173 }
174 let el: i64 = (sys_now_us() - t0) / MV_US_PER_MS
175 if el <= 0 { rm_puts("NX-MEMVEL verdict=UNMEASURED why=zero-length observation\n" as *u8); return MV_EXIT_UNMEASURED }
176
177 let tp: *i64 = sys_mmap(MV_TOPK_BYTES) as *i64
178 let tr: *i64 = sys_mmap(MV_TOPK_BYTES) as *i64
179 var used: i64 = 0
180 var q: i64 = 0
181 while q < nacc { used = ct_topk_insert(tp, tr, MV_TOPK, used, ap[q], asum[q]); q = q + 1 }
182
183 rm_puts("NX-MEMVEL rounds=" as *u8); rm_num(MV_ROUNDS)
184 rm_puts(" total_ms=" as *u8); rm_num(el)
185 rm_puts(" procs=" as *u8); rm_num(nb)
186 rm_puts(" grew_kb=" as *u8); rm_num(grew_total)
187 rm_puts(" growers=" as *u8); rm_num(nacc); rm_puts("\n" as *u8)
188 var k: i64 = 0
189 while k < used {
190 let s2: i64 = ct_find(ap, nacc, tp[k])
191 rm_puts(" #" as *u8); rm_num(k + 1)
192 rm_puts(" pid=" as *u8); rm_num(tp[k])
193 rm_puts(" kb_per_sec=" as *u8); rm_num(pc_rate(tr[k], el))
194 rm_puts(" grew_in=" as *u8)
195 if s2 >= 0 { rm_num(acnt[s2]) } else { rm_num(0) }
196 rm_puts("/" as *u8); rm_num(MV_ROUNDS)
197 rm_puts(" name=" as *u8)
198 mv_putname(tp[k], path, sbuf)
199 rm_puts("\n" as *u8)
200 k = k + 1
201 }
202 rm_puts("READ grew_in FIRST: k/" as *u8); rm_num(MV_ROUNDS)
203 rm_puts(" = SUSTAINED leak; anything less is a rotating top slot = NOISE, not a finding (seq1376)\n" as *u8)
204 if nb >= MV_MAXP { rm_puts(" WARNING scan CAPPED -- coverage INCOMPLETE, totals are a floor\n" as *u8) }
205 return 0
206}