@lensmcp/memory-tracker
v1.16.33
Published
LensMCP container/memory mutation tracking (Map/Set/Array instrumentation).
Maintainers
Readme
@lensmcp/memory-tracker
Track memory growth in long-lived JavaScript containers, attribute it to the flow that caused it, and surface suspected leaks as LensMCP events.
@lensmcp/memory-tracker instruments the mutating methods of Map, Set, and Array instances in place. Every mutation updates a per-container "owner" record (item count plus a rough byte estimate) and is attributed to the active request/flow. When a flow grows a container past a threshold and the items do not shrink back, the tracker emits a memory-retention ("memory leak suspected") event. It also reports stale generations of disposed singletons that still retain bytes. The result is a deterministic, attributable memory signal in the LensMCP lens — instead of a flat heap graph, you see which owner grew, by how much, and under which flow.
It is designed to run in "light mode" inside the LensMCP node/NestJS instrumentation: zero sampling timers in the hot path, bounded per-mutation work, and a pluggable event sink so the host process forwards events to the LensMCP bus.
Install
yarn add @lensmcp/memory-trackerMost users do not install this directly. It is normally enabled through the memory option of @lensmcp/nest-instrumentation (or the node instrumentation), which calls configureMemoryTracker and wires the flow brackets for you. Reach for the direct API only when instrumenting a runtime that LensMCP does not auto-wire.
Usage
Configure the tracker once at startup, track the containers you care about, then bracket each unit of work so growth can be attributed and leaks detected.
import {
configureMemoryTracker,
trackContainer,
onFlowStart,
onFlowSettled,
} from '@lensmcp/memory-tracker';
// 1. Configure once. `sink` receives every memory BaseEvent; the host
// forwards it to the LensMCP bus. `contextGetter` lets each mutation
// attribute itself to the active flow (e.g. an AsyncLocalStorage store).
configureMemoryTracker({
sessionId: 'session-123',
sink: (event) => bus.publish(event),
leakItemThreshold: 50, // items of growth that flips a suspected leak (default 50)
staleGenerationBytesThreshold: 1024 * 1024, // bytes a disposed singleton may retain (default 1 MB)
settleMs: 1000, // grace period after a flow ends (default 1000)
contextGetter: () => currentLensmcpContext(), // optional: returns { sessionId, flowId?, requestId?, originNodeId? }
});
// 2. Track a container. Patching is in-place, non-destructive, and
// idempotent (re-tracking the same instance is a no-op).
class SessionCache {
private readonly entries = new Map<string, Session>();
constructor(instanceId: string) {
trackContainer({
ownerInstanceId: instanceId, // identifies the owning instance
fieldName: 'entries', // the field being tracked
container: this.entries, // the Map / Set / Array to instrument
});
}
}
// 3. Bracket a flow. Mutations between start and settle are attributed to
// this flow; owners that grew past the threshold and stayed resident are
// flagged and emitted as `memory-retention` events.
onFlowStart('req-42');
// ... handle request: cache.entries.set(...) etc.
const flaggedOwnerIds = onFlowSettled('req-42'); // string[] of suspected-leak ownerstrackContainer only patches Map, Set, and Array instances — anything else is ignored. Tracked mutations are: set / delete / clear (Map), add / delete / clear (Set), and push / splice (Array).
API
configureMemoryTracker(opts)— Set the active session id, the eventsink, the optionalcontextGetter, and detector thresholds (leakItemThreshold,staleGenerationBytesThreshold,settleMs). Returns the resolved options. Must be called before events are emitted.trackContainer({ ownerInstanceId, fieldName, container })— Instrument aMap/Set/Arrayin place so each mutation updates its owner record and emits amemory-mutationevent. Idempotent and non-destructive.onFlowStart(flowId)— Snapshot every owner's item count at the start of a flow.onFlowSettled(flowId)— Compare each owner's attributed growth againstleakItemThreshold; emitmemory-retentionfor owners that grew and stayed resident. Returns the flagged owner ids.checkStaleGeneration({ logicalId, instanceId, generation, retainedBytes })— On singleton disposal, emitsingleton-stale-generationwhen retained bytes exceedstaleGenerationBytesThreshold. Returns whether it flagged.allOwners()/getOwner(ownerId)/ownerKey(ownerInstanceId, fieldName)— Inspect the owner registry: list owner snapshots, fetch one record, or compute an owner key (<ownerInstanceId>::<fieldName>).emitMutation/emitRetention/emitStaleGeneration— Low-level emit helpers that build each memoryBaseEventand push it to the sink (used internally; exported for advanced hosts).activeMemoryOptions()/currentMemoryContext()/resetMemoryTracker()— Read the resolved options, read the current flow context, or reset tracker state (test hook).markFlowStart/recordFlowDelta/flowGrowth/ensureOwner/resetOwners— Lower-level owner/flow primitives behind the detectors.- Types —
MemoryEventSink,MemoryContext,ResolvedMemoryOptions,MemoryOwnerSnapshot,MemoryContainerKind,MemoryMutationOperation, and theTrackContainerArgsinput.
How it fits
@lensmcp/memory-tracker is enabled by @lensmcp/nest-instrumentation via its memory option (the node instrumentation does the same). The host supplies the sessionId, the event sink, and a contextGetter bound to its own request context, and brackets each request with onFlowStart / onFlowSettled.
Every event is a @lensmcp/protocol-types BaseEvent with source and category of memory, carrying one of three payload kinds:
memory-mutation—debugseverity; per-mutation before/after counts and an estimated byte delta.memory-retention—warningseverity; the "memory leak suspected" signal (owner id, flow, growth) surfaced in the lens.singleton-stale-generation—warningseverity; a disposed singleton generation still retaining bytes.
These events flow through the host's sink to the LensMCP bus, where the lens renders them in the "memory leak suspected" channel.
Part of LensMCP. Apache-2.0.
