Memory & GC

September 5, 2026 ยท View on GitHub

Metrics source: dsh.runtime.memory{area} is exposed by this plugin; dsh_v8js_memory_* / dsh_v8js_gc_* come from the community package @opentelemetry/instrumentation-runtime-node (dependency). Related: Event Loop Delay (GC is the top suspect for p99 long pauses) ยท README


1. Two-Layer Perspective

LayerMetricPrometheus NameQuestion Answered
Processdsh.runtime.memory{area}dsh_dsh_runtime_memory_bytesOS perspective: how much this process uses (RSS/heap/external)
V8dsh_v8js_memory_heap_space_size_bytes{v8js_heap_space_name}Same nameEngine perspective: per-space heap usage, key for leak localization
Pressuredsh_v8js_memory_heap_space_available_size_bytesSame nameAvailable allocation per space, OOM precursor signal
Costdsh_v8js_gc_duration_seconds_*HistogramGC frequency and pause, converts memory issues to latency cost

area / space Field Reference

Plugin {area}: rss (resident memory, includes external), heap_used / heap_total (V8 heap), external (Buffer and other non-heap), array_buffers.

V8 spaces to focus on: old_space (old generation, main battleground for leaks โ€” surviving objects accumulate here), new_space (young generation, frequent Scavenge collection), large_object_space (large objects >256KB Buffer/arrays go directly here).

2. Diagnosis Matrix

SignalThreshold ReferenceMeaning
RSS stableSmooth fluctuationHealthy
RSS step-increase without dropMonotonic increase over daysLeak or unbounded cache: cross-check heap_used โ€” synchronized growth = heap leak; heap flat but RSS growing = external/ArrayBuffer leak (Buffer not released)
heap_used / heap_totalSustained > 90%Heap pressure, increased GC stress
old_space availableApproaching 0OOM precursor (this community metric is the preferred heap pressure alert)
GC major frequencyNoticeably increasing / longer durationFrequent old-gen collection = leak or heap config too small
GC p95 pause> 50msStarting to consume latency budget; > 200ms directly creates event loop p99 spikes

3. Diagnosis Example

InstanceRSSheap_used/totalold_space UsageGC (1h major count / p95 pause)Assessment
dsh-agent1.73GB608MB / 722MB544MB~720 / 19.3msHeap stable, GC frequent but pauses small, healthy
dsh-agent-web22.22GB1.51GB / 1.55GB (98%)461MB, but large_object_space at 1.03GB19 / 9.4ms๐Ÿ”ด Two risk points (see below)

web2 two concerns:

  1. heap_used/total = 98%: Heap nearly full. GC pauses currently small (p95 9.4ms), but old-gen at 98% utilization means each major GC runs at the limit; further heap_total growth triggers "allocate-then-GC" death spiral
  2. large_object_space 1.03GB: Nearly 2/3 of heap is large objects (large Buffers/JSON strings/arrays going directly to large-object space). The correlation with event loop p99 โ‰ˆ3s is a key clue โ€” large object creation/serialization/copying occupies both heap and main thread, high probability that a single root cause explains both symptoms (Pyroscope heap profile can confirm)

4. Remediation Playbook

  1. Classify: RSS โ†‘ + heap_used โ†‘ = heap issue; RSS โ†‘ + heap flat = check external/array_buffers (Buffer not released / native modules)
  2. Locate: Grafana โ†’ Pyroscope โ†’ service_name=<instance> โ†’ Heap profile (production-ready, non-invasive) sorted by retained size to find holders
  3. Short-term mitigation: Restart target process to release heap (if GUI host, restarting kills active sessions โ€” requires maintenance window)
  4. Root fix: Large object chunked streaming, explicit caches with LRU limits, check global arrays for grow-only pattern
  5. Verify: old_space usage drops + GC major frequency drops + event loop p99 drops (all three must move in same direction to confirm fix)

5. Common Queries

Prometheus metric names may vary based on Collector transformation โ€” verify with actual deployment.

# Process memory overview (MB)
dsh_dsh_runtime_memory_bytes / 1024 / 1024

# Heap utilization ratio (approaching 1 = nearly full)
dsh_v8js_memory_heap_used_bytes / dsh_v8js_memory_heap_total_bytes

# old_space leak trend (6h linear extrapolation; slope > 0 sustained = leak signal)
deriv(dsh_v8js_memory_heap_space_size_bytes{v8js_heap_space_name="old_space"}[6h])

# GC pause p95 and frequency
histogram_quantile(0.95, sum by (le, exported_job) (rate(dsh_v8js_gc_duration_seconds_bucket[1h])))
sum by (exported_job, v8js_gc_type) (increase(dsh_v8js_gc_duration_seconds_count[1h]))

# External suspicious growth (check when RSS grows but heap doesn't)
dsh_dsh_runtime_memory_bytes{area=~"external|array_buffers"} / 1024 / 1024