# 📊 heapster

[![crates.io](https://img.shields.io/crates/v/heapster)](https://crates.io/crates/heapster)
[![docs.rs](https://docs.rs/heapster/badge.svg)](https://docs.rs/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_std` by default**: Uses only `core` and `alloc` in the default build, with no third-party dependencies. The `fmt` and `serde` features add `std` requirements.
- **Generic over any allocator**: Wraps `System`, jemalloc, mimalloc, or any custom `GlobalAlloc`.
- **Size histograms**: Power-of-two buckets for allocations and reallocations make the size distribution visible at a glance.
- **Realloc classification**: Distinguishes between reallocations that grew in-place, shrank in-place, or forced a full memory move (copying).
- **Snapshot diffing**: `measure()` returns a `Stats` delta 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`.

```toml
[dependencies]
heapster = { version = "0.X" }
```

Wrap your global allocator of choice (e.g., `System`) in your `main.rs` or `lib.rs`:

```rust
use heapster::Heapster;
use std::alloc::System;

#[global_allocator]
static GLOBAL: Heapster<System> = Heapster::new(System);

fn main() {
    // ... do some heavy work ...

    // See what has transpired in the heap
    println!("{}", GLOBAL.stats());
}
```

### 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.

```rust
let (result, heap_diff) = GLOBAL.measure(|| operation_to_measure());
assert!(heap_diff.alloc_count < 10, "Regression: The operation allocated too many times!");
```

**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 underlying `fetch_max` CAS loop is the most expensive operation in the crate. *(Note: Enabling this automatically enables `use_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 the `std` library 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.

```plaintext
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](LICENSE-CC0))
- MIT License ([LICENSE-MIT](LICENSE-MIT))

at your option.
