feat(runtime-metrics): sample event loop delay per iteration on Node 26.5+#9273
Conversation
Overall package sizeSelf size: 6.71 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.1 | 122.62 kB | 438.86 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 9c89df5 | Docs | Datadog PR Page | Give us feedback! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9273 +/- ##
========================================
Coverage 96.56% 96.57%
========================================
Files 918 918
Lines 121521 121570 +49
Branches 21522 21186 -336
========================================
+ Hits 117347 117402 +55
+ Misses 4174 4168 -6 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
BenchmarksBenchmark execution time: 2026-07-13 18:36:35 Comparing candidate commit 9c89df5 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2318 metrics, 40 unstable metrics.
|
…26.5+
Node 26.5.0 adds monitorEventLoopDelay({ samplePerIteration }), which samples
the event loop delay once per iteration via libuv prepare/check hooks. It is
strictly more accurate than the interval-timer mode and lets us collect
CPU/GC/heap metrics entirely from JS APIs, so on 26.5+ we skip the
@datadog/native-metrics addon for event loop tracking altogether.
- Gate on the runtime version (NODE_MAJOR/NODE_MINOR): older Node silently
ignores the option, so feature detection is not possible.
- Reset the observer with reset() instead of recreating it each flush; this
preserves the samplePerIteration config (recreating dropped it) and avoids a
per-flush allocation.
- Drop the resolution offset from the delay stats in per-iteration mode.
- Add a version-gated test that exercises the real API on Node >= 26.5.0 and
proves the native addon is never started, plus a faked-version test that
covers the gating logic on every runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
475a51b to
a8b238a
Compare
BridgeAR
left a comment
There was a problem hiding this comment.
LGTM with two minor suggestions
| const avg = Math.max(eventLoopDelayObserver.mean - minimum, 0) | ||
| const sum = Math.round(avg * eventLoopDelayObserver.count) | ||
|
|
||
| if (sum !== 0) { | ||
| // Normalize the metrics to the same format as the native metrics. | ||
| const stats = { | ||
| min: Math.max(eventLoopDelayObserver.min - minimum, 0), | ||
| max: Math.max(eventLoopDelayObserver.max - minimum, 0), | ||
| sum, | ||
| total: sum, | ||
| avg, | ||
| count: eventLoopDelayObserver.count, | ||
| p95: Math.max(eventLoopDelayObserver.percentile(95) - minimum, 0), | ||
| } |
There was a problem hiding this comment.
I believe a couple of steps could be skipped in case EVENT_LOOP_SAMPLE_PER_ITERATION_AVAILABLE is available. That way there is less overhead :)
There was a problem hiding this comment.
Yes, I did try but benchmarks showed a very minimal improvement (still an improvement though) hence I left the changes as simple as possible.
There was a problem hiding this comment.
Understandable (we do not call these frequent enough to show as actual overhead)
…26.5+ (#9273) Node 26.5.0 adds monitorEventLoopDelay({ samplePerIteration }), which samples the event loop delay once per iteration via libuv prepare/check hooks. It is strictly more accurate than the interval-timer mode and lets us collect CPU/GC/heap metrics entirely from JS APIs, so on 26.5+ we skip the @datadog/native-metrics addon for event loop tracking altogether. - Gate on the runtime version (NODE_MAJOR/NODE_MINOR): older Node silently ignores the option, so feature detection is not possible. - Reset the observer with reset() instead of recreating it each flush; this preserves the samplePerIteration config (recreating dropped it) and avoids a per-flush allocation. - Drop the resolution offset from the delay stats in per-iteration mode. - Add a version-gated test that exercises the real API on Node >= 26.5.0 and proves the native addon is never started, plus a faked-version test that covers the gating logic on every runtime.
…26.5+ (#9273) Node 26.5.0 adds monitorEventLoopDelay({ samplePerIteration }), which samples the event loop delay once per iteration via libuv prepare/check hooks. It is strictly more accurate than the interval-timer mode and lets us collect CPU/GC/heap metrics entirely from JS APIs, so on 26.5+ we skip the @datadog/native-metrics addon for event loop tracking altogether. - Gate on the runtime version (NODE_MAJOR/NODE_MINOR): older Node silently ignores the option, so feature detection is not possible. - Reset the observer with reset() instead of recreating it each flush; this preserves the samplePerIteration config (recreating dropped it) and avoids a per-flush allocation. - Drop the resolution offset from the delay stats in per-iteration mode. - Add a version-gated test that exercises the real API on Node >= 26.5.0 and proves the native addon is never started, plus a faked-version test that covers the gating logic on every runtime.
What does this PR do?
Uses Node 26.5.0's
monitorEventLoopDelay({ samplePerIteration })to sample the event loop delay once per iteration (via libuv prepare/check hooks). On runtimes that support it, we now skip the@datadog/native-metricsaddon for event loop tracking entirely and collect CPU/GC/heap metrics from JS APIs.NODE_MAJOR/NODE_MINOR) — older Node silently ignores the option, so feature detection isn't possible.reset()instead of recreating it each flush. This preserves thesamplePerIterationconfiguration (recreating dropped it after the first flush) and avoids a per-flush allocation.Motivation
The per-iteration sampler is strictly more accurate than the interval-timer mode, and lets us drop the native addon dependency for event loop tracking on newer runtimes.
Additional Notes
master; will rebase before merge.samplePerIterationactually shipped in Node v26.5.0 — the version gate depends on it.🤖 Generated with Claude Code