nx_ioadmit_lib.nx
buildroot/runtime/nx_ioadmit_lib.nx
about
nx_ioadmit_lib.nx -- THE I/O-STORM ADMISSION RULER, importable: ONE ruler for every process that is
about to ADD uninterruptible I/O to a shared array (torrent workers, crawlers, bulk writers) -- the same
witness nx_build_admit already uses to refuse a compiler fork, so a spawner and the build gate can
never disagree about whether the box is in an I/O storm.
WHY (measured 2026-08-19): the box sat at load 12-17 on 8 CPUs for days with procs_blocked=8, and 5 of
the 8 blocked pids were nx_torrent_get workers -- the torrent daemon resumed EVERY active torrent at
once (no budget, no resource awareness), and that alone pushed procs_blocked to ncpu, where the build
admission's I/O-storm witness correctly refused every lane's builds. The cure is not a higher ceiling;
it is that an I/O spawner asks the same question the build gate asks BEFORE it forks.
THE RULER (unchanged from nx_build_admit.ba_verdict, 2026-08-17): procs_blocked >= per_cpu x ncpu is an
I/O storm. ioa_spawn_budget answers "how many MORE blocked processes may I add and stay under that line,
leaving reserve slots for everyone else" -- a number DERIVED from the box, never chosen. A third state
is first-class: /proc/stat unreadable or truncated -> IOA_UNREADABLE, never a guess (the caller owns
its fallback and must NAME it).
license_tier: ORIGINAL No hw writes (Rule 26). lib (no main)
dependencies 3 imports · 16 importers
diagram shows first 10 each side; +0 more imports, +6 more importers in the complete lists below.
imports: nx_syscalls.nxnx_resmon_lib.nxnx_itoa_lib.nx
imported by: nx_blkprofile.nxnx_build_admit.nxnx_clock_driver_sched_ar.nxnx_clock_sched.nxnx_clock_sched_reserved_fault_v2_ar.nxnx_clock_sched_reserved_v2_ar.nxnx_compare_regen.nxnx_compare_regen_scoped.nxnx_compare_regen_t138.nxnx_heavyio_lib.nxnx_ioadmit_gate.nxnx_ioconfirm.nxnx_ioconfirm_checked_test_20260907.nxnx_memvel.nxnx_procchurn_lib.nxnx_torrent_daemon.nx
structs
| none |
consts
| 22 | const IOA_STAT: *u8 = "/proc/stat" |
| 26 | const IOA_STAT_CAP: i64 = 65536 |
| 27 | const IOA_UNREADABLE: i64 = 0 - 1 |
| 31 | const IOA_BLOCKED_PER_CPU: i64 = 1 |
| 35 | const IOA_RESERVE_SLOTS: i64 = 1 |
| 38 | const IOA_COMM_MAX: i64 = 15 |
| 39 | const IOA_DIRBUF: i64 = 65536 // ONE getdents64 transfer; the walk loops until it returns 0 |
| 40 | const IOA_STATBUF: i64 = 1024 // one /proc/<pid>/stat line; comm sits inside the first 64 bytes |
| 41 | const IOA_PATHBUF: i64 = 64 // "/proc/<pid>/stat" |
| 42 | const IOA_RECLEN_OFF: i64 = 16 // linux_dirent64: d_reclen at 16, d_name at 19 |
| 43 | const IOA_NAME_OFF: i64 = 19 |
| 103 | const IOA_ROOTS_ONLY: i64 = 1 |
| 104 | const IOA_ALL_PROCS: i64 = 0 |
| 247 | const IOA_MEMINFO: *u8 = "/proc/meminfo" |
| 248 | const IOA_MEMINFO_CAP: i64 = 65536 |
| 250 | const IOA_CONG_CLEAR: i64 = 0 |
| 251 | const IOA_CONG_RISING: i64 = 1 |
| 252 | const IOA_CONG_STORM: i64 = 2 |
| 253 | const IOA_CONG_UNREADABLE: i64 = 0 - 1 |
| 312 | const IOA_DISKSTATS: *u8 = "/proc/diskstats" |
| 316 | const IOA_DISKSTATS_CAP: i64 = 262144 |
| 317 | const IOA_DS_SCRATCH_BYTES: i64 = 16 |
| 319 | const IOA_DS_FIELDS: i64 = 11 |
| 320 | const IOA_DS_SECTORS_READ: i64 = 2 |
| 321 | const IOA_DS_SECTORS_WRITTEN: i64 = 6 |
| 322 | const IOA_DS_IOS_IN_FLIGHT: i64 = 8 |
| 323 | const IOA_DS_MS_DOING_IO: i64 = 9 |
| 324 | const IOA_SECTOR_BYTES: i64 = 512 |
| 325 | const IOA_MS_PER_S: i64 = 1000 |
| 326 | const IOA_BYTES_PER_KB: i64 = 1024 |
| 328 | const IOA_CH_SPACE: i64 = 32 |
| 329 | const IOA_CH_NL: i64 = 10 |
| 330 | const IOA_CH_D0: i64 = 48 |
| 331 | const IOA_CH_D9: i64 = 57 |
| 488 | const IOA_MEDIAN_MAX_K: i64 = 15 |
| 489 | const IOA_MEDIAN_SLOTS: i64 = 128 // IOA_MEDIAN_MAX_K i64 slots with headroom |
| 490 | const IOA_MEAS_SLOTS: i64 = 32 // the out[] ioa_measure fills |
| 537 | const IOA_OWN_SLOTS: i64 = 64 |
| 538 | const IOA_STATE_OFF: i64 = 2 // "(comm) S ..." -- the state char sits 2 bytes past the closing paren |
| 539 | const IOA_CH_D: i64 = 68 // 'D', uninterruptible sleep: the state admission counts |
| 623 | const IOA_PERMIL: i64 = 1000 |
| 695 | const IOADM_GREEN: i64 = 0 |
| 696 | const IOADM_RED: i64 = 1 |
| 697 | const IOADM_AMBER: i64 = 2 |
| 698 | const IOADM_UNOBS: i64 = 3 |
| 699 | const IOADM_BUF: i64 = 65536 |
| 700 | const IOADM_NAME: i64 = 512 |
| 701 | const IOADM_PATH: i64 = 1024 |
| 702 | const IOADM_RECLEN_OFF: i64 = 16 |
| 703 | const IOADM_NAME_OFF: i64 = 19 |
| 704 | const IOADM_GETDENTS: i64 = 217 |
| 705 | const IOADM_OPENDIR: i64 = 0x10000 |
| 706 | const IOADM_AT_FDCWD: i64 = 0 - 100 |
| 707 | const IOADM_PLUS: i64 = 43 |
| 708 | const IOADM_DOT: i64 = 46 |
| 709 | const IOADM_LBRACK: i64 = 91 |
| 710 | const IOADM_SLASH: i64 = 47 |
| 711 | const IOADM_RBRACK: i64 = 93 |
| 712 | const IOADM_D0: i64 = 48 |
| 713 | const IOADM_D9: i64 = 57 |
| 714 | const IOADM_OUT_SLOTS: i64 = 5 |
functions
| 45 | func ioa_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 47 | func ioa_ncpu(buf: *u8, n: i64) -> i64 |
| 63 | func ioa_measure(out: *i64) -> i64 |
| 89 | func ioa_spawn_budget(ncpu: i64, procs_blk: i64, per_cpu: i64, reserve: i64) -> i64 |
| 108 | func ioa_stat_parse(sbuf: *u8, sn: i64, out: *i64) -> i64 |
| 142 | func ioa_read_pid_stat(pid: i64, sbuf: *u8, path: *u8) -> i64 called by 3: ioa_count_comm_modeioa_ownership_censuspc_ppid_of calls 4: nxi_bufsys_openat_rdsys_readsys_close |
| 159 | func ioa_comm_eq(sbuf: *u8, cs: i64, cl: i64, name: *u8, nl: i64) -> i64 called by 1: ioa_count_comm_mode |
| 169 | func ioa_count_comm_mode(name: *u8, mode: i64) -> i64 |
| 225 | func ioa_count_comm(name: *u8) -> i64 { return ioa_count_comm_mode(name, IOA_ALL_PROCS) } |
| 226 | func ioa_count_comm_roots(name: *u8) -> i64 { return ioa_count_comm_mode(name, IOA_ROOTS_ONLY) } |
| 261 | func ioa_dirty(out: *i64) -> i64 |
| 283 | func ioa_congestion(dirty_kb: i64, wb_kb: i64, warn_kb: i64, storm_kb: i64) -> i64 called by 1: main |
| 295 | func ioa_cong_name(state: i64) -> *u8 |
| 334 | func ioa_ds_skip_sp(buf: *u8, n: i64, p: i64) -> i64 called by 1: ioa_ds_parse |
| 346 | func ioa_ds_int(buf: *u8, n: i64, p: i64, out: *i64) -> i64 called by 1: ioa_ds_parse |
| 366 | func ioa_ds_tok_eq(buf: *u8, n: i64, p: i64, name: *u8) -> i64 called by 1: ioa_ds_parse |
| 392 | func ioa_ds_parse(buf: *u8, n: i64, dev: *u8, out: *i64) -> i64 |
| 444 | func ioa_diskstats(dev: *u8, out: *i64) -> i64 |
| 462 | func ioa_sector_rate_kbs(sectors_delta: i64, elapsed_ms: i64) -> i64 called by 1: main |
| 493 | func ioa_sleep_ms(ms: i64) -> i64 { return sys_poll(0 as *u8, 0, ms) } |
| 501 | func ioa_median(vals: *i64, k: i64) -> i64 |
| 543 | func ioa_comm_starts(sbuf: *u8, cs: i64, cl: i64, name: *u8, nl: i64) -> i64 called by 1: ioa_ownership_census |
| 557 | func ioa_ownership_census(prefix: *u8, out: *i64) -> i64 called by 1: main calls 8: ioa_slensys_openat_rdsys_mmapsys_getdents64ioa_read_pid_statioa_stat_parse+2 |
| 627 | func ioa_share_permil(part: i64, whole: i64) -> i64 called by 1: main |
| 633 | func ioa_measure_median(out: *i64, k: i64, gap_ms: i64) -> i64 |
| 716 | func ioadm_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: ioadm_find |
| 717 | func ioadm_cat(dst: *u8, a: *u8, b: *u8, c: *u8) -> i64 |
| 728 | func ioadm_read(path: *u8, buf: *u8, cap: i64) -> i64 |
| 743 | func ioadm_find(buf: *u8, n: i64, needle: *u8) -> i64 |
| 759 | func ioadm_int_after(buf: *u8, n: i64, key: *u8) -> i64 |
| 779 | func ioadm_degraded(buf: *u8, n: i64) -> i64 called by 1: ioa_dmcache |
| 802 | func ioadm_instance(root: *u8, out: *u8) -> i64 |
| 846 | func ioa_dmcache(root: *u8, out: *i64) -> i64 |