code wiki / _hdl_build / nx_procchurn.nx
nx_procchurn.nx
buildroot/runtime/_hdl_build/nx_procchurn.nx
about
nx_procchurn.nx -- the PROCESS-CHURN axis the health plane never had (debt seq1317 / seq1318).
WHY THIS EXISTS: 2026-07-30, nx_health said {overall:OK, degraded:0, down:0} with all 14 services UP
while the NAS was forking 78.3 processes/sec sustained, burning 231 permil of an 8-core box in the
KERNEL against 98 permil in userspace (a 2.36x kernel:user inversion) and taking 91718 context
switches/sec. nx_resmon owns MEMORY, nx_netobs owns NETWORK -- the process-churn axis had NO organ, so
the largest CPU consumer on the platform was invisible to every instrument we owned and was found by
hand. This is that instrument.
LAW: liveness is not performance. A board that only asks "is it up" cannot see the machine burning.
WHAT IT MEASURES (two /proc/stat samples bracketing a real window -- deltas, never since-boot totals,
because a since-boot average hides a storm that started an hour ago):
forks/sec -- `processes` delta. THE headline: fork+exec is the most expensive thing a kernel does.
ctxsw/sec -- `ctxt` delta. Rises with churn AND with thrash; corroborates, never diagnoses alone.
intr/sec -- `intr` delta, first field. Device pressure, reported for context (not thresholded).
kernel:user -- system:user+nice jiffies as permil. 1000 = parity. THE inversion detector.
busy/idle -- share of measured capacity, self-normalising (no HZ and no core count needed --
every share is a fraction of the SAME jiffy total, so the arithmetic cannot drift
when the box changes size. That is what makes this instrument hardware-portable).
Policy lives in nx_procchurn_lib.nx so the gate exercises the SAME code (rule 9 + rule 15); thresholds
are data-driven from knowledge/status/procchurn.conf (rule 11). Read-only. No hw writes (Rule 26).
Exit code IS the verdict so a caller can gate on it: 0=GREEN 1=AMBER 2=RED 3=UNMEASURED.
license_tier: ORIGINAL expect_exit: 0
dependencies 1 imports · 0 importers
imports: nx_procchurn_lib.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const PC_STATBUF: i64 = 8192 |
| 28 | const PC_CONFBUF: i64 = 4096 |
| 29 | const PC_FIELD_BYTES: i64 = 256 |
| 30 | const PC_NCPU_FIELDS: i64 = 10 |
| 31 | const PC_DEF_WINDOW_MS: i64 = 3000 |
| 32 | const PC_MIN_WINDOW_MS: i64 = 1000 |
| 33 | const PC_MAX_WINDOW_MS: i64 = 10000 |
| 34 | const PC_US_PER_MS: i64 = 1000 |
| 35 | const PC_DEF_F_AMBER: i64 = 20 |
| 36 | const PC_DEF_F_RED: i64 = 50 |
| 37 | const PC_DEF_R_AMBER: i64 = 1000 |
| 38 | const PC_DEF_R_RED: i64 = 1500 |
| 39 | const PC_DEF_C_AMBER: i64 = 20000 |
| 40 | const PC_DEF_C_RED: i64 = 50000 |
| 41 | const PC_EXIT_UNMEASURED: i64 = 3 |
| 42 | const PC_JBUF: i64 = 512 |
| 43 | const PC_MODE_644: i64 = 420 |
functions
| 49 | func pc_jappend(path: *u8, buf: *u8, n: i64) -> i64 |
| 58 | func pc_scalar(buf: *u8, n: i64, key: *u8, out: *i64) -> i64 |
| 64 | func main() -> i64 |