V8 heap metrics exported for precise memory sizing

Per-worker Node.js memory metrics are now exported to OpenTelemetry, allowing operators to size V8 heap caps against actual usage rather than relying on overstated RSS values.
OpenTelemetry host metrics typically publish memory usage based solely on Resident Set Size (RSS), which bundles the V8 heap together with external and native memory. This overstates actual heap usage, making it difficult to accurately size V8 heap limits based on metrics alone.
Six new per-worker Node.js memory gauges are now exported to the webapp's OpenTelemetry meter. These track actual V8 heap usage, total reserved heap, configured heap limits, external memory, array buffers, and total RSS.
Memory settings can be sized based on observed heap peaks rather than total process footprint. By exposing the configured heap limit alongside current usage, capacity is visible in a single dashboard without cross-referencing environment variables. These metrics are added to the telemetry pipeline with zero overhead when disabled.
View Original GitHub Description
Summary
Adds direct V8 heap and process-memory gauges to the webapp's OpenTelemetry meter. The webapp already exports per-cluster-worker Node.js runtime metrics (event-loop lag / utilization, active handles, active requests, libuv threadpool size) via a custom meter under the trigger.dev scope. Heap and memory were missing; this PR adds them alongside, in the same observable-batch pattern.
New gauges
| Metric | Source | Unit |
|---|---|---|
nodejs.memory.heap.used | process.memoryUsage().heapUsed | bytes |
nodejs.memory.heap.total | process.memoryUsage().heapTotal | bytes |
nodejs.memory.heap.limit | v8.getHeapStatistics().heap_size_limit | bytes |
nodejs.memory.external | process.memoryUsage().external | bytes |
nodejs.memory.array_buffers | process.memoryUsage().arrayBuffers | bytes |
nodejs.memory.rss | process.memoryUsage().rss | bytes |
Gated by the existing INTERNAL_OTEL_NODEJS_METRICS_ENABLED flag, same as the adjacent event-loop / handle gauges. Zero overhead when disabled.
Why
@opentelemetry/host-metrics publishes process.memory.usage, which is RSS only. RSS is the sum of V8 heap, external memory (Buffers, etc.), native code, and thread stacks. Without a direct heap metric it is not possible to size the V8 heap cap (--max-old-space-size) from metrics alone, because RSS overstates heap by the external + native footprint. A worker can have a 4 GB RSS with a 2.5 GB heap and 1.5 GB of buffers; the former constrains --max-old-space-size, the latter does not.
nodejs.memory.heap.limit also surfaces the configured --max-old-space-size (read from v8.getHeapStatistics().heap_size_limit), so operators can see the current limit in the same dashboard as actual usage rather than cross-referencing container environment variables.
Risk
Minimal. Observable gauges are sampled at the configured metric-export interval. v8.getHeapStatistics() and process.memoryUsage() are each microsecond-level calls, and six gauges are added to the same batch callback that already reads ~20 other Node.js runtime values per sample. Same registration pattern as the existing event-loop metrics in the file.
Test plan
- Deploy and confirm the six new gauges appear at the configured exporter
- In cluster mode, confirm per-worker granularity (one series per cluster worker, tagged by
process.executable.name/service.instance.id) - Confirm
nodejs.memory.heap.limitreports the configured--max-old-space-sizevalue in bytes