feat(nodejs): make Node.js event loop tracing opt-in, default off - #331
Merged
Merged
Conversation
Attaching the libuv probes reads the entire ELF symbol table of every Node.js process's binary, per pid. node is a large statically linked binary with V8 embedded, so this is expensive: a customer CPU profile attributed 17.5% of a core to instrumentNodejs -> GetSymbol -> readSymbols. What it buys is a single metric, container_nodejs_event_loop_blocked_time_seconds_total. An org-wide code search returns exactly one hit, the metric's own definition — no dashboard, alert, query or application code consumes it. It is collected and stored (24 series on dev) but never read. ENABLE_NODEJS_TRACING defaults to false, matching ENABLE_DOTNET_TRACING rather than the always-on Python probes. Blast radius is limited to that metric. NodejsStats carries only EventLoopBlockedTime, and the probes attach nothing but uv_io_poll_* and uv_io_cb_*. TLS interception for Node.js processes is unaffected — tls.go has no Node-specific handling and works through the libssl/gotls paths — as is L7 tracing, which reads the syscalls rather than libuv. Checked before nodejsChecked so that turning the flag on and restarting instruments processes that were skipped while it was off. Note this does not remove the symbol-table cost generally: python.go and tls.go take the same uncached per-pid path and remain always-on.
There was a problem hiding this comment.
Code Review
This pull request introduces a new configuration flag, EnableNodejsTracing, which is disabled by default, to control Node.js event loop tracing. It also updates the instrumentNodejs function in containers/process.go to respect this flag, ensuring that Node.js processes are only instrumented when the flag is enabled. I have no feedback to provide.
RamanKharchee
approved these changes
Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Attaching the libuv probes reads the entire ELF symbol table of every Node.js process's binary, per pid.
nodeis a large statically linked binary with V8 embedded, so this is expensive — a customer CPU profile attributed 17.5% of a core to it:What it buys is a single metric:
container_nodejs_event_loop_blocked_time_seconds_total.An org-wide code search returns exactly one hit — the metric's own definition. No dashboard, alert, query, or application code consumes it. It is collected and stored (24 series on our dev cluster) but never read.
Given no current use case, the cost isn't justified by default.
Change
ENABLE_NODEJS_TRACING, defaultfalse. Opt-in matchesENABLE_DOTNET_TRACINGrather than the always-on Python probes.Gated before
nodejsChecked, so enabling the flag and restarting instruments processes that were skipped while it was off.Blast radius
Verified, not assumed — limited to that one metric:
NodejsStatscarries onlyEventLoopBlockedTimeuv_io_poll_enter/exitanduv_io_cb_enter/exittls.gohas no Node-specific handling and works via the libssl/gotls pathsTwo things reviewers should weigh:
This does not fix the symbol-table cost generally
python.goandtls.gotake the same uncached per-pid full-table path and remain always-on:This PR removes the Node.js share, which happened to dominate that particular customer's profile. #330 caches the lookups by binary identity and is the general fix — the two are complementary, and #330 still matters for the paths that stay always-on (and for anyone who turns Node.js tracing back on).
Testing
No behavioural test added — the change is a flag guard on a code path that requires live Node.js processes and eBPF attachment to exercise meaningfully.