📊 heapster
Lightweight heap telemetry for Rust, built on relaxed atomics.
heapster is a lightweight, generic wrapper over any GlobalAlloc that tracks allocations, deallocations, and reallocations using pure relaxed atomics.
It is designed to be always-on, allowing you to identify allocation patterns, diff heap usage between code paths, and export raw allocator metrics to your telemetry dashboards with minimal overhead.
Why Heapster?
Heap profilers like dhat or heaptrack capture rich per-allocation data but add significant overhead and require dedicated viewers. Heapster occupies a lighter tier: aggregate counters and histograms only, with overhead low enough to leave on in production.
- Atomics-only: No mutexes, no thread-locals, no external viewer files. Just relaxed atomic counters.
no_stdby default: Uses onlycoreandallocin the default build. The sole default dependency isportable-atomic, which compiles down tocoreatomics on targets with native 64-bit atomics and provides a fallback on those without them. Thefmtandserdefeatures addstdrequirements.- Generic over any allocator: Wraps
System, jemalloc, mimalloc, or any customGlobalAlloc. - Size histograms: Power-of-two buckets for allocations and reallocations make the size distribution visible at a glance.
- Realloc classification: Tracks growth and shrinkage of reallocations, and (orthogonally) whether they forced a full memory move (copying) — a moved growth realloc counts towards both metrics.
- Snapshot diffing:
measure()returns aStatsdelta for a closure, suitable for assertion-style tests and benchmark comparisons. - Dual view of byte movement: separate "fresh-only" and "inclusive" sums let you reason about allocation pattern shape independently from total heap pressure.
Quickstart
Add heapster to your Cargo.toml.
[]
= { = "0.X" }
Wrap your global allocator of choice (e.g., System) in your main.rs or lib.rs:
use Heapster;
use System;
static GLOBAL: = new;
Use Cases
1. Benchmarking and regression tests
Stop guessing if a PR increased allocations. heapster lets you measure the heap stats of critical sections of code.
let = GLOBAL.measure;
assert!;
2. Catching reallocation thrashing
When a Vec or String grows beyond its capacity, the underlying buffer may be moved to a new location, copying the contents. Heapster's realloc_move_count makes these moves visible so you can pre-size collections that thrash.
3. Always-On production metrics
Overhead is a small constant per allocation (typically tens of nanoseconds for the atomic operations), so Heapster can be left on in production. stats() exposes a Stats struct that's straightforward to wire into a Prometheus or other metrics endpoint, especially with the serde feature enabled.
Feature Guide
baseline(no default features): Tracks total allocation counts, sums, and breaks down reallocations by growth and shrinkage. This provides the fastest operational speed while granting solid insights into heap use.histograms: Generates power-of-two size distribution charts for allocations and reallocations. Disabling this saves the math required to compute the bucket offset and the atomic increment to the bucket array.use_curr: Tracks the current active heap usage by incrementing on allocations and decrementing on deallocations.use_max: Tracks the high-water mark of your heap usage. Disable this if you are experiencing thread-spinning under heavy load, as the underlyingfetch_maxCAS loop is the most expensive operation in the crate. (Note: Enabling this automatically enablesuse_curr).realloc_moves: Tracks instances where a reallocation forced a full memory copy. When disabled, you still see the net growth or shrinkage of the reallocation, but skip the extra bookkeeping for pointer moves.fmt/serde: Strictly formatting and serialization traits. These have zero impact on the hot path but will require thestdlibrary if enabled.full: Enables all the features.
Feature Flags vs Performance
While all metrics are tracked using pure, lock-free atomics, high-concurrency workloads can still suffer from cache line bouncing when multiple threads rapidly update the same shared counters.
To squeeze out maximum performance in extreme contention scenarios, you can opt out of the default features to drastically reduce the number of atomic memory operations (AMOs) executed per allocator call.
This table illustrates the maximum number of atomic operations executed on the "happy path" (successful allocations/deallocations).
| Feature Flag | alloc |
dealloc |
realloc (in-place) |
realloc (move) |
Performance Impact |
|---|---|---|---|---|---|
| baseline (no features) | 2 | 2 | 2 | 2 | Ultra-low overhead. Relies purely on fast fetch_add instructions for totals and counts. |
use_curr |
+1 | +1 | +1 | +1 | Low. Adds a fetch_add/fetch_sub pair to track the live size of the heap. |
use_max |
+1 | 0 | +1 (growth only) | +1 (growth only) | High under contention. Relies on fetch_max, which may compile to a Compare-And-Swap (CAS) loop on some hardware architectures. |
histograms |
+1 | 0 | +1 | +1 | Moderate. Adds one fetch_add to a calculated bucket array slot. |
realloc_moves |
0 | 0 | 0 | +2 | Moderate on memory moves. Adds two atomics only when the allocator is forced to move memory to satisfy a reallocation. |
Simple Histogram Output
The fmt feature provides Display impls that render stats and ASCII histograms.
alloc_count: 10,949,628
alloc_avg: 2.45 KiB
dealloc_count: 10,949,372
dealloc_avg: 4.09 KiB
realloc_growth_count: 365,968
realloc_growth_avg: 49.12 KiB
realloc_move_count: 351,933
realloc_move_avg: 7.21 KiB
use_curr: 260.39 KiB
use_max: 25.01 MiB
alloc_histogram:
[ 4 B .. 8 B): 2 █
[ 8 B .. 16 B): 642,064 ███████████
[ 16 B .. 32 B): 155 █
[ 32 B .. 64 B): 1,639,279 █████████████████████████████
[ 64 B .. 128 B): 1,926,643 ██████████████████████████████████
[ 128 B .. 256 B): 1,123,746 ████████████████████
[ 256 B .. 512 B): 1,284,154 ██████████████████████
[ 512 B .. 1 KiB): 2,246,658 ████████████████████████████████████████
[ 1 KiB .. 2 KiB): 1,283,935 ██████████████████████
[ 2 KiB .. 4 KiB): 160,612 ██
[ 4 KiB .. 8 KiB): 411 █
[ 8 KiB .. 16 KiB): 320,985 █████
[ 16 KiB .. 32 KiB): 1 █
[ 32 KiB .. 64 KiB): 1 █
[ 64 KiB .. 128 KiB): 320,982 █████
realloc_growth_histogram:
[ 1 B .. 2 B): 16 █
[ 2 B .. 4 B): 0
[ 4 B .. 8 B): 0
[ 8 B .. 16 B): 0
[ 16 B .. 32 B): 0
[ 32 B .. 64 B): 25,477 ███
[ 64 B .. 128 B): 14,976 █
[ 128 B .. 256 B): 4,411 █
[ 256 B .. 512 B): 106 █
[ 512 B .. 1 KiB): 0
[ 1 KiB .. 2 KiB): 0
[ 2 KiB .. 4 KiB): 0
[ 4 KiB .. 8 KiB): 0
[ 8 KiB .. 16 KiB): 0
[ 16 KiB .. 32 KiB): 0
[ 32 KiB .. 64 KiB): 320,982 ████████████████████████████████████████
License
Dual-licensed under either of:
- Creative Commons Zero v1.0 Universal (LICENSE-CC0)
- MIT License (LICENSE-MIT)
at your option.