Skip to content

Commit fde0294

Browse files
feat: add exec subcommand and refactor run subcommand
Refactor aims to extract common behavior between exec and run. It mostly moves out the run::Config into the executor module, as well as moving the run_environment module to the root of the repo. The main aim is to offload the overloaded run module that has outgrown its initial purpose of a clap subcommand.
1 parent b00d70d commit fde0294

File tree

97 files changed

+485
-141138
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+485
-141138
lines changed

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
testdata/perf_map/* filter=lfs diff=lfs merge=lfs -text
2-
src/run/runner/wall_time/perf/snapshots/*.snap filter=lfs diff=lfs merge=lfs -text
2+
*.snap filter=lfs diff=lfs merge=lfs -text

src/app.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
api_client::CodSpeedAPIClient,
55
auth,
66
config::CodSpeedConfig,
7+
exec,
78
local_logger::{CODSPEED_U8_COLOR_CODE, init_local_logger},
89
prelude::*,
910
run, setup,
@@ -59,8 +60,11 @@ pub struct Cli {
5960

6061
#[derive(Subcommand, Debug)]
6162
enum Commands {
62-
/// Run the bench command and upload the results to CodSpeed
63+
/// Run a benchmark program that already contains the CodSpeed instrumentation and upload the results to CodSpeed
6364
Run(Box<run::RunArgs>),
65+
/// Run a command after adding CodSpeed instrumentation to it and upload the results to
66+
/// CodSpeed
67+
Exec(Box<exec::ExecArgs>),
6468
/// Manage the CLI authentication state
6569
Auth(auth::AuthArgs),
6670
/// Pre-install the codspeed executors
@@ -80,7 +84,7 @@ pub async fn run() -> Result<()> {
8084
let setup_cache_dir = setup_cache_dir.as_deref();
8185

8286
match cli.command {
83-
Commands::Run(_) => {} // Run is responsible for its own logger initialization
87+
Commands::Run(_) | Commands::Exec(_) => {} // Run and Exec are responsible for their own logger initialization
8488
_ => {
8589
init_local_logger()?;
8690
}
@@ -90,6 +94,9 @@ pub async fn run() -> Result<()> {
9094
Commands::Run(args) => {
9195
run::run(*args, &api_client, &codspeed_config, setup_cache_dir).await?
9296
}
97+
Commands::Exec(args) => {
98+
exec::run(*args, &api_client, &codspeed_config, setup_cache_dir).await?
99+
}
93100
Commands::Auth(args) => auth::run(args, &api_client, cli.config_name.as_deref()).await?,
94101
Commands::Setup => setup::setup(setup_cache_dir).await?,
95102
}

src/exec/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use crate::api_client::CodSpeedAPIClient;
2+
use crate::config::CodSpeedConfig;
3+
use crate::prelude::*;
4+
use clap::Args;
5+
use std::path::Path;
6+
7+
#[derive(Args, Debug)]
8+
pub struct ExecArgs {
9+
#[command(flatten)]
10+
pub shared: crate::run::ExecAndRunSharedArgs,
11+
12+
/// Optional benchmark name (defaults to command filename)
13+
#[arg(long)]
14+
pub name: Option<String>,
15+
16+
/// The command to execute with the exec harness
17+
pub command: Vec<String>,
18+
}
19+
20+
pub async fn run(
21+
args: ExecArgs,
22+
api_client: &CodSpeedAPIClient,
23+
codspeed_config: &CodSpeedConfig,
24+
setup_cache_dir: Option<&Path>,
25+
) -> Result<()> {
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(" ");
32+
33+
info!("Executing: {warped_command_string}");
34+
35+
// Convert ExecArgs to executor::Config using shared args
36+
let config = crate::executor::Config {
37+
upload_url: args
38+
.shared
39+
.upload_url
40+
.as_ref()
41+
.map(|url| url.parse())
42+
.transpose()
43+
.map_err(|e| anyhow!("Invalid upload URL: {e}"))?
44+
.unwrap_or_else(|| {
45+
"https://api.codspeed.io/upload"
46+
.parse()
47+
.expect("Default URL should be valid")
48+
}),
49+
token: args.shared.token,
50+
repository_override: args
51+
.shared
52+
.repository
53+
.map(|repo| {
54+
crate::executor::config::RepositoryOverride::from_arg(repo, args.shared.provider)
55+
})
56+
.transpose()?,
57+
working_directory: args.shared.working_directory,
58+
command: warped_command_string,
59+
mode: args.shared.mode,
60+
instruments: crate::instruments::Instruments { mongodb: None }, // exec doesn't support MongoDB
61+
enable_perf: args.shared.perf_run_args.enable_perf,
62+
perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode,
63+
profile_folder: args.shared.profile_folder,
64+
skip_upload: args.shared.skip_upload,
65+
skip_run: args.shared.skip_run,
66+
skip_setup: args.shared.skip_setup,
67+
allow_empty: args.shared.allow_empty,
68+
};
69+
70+
// Delegate to shared execution logic
71+
crate::executor::execute_benchmarks(config, api_client, codspeed_config, setup_cache_dir, false)
72+
.await
73+
}

src/run/config.rs renamed to src/executor/config.rs

Lines changed: 89 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use crate::instruments::Instruments;
22
use crate::prelude::*;
3+
use crate::run::{RunArgs, UnwindingMode};
4+
use crate::run_environment::RepositoryProvider;
5+
use crate::runner_mode::RunnerMode;
36
use std::path::PathBuf;
47
use url::Url;
58

6-
use crate::run::RunArgs;
7-
use crate::run::run_environment::RepositoryProvider;
8-
9-
use super::{RunnerMode, UnwindingMode};
10-
119
#[derive(Debug)]
1210
pub struct Config {
1311
pub upload_url: Url,
@@ -35,6 +33,23 @@ pub struct RepositoryOverride {
3533
pub repository_provider: RepositoryProvider,
3634
}
3735

36+
impl RepositoryOverride {
37+
/// Creates a RepositoryOverride from an "owner/repository" string
38+
pub fn from_arg(
39+
repository_and_owner: String,
40+
provider: Option<RepositoryProvider>,
41+
) -> Result<Self> {
42+
let (owner, repository) = repository_and_owner
43+
.split_once('/')
44+
.context("Invalid owner/repository format")?;
45+
Ok(Self {
46+
owner: owner.to_string(),
47+
repository: repository.to_string(),
48+
repository_provider: provider.unwrap_or_default(),
49+
})
50+
}
51+
}
52+
3853
impl Config {
3954
pub fn set_token(&mut self, token: Option<String>) {
4055
self.token = token;
@@ -70,47 +85,36 @@ impl TryFrom<RunArgs> for Config {
7085
type Error = Error;
7186
fn try_from(args: RunArgs) -> Result<Self> {
7287
let instruments = Instruments::try_from(&args)?;
73-
let raw_upload_url = args.upload_url.unwrap_or_else(|| DEFAULT_UPLOAD_URL.into());
88+
let raw_upload_url = args
89+
.shared
90+
.upload_url
91+
.unwrap_or_else(|| DEFAULT_UPLOAD_URL.into());
7492
let upload_url = Url::parse(&raw_upload_url)
7593
.map_err(|e| anyhow!("Invalid upload URL: {raw_upload_url}, {e}"))?;
7694

7795
Ok(Self {
7896
upload_url,
79-
token: args.token,
97+
token: args.shared.token,
8098
repository_override: args
99+
.shared
81100
.repository
82-
.map(|respository_and_owner| -> Result<RepositoryOverride> {
83-
let (owner, repository) =
84-
extract_owner_and_repository_from_arg(&respository_and_owner)?;
85-
Ok(RepositoryOverride {
86-
owner,
87-
repository,
88-
repository_provider: args.provider.unwrap_or_default(),
89-
})
90-
})
101+
.map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider))
91102
.transpose()?,
92-
working_directory: args.working_directory,
93-
mode: args.mode,
103+
working_directory: args.shared.working_directory,
104+
mode: args.shared.mode,
94105
instruments,
95-
perf_unwinding_mode: args.perf_run_args.perf_unwinding_mode,
96-
enable_perf: args.perf_run_args.enable_perf,
106+
perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode,
107+
enable_perf: args.shared.perf_run_args.enable_perf,
97108
command: args.command.join(" "),
98-
profile_folder: args.profile_folder,
99-
skip_upload: args.skip_upload,
100-
skip_run: args.skip_run,
101-
skip_setup: args.skip_setup,
102-
allow_empty: args.allow_empty,
109+
profile_folder: args.shared.profile_folder,
110+
skip_upload: args.shared.skip_upload,
111+
skip_run: args.shared.skip_run,
112+
skip_setup: args.shared.skip_setup,
113+
allow_empty: args.shared.allow_empty,
103114
})
104115
}
105116
}
106117

107-
fn extract_owner_and_repository_from_arg(owner_and_repository: &str) -> Result<(String, String)> {
108-
let (owner, repository) = owner_and_repository
109-
.split_once('/')
110-
.context("Invalid owner/repository format")?;
111-
Ok((owner.to_string(), repository.to_string()))
112-
}
113-
114118
#[cfg(test)]
115119
mod tests {
116120
use crate::instruments::MongoDBConfig;
@@ -121,24 +125,26 @@ mod tests {
121125
#[test]
122126
fn test_try_from_env_empty() {
123127
let config = Config::try_from(RunArgs {
124-
upload_url: None,
125-
token: None,
126-
repository: None,
127-
provider: None,
128-
working_directory: None,
129-
mode: RunnerMode::Simulation,
128+
shared: crate::run::ExecAndRunSharedArgs {
129+
upload_url: None,
130+
token: None,
131+
repository: None,
132+
provider: None,
133+
working_directory: None,
134+
mode: RunnerMode::Simulation,
135+
profile_folder: None,
136+
skip_upload: false,
137+
skip_run: false,
138+
skip_setup: false,
139+
allow_empty: false,
140+
perf_run_args: PerfRunArgs {
141+
enable_perf: false,
142+
perf_unwinding_mode: None,
143+
},
144+
},
130145
instruments: vec![],
131146
mongo_uri_env_name: None,
132147
message_format: None,
133-
profile_folder: None,
134-
skip_upload: false,
135-
skip_run: false,
136-
skip_setup: false,
137-
allow_empty: false,
138-
perf_run_args: PerfRunArgs {
139-
enable_perf: false,
140-
perf_unwinding_mode: None,
141-
},
142148
command: vec!["cargo".into(), "codspeed".into(), "bench".into()],
143149
})
144150
.unwrap();
@@ -157,24 +163,26 @@ mod tests {
157163
#[test]
158164
fn test_try_from_args() {
159165
let config = Config::try_from(RunArgs {
160-
upload_url: Some("https://example.com/upload".into()),
161-
token: Some("token".into()),
162-
repository: Some("owner/repo".into()),
163-
provider: Some(RepositoryProvider::GitLab),
164-
working_directory: Some("/tmp".into()),
165-
mode: RunnerMode::Simulation,
166+
shared: crate::run::ExecAndRunSharedArgs {
167+
upload_url: Some("https://example.com/upload".into()),
168+
token: Some("token".into()),
169+
repository: Some("owner/repo".into()),
170+
provider: Some(RepositoryProvider::GitLab),
171+
working_directory: Some("/tmp".into()),
172+
mode: RunnerMode::Simulation,
173+
profile_folder: Some("./codspeed.out".into()),
174+
skip_upload: true,
175+
skip_run: true,
176+
skip_setup: true,
177+
allow_empty: true,
178+
perf_run_args: PerfRunArgs {
179+
enable_perf: false,
180+
perf_unwinding_mode: Some(UnwindingMode::FramePointer),
181+
},
182+
},
166183
instruments: vec!["mongodb".into()],
167184
mongo_uri_env_name: Some("MONGODB_URI".into()),
168185
message_format: Some(crate::run::MessageFormat::Json),
169-
profile_folder: Some("./codspeed.out".into()),
170-
skip_upload: true,
171-
skip_run: true,
172-
skip_setup: true,
173-
allow_empty: true,
174-
perf_run_args: PerfRunArgs {
175-
enable_perf: false,
176-
perf_unwinding_mode: Some(UnwindingMode::FramePointer),
177-
},
178186
command: vec!["cargo".into(), "codspeed".into(), "bench".into()],
179187
})
180188
.unwrap();
@@ -210,16 +218,27 @@ mod tests {
210218
}
211219

212220
#[test]
213-
fn test_extract_owner_and_repository_from_arg() {
214-
let owner_and_repository = "CodSpeedHQ/runner";
215-
let (owner, repository) =
216-
extract_owner_and_repository_from_arg(owner_and_repository).unwrap();
217-
assert_eq!(owner, "CodSpeedHQ");
218-
assert_eq!(repository, "runner");
221+
fn test_repository_override_from_arg() {
222+
let override_result =
223+
RepositoryOverride::from_arg("CodSpeedHQ/runner".to_string(), None).unwrap();
224+
assert_eq!(override_result.owner, "CodSpeedHQ");
225+
assert_eq!(override_result.repository, "runner");
226+
assert_eq!(
227+
override_result.repository_provider,
228+
RepositoryProvider::GitHub
229+
);
219230

220-
let owner_and_repository = "CodSpeedHQ_runner";
231+
let override_with_provider = RepositoryOverride::from_arg(
232+
"CodSpeedHQ/runner".to_string(),
233+
Some(RepositoryProvider::GitLab),
234+
)
235+
.unwrap();
236+
assert_eq!(
237+
override_with_provider.repository_provider,
238+
RepositoryProvider::GitLab
239+
);
221240

222-
let result = extract_owner_and_repository_from_arg(owner_and_repository);
241+
let result = RepositoryOverride::from_arg("CodSpeedHQ_runner".to_string(), None);
223242
assert!(result.is_err());
224243
}
225244
}

src/executor/helpers/get_bench_command.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::executor::Config;
12
use crate::prelude::*;
2-
use crate::run::config::Config;
33

44
pub fn get_bench_command(config: &Config) -> Result<String> {
55
let bench_command = &config.command.trim();

0 commit comments

Comments
 (0)