first commit
This commit is contained in:
66
docs/devtools/tooling/profiler/src/memory/memory.md
Normal file
66
docs/devtools/tooling/profiler/src/memory/memory.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# memory
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<MemoryProfilerEventMap>` — `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.
|
||||
Reference in New Issue
Block a user