# memory _(no submodules)_ ## Purpose `MemoryProfiler` captures periodic heap snapshots, records labelled allocation totals, tracks a peak-heap watermark, and runs a simple trend-based leak detector. Heap readings come from an injectable `HeapCapture` so test runs are deterministic and non-Node environments can plug their own probe. Timer source is `TimersLike`, matching `Sampler`. ## Features - Zod-validated config (`sampleIntervalMs > 0`, `maxSnapshots > 0`, `leakThresholdBytes ≥ 0`, `leakWindowSize ≥ 2`). - Injectable `Clock`, `TimersLike`, `HeapCapture`. Defaults read `process.memoryUsage` then `performance.memory` then return zeros. - `start()` / `stop()` are idempotent — double-start and double-stop are no-ops. - `takeSnapshot(label?)` records a snapshot; FIFO-evicts beyond `maxSnapshots`. - `recordAllocation(label, bytes)` aggregates `allocations` / `totalBytes` / `peakBytes` per label. - `detectLeak()` returns true when the last `leakWindowSize` snapshots are monotonically non-decreasing AND total growth meets `leakThresholdBytes`. - `triggerGC()` invokes `globalThis.gc()` when Node was started with `--expose-gc`. - `events: EventBus` — `memory:start`, `memory:stop`, `memory:snapshot`, `memory:leak-detected`. - Telemetry: `profiler_memory_snapshots_total` counter on every snapshot. ## Usage: Basic ```ts import { MemoryProfiler } from "@suaveplan/profiler"; const mp = new MemoryProfiler({ sampleIntervalMs: 1000 }); mp.start(); // ... mp.stop(); if (mp.detectLeak()) console.warn("leak suspected"); ``` ## Usage: Advanced ```ts import { MemoryProfiler } from "@suaveplan/profiler"; const fakeHeap: Array<{ heapUsed: number; heapTotal: number }> = [ { heapUsed: 1_000_000, heapTotal: 10_000_000 }, { heapUsed: 2_000_000, heapTotal: 10_000_000 }, { heapUsed: 3_000_000, heapTotal: 10_000_000 }, ]; let i = 0; const mp = new MemoryProfiler({ leakWindowSize: 3, leakThresholdBytes: 1_500_000, heapCapture: () => fakeHeap[i++] ?? fakeHeap[fakeHeap.length - 1]!, }); mp.events.on("memory:leak-detected", ({ snapshots }) => { log("leak window", snapshots); }); for (let n = 0; n < 3; n++) mp.takeSnapshot(); mp.detectLeak(); // true → emits memory:leak-detected ``` ## API Reference - `class MemoryProfiler` — `new MemoryProfiler(options?: MemoryProfilerOptions)`. - `interface MemoryProfilerOptions extends MemoryProfilerConfig { clock?, timers?, heapCapture? }`. - Methods: `start`, `stop`, `takeSnapshot`, `recordAllocation`, `getAllocations`, `getAllocation`, `getSnapshots`, `getPeakHeapUsed`, `isRunning`, `detectLeak`, `triggerGC`, `clear`. ## Implementation Notes The leak detector walks the last `leakWindowSize` snapshots and counts strictly-increasing transitions; a leak is declared only when EVERY transition is non-decreasing AND end-minus-start exceeds the threshold. That eliminates the false-positive case where one large jump in an otherwise flat window would otherwise read as a leak. `triggerGC` is a best-effort hook — Node only exposes `globalThis.gc` under `--expose-gc`, so the false branch matters in production.