Expand description
§navian-memcheck
Resource-leak / soak testing as a cargo test assertion.
The bug that took down a 52 GB process in production was not a leak in the
classic sense: every byte was reachable. A per-session map simply grew
without bound because nothing ever evicted it. LeakSanitizer and Valgrind
memcheck are blind to this — the memory is still referenced, so to them it
is “in use”, not “lost”. A heap profiler would show it, but only if a human
sat and eyeballed a flamegraph.
navian-memcheck asserts the property that was actually violated: after
a warmup period, live memory PLATEAUS. You drive a workload under sustained
load; the crate samples live heap on a fixed cadence, fits a line through the
back half of the run, and fails if the slope is still climbing or a hard cap
is breached. It is a pass/fail check you drop into a test — no profiler, no
flamegraph, no platform.
use navian_memcheck::{soak, SoakConfig};
let report = soak(&SoakConfig::iterations(200_000), |i| {
process_one_event(i); // your real per-event work
});
report.assert(); // panics with a readable summary if memory kept growing§Activation: one dependency, one test, zero code changes
Add the crate and write one test. Nothing in your production code changes.
[dev-dependencies]
navian-memcheck = "0.1"The default RssSampler reads the OS, so there is no allocator to install
and no global state to set up.
§Two surfaces
- In-process (
soak,assert_bounded,assert_linear_in) — drive a workload closure and watch this process’s memory. Runs in your test suite. - Out-of-process — the
navian-memcheckCLI soaks any command’s RSS over a duration and gates CI on the same plateau property, with no code at all. See thenavian-memcheck-clicrate.
§Sampling precision (optional)
The default reads process RSS — coarser (page-granular, includes allocator
retention) but zero-setup. For a cleaner in-process signal, enable the
jemalloc feature and pass JemallocSampler to soak_with; it reads
jemalloc stats.allocated (live bytes, no page noise). That requires jemalloc
to be the global allocator — one line, and free for services already on it:
#[global_allocator]
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;§Determinism
Memory boundedness is a property, and like any property it is only trustworthy
if the run that checks it is reproducible. Drive your workload from a seeded
RNG (or under navian-dst) so
that a soak which fails on seed N fails again, identically, on seed N — and
so you can bisect the growth to the event that caused it.
Structs§
- Fit
- Result of an ordinary-least-squares fit of
y = slope * x + intercept. - Jemalloc
Sampler - In-process live-heap sampler backed by jemalloc
stats.allocated. - RssSampler
- Process resident-set-size sampler. Always available, no allocator requirement, but coarser than jemalloc: page-granular and inflated by allocator retention.
- Soak
Config - How to run a soak: how long, how often to sample, and what “still growing” and “too big” mean.
- Soak
Report - The outcome of a
soakrun: the raw samples plus the computed verdict.
Enums§
- Verdict
- Why a soak passed or failed.
Traits§
- Sampler
- A source of “live memory, in bytes, right now”.
Functions§
- assert_
bounded - Assert that a size metric never exceeds
capas a driver sweeps overdrivers. Use this to prove a structure is bounded regardless of input scale — e.g. “no matter how many distinct sessions arrive, the session map holds at mostcapentries.” - assert_
linear_ in - Assert that memory grows at most linearly in a driver and no faster than
max_bytes_per_unit. Catches super-linear blowups (e.g. an accidental O(n²) retained buffer) that a single-point check would miss. - assert_
plateau - One-line convenience: soak
iterationsofworkwith default settings and assert the result plateaus. Equivalent tosoak(&SoakConfig::iterations(iterations), work).assert(). - fit_
growth - Fit
(driver, bytes)growth points and return theFit.slopeis bytes per unit of driver; a bounded-per-item structure has a small, stable slope and highr2. - linear_
fit - Least-squares fit of
ysagainstxs. Returns a zero-slope fit when there are fewer than two points orxshas no spread. - report_
from_ samples - Build a
SoakReportfrom samples collected outside this process — e.g. the CLI polling another process’s RSS. Pass(tick, bytes)pairs with one tick per sample and setcfg.sample_every == 1so the slope is reported in bytes-per-sample. - soak
- Run
workforcfg.iterations, sampling live heap with theDefaultSampler, and return aSoakReport. Seesoak_withto supply a sampler. - soak_
with - Like
soakbut with an explicitSampler.
Type Aliases§
- Default
Sampler - The sampler
soakand friends use when you don’t pass one explicitly.