code wiki / _hdl_build / nx_ctxtop.nx
nx_ctxtop.nx source
↩ module page · 358 lines · 19098 B
1// nx_ctxtop.nx -- WHO is producing the context switches. The producer-finder for seq1340.
2//
3// nx_procchurn measured the box at 81k-93k ctxsw/sec in every sample (the one CONSTANT in the perf lane)
4// and DISPROVED forks as the driver -- forks swing 15->179->10 while ctxsw stays flat. A box-level rate
5// names no culprit, so this walks /proc/<pid>/status twice and attributes the switches per process.
6// LAW (rising-debt-means-find-the-producer): a rate with no owner is a symptom, not a diagnosis.
7//
8// ★Reports voluntary and nonvoluntary SEPARATELY because they demand OPPOSITE fixes: voluntary = the
9// process blocked itself (poll/read/sleep) -> batch, lengthen polls, go event-driven; nonvoluntary = the
10// scheduler preempted it -> reduce concurrency. A tool printing only the sum sends the fix the wrong way.
11// Exit code = 0 when the sample completed, 3 UNMEASURED (fail-closed, never a confident empty answer).
12//
13// ★2026-08-12 -- PRODUCER-CLASS ATTRIBUTION: the ranked rows name WHO; a final NX-CTXTOP-CLASS line names
14// WHAT KIND (SYSTEM=DSM/Synology, ESTATE=nx_* organs, KERNEL=kthreads, OTHER) so a RED churn axis carries
15// its own verdict instead of a human minting a cap and eyeballing comm strings. Field-measured 2026-08-12:
16// every top-8 producer was DSM (ssrtmpserverd/synorelayd/syno-cloud-clie/redis/kworkers), zero estate.
17// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). expect_exit: 0
18import "nx_ctxtop_lib.nx"
19import "nx_proc_ctl.nx"
20
21const CT_MAXP: i64 = 2048
22const CT_TABLE_BYTES: i64 = 16384
23const CT_DIRBUF: i64 = 65536
24const CT_STATBUF: i64 = 4096
25const CT_TOPK: i64 = 8
26const CT_TOPK_BYTES: i64 = 128
27const CT_WINDOW_MS: i64 = 3000
28const CT_US_PER_MS: i64 = 1000
29const CT_NAMEOFF: i64 = 6
30const CT_EXIT_UNMEASURED: i64 = 3
31const CT_SLASH: i64 = 47
32const CT_EXIT_REFUSED: i64 = 4
33// CT_ADMIT_DEFAULT now lives in nx_ctxtop_lib.nx beside ct_admit_now(), because the driver gates need
34// the SAME calibration and a second copy of 800 is one threshold in two files that can never be tuned
35// together. Moved, not copied.
36const CT_CONFBUF: i64 = 4096
37const CT_LOADBUF: i64 = 128
38
39// producer classes for the attribution verdict (2026-08-12)
40const CTC_SYSTEM: i64 = 0
41const CTC_ESTATE: i64 = 1
42const CTC_KERNEL: i64 = 2
43const CTC_OTHER: i64 = 3
44
45// Walk /proc, recording pid + both ctxsw counters. Returns the number of processes captured.
46// Reports its own capping honestly (L011: partial-as-complete is a bug) via the returned count vs CT_MAXP.
47func ct_scan(pids: *i64, vols: *i64, nvs: *i64, dbuf: *u8, path: *u8, sbuf: *u8) -> i64 {
48 var cnt: i64 = 0
49 // SECOND dirent buffer: the task/ walk is NESTED inside the /proc walk, so it cannot reuse dbuf --
50 // doing so would corrupt the outer getdents cursor mid-iteration and silently skip processes.
51 let tbuf: *u8 = sys_mmap(CT_DIRBUF)
52 let fd: i64 = sys_openat_rd("/proc" as *u8)
53 if fd < 0 { return 0 }
54 var run: i64 = 1
55 while run == 1 {
56 let n: i64 = sys_getdents64(fd, dbuf, CT_DIRBUF)
57 if n <= 0 { run = 0 } else {
58 var off: i64 = 0
59 while off < n {
60 let rec: *u8 = ((dbuf as i64) + off) as *u8
61 let reclen: i64 = dirent_reclen(rec)
62 if reclen <= 0 { off = n } else {
63 let nm: *u8 = dirent_name(rec)
64 if nm[0] >= (48 as u8) { if nm[0] <= (57 as u8) { if cnt < CT_MAXP {
65 var p: i64 = 0
66 let pre: *u8 = "/proc/" as *u8
67 var a: i64 = 0
68 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
69 var pidv: i64 = 0
70 a = 0
71 while nm[a] != (0 as u8) { path[p] = nm[a]; pidv = pidv * 10 + ((nm[a] as i64) - 48); p = p + 1; a = a + 1 }
72 let suf: *u8 = "/status" as *u8
73 a = 0
74 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
75 path[p] = 0 as u8
76 // THREAD-LEVEL WALK (seq1364 gap closed). Reading only /proc/<pid>/status counts
77 // the MAIN THREAD's counters and silently ignores every sibling thread: the box
78 // carries 1809 threads across ~809 processes, so a process-only walk attributed
79 // 40480 of a box-level 116-130k ctxsw/s and left TWO THIRDS unexplained -- while
80 // looking like a complete top-8. Every task has /proc/<pid>/task/<pid>, so walking
81 // task/ ALONE covers the main thread too: no double-count, full coverage.
82 // LAW: A PER-PROCESS COUNTER ON A MULTI-THREADED SYSTEM IS A FLOOR WEARING THE
83 // COSTUME OF A TOTAL.
84 var tp: i64 = p - 7
85 let tsuf: *u8 = "/task" as *u8
86 var ta: i64 = 0
87 while tsuf[ta] != (0 as u8) { path[tp] = tsuf[ta]; tp = tp + 1; ta = ta + 1 }
88 path[tp] = 0 as u8
89 let tfd: i64 = sys_openat_rd(path)
90 if tfd >= 0 {
91 var trun: i64 = 1
92 while trun == 1 {
93 let tn: i64 = sys_getdents64(tfd, tbuf, CT_DIRBUF)
94 if tn <= 0 { trun = 0 } else {
95 var toff: i64 = 0
96 while toff < tn {
97 let trec: *u8 = ((tbuf as i64) + toff) as *u8
98 let treclen: i64 = dirent_reclen(trec)
99 if treclen <= 0 { toff = tn } else {
100 let tnm: *u8 = dirent_name(trec)
101 if tnm[0] >= (48 as u8) { if tnm[0] <= (57 as u8) { if cnt < CT_MAXP {
102 var q: i64 = tp
103 path[q] = CT_SLASH as u8; q = q + 1
104 var tidv: i64 = 0
105 var tb: i64 = 0
106 while tnm[tb] != (0 as u8) { path[q] = tnm[tb]; tidv = tidv * 10 + ((tnm[tb] as i64) - 48); q = q + 1; tb = tb + 1 }
107 let ssuf: *u8 = "/status" as *u8
108 tb = 0
109 while ssuf[tb] != (0 as u8) { path[q] = ssuf[tb]; q = q + 1; tb = tb + 1 }
110 path[q] = 0 as u8
111 let sn2: i64 = rm_read(path, sbuf, CT_STATBUF)
112 if sn2 > 0 {
113 let v2: i64 = rm_field(sbuf, sn2, "voluntary_ctxt_switches:" as *u8)
114 let w2: i64 = rm_field(sbuf, sn2, "nonvoluntary_ctxt_switches:" as *u8)
115 if v2 >= 0 { if w2 >= 0 {
116 pids[cnt] = tidv
117 vols[cnt] = v2
118 nvs[cnt] = w2
119 cnt = cnt + 1
120 } }
121 }
122 } } }
123 toff = toff + treclen
124 }
125 }
126 }
127 }
128 sys_close(tfd)
129 }
130 } } }
131 off = off + reclen
132 }
133 }
134 }
135 }
136 sys_close(fd)
137 return cnt
138}
139
140// print "/proc/<pid>/status" Name: field (comm starts at byte 6, ends at the first newline)
141func ct_putname(pid: i64, path: *u8, sbuf: *u8) -> i64 {
142 var p: i64 = 0
143 let pre: *u8 = "/proc/" as *u8
144 var a: i64 = 0
145 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
146 p = pc_catn(path, p, pid)
147 let suf: *u8 = "/status" as *u8
148 a = 0
149 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
150 path[p] = 0 as u8
151 let sn: i64 = rm_read(path, sbuf, CT_STATBUF)
152 if sn <= 0 { rm_puts("<gone>" as *u8); return 0 }
153 var z: i64 = CT_NAMEOFF
154 let out: *u8 = sys_mmap(64)
155 var w: i64 = 0
156 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 } }
157 out[w] = 0 as u8
158 rm_puts(out)
159 sys_munmap(out, 64)
160 return 0
161}
162
163// substring-contains: 1 if needle occurs in the first n bytes of hay, else 0. needle is a *u8 parameter
164// (NOT an inline-cast literal), so hay/needle indexing is safe -- the caller hoists every literal into a
165// `let` before passing it (the ("lit" as *u8)[j] index NEVER matches and compiles clean; §32 hazard).
166// The inner match uses an explicit `ok` flag and only clobbers the INNER cursor j to break, never i --
167// a loop that exits by clobbering its own search cursor erases the answer (§29).
168func ct_has(hay: *u8, n: i64, needle: *u8) -> i64 {
169 var m: i64 = 0
170 while needle[m] != (0 as u8) { m = m + 1 }
171 if m == 0 { return 0 }
172 if n < m { return 0 }
173 let last: i64 = n - m
174 var i: i64 = 0
175 while i <= last {
176 var j: i64 = 0
177 var ok: i64 = 1
178 while j < m {
179 if hay[(i + j)] != needle[j] { ok = 0; j = m } else { j = j + 1 }
180 }
181 if ok == 1 { return 1 }
182 i = i + 1
183 }
184 return 0
185}
186
187// classify a tid by its OWNING-PROCESS cmdline (all threads share /proc/<tid>/cmdline = the process argv).
188// A kernel thread has an EMPTY cmdline -> KERNEL (a reliable, path-independent detector). DSM/Synology
189// binaries live under /syno, /var/packages, or SurveillanceStation; the estate under nishihost /
190// elderwesto / /volume1/ai. ESTATE is tested FIRST so an estate path is never mistaken for system.
191func ct_class_of(tid: i64, path: *u8, cbuf: *u8) -> i64 {
192 var p: i64 = 0
193 let pre: *u8 = "/proc/" as *u8
194 var a: i64 = 0
195 while pre[a] != (0 as u8) { path[p] = pre[a]; p = p + 1; a = a + 1 }
196 p = pc_catn(path, p, tid)
197 let suf: *u8 = "/cmdline" as *u8
198 a = 0
199 while suf[a] != (0 as u8) { path[p] = suf[a]; p = p + 1; a = a + 1 }
200 path[p] = 0 as u8
201 let n: i64 = rm_read(path, cbuf, CT_STATBUF)
202 if n <= 0 { return CTC_KERNEL }
203 let e1: *u8 = "nishihost" as *u8
204 if ct_has(cbuf, n, e1) == 1 { return CTC_ESTATE }
205 let e2: *u8 = "elderwesto" as *u8
206 if ct_has(cbuf, n, e2) == 1 { return CTC_ESTATE }
207 let e3: *u8 = "/volume1/ai" as *u8
208 if ct_has(cbuf, n, e3) == 1 { return CTC_ESTATE }
209 let s1: *u8 = "syno" as *u8
210 if ct_has(cbuf, n, s1) == 1 { return CTC_SYSTEM }
211 let s2: *u8 = "Surveillance" as *u8
212 if ct_has(cbuf, n, s2) == 1 { return CTC_SYSTEM }
213 let s3: *u8 = "packages/" as *u8
214 if ct_has(cbuf, n, s3) == 1 { return CTC_SYSTEM }
215 let s4: *u8 = "redis" as *u8
216 if ct_has(cbuf, n, s4) == 1 { return CTC_SYSTEM }
217 return CTC_OTHER
218}
219
220func main(argc: i64, argv: *i64) -> i64 {
221 // ADMISSION CONTROL (seq341 / seq1389): this organ walks ALL of /proc and stats every
222 // /proc/<pid>/status -- an O(processes) syscall storm, the same shape seq1318 indicts the supervisor
223 // for. Running it on an already-saturated box is how a diagnostic becomes an outage. Refuse instead.
224 // ★LAW: A DIAGNOSTIC THAT CANNOT REFUSE TO RUN IS A LOAD GENERATOR WITH GOOD INTENTIONS.
225 // 2026-09-02 ARGV CONF (Rule 17: argv > conf file > default), the same shape nx_dstate ships. During
226 // today's storm every producer-naming organ refused to look, so the seat could measure THAT the box was
227 // saturated and could not say BY WHAT -- and a refusal that blinds the operator during the one condition
228 // it exists for protects nothing. Two bounded /proc walks are negligible beside load 30 on an I/O-bound
229 // box. The operator hands a conf path on argv and BOTH the refusal and the report print its provenance,
230 // so an elevated ceiling is announced, never silent; the shipped procchurn.conf is untouched.
231 var confp: *u8 = "knowledge/status/procchurn.conf" as *u8
232 var confsrc: *u8 = "conf-default" as *u8
233 if argc >= 2 { confp = argv[1] as *u8; confsrc = "conf-argv" as *u8 }
234 let cbuf: *u8 = sys_mmap(CT_CONFBUF)
235 let cn: i64 = rm_read(confp, cbuf, CT_CONFBUF)
236 let maxload: i64 = rm_conf(cbuf, cn, "admit-max-load-centi" as *u8, CT_ADMIT_DEFAULT)
237 let lbuf: *u8 = sys_mmap(CT_LOADBUF)
238 let ln: i64 = rm_read("/proc/loadavg" as *u8, lbuf, CT_LOADBUF)
239 let loadc: i64 = ct_load_centi(lbuf, ln)
240 if ct_admit(loadc, maxload) == 0 {
241 rm_puts("NX-CTXTOP verdict=REFUSED load_centi=" as *u8); rm_num(loadc)
242 rm_puts(" max=" as *u8); rm_num(maxload)
243 rm_puts(" src=" as *u8); rm_puts(confsrc); rm_puts(" path=" as *u8); rm_puts(confp)
244 rm_puts(" why=host already saturated; a /proc-walking diagnostic must not add to it (seq341); pass a conf path on argv to raise the ceiling for ONE announced walk\n" as *u8)
245 return CT_EXIT_REFUSED
246 }
247 let p1: *i64 = sys_mmap(CT_TABLE_BYTES) as *i64
248 let v1: *i64 = sys_mmap(CT_TABLE_BYTES) as *i64
249 let w1: *i64 = sys_mmap(CT_TABLE_BYTES) as *i64
250 let p2: *i64 = sys_mmap(CT_TABLE_BYTES) as *i64
251 let v2: *i64 = sys_mmap(CT_TABLE_BYTES) as *i64
252 let w2: *i64 = sys_mmap(CT_TABLE_BYTES) as *i64
253 let dbuf: *u8 = sys_mmap(CT_DIRBUF)
254 let path: *u8 = sys_mmap(256)
255 let sbuf: *u8 = sys_mmap(CT_STATBUF)
256
257 let t1: i64 = sys_now_us()
258 let n1: i64 = ct_scan(p1, v1, w1, dbuf, path, sbuf)
259 sys_sleep_ms(CT_WINDOW_MS)
260 let t2: i64 = sys_now_us()
261 let n2: i64 = ct_scan(p2, v2, w2, dbuf, path, sbuf)
262 let el: i64 = (t2 - t1) / CT_US_PER_MS
263
264 if n1 <= 0 { rm_puts("NX-CTXTOP verdict=UNMEASURED why=/proc walk returned nothing\n" as *u8); return CT_EXIT_UNMEASURED }
265 if el <= 0 { rm_puts("NX-CTXTOP verdict=UNMEASURED why=zero-length window\n" as *u8); return CT_EXIT_UNMEASURED }
266
267 let tp: *i64 = sys_mmap(CT_TOPK_BYTES) as *i64
268 let tr: *i64 = sys_mmap(CT_TOPK_BYTES) as *i64
269 var used: i64 = 0
270 var tot: i64 = 0
271 var totv: i64 = 0
272 var totn: i64 = 0
273 var i: i64 = 0
274 while i < n2 {
275 let j: i64 = ct_find(p1, n1, p2[i])
276 if j >= 0 {
277 let dv: i64 = v2[i] - v1[j]
278 let dn: i64 = w2[i] - w1[j]
279 if dv >= 0 { if dn >= 0 {
280 let r: i64 = pc_rate(dv + dn, el)
281 if r > 0 {
282 tot = tot + r
283 totv = totv + pc_rate(dv, el)
284 totn = totn + pc_rate(dn, el)
285 used = ct_topk_insert(tp, tr, CT_TOPK, used, p2[i], r)
286 }
287 } }
288 }
289 i = i + 1
290 }
291
292 rm_puts("NX-CTXTOP window_ms=" as *u8); rm_num(el)
293 rm_puts(" procs=" as *u8); rm_num(n2)
294 rm_puts(" attributed_ctxsw_per_sec=" as *u8); rm_num(tot)
295 rm_puts(" vol=" as *u8); rm_num(totv)
296 rm_puts(" nonvol=" as *u8); rm_num(totn)
297 rm_puts(" admit_max=" as *u8); rm_num(maxload); rm_puts(" admit_src=" as *u8); rm_puts(confsrc); rm_puts(" admit_path=" as *u8); rm_puts(confp); rm_puts("\n" as *u8)
298 var k: i64 = 0
299 while k < used {
300 rm_puts(" #" as *u8); rm_num(k + 1)
301 rm_puts(" pid=" as *u8); rm_num(tp[k])
302 rm_puts(" ctxsw/s=" as *u8); rm_num(tr[k])
303 rm_puts(" share=" as *u8); rm_num(ct_share_permil(tr[k], tot)); rm_puts("permil name=" as *u8)
304 ct_putname(tp[k], path, sbuf)
305 rm_puts("\n" as *u8)
306 k = k + 1
307 }
308 if n2 >= CT_MAXP { rm_puts(" WARNING scan CAPPED at " as *u8); rm_num(CT_MAXP); rm_puts(" pids -- coverage INCOMPLETE, totals are a floor\n" as *u8) }
309
310 // ---- PRODUCER-CLASS ATTRIBUTION (2026-08-12) ----
311 // Name WHAT KIND of producer the ranked churn is, so a RED axis carries its own verdict. Classified
312 // over the ranked top-K (which carried the bulk of attributed churn in field samples) by owning-
313 // process cmdline; scope is printed as top<used> so it is never read as a whole-box population claim
314 // (L: label a bound as a bound). Reuses path/sbuf -- the print loop above has finished with them, so
315 // no extra mmap (the guard organ must not become the load; §admission).
316 if used <= 0 { rm_puts("NX-CTXTOP-CLASS scope=top0 note=no attributed churn to classify\n" as *u8); return 0 }
317 if tot <= 0 { rm_puts("NX-CTXTOP-CLASS scope=top0 note=zero attributed total\n" as *u8); return 0 }
318 var sys_s: i64 = 0
319 var est_s: i64 = 0
320 var ker_s: i64 = 0
321 var oth_s: i64 = 0
322 var kk: i64 = 0
323 while kk < used {
324 let cl: i64 = ct_class_of(tp[kk], path, sbuf)
325 if cl == CTC_SYSTEM { sys_s = sys_s + tr[kk] }
326 if cl == CTC_ESTATE { est_s = est_s + tr[kk] }
327 if cl == CTC_KERNEL { ker_s = ker_s + tr[kk] }
328 if cl == CTC_OTHER { oth_s = oth_s + tr[kk] }
329 kk = kk + 1
330 }
331 // dominant class among the ranked top-K; strict > keeps the incumbent on a tie (conservative,
332 // and label + verdict both derive from the same code so they can never disagree).
333 var domc: i64 = CTC_SYSTEM
334 var domv: i64 = sys_s
335 if est_s > domv { domc = CTC_ESTATE; domv = est_s }
336 if ker_s > domv { domc = CTC_KERNEL; domv = ker_s }
337 if oth_s > domv { domc = CTC_OTHER; domv = oth_s }
338 var dom: *u8 = "SYSTEM" as *u8
339 if domc == CTC_ESTATE { dom = "ESTATE" as *u8 }
340 if domc == CTC_KERNEL { dom = "KERNEL" as *u8 }
341 if domc == CTC_OTHER { dom = "OTHER" as *u8 }
342 rm_puts("NX-CTXTOP-CLASS scope=top" as *u8); rm_num(used)
343 rm_puts(" system_permil=" as *u8); rm_num(ct_share_permil(sys_s, tot))
344 rm_puts(" estate_permil=" as *u8); rm_num(ct_share_permil(est_s, tot))
345 rm_puts(" kernel_permil=" as *u8); rm_num(ct_share_permil(ker_s, tot))
346 rm_puts(" other_permil=" as *u8); rm_num(ct_share_permil(oth_s, tot))
347 rm_puts(" dominant=" as *u8); rm_puts(dom); rm_puts("\n" as *u8)
348 // The strong "not the estate's to fix" claim is made ONLY against the ACTUAL dominant class --
349 // never asserted when the dominant is OTHER, and never when ESTATE is on top.
350 if domc == CTC_ESTATE {
351 rm_puts(" ATTRIBUTION: ESTATE-DOMINATED -- nx_* organs are the top churn producer; investigate the named pids\n" as *u8)
352 } else {
353 rm_puts(" ATTRIBUTION: estate is NOT the dominant churn class (dominant=" as *u8); rm_puts(dom)
354 rm_puts(", estate_permil=" as *u8); rm_num(ct_share_permil(est_s, tot))
355 rm_puts("); the ranked churn is largely not the estate's to fix via the sovereign API\n" as *u8)
356 }
357 return 0
358}