Skip to content

Commit ac44deb

Browse files
--wip-- [skip ci]
1 parent e597063 commit ac44deb

File tree

14 files changed

+290
-168
lines changed

14 files changed

+290
-168
lines changed

Cargo.lock

Lines changed: 11 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ path = "src/main.rs"
1111

1212

1313
[dependencies]
14-
anyhow = "1.0.75"
14+
anyhow = { workspace = true }
1515
clap = { workspace = true }
1616
itertools = "0.11.0"
1717
lazy_static = "1.4.0"
@@ -26,8 +26,8 @@ reqwest = { version = "0.11.22", features = [
2626
] }
2727
reqwest-middleware = "0.2.4"
2828
reqwest-retry = "0.3.0"
29-
serde = { version = "1.0.192", features = ["derive"] }
30-
serde_json = { version = "1.0.108", features = ["preserve_order"] }
29+
serde = { workspace = true }
30+
serde_json = { workspace = true }
3131
url = "2.4.1"
3232
sha256 = "1.4.0"
3333
tokio = { version = "1", features = ["macros", "rt"] }
@@ -51,10 +51,10 @@ async-trait = "0.1.82"
5151
libc = "0.2.171"
5252
bincode = "1.3.3"
5353
object = "0.36.7"
54-
linux-perf-data = "0.11.0"
54+
linux-perf-data = { git = "ssh://[email protected]/CodSpeedHQ/linux-perf-data.git", branch = "feat/support-perf-pipe-data-parsing" }
5555
debugid = "0.8.0"
5656
memmap2 = "0.9.5"
57-
nix = { version = "0.29.0", features = ["fs", "time", "user"] }
57+
nix = { workspace = true, features = ["fs", "time", "user"] }
5858
futures = "0.3.31"
5959
runner-shared = { path = "crates/runner-shared" }
6060
shellexpand = { version = "3.1.1", features = ["tilde"] }
@@ -79,7 +79,11 @@ shell-quote = "0.7.2"
7979
members = ["crates/exec-harness", "crates/runner-shared"]
8080

8181
[workspace.dependencies]
82+
anyhow = "1.0.75"
8283
clap = { version = "4.4.8", features = ["derive", "env", "color"] }
84+
nix = "0.29.0"
85+
serde = { version = "1.0.192", features = ["derive"] }
86+
serde_json = { version = "1.0.108", features = ["preserve_order"] }
8387

8488
[workspace.metadata.release]
8589
sign-tag = true

crates/exec-harness/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ version = "4.4.1"
44
edition = "2024"
55

66
[dependencies]
7+
anyhow = { workspace = true }
78
codspeed = "4.1.0"
89
clap = { workspace = true }
10+
serde_json = { workspace = true }
11+
serde = { workspace = true }
12+
nix = { workspace = true, features = ["signal"] }

crates/exec-harness/src/main.rs

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
use crate::walltime::WalltimeResults;
12
use clap::Parser;
23
use codspeed::instrument_hooks::InstrumentHooks;
4+
use codspeed::walltime_results::WalltimeBenchmark;
5+
use std::path::PathBuf;
6+
use std::process;
7+
8+
mod walltime;
39

410
#[derive(Parser, Debug)]
511
#[command(name = "exec-harness")]
@@ -18,7 +24,7 @@ fn main() {
1824

1925
if args.command.is_empty() {
2026
eprintln!("Error: No command provided");
21-
std::process::exit(1);
27+
process::exit(1);
2228
}
2329

2430
// Derive benchmark name from command if not provided
@@ -34,27 +40,75 @@ fn main() {
3440

3541
let hooks = InstrumentHooks::instance();
3642

43+
// TODO: Change this to avoid impersonating `codspeed-rust`
3744
hooks
38-
.set_integration("codspeed-exec", env!("CARGO_PKG_VERSION"))
45+
.set_integration("codspeed-rust", env!("CARGO_PKG_VERSION"))
3946
.unwrap();
47+
48+
const NUM_ITERATIONS: usize = 10;
49+
let mut times_per_round_ns = Vec::with_capacity(NUM_ITERATIONS);
50+
4051
hooks.start_benchmark().unwrap();
52+
for _ in 0..NUM_ITERATIONS {
53+
// Start monotonic timer for this iteration
54+
let bench_start = InstrumentHooks::current_timestamp();
4155

42-
// Execute the command
43-
let status = std::process::Command::new(&args.command[0])
44-
.args(&args.command[1..])
45-
.status();
56+
// Spawn the command
57+
let mut child = match process::Command::new(&args.command[0])
58+
.args(&args.command[1..])
59+
.spawn()
60+
{
61+
Ok(child) => child,
62+
Err(e) => {
63+
eprintln!("Failed to spawn command: {e}");
64+
process::exit(1);
65+
}
66+
};
67+
// Wait for the process to complete
68+
let status = match child.wait() {
69+
Ok(status) => status,
70+
Err(e) => {
71+
eprintln!("Failed to wait for command: {e}");
72+
process::exit(1);
73+
}
74+
};
4675

47-
hooks.stop_benchmark().unwrap();
48-
hooks.set_executed_benchmark(&bench_name).unwrap();
76+
// Measure elapsed time
77+
let bench_end = InstrumentHooks::current_timestamp();
78+
hooks.add_benchmark_timestamps(bench_start, bench_end);
4979

50-
// Propagate exit code
51-
match status {
52-
Ok(exit_status) => {
53-
std::process::exit(exit_status.code().unwrap_or(1));
54-
}
55-
Err(e) => {
56-
eprintln!("Failed to execute command: {e}");
57-
std::process::exit(1);
80+
// Exit immediately if any iteration fails
81+
if !status.success() {
82+
eprintln!("Command failed with exit code: {:?}", status.code());
83+
process::exit(status.code().unwrap_or(1));
5884
}
85+
86+
// Calculate and store the elapsed time in nanoseconds
87+
let elapsed_ns = (bench_end - bench_start) as u128;
88+
times_per_round_ns.push(elapsed_ns);
5989
}
90+
91+
hooks.stop_benchmark().unwrap();
92+
hooks.set_executed_benchmark(&bench_name).unwrap();
93+
94+
// Collect walltime results
95+
let max_time_ns = times_per_round_ns.iter().copied().max();
96+
let walltime_benchmark = WalltimeBenchmark::from_runtime_data(
97+
bench_name.clone(),
98+
format!("standalone_run::{bench_name}"),
99+
vec![1; NUM_ITERATIONS],
100+
times_per_round_ns,
101+
max_time_ns,
102+
);
103+
104+
let walltime_results = WalltimeResults::from_benchmarks(vec![walltime_benchmark])
105+
.expect("Failed to create walltime results");
106+
107+
walltime_results
108+
.save_to_file(
109+
std::env::var("CODSPEED_PROFILE_FOLDER")
110+
.map(PathBuf::from)
111+
.unwrap_or_else(|_| std::env::current_dir().unwrap().join(".codspeed")),
112+
)
113+
.expect("Failed to save walltime results");
60114
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use anyhow::Context;
2+
use anyhow::Result;
3+
use codspeed::walltime_results::WalltimeBenchmark;
4+
use serde::Deserialize;
5+
use serde::Serialize;
6+
use std::path::Path;
7+
8+
#[derive(Debug, Serialize, Deserialize)]
9+
struct Instrument {
10+
#[serde(rename = "type")]
11+
type_: String,
12+
}
13+
14+
#[derive(Debug, Serialize, Deserialize)]
15+
struct Creator {
16+
name: String,
17+
version: String,
18+
pid: u32,
19+
}
20+
21+
#[derive(Debug, Serialize, Deserialize)]
22+
pub struct WalltimeResults {
23+
creator: Creator,
24+
instrument: Instrument,
25+
benchmarks: Vec<WalltimeBenchmark>,
26+
}
27+
28+
impl WalltimeResults {
29+
pub fn from_benchmarks(benchmarks: Vec<WalltimeBenchmark>) -> Result<Self> {
30+
Ok(WalltimeResults {
31+
instrument: Instrument {
32+
type_: "walltime".to_string(),
33+
},
34+
creator: Creator {
35+
name: "codspeed-rust".to_string(),
36+
version: env!("CARGO_PKG_VERSION").to_string(),
37+
pid: std::process::id(),
38+
},
39+
benchmarks,
40+
})
41+
}
42+
43+
pub fn save_to_file<P: AsRef<Path>>(&self, profile_folder: P) -> Result<()> {
44+
let results_path = {
45+
let results_dir = profile_folder.as_ref().join("results");
46+
std::fs::create_dir_all(&results_dir).with_context(|| {
47+
format!(
48+
"Failed to create results directory: {}",
49+
results_dir.display()
50+
)
51+
})?;
52+
53+
results_dir.join(format!("{}.json", self.creator.pid))
54+
};
55+
56+
let file = std::fs::File::create(&results_path)
57+
.with_context(|| format!("Failed to create file: {}", results_path.display()))?;
58+
serde_json::to_writer_pretty(file, &self)
59+
.with_context(|| format!("Failed to write JSON to file: {}", results_path.display()))?;
60+
Ok(())
61+
}
62+
}

src/api_client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ impl CodSpeedAPIClient {
212212
Err(err) if err.contains_error_code("UNAUTHENTICATED") => {
213213
bail!("Your session has expired, please login again using `codspeed auth login`")
214214
}
215-
Err(err) => bail!("Failed to fetch local run report: {err}"),
215+
Err(err) => bail!("Failed to fetch local run report: {err:?}"),
216216
}
217217
}
218218
}

src/exec/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ use crate::prelude::*;
44
use clap::Args;
55
use std::path::Path;
66

7-
mod run_with_harness;
8-
use run_with_harness::wrap_command_with_harness;
9-
107
#[derive(Args, Debug)]
118
pub struct ExecArgs {
129
#[command(flatten)]
@@ -26,10 +23,14 @@ pub async fn run(
2623
codspeed_config: &CodSpeedConfig,
2724
setup_cache_dir: Option<&Path>,
2825
) -> Result<()> {
29-
// Wrap the user's command with exec-harness BEFORE creating config
30-
let wrapped_command = wrap_command_with_harness(&args.command, args.name.as_deref())?;
26+
// Assume exec-harness is in the PATH for now
27+
let wrapped_command = std::iter::once("exec-harness".to_string())
28+
.chain(args.command)
29+
.collect::<Vec<String>>();
30+
31+
let warped_command_string = wrapped_command.join(" ");
3132

32-
info!("Executing: {}", wrapped_command.join(" "));
33+
info!("Executing: {warped_command_string}");
3334

3435
// Convert ExecArgs to executor::Config using shared args
3536
let config = crate::executor::Config {
@@ -54,7 +55,7 @@ pub async fn run(
5455
})
5556
.transpose()?,
5657
working_directory: args.shared.working_directory,
57-
command: wrapped_command.join(" "), // Use wrapped command
58+
command: warped_command_string,
5859
mode: args.shared.mode,
5960
instruments: crate::instruments::Instruments { mongodb: None }, // exec doesn't support MongoDB
6061
enable_perf: args.shared.perf_run_args.enable_perf,

0 commit comments

Comments
 (0)