Skip to content

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

Merged
not-matthias merged 1 commit into
mainfrom
fix/obj-skip-running-node-executable
Oct 28, 2026
Merged

not-matthias merged 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.

@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

Comment thread packages/core/src/index.ts
@codspeed

codspeed Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 12.74%

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

⚠️ 14 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

ℹ️ 16 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

⚡ 7 improved benchmarks
❌ 4 regressed benchmarks
✅ 253 untouched benchmarks
⏩ 56 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.6 µs 597.9 µs -58.92%
❌ CPU Simulation recursive fibo 10 338.6 µs 692.6 µs -51.12%
❌ CPU Simulation recursive fibo 10 338.7 µs 688.9 µs -50.83%
❌ Memory wait 1sec 22 KB 28.1 KB -21.44%
⚡ Memory wait 500ms 28.2 KB 22.7 KB +24.27%
⚡ CPU Simulation wait 500ms 46.1 ms 38.7 ms +19.21%
⚡ CPU Simulation wait 500ms 45.3 ms 38.1 ms +18.84%
⚡ CPU Simulation wait 1sec 84.8 ms 72.9 ms +16.27%
⚡ CPU Simulation wait 1sec 83.2 ms 72.9 ms +14.13%
⚡ Walltime fibo darwin 9.2 ms 8.2 ms +12%
⚡ Walltime test_iterative_fibo_10 132 ns 120 ns +10%

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 (727b808) with main (5e52a1d)

Open in CodSpeed

Footnotes

  1. 56 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. ↩

@GuillaumeLagrange GuillaumeLagrange left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm BUT i'm a bit skeptical about the introspected node thing if this is actually a real thing 🤔

Comment thread packages/core/src/index.ts Outdated
@not-matthias
not-matthias force-pushed the fix/obj-skip-running-node-executable branch from a6af4e3 to 10793c6 Compare October 28, 2026 10:01
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 force-pushed the fix/obj-skip-running-node-executable branch from 10793c6 to 727b808 Compare October 28, 2026 16:40
@not-matthias
not-matthias merged commit 727b808 into main Oct 28, 2026
47 of 48 checks passed
@not-matthias
not-matthias deleted the fix/obj-skip-running-node-executable branch October 28, 2026 16:49
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.

2 participants