-
Notifications
You must be signed in to change notification settings - Fork 6
Manage valgrind instrumentation with profiling cli #209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
GuillaumeLagrange
merged 8 commits into
main
from
cod-1739-manage-valgrind-instrumentation-with-profiling-cli
Jan 23, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1bf9f29
feat(exec-harness): print command under test before running it
GuillaumeLagrange 1ee2e06
feat(exec-harness): add a codspeed_preload library for valgrind instr…
GuillaumeLagrange 0b8c217
feat(exec-harness): embed the preload library in the binary
GuillaumeLagrange 4bee3df
refactor: move the preload_file management to its own module
GuillaumeLagrange dd2113d
feat(exec-harness): bail when used with a statically linked binary
GuillaumeLagrange e29c367
feat: clean-up the build script
GuillaumeLagrange bb33f05
chore: change arm runner
GuillaumeLagrange a90b8ff
chore: build exec-harness in gnu for release
GuillaumeLagrange File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| /target | ||
| .DS_Store | ||
| .codspeed | ||
| compile_commands.json | ||
| .cache |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| //! Build script for exec-harness | ||
| //! | ||
| //! This script compiles the `libcodspeed_preload.so` shared library that is used | ||
| //! to inject instrumentation into child processes via LD_PRELOAD. | ||
| //! | ||
| //! The library is built using the `core.c` and headers from the `codspeed` crate's | ||
| //! `instrument-hooks` directory. | ||
| //! | ||
| //! # Environment Variables | ||
| //! | ||
| //! - `CODSPEED_INSTRUMENT_HOOKS_DIR`: Optional override for the instrument-hooks | ||
| //! source directory. If not set, the build script will locate it from the | ||
| //! `codspeed` crate in the cargo registry. | ||
|
|
||
| use cargo_metadata::MetadataCommand; | ||
| use std::env; | ||
| use std::path::PathBuf; | ||
|
|
||
| /// Shared constants for the preload library. | ||
| /// These are passed as C defines during compilation and exported as environment | ||
| /// variables for the Rust code to use via `env!()`. | ||
| struct PreloadConstants { | ||
| /// Environment variable name for the benchmark URI. | ||
| uri_env: &'static str, | ||
| /// Integration name reported to CodSpeed. | ||
| integration_name: &'static str, | ||
| /// Integration version reported to CodSpeed. | ||
| /// This should match the version of the `codspeed` crate dependency. | ||
| integration_version: &'static str, | ||
| /// Filename for the preload shared library. | ||
| preload_lib_filename: &'static str, | ||
| } | ||
|
|
||
| fn main() { | ||
| println!("cargo:rerun-if-changed=preload/codspeed_preload.c"); | ||
| println!("cargo:rerun-if-env-changed=CODSPEED_INSTRUMENT_HOOKS_DIR"); | ||
|
|
||
| let preload_constants: PreloadConstants = PreloadConstants::default(); | ||
|
|
||
| // Export constants as environment variables for the Rust code | ||
| println!( | ||
| "cargo:rustc-env=CODSPEED_URI_ENV={}", | ||
| preload_constants.uri_env | ||
| ); | ||
| println!( | ||
| "cargo:rustc-env=CODSPEED_INTEGRATION_NAME={}", | ||
| preload_constants.integration_name | ||
| ); | ||
| println!( | ||
| "cargo:rustc-env=CODSPEED_INTEGRATION_VERSION={}", | ||
| preload_constants.integration_version | ||
| ); | ||
| println!( | ||
| "cargo:rustc-env=CODSPEED_PRELOAD_LIB_FILENAME={}", | ||
| preload_constants.preload_lib_filename | ||
| ); | ||
|
|
||
| let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); | ||
|
|
||
| // Try to get the instrument-hooks directory from the environment variable first, | ||
| // otherwise locate it from the codspeed crate | ||
| let instrument_hooks_dir = match env::var("CODSPEED_INSTRUMENT_HOOKS_DIR") { | ||
| Ok(dir) => PathBuf::from(dir), | ||
| Err(_) => find_codspeed_instrument_hooks_dir(), | ||
| }; | ||
|
|
||
| // Build the preload shared library | ||
| let paths = PreloadBuildPaths { | ||
| preload_c: manifest_dir.join("preload/codspeed_preload.c"), | ||
| core_c: instrument_hooks_dir.join("dist/core.c"), | ||
| includes_dir: instrument_hooks_dir.join("includes"), | ||
| }; | ||
| paths.check_sources_exist(); | ||
| build_shared_library(&paths, &preload_constants); | ||
| } | ||
|
|
||
| /// Build the shared library using the cc crate | ||
| fn build_shared_library(paths: &PreloadBuildPaths, constants: &PreloadConstants) { | ||
| let uri_env_val = format!("\"{}\"", constants.uri_env); | ||
| let integration_name_val = format!("\"{}\"", constants.integration_name); | ||
| let integration_version_val = format!("\"{}\"", constants.integration_version); | ||
| let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); | ||
| let out_file = out_dir.join(constants.preload_lib_filename); | ||
|
|
||
| let mut build = cc::Build::new(); | ||
| build | ||
| .file(&paths.preload_c) | ||
| .file(&paths.core_c) | ||
| .include(&paths.includes_dir) | ||
| .pic(true) | ||
| .opt_level(3) | ||
| // There's no need to output cargo metadata as we are just building a shared library | ||
| // that will be copied to disk and loaded through LD_PRELOAD at runtime | ||
| .cargo_metadata(false) | ||
| // Pass constants as C defines | ||
| .define("CODSPEED_URI_ENV", uri_env_val.as_str()) | ||
| .define("CODSPEED_INTEGRATION_NAME", integration_name_val.as_str()) | ||
| .define( | ||
| "CODSPEED_INTEGRATION_VERSION", | ||
| integration_version_val.as_str(), | ||
| ) | ||
| // Suppress warnings from generated Zig code | ||
| .flag("-Wno-format") | ||
| .flag("-Wno-format-security") | ||
| .flag("-Wno-unused-but-set-variable") | ||
| .flag("-Wno-unused-const-variable") | ||
| .flag("-Wno-type-limits") | ||
| .flag("-Wno-uninitialized") | ||
GuillaumeLagrange marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .flag("-Wno-overflow") | ||
| .flag("-Wno-unused-function"); | ||
|
|
||
| // Compile source files to object files | ||
| let objects = build.compile_intermediates(); | ||
|
|
||
| // Link object files into shared library | ||
| let compiler = build.get_compiler(); | ||
| let mut link_cmd = compiler.to_command(); | ||
| link_cmd | ||
| .arg("-shared") | ||
| .arg("-o") | ||
| .arg(&out_file) | ||
| .args(&objects) | ||
| .arg("-lpthread"); | ||
|
|
||
| let status = link_cmd.status().expect("Failed to run linker"); | ||
| if !status.success() { | ||
| panic!("Failed to link libcodspeed_preload.so"); | ||
| } | ||
| } | ||
|
|
||
| /// Find the instrument-hooks directory from the codspeed crate using cargo_metadata | ||
| fn find_codspeed_instrument_hooks_dir() -> PathBuf { | ||
GuillaumeLagrange marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let metadata = MetadataCommand::new() | ||
| .exec() | ||
| .expect("Failed to run cargo metadata"); | ||
|
|
||
| // Find the codspeed package in the resolved dependencies | ||
| let codspeed_pkg = metadata | ||
| .packages | ||
| .iter() | ||
| .find(|p| p.name == "codspeed") | ||
| .expect("codspeed crate not found in dependencies"); | ||
|
|
||
| let codspeed_dir = codspeed_pkg | ||
| .manifest_path | ||
| .parent() | ||
| .expect("Failed to get codspeed crate directory"); | ||
|
|
||
| let instrument_hooks_dir = codspeed_dir.join("instrument-hooks"); | ||
|
|
||
| if !instrument_hooks_dir.exists() { | ||
| panic!("instrument-hooks directory not found at {instrument_hooks_dir}"); | ||
| } | ||
|
|
||
| instrument_hooks_dir.into_std_path_buf() | ||
| } | ||
|
|
||
| impl Default for PreloadConstants { | ||
| // TODO(COD-1736): Stop impersonating codspeed-rust 🥸 | ||
| fn default() -> Self { | ||
| Self { | ||
| uri_env: "CODSPEED_BENCH_URI", | ||
| integration_name: "codspeed-rust", | ||
| integration_version: "4.2.0", | ||
| preload_lib_filename: "libcodspeed_preload.so", | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Paths required to build the preload shared library. | ||
| struct PreloadBuildPaths { | ||
| /// Path to the preload C source file (codspeed_preload.c). | ||
| preload_c: PathBuf, | ||
| /// Path to the core C source file from instrument-hooks. | ||
| core_c: PathBuf, | ||
| /// Path to the includes directory from instrument-hooks. | ||
| includes_dir: PathBuf, | ||
| } | ||
|
|
||
| impl PreloadBuildPaths { | ||
GuillaumeLagrange marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// Verify that all required source files and directories exist. | ||
| /// Panics with a descriptive message if any path is missing. | ||
| fn check_sources_exist(&self) { | ||
| if !self.core_c.exists() { | ||
| panic!( | ||
| "core.c not found at {}. Make sure the codspeed crate is available.", | ||
| self.core_c.display() | ||
| ); | ||
| } | ||
| if !self.includes_dir.exists() { | ||
| panic!( | ||
| "includes directory not found at {}. Make sure the codspeed crate is available.", | ||
| self.includes_dir.display() | ||
| ); | ||
| } | ||
| if !self.preload_c.exists() { | ||
| panic!( | ||
| "codspeed_preload.c not found at {}", | ||
| self.preload_c.display() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // LD_PRELOAD library for enabling Valgrind instrumentation in child processes | ||
| // | ||
| // This library is loaded via LD_PRELOAD into benchmark processes spawned by | ||
| // exec-harness. It enables callgrind instrumentation on load and disables it on | ||
| // exit, allowing exec-harness to measure arbitrary commands without requiring | ||
| // them to link against instrument-hooks. | ||
| // | ||
| // Environment variables: | ||
| // CODSPEED_BENCH_URI - The benchmark URI to report (required) | ||
| // CODSPEED_PRELOAD_LOCK - Set by the first process to prevent child processes | ||
| // from re-initializing instrumentation | ||
|
|
||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include "core.h" | ||
|
|
||
| #ifndef RUNNING_ON_VALGRIND | ||
| // If somehow the core.h did not include the valgrind header, something is | ||
| // wrong, but still have a fallback | ||
| #warning "RUNNING_ON_VALGRIND not defined, headers may be missing" | ||
| #define RUNNING_ON_VALGRIND 0 | ||
| #endif | ||
|
|
||
| static const char *LOCK_ENV = "CODSPEED_PRELOAD_LOCK"; | ||
|
|
||
| // These constants are defined by the build script (build.rs) via -D flags | ||
| #ifndef CODSPEED_URI_ENV | ||
| #error "CODSPEED_URI_ENV must be defined by the build system" | ||
| #endif | ||
not-matthias marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #ifndef CODSPEED_INTEGRATION_NAME | ||
| #error "CODSPEED_INTEGRATION_NAME must be defined by the build system" | ||
| #endif | ||
| #ifndef CODSPEED_INTEGRATION_VERSION | ||
| #error "CODSPEED_INTEGRATION_VERSION must be defined by the build system" | ||
| #endif | ||
|
|
||
| static const char *URI_ENV = CODSPEED_URI_ENV; | ||
| static const char *INTEGRATION_NAME = CODSPEED_INTEGRATION_NAME; | ||
| static const char *INTEGRATION_VERSION = CODSPEED_INTEGRATION_VERSION; | ||
|
|
||
| static InstrumentHooks *g_hooks = NULL; | ||
| static const char *g_bench_uri = NULL; | ||
|
|
||
| __attribute__((constructor)) static void codspeed_preload_init(void) { | ||
| // Skip initialization if not running under Valgrind yet. | ||
| // When using LD_PRELOAD with Valgrind, the constructor runs twice: | ||
| // once before Valgrind takes over, and once after. We only want to | ||
| // initialize when Valgrind is active. | ||
| // | ||
| // This is purely empirical, and is not (yet) backed up by documented | ||
| // behavior. | ||
| if (!RUNNING_ON_VALGRIND) { | ||
| return; | ||
| } | ||
|
|
||
| // Check if another process already owns the instrumentation | ||
| if (getenv(LOCK_ENV)) { | ||
| return; | ||
| } | ||
|
|
||
| // Set the lock to prevent child processes from initializing | ||
| setenv(LOCK_ENV, "1", 1); | ||
|
|
||
| g_bench_uri = getenv(URI_ENV); | ||
| if (!g_bench_uri) { | ||
| return; | ||
| } | ||
|
|
||
| g_hooks = instrument_hooks_init(); | ||
| if (!g_hooks) { | ||
| return; | ||
| } | ||
|
|
||
| instrument_hooks_set_integration(g_hooks, INTEGRATION_NAME, | ||
| INTEGRATION_VERSION); | ||
|
|
||
| if (instrument_hooks_start_benchmark_inline(g_hooks) != 0) { | ||
| instrument_hooks_deinit(g_hooks); | ||
| g_hooks = NULL; | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| __attribute__((destructor)) static void codspeed_preload_fini(void) { | ||
| // If the process is not the owner of the lock, this means g_hooks was not | ||
| // initialized | ||
| if (!g_hooks) { | ||
GuillaumeLagrange marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| instrument_hooks_stop_benchmark_inline(g_hooks); | ||
|
|
||
| int32_t pid = getpid(); | ||
| instrument_hooks_set_executed_benchmark(g_hooks, pid, g_bench_uri); | ||
|
|
||
| instrument_hooks_deinit(g_hooks); | ||
| g_hooks = NULL; | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.