Skip to main content

Crate navian_memcheck

Crate navian_memcheck 

Source
Expand description

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-memcheck CLI soaks any command’s RSS over a duration and gates CI on the same plateau property, with no code at all. See the navian-memcheck-cli crate.

§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.
JemallocSampler
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.
SoakConfig
How to run a soak: how long, how often to sample, and what “still growing” and “too big” mean.
SoakReport
The outcome of a soak run: 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 cap as a driver sweeps over drivers. 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 most cap entries.”
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 iterations of work with default settings and assert the result plateaus. Equivalent to soak(&SoakConfig::iterations(iterations), work).assert().
fit_growth
Fit (driver, bytes) growth points and return the Fit. slope is bytes per unit of driver; a bounded-per-item structure has a small, stable slope and high r2.
linear_fit
Least-squares fit of ys against xs. Returns a zero-slope fit when there are fewer than two points or xs has no spread.
report_from_samples
Build a SoakReport from samples collected outside this process — e.g. the CLI polling another process’s RSS. Pass (tick, bytes) pairs with one tick per sample and set cfg.sample_every == 1 so the slope is reported in bytes-per-sample.
soak
Run work for cfg.iterations, sampling live heap with the DefaultSampler, and return a SoakReport. See soak_with to supply a sampler.
soak_with
Like soak but with an explicit Sampler.

Type Aliases§

DefaultSampler
The sampler soak and friends use when you don’t pass one explicitly.