Skip to content

Commit 727b808

Browse files
committed
fix(core): exclude the running node executable from callgrind
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.
1 parent 5e52a1d commit 727b808

5 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎packages/core/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const setupCore = () => {
2626
}
2727

2828
native_core.InstrumentHooks.setIntegration("codspeed-node", __VERSION__);
29+
// The `node` on the runner's PATH may not be the one executing (e.g. Bazel), so skip the real one.
30+
native_core.InstrumentHooks.callgrindAddObjSkip(process.execPath);
2931
// In walltime, we use node's native profiling options rather than our own, cf `getV8Flags`
3032
if (getCodspeedRunnerMode() !== "walltime") {
3133
linuxPerf.start();

‎packages/core/src/native_core/index.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ try {
6767
addMarker: (_pid: number, _markerType: number, _timestamp: bigint) => {
6868
return 0;
6969
},
70+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
71+
callgrindAddObjSkip: (_path: string) => {
72+
return 0;
73+
},
7074
__codspeed_root_frame__: <T>(callback: () => T): T => {
7175
return callback();
7276
},

‎packages/core/src/native_core/instruments/hooks.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ export interface InstrumentHooks {
6363
*/
6464
addMarker(pid: number, markerType: number, timestamp: bigint): number;
6565

66+
/**
67+
* Add an object file to callgrind's obj-skip list, as `--obj-skip` does.
68+
* Only affects code callgrind has not instrumented yet. No-op outside
69+
* Valgrind.
70+
* @param path Path of the object file, resolved to its real path
71+
* @returns 0 on success, non-zero on error
72+
*/
73+
callgrindAddObjSkip(path: string): number;
74+
6675
/**
6776
* Execute a callback function with __codspeed_root_frame__ in its stack trace
6877
* @param callback Function to execute

‎packages/core/src/native_core/instruments/hooks_wrapper.cc‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ Napi::BigInt CurrentTimestamp(const Napi::CallbackInfo &info) {
135135
return Napi::BigInt::New(env, ts);
136136
}
137137

138+
Napi::Number CallgrindAddObjSkip(const Napi::CallbackInfo &info) {
139+
Napi::Env env = info.Env();
140+
141+
if (info.Length() != 1) {
142+
Napi::TypeError::New(env, "Expected 1 argument: path")
143+
.ThrowAsJavaScriptException();
144+
return Napi::Number::New(env, 1);
145+
}
146+
147+
if (!info[0].IsString()) {
148+
Napi::TypeError::New(env, "Expected string (path)")
149+
.ThrowAsJavaScriptException();
150+
return Napi::Number::New(env, 1);
151+
}
152+
153+
std::string path = info[0].As<Napi::String>().Utf8Value();
154+
155+
uint8_t result = instrument_hooks_callgrind_add_obj_skip(path.c_str());
156+
return Napi::Number::New(env, result);
157+
}
158+
138159
Napi::Number AddMarker(const Napi::CallbackInfo &info) {
139160
Napi::Env env = info.Env();
140161

@@ -220,6 +241,8 @@ Napi::Object Initialize(Napi::Env env, Napi::Object exports) {
220241
Napi::Function::New(env, CurrentTimestamp));
221242
instrumentHooksObj.Set(Napi::String::New(env, "addMarker"),
222243
Napi::Function::New(env, AddMarker));
244+
instrumentHooksObj.Set(Napi::String::New(env, "callgrindAddObjSkip"),
245+
Napi::Function::New(env, CallgrindAddObjSkip));
223246
instrumentHooksObj.Set(Napi::String::New(env, "__codspeed_root_frame__"),
224247
Napi::Function::New(env, __codspeed_root_frame__));
225248

‎packages/core/src/native_core/instruments/hooks_wrapper.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Napi::Number SetEnvironment(const Napi::CallbackInfo &info);
2121
Napi::Number WriteEnvironment(const Napi::CallbackInfo &info);
2222
Napi::BigInt CurrentTimestamp(const Napi::CallbackInfo &info);
2323
Napi::Number AddMarker(const Napi::CallbackInfo &info);
24+
Napi::Number CallgrindAddObjSkip(const Napi::CallbackInfo &info);
2425
Napi::Object Initialize(Napi::Env env, Napi::Object exports);
2526

2627
} // namespace hooks_wrapper

0 commit comments

Comments
 (0)