Skip to content

Commit c58ae98

Browse files
feat(exec-harness): embed the preload library in the binary
1 parent 4a77f86 commit c58ae98

3 files changed

Lines changed: 58 additions & 15 deletions

File tree

crates/exec-harness/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ serde_json = { workspace = true }
1919
serde = { workspace = true }
2020
humantime = "2.1"
2121
runner-shared = { path = "../runner-shared" }
22+
tempfile = { workspace = true }
2223

2324
[build-dependencies]
2425
cargo_metadata = "0.19"
2526
cc = "1"
2627

27-
[dev-dependencies]
28-
tempfile = { workspace = true }
29-
3028
[package.metadata.dist]
3129
targets = ["aarch64-unknown-linux-musl", "x86_64-unknown-linux-musl"]

crates/exec-harness/build.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ const PRELOAD_CONSTANTS: PreloadConstants = PreloadConstants {
3636
integration_version: "4.2.0",
3737
};
3838

39+
/// Filename for the preload shared library.
40+
const PRELOAD_LIB_FILENAME: &str = "libcodspeed_preload.so";
41+
3942
/// Paths required to build the preload shared library.
4043
struct PreloadBuildPaths {
4144
/// Path to the preload C source file (codspeed_preload.c).
@@ -65,6 +68,7 @@ fn main() {
6568
"cargo:rustc-env=CODSPEED_INTEGRATION_VERSION={}",
6669
PRELOAD_CONSTANTS.integration_version
6770
);
71+
println!("cargo:rustc-env=CODSPEED_PRELOAD_LIB_FILENAME={PRELOAD_LIB_FILENAME}");
6872

6973
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
7074
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
@@ -81,16 +85,10 @@ fn main() {
8185
preload_c: manifest_dir.join("preload/codspeed_preload.c"),
8286
core_c: instrument_hooks_dir.join("dist/core.c"),
8387
includes_dir: instrument_hooks_dir.join("includes"),
84-
output_lib: out_dir.join("libcodspeed_preload.so"),
88+
output_lib: out_dir.join(PRELOAD_LIB_FILENAME),
8589
};
86-
paths.check_all_exist();
90+
paths.check_sources_exist();
8791
build_shared_library(&paths, &PRELOAD_CONSTANTS);
88-
89-
// Export the library path for use by the main crate
90-
println!(
91-
"cargo:rustc-env=CODSPEED_PRELOAD_LIB_PATH={}",
92-
paths.output_lib.display()
93-
);
9492
}
9593

9694
/// Build the shared library using the cc crate
@@ -173,7 +171,7 @@ fn find_codspeed_instrument_hooks_dir() -> PathBuf {
173171
impl PreloadBuildPaths {
174172
/// Verify that all required source files and directories exist.
175173
/// Panics with a descriptive message if any path is missing.
176-
fn check_all_exist(&self) {
174+
fn check_sources_exist(&self) {
177175
if !self.core_c.exists() {
178176
panic!(
179177
"core.c not found at {}. Make sure the codspeed crate is available.",

crates/exec-harness/src/analysis.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use crate::BenchmarkCommand;
44
use crate::constants;
55
use crate::uri;
66
use codspeed::instrument_hooks::InstrumentHooks;
7+
use std::io::Write;
8+
use std::os::unix::fs::PermissionsExt;
79
use std::process::Command;
10+
use std::sync::OnceLock;
811

912
pub fn perform(commands: Vec<BenchmarkCommand>) -> Result<()> {
1013
let hooks = InstrumentHooks::instance();
@@ -30,19 +33,63 @@ pub fn perform(commands: Vec<BenchmarkCommand>) -> Result<()> {
3033
Ok(())
3134
}
3235

33-
/// Path to the preload library built during compilation.
36+
/// Filename for the preload shared library.
37+
const PRELOAD_LIB_FILENAME: &str = env!("CODSPEED_PRELOAD_LIB_FILENAME");
38+
39+
/// The preload library binary embedded at compile time.
3440
/// This library is used for LD_PRELOAD-based instrumentation injection.
35-
pub const PRELOAD_LIB_PATH: &str = env!("CODSPEED_PRELOAD_LIB_PATH");
41+
const PRELOAD_LIB_BYTES: &[u8] = include_bytes!(concat!(
42+
env!("OUT_DIR"),
43+
"/",
44+
env!("CODSPEED_PRELOAD_LIB_FILENAME")
45+
));
46+
47+
/// Lazily initialized path to the extracted preload library.
48+
static PRELOAD_LIB_PATH: OnceLock<std::path::PathBuf> = OnceLock::new();
49+
50+
/// Extracts the preload library to a temp file and returns the path.
51+
fn extract_preload_lib() -> Result<std::path::PathBuf> {
52+
let lib_path = std::env::temp_dir().join(PRELOAD_LIB_FILENAME);
53+
54+
let mut file = std::fs::File::create(&lib_path)
55+
.context("Failed to create temp file for preload library")?;
56+
57+
file.write_all(PRELOAD_LIB_BYTES)
58+
.context("Failed to write preload library to temp file")?;
59+
60+
// Make the library executable
61+
let mut permissions = file
62+
.metadata()
63+
.context("Failed to get temp file metadata")?
64+
.permissions();
65+
permissions.set_mode(0o755);
66+
file.set_permissions(permissions)
67+
.context("Failed to set temp file permissions")?;
68+
69+
Ok(lib_path)
70+
}
71+
72+
/// Returns the path to the preload library, extracting it to a temp file if needed.
73+
fn get_preload_lib_path() -> Result<&'static std::path::Path> {
74+
if let Some(path) = PRELOAD_LIB_PATH.get() {
75+
return Ok(path);
76+
}
77+
78+
let path = extract_preload_lib()?;
79+
Ok(PRELOAD_LIB_PATH.get_or_init(|| path))
80+
}
3681

3782
pub fn perform_with_valgrind(commands: Vec<BenchmarkCommand>) -> Result<()> {
83+
let preload_lib_path = get_preload_lib_path()?;
84+
3885
for benchmark_cmd in commands {
3986
let name_and_uri = uri::generate_name_and_uri(&benchmark_cmd.name, &benchmark_cmd.command);
4087
name_and_uri.print_executing();
4188

4289
let mut cmd = Command::new(&benchmark_cmd.command[0]);
4390
cmd.args(&benchmark_cmd.command[1..]);
4491
// Use LD_PRELOAD to inject instrumentation into the child process
45-
cmd.env("LD_PRELOAD", PRELOAD_LIB_PATH);
92+
cmd.env("LD_PRELOAD", preload_lib_path);
4693
cmd.env(constants::URI_ENV, &name_and_uri.uri);
4794

4895
let status = cmd.status().context("Failed to execute command")?;

0 commit comments

Comments
 (0)