Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nordisk-alloc/src/lib.rs

//! # nordisk-alloc — the fleet's allocator, configured for a process that lives for weeks
//!
//! Every fat nordisk binary installs `mimalloc` as its process-wide global
//! allocator. That choice is right: the workloads are many-thread allocation storms
//! (nornir's deep-scan flush, korp's ingest, gunnar's pack walk) and mimalloc's
//! per-thread heaps scale them near-linear where glibc's arena contention does not.
//!
//! What was missing everywhere is the other half of that choice — **telling mimalloc
//! to hand freed memory back to the OS.**
//!
//! ## The defect, and why it is a FLEET defect
//!
//! * `mimalloc = "0.1"` resolves `libmimalloc-sys`, whose `build.rs` compiles the
//!   **v3** C sources unless the `v2` cargo feature is set. Nothing in this
//!   constellation sets it, so every fat node ships mimalloc v3.
//! * mimalloc **v2** defaulted `purge_delay` to **10 ms**. mimalloc **v3** defaults it
//!   to **1000 ms** (`c_src/mimalloc/v3/src/options.c`, the `purge_delay` row).
//! * MEASURED on `mimalloc` 0.1.52 / `libmimalloc-sys` 0.1.49, a program that builds a
//!   2 GB heap of ~1 KB `String`s and drops it, three times over:
//!
//!   | configuration                | peak RSS | RSS after the drop |
//!   |------------------------------|----------|--------------------|
//!   | as shipped (v3 default 1000) | 2006 MB  | **2006 MB** — never returned |
//!   | `MIMALLOC_PURGE_DELAY=10`    | 2006 MB  | 156 MB |
//!   | `MIMALLOC_PURGE_DELAY=0`     | 2006 MB  | **7 MB** |
//!
//!   At the shipped default the freed 2 GB is retained by the allocator for the life
//!   of the process. RSS is therefore not "memory in use" — it is the **running
//!   maximum** of every transient the process has ever built.
//!
//! ### INCIDENT 2026-08-17/18 — `dwarves-server` OOM-killed the 62 GB `dwarf` box
//!
//! Eleven kills over two days, the last four at 22:43 · 23:23 · 00:20 · 01:15, each at
//! `anon-rss` 49–53 GB with `total-vm` 61–73 GB, after which the host itself died at
//! 02:17:31 with journald reporting 152 × "Under memory pressure, flushing caches".
//! `anon-rss` at each kill EQUALLED systemd's reported `memory peak` for that
//! invocation — RSS *being* the high-water mark is exactly what that equality means.
//! Near-identical kill RSS across all eleven: the ceiling is set by the same sweep
//! peak each time, not by anything that accumulates per request.
//!
//! On a real embedded-nornir republish (357 dep crates, 13 344 files, 12 023 symbol
//! scans), two independent pairs, same binary, allocator the only variable:
//!
//! | run              | PEAK RSS | settled floor |
//! |------------------|----------|---------------|
//! | as shipped       | 3.08 GB  | 2.53 GB       |
//! | `purge_delay=10` | 2.71 GB  | 2.46 GB       |
//! | as shipped       | 3.38 GB  | 2.49 GB       |
//! | `purge_delay=10` | 2.72 GB  | 2.43 GB       |
//!
//! The **peak** falls 12–20 %, reproducibly — the term that matters for an OOM kill,
//! since the process dies AT its high-water mark. The settled **floor** barely moves,
//! because at ~2.5 GB it is *live* memory, not allocator-held. **This crate lowers the
//! ceiling the kernel trips on. It is not a claim that 50 GB becomes 500 MB.**
//!
//! ## Why a shared leaf and not a copy per repo
//!
//! The fix was measured, written and proved inside `dwarves` — and stayed there. As of
//! 2026-08-25 `alloc.rs` existed only under `dwarves/crates/dwarves/src/`, while
//! `nornir/src/bin/nornir-server.rs:19` still installed a bare
//! `mimalloc::MiMalloc` with no tuning at all, and `purge_delay` had **zero hits**
//! anywhere in edda. So the one process that was measured ratcheting got the fix and
//! every other nordisk binary — each running the same allocator, with the same v3
//! default, for the same weeks at a time — did not. LAW #5: one owner, and the
//! consumers point at it.
//!
//! Nothing here is re-derived. The option indices, the fingerprint check, the 10 ms
//! value and the reasoning are the ones dwarves MEASURED; this leaf is where they
//! live so the other five binaries can have them.
//!
//! ## What this crate does — and what it deliberately does not do
//!
//! [`tune_for_long_lived_server`] sets `purge_delay` to [`DEFAULT_PURGE_DELAY_MS`]
//! (10 ms — mimalloc v2's long-standing value) and pins `arena_purge_mult` to 1, so a
//! freed arena slice is `madvise`d back on the same schedule as a freed page. A
//! transient becomes transient again and RSS tracks live memory instead of history.
//!
//! It is **not** a memory limit (that is a `MemoryMax=` in the unit —
//! `skidbladnir::service::ServiceSpec::memory_max`), **not** a periodic `mi_collect`
//! sweeper, and it does not make any individual transient smaller. Shrinking the
//! transients themselves belongs to whichever repo builds them.
//!
//! ## Why the option is set through FFI and not `MIMALLOC_PURGE_DELAY`
//!
//! mimalloc initialises each option lazily on first read and then latches it
//! (`MI_OPTION_UNINIT` → `MI_OPTION_INITIALIZED`). `purge_delay` is already read
//! during pre-`main` allocation, so `std::env::set_var` at the top of `main` is too
//! late — MEASURED: the env-in-`main` variant retained the full 2006 MB, exactly like
//! the untuned default, while the `mi_option_set` variant returned it. Only
//! `mi_option_set` works after the fact, which is why `libmimalloc-sys` is linked with
//! its `extended` feature. An operator who wants the env route still has it: setting
//! `MIMALLOC_PURGE_DELAY` in the unit is read by mimalloc *before* `main` and the
//! `off` opt-out below then leaves it alone.
//!
//! This is the LAW's last-resort FFI clause used honestly: mimalloc is a C allocator,
//! there is no Rust reimplementation of it to reach for, and the alternative is not a
//! Rust version of the option table — it is no fix at all.
//!
//! ## Using it
//!
//! ```no_run
//! // In the final artifact — only an artifact may declare a global allocator.
//! #[global_allocator]
//! static GLOBAL: nordisk_alloc::MiMalloc = nordisk_alloc::MiMalloc;
//!
//! fn main() {
//!     // As early as possible; the product name picks the per-product env override.
//!     eprintln!("{}", nordisk_alloc::tune_for_long_lived_server("nornir").log_line());
//! }
//! ```

pub mod selftest;

/// **The allocator itself**, re-exported so a consumer needs ONE dependency to both
/// install and tune it: `#[global_allocator] static G: nordisk_alloc::MiMalloc = …`.
pub use mimalloc::MiMalloc;

/// `mi_option_purge_delay`, as an index into mimalloc's option table.
///
/// `libmimalloc-sys` exports constants only for the stable options, and `purge_delay`
/// is not one of them, so the index is named here. It is the 16th row of the table in
/// `c_src/mimalloc/v3/src/options.c` (0-based: 15), and [`tune_for_long_lived_server`]
/// **verifies** it before writing: v3 gives `purge_delay` the default 1000, and no
/// other option in that table defaults to 1000, so reading 1000 back identifies the row.
const MI_OPTION_PURGE_DELAY: i32 = 15;

/// `mi_option_arena_purge_mult`, likewise by index (row 25, 0-based 24). The v3
/// default is already 1; it is pinned anyway so a future default change cannot
/// silently re-lengthen the arena purge deadline behind this fix.
const MI_OPTION_ARENA_PURGE_MULT: i32 = 24;

/// The `purge_delay` this crate installs, in milliseconds — mimalloc **v2**'s
/// long-standing default, and the value measured above to return a dropped 2 GB
/// transient to the OS.
///
/// Not 0: purging on every free trades the OOM for `madvise` churn on the hot
/// allocation path. 10 ms is long enough to let a burst reuse its own pages and short
/// enough that the deadline actually expires within the burst.
pub const DEFAULT_PURGE_DELAY_MS: i64 = 10;

/// The **fleet-wide** env override: a value in milliseconds, or `off`.
///
/// Consulted second, after the per-product variable — see [`product_purge_delay_env`].
/// It exists so an operator can set one variable in a slice/environment file and have
/// every nordisk service on the box honour it, rather than needing to know each
/// binary's own spelling.
pub const GENERIC_PURGE_DELAY_ENV: &str = "NORDISK_MIMALLOC_PURGE_DELAY_MS";

/// The **per-product** env override for `product`, consulted FIRST:
/// `product_purge_delay_env("dwarves")` is `"DWARVES_MIMALLOC_PURGE_DELAY_MS"`.
///
/// This is not decoration. `DWARVES_MIMALLOC_PURGE_DELAY_MS` is the variable the fix
/// already shipped with and that an operator may already have set on `dwarf`;
/// deriving the name per product means dwarves repoints at this leaf without any
/// deployed override silently ceasing to work.
///
/// `-` becomes `_` and the name is upper-cased, so `"korp-server"` gives
/// `KORP_SERVER_MIMALLOC_PURGE_DELAY_MS`.
pub fn product_purge_delay_env(product: &str) -> String {
    let stem: String = product
        .chars()
        .map(|c| if c == '-' || c == '.' { '_' } else { c })
        .collect();
    format!("{}_MIMALLOC_PURGE_DELAY_MS", stem.to_uppercase())
}

/// The value mimalloc **v3** ships as the `purge_delay` default — the number this
/// crate exists because of, and the fingerprint [`tune_for_long_lived_server`] uses to
/// confirm it is writing to the row it means to.
pub const MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS: i64 = 1000;

/// What [`purge_delay_ms`] decided, and why — a value rather than a log line, so the
/// decision is unit-testable without a process.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PurgeDelayPlan {
    /// Install this many milliseconds (the default, or an operator's override).
    Set(i64),
    /// An override said `off` — change nothing.
    LeaveAlone,
}

/// Decide the `purge_delay` to install from ONE explicit env value — pure, so the
/// ladder is testable without mutating process environment.
///
/// * `None` / empty ⇒ [`PurgeDelayPlan::Set`]`(`[`DEFAULT_PURGE_DELAY_MS`]`)`.
/// * `off` (any case) ⇒ [`PurgeDelayPlan::LeaveAlone`].
/// * a non-negative integer ⇒ [`PurgeDelayPlan::Set`] of it.
/// * anything else ⇒ the default. A typo must not silently disable the fix that keeps
///   the box alive, so an unparseable value falls FORWARD to the safe value rather
///   than back to mimalloc's.
pub fn purge_delay_plan(env: Option<&str>) -> PurgeDelayPlan {
    let raw = match env.map(str::trim) {
        None | Some("") => return PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS),
        Some(v) => v,
    };
    if raw.eq_ignore_ascii_case("off") {
        return PurgeDelayPlan::LeaveAlone;
    }
    match raw.parse::<i64>() {
        Ok(ms) if ms >= 0 => PurgeDelayPlan::Set(ms),
        _ => PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS),
    }
}

/// The two-rung ladder, pure: the per-product value wins over the fleet-wide one, and
/// an EMPTY per-product value is not a decision — it falls through to the generic
/// rather than shadowing it.
///
/// That fall-through is the whole reason this is a separate function: a service
/// started from a unit that mentions the variable without giving it a value would
/// otherwise silently override an operator's fleet-wide setting with the default.
pub fn purge_delay_plan_from(product: Option<&str>, generic: Option<&str>) -> PurgeDelayPlan {
    fn said_something(v: Option<&str>) -> Option<&str> {
        v.map(str::trim).filter(|s| !s.is_empty())
    }
    purge_delay_plan(said_something(product).or_else(|| said_something(generic)))
}

/// [`purge_delay_plan_from`] over the real environment: `<PRODUCT>_…` then
/// [`GENERIC_PURGE_DELAY_ENV`].
pub fn purge_delay_ms(product: &str) -> PurgeDelayPlan {
    let specific = std::env::var(product_purge_delay_env(product)).ok();
    let generic = std::env::var(GENERIC_PURGE_DELAY_ENV).ok();
    purge_delay_plan_from(specific.as_deref(), generic.as_deref())
}

/// What [`tune_for_long_lived_server`] actually did to the running allocator —
/// returned (not just logged) so a caller and a test can both read it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AllocTuning {
    /// The product name the tuning was asked for — it names the process in
    /// [`log_line`](AllocTuning::log_line) and picks the env override.
    pub product: String,
    /// `purge_delay` before the call (mimalloc's own resolution).
    pub purge_delay_before_ms: i64,
    /// `purge_delay` after the call.
    pub purge_delay_after_ms: i64,
    /// `arena_purge_mult` after the call.
    pub arena_purge_mult: i64,
    /// False when NOTHING was written — either an override said `off`, or the option
    /// row did not carry a value this crate recognises (see [`MI_OPTION_PURGE_DELAY`]).
    pub applied: bool,
}

impl AllocTuning {
    /// The one boot line an operator reads. Says what changed, or why it did not —
    /// and names the variable that would turn it back on, for THIS product.
    pub fn log_line(&self) -> String {
        if !self.applied {
            return format!(
                "{}: allocator purge_delay left at {} ms (`{}`/`{GENERIC_PURGE_DELAY_ENV}` \
                 said off, or an unrecognised mimalloc option layout) — a dropped \
                 transient may stay resident; see nordisk_alloc",
                self.product,
                self.purge_delay_before_ms,
                product_purge_delay_env(&self.product),
            );
        }
        format!(
            "{}: allocator purge_delay {} ms → {} ms (arena_purge_mult {}) — freed \
             transients return to the OS",
            self.product,
            self.purge_delay_before_ms,
            self.purge_delay_after_ms,
            self.arena_purge_mult,
        )
    }
}

/// **Configure the running mimalloc so freed memory goes back to the OS.** Call once,
/// as early in `main` as the process can.
///
/// `product` names the binary for the log line and picks its env override — see
/// [`product_purge_delay_env`].
///
/// Idempotent: calling it twice writes the same values and the second call simply
/// reports `before == after`.
///
/// Refuses to write when the option row does not read back
/// [`MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS`] or the value this crate already installed —
/// if a future mimalloc reorders its option table, poking a numbered row would
/// configure something else entirely, and a wrong write is worse than no write. The
/// refusal is reported, never silent, and the behavioural regression test
/// (`tests/mimalloc_transient_peak.rs`) goes red on it.
pub fn tune_for_long_lived_server(product: &str) -> AllocTuning {
    // SAFETY: `mi_option_get`/`mi_option_set` are thread-safe reads/writes of
    // mimalloc's static option table (`options.c`), valid at any point in the
    // process' life. The allocator is already initialised — the Rust runtime
    // allocated before `main`.
    let before = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) } as i64;
    let mult = || unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_ARENA_PURGE_MULT) } as i64;
    let untouched = |arena_purge_mult| AllocTuning {
        product: product.to_string(),
        purge_delay_before_ms: before,
        purge_delay_after_ms: before,
        arena_purge_mult,
        applied: false,
    };

    let want = match purge_delay_ms(product) {
        PurgeDelayPlan::LeaveAlone => return untouched(mult()),
        PurgeDelayPlan::Set(ms) => ms,
    };

    // The row fingerprint: mimalloc v3's own default, or a value we already wrote.
    if !(before == MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS || before == want) {
        return untouched(mult());
    }

    // SAFETY: as above.
    unsafe {
        libmimalloc_sys::mi_option_set(MI_OPTION_PURGE_DELAY, want as std::os::raw::c_long);
        libmimalloc_sys::mi_option_set(MI_OPTION_ARENA_PURGE_MULT, 1);
    }
    AllocTuning {
        product: product.to_string(),
        purge_delay_before_ms: before,
        purge_delay_after_ms: current_purge_delay_ms(),
        arena_purge_mult: mult(),
        applied: true,
    }
}

/// The `purge_delay` the running allocator is currently using, in milliseconds.
///
/// Exposed so the behavioural cell (`tests/mimalloc_transient_peak.rs`) can state its
/// premise — "the shipped default really is [`MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS`]" —
/// without duplicating the option index that [`tune_for_long_lived_server`] guards.
pub fn current_purge_delay_ms() -> i64 {
    // SAFETY: a thread-safe read of mimalloc's static option table.
    unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) as i64 }
}

/// This process' resident set in KiB, read from `/proc/self/statm` (field 2, in
/// pages). The one measurement that distinguishes "freed" from "returned", and the
/// thing the regression test asserts on. `None` off Linux or when `/proc` is absent.
pub fn self_rss_kb() -> Option<u64> {
    let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
    let pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
    let page_kb = 4u64; // every target this ships on
    Some(pages * page_kb)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The env ladder, including the two ways it must NOT fail: a typo and a negative
    /// number both fall FORWARD to the safe default rather than back to mimalloc's
    /// 1000 ms.
    #[test]
    fn the_purge_delay_ladder_never_falls_back_to_the_default_that_caused_the_oom() {
        assert_eq!(
            purge_delay_plan(None),
            PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
        );
        assert_eq!(
            purge_delay_plan(Some("  ")),
            PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
        );
        assert_eq!(purge_delay_plan(Some("0")), PurgeDelayPlan::Set(0));
        assert_eq!(purge_delay_plan(Some(" 25 ")), PurgeDelayPlan::Set(25));
        assert_eq!(purge_delay_plan(Some("OFF")), PurgeDelayPlan::LeaveAlone);
        assert_eq!(purge_delay_plan(Some("off")), PurgeDelayPlan::LeaveAlone);
        // A typo must not disable the fix.
        assert_eq!(
            purge_delay_plan(Some("ten")),
            PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
        );
        assert_eq!(
            purge_delay_plan(Some("-1")),
            PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
        );
        // And the default we install must not be mimalloc v3's.
        assert_ne!(
            DEFAULT_PURGE_DELAY_MS, MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
            "installing v3's own default would be a no-op fix"
        );
    }

    /// **The per-product rung — the one thing this leaf adds over the copy it came
    /// from, and the reason repointing dwarves cannot break a deployed override.**
    ///
    /// dwarves shipped with `DWARVES_MIMALLOC_PURGE_DELAY_MS`. If moving the code here
    /// only understood `NORDISK_…`, an operator who had set the old variable on `dwarf`
    /// would get the default back with no message anywhere — the silent kind of
    /// regression a lift-and-shift produces.
    #[test]
    fn the_products_own_variable_still_works_and_an_empty_one_does_not_shadow_the_fleet() {
        assert_eq!(
            product_purge_delay_env("dwarves"),
            "DWARVES_MIMALLOC_PURGE_DELAY_MS"
        );
        assert_eq!(
            product_purge_delay_env("nornir"),
            "NORNIR_MIMALLOC_PURGE_DELAY_MS"
        );
        // A hyphenated binary name is a legal env stem only after the hyphen goes.
        assert_eq!(
            product_purge_delay_env("korp-server"),
            "KORP_SERVER_MIMALLOC_PURGE_DELAY_MS"
        );

        // The product rung wins.
        assert_eq!(
            purge_delay_plan_from(Some("off"), Some("25")),
            PurgeDelayPlan::LeaveAlone
        );
        assert_eq!(
            purge_delay_plan_from(Some("40"), Some("25")),
            PurgeDelayPlan::Set(40)
        );
        // …and the fleet rung is reached when the product says nothing.
        assert_eq!(
            purge_delay_plan_from(None, Some("25")),
            PurgeDelayPlan::Set(25)
        );
        // AN EMPTY PRODUCT VALUE IS NOT A DECISION. A unit that mentions the variable
        // without a value must not shadow the operator's fleet-wide setting.
        assert_eq!(
            purge_delay_plan_from(Some(""), Some("off")),
            PurgeDelayPlan::LeaveAlone
        );
        assert_eq!(
            purge_delay_plan_from(Some("   "), Some("25")),
            PurgeDelayPlan::Set(25)
        );
        // Neither says anything ⇒ the safe default.
        assert_eq!(
            purge_delay_plan_from(None, None),
            PurgeDelayPlan::Set(DEFAULT_PURGE_DELAY_MS)
        );
    }

    /// The option row is still `purge_delay`, the tuning is a real write, and it is
    /// idempotent. **One test, not three**, because the option table is process-global
    /// mutable state and cargo runs `#[test]`s on parallel threads — split up, the
    /// row-identity assertion would race the write and flake.
    ///
    /// RED WHEN BROKEN: a mimalloc upgrade that reorders its option table (or changes
    /// the default) fails the first assertion, in a named test, instead of silently
    /// configuring some unrelated option in production.
    ///
    /// Note the test harness does NOT install `MiMalloc` as its global allocator (only
    /// a final artifact may), but the C library is linked and its option table is the
    /// same static — so the row identity is exactly what a fat binary would see.
    #[test]
    fn the_named_option_row_is_purge_delay_and_the_tuning_applies_idempotently() {
        // SAFETY: a thread-safe read of mimalloc's static option table.
        let got = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_PURGE_DELAY) } as i64;
        assert_eq!(
            got, MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
            "option row {MI_OPTION_PURGE_DELAY} no longer reads mimalloc v3's \
             purge_delay default ({MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS} ms) but {got} — \
             the option table moved; re-derive the index from \
             libmimalloc-sys/c_src/mimalloc/v3/src/options.c before trusting it"
        );
        // arena_purge_mult's v3 default is 1 — the other half of the fingerprint.
        let mult = unsafe { libmimalloc_sys::mi_option_get(MI_OPTION_ARENA_PURGE_MULT) } as i64;
        assert_eq!(
            mult, 1,
            "option row {MI_OPTION_ARENA_PURGE_MULT} no longer reads arena_purge_mult's \
             v3 default (1) but {mult}"
        );

        let first = tune_for_long_lived_server("nordisk-alloc-selftest");
        assert!(first.applied, "{}", first.log_line());
        assert_eq!(first.purge_delay_after_ms, DEFAULT_PURGE_DELAY_MS);
        assert_eq!(first.arena_purge_mult, 1);
        assert!(
            first.log_line().contains("return to the OS"),
            "{}",
            first.log_line()
        );

        let second = tune_for_long_lived_server("nordisk-alloc-selftest");
        assert!(
            second.applied,
            "a second call is a no-op write, not a refusal"
        );
        assert_eq!(second.purge_delay_before_ms, DEFAULT_PURGE_DELAY_MS);
        assert_eq!(second.purge_delay_after_ms, DEFAULT_PURGE_DELAY_MS);
    }

    /// The opt-out is a real escape hatch and it is HONEST about it — an operator who
    /// turns the fix off gets a line saying a dropped transient may stay resident, and
    /// the line names THAT PRODUCT's variable, not some other binary's.
    #[test]
    fn the_opt_out_is_reported_and_never_silent() {
        let untuned = AllocTuning {
            product: "nornir".into(),
            purge_delay_before_ms: MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
            purge_delay_after_ms: MIMALLOC_V3_DEFAULT_PURGE_DELAY_MS,
            arena_purge_mult: 1,
            applied: false,
        };
        let line = untuned.log_line();
        assert!(line.contains("may stay resident"), "{line}");
        assert!(
            line.contains("1000"),
            "the line must name the value in force: {line}"
        );
        assert!(
            line.contains("NORNIR_MIMALLOC_PURGE_DELAY_MS"),
            "the line must name the variable that turns it back on FOR THIS PRODUCT: {line}"
        );
        assert!(line.starts_with("nornir:"), "{line}");
    }

    /// `/proc/self/statm` is readable and plausible — the regression test's whole
    /// measurement rests on it.
    #[test]
    fn self_rss_is_readable_and_nonzero() {
        let rss = self_rss_kb().expect("/proc/self/statm on linux");
        assert!(rss > 1024, "an rss of {rss} KiB is not a running process");
    }
}