Skip to content

fix(core): exclude the running node executable from callgrind - #102

Open
not-matthias wants to merge 1 commit into
mainfrom
fix/obj-skip-running-node-executable
Open

not-matthias wants to merge 1 commit into
mainfrom
fix/obj-skip-running-node-executable

Conversation

@not-matthias

Copy link
Copy Markdown
Member

In simulation mode, register the running Node.js executable (process.execPath) on callgrind's obj-skip list when setupCore runs.

The runner only passes --obj-skip for the node it resolves on PATH. When benchmarks run a different binary, for example one provided by a Bazel toolchain, that skip never matches and callgrind instruments the whole V8 runtime. The profile then holds thousands of native V8 functions between the JS frames: the flamegraph is drowned in runtime internals and becomes very expensive to process.

This uses the existing instrument_hooks_callgrind_add_obj_skip client request (instrument-hooks resolves the real path), the same mechanism pytest-codspeed uses to skip sys.executable. The call happens in setupCore, before any benchmark starts the instrumentation, so the skip applies to all code the benchmark runs. Outside Valgrind it is a no-op, so walltime and memory modes are unaffected.

Checked locally under valgrind-codspeed with a copy of node at a path that differs from the one passed to --obj-skip: without the call, callgrind recorded 2,655 functions of that executable (1.5 MB profile). With it, it recorded none (60 KB profile). I did not test vitest workers running under Bazel.

The runner only passes `--obj-skip` for the `node` it resolves on PATH.
When benchmarks run another binary, such as one provided by a Bazel
toolchain, callgrind records the whole V8 runtime between the JS frames:
thousands of native functions that drown the flamegraph and make it
expensive to process.

Register `process.execPath` on callgrind's obj-skip list in `setupCore`,
through the instrument-hooks client request, as pytest-codspeed does for
the Python executable. It runs before any benchmark starts the
instrumentation, and is a no-op outside Valgrind.
@not-matthias
not-matthias marked this pull request as ready for review October 22, 2026 14:37
@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches Callgrind profiling across Node benchmark integrations.

The PR appears safe to merge, with a non-blocking request for regression coverage.

Fix All in Claude CodeFindings

  1. P2 Alternate executable lacks test coverage ▶
Fix with agent prompt
### Issue 1
packages/core/src/index.ts:32
No automated test covers the alternate executable this fixes. Add a regression test that runs a copied Node binary under callgrind while `--obj-skip` names a different binary, then checks that the running binary contributes no native functions. The manual check would not catch a later change that removes this call or moves it after instrumentation starts.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR excludes the running Node executable from callgrind during setupCore.

  • Callgrind skips the Node.js executable that runs each benchmark.

Diagram

sequenceDiagram
    participant Core as setupCore
    participant Native as Native wrapper
    participant Hooks as instrument-hooks
    participant Callgrind
    Core->>Native: callgrindAddObjSkip(process.execPath)
    Native->>Hooks: instrument_hooks_callgrind_add_obj_skip(path)
    Hooks->>Hooks: Resolve real path
    Hooks->>Callgrind: Add object to skip list
    Note over Core,Callgrind: Runs before the benchmark starts instrumentation
Loading

Reviews (1) · Last reviewed commit: "fix(core): exclude the running node exec..." · Reviewed by Greptile

// The runner only obj-skips the `node` it finds on PATH. Skip the executable
// that actually runs, since a toolchain such as Bazel's brings its own: when
// left in, callgrind records the whole V8 runtime between the JS frames.
native_core.InstrumentHooks.callgrindAddObjSkip(process.execPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Alternate executable lacks test coverage

No automated test covers the alternate executable this fixes. Add a regression test that runs a copied Node binary under callgrind while --obj-skip names a different binary, then checks that the running binary contributes no native functions. The manual check would not catch a later change that removes this call or moves it after instrumentation starts.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/index.ts
Line: 32

Comment:
**Alternate executable lacks test coverage**

No automated test covers the alternate executable this fixes. Add a regression test that runs a copied Node binary under callgrind while `--obj-skip` names a different binary, then checks that the running binary contributes no native functions. The manual check would not catch a later change that removes this call or moves it after instrumentation starts.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit overkill. I'll test it manually before merging

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point—the regression test would need a copied Node binary and a callgrind setup, which is likely disproportionate for this narrow setup hook. Your manual check exercises the specific failure mode, so the automated-test suggestion isn’t necessary for this PR.

@codspeed

codspeed Bot commented Oct 22, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 6.72%

3 warnings, 1 note

⚠️ Walltime results may be unreliable

Some benchmarks used the Walltime instrument outside CodSpeed Macro Runners, which can make the results inconsistent.

Learn more about Macro Runners

See affected benchmarks

⚠️ 10 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

ℹ️ 26 benchmarks have no flamegraph

The flamegraph wasn't generated: most of the time was spent in runtime internals, which would leave nothing useful to analyze.

See affected benchmarks

⚡ 6 improved benchmarks
❌ 2 regressed benchmarks
✅ 194 untouched benchmarks
⏩ 118 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ CPU Simulation switch 1 245.7 µs 1,072.2 µs -77.09%
❌ Walltime switch 1 72 ns 84 ns -14.29%
⚡ Memory wait 1sec 21.5 KB 15.5 KB +38.87%
⚡ Walltime test sync baz 10 144 ns 120 ns +20%
⚡ CPU Simulation wait 500ms 47.1 ms 40 ms +17.99%
⚡ CPU Simulation wait 1sec 85.1 ms 72.9 ms +16.72%
⚡ Walltime test_recursive_cached_fibo_10 2.5 µs 2.2 µs +15.22%
⚡ Walltime short body 1.8 µs 1.6 µs +10.37%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/obj-skip-running-node-executable (a6af4e3) with main (d15b97d)

Open in CodSpeed

Footnotes

  1. 118 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant