Skip to content

Commit 79ac610

Browse files
refactor: introduce Orchestrator to support multi-mode execution
Add an Orchestrator struct that owns the shared run-time state (provider, system_info, logger) and drives the full execution lifecycle: setup → run all modes → upload all results → poll. This replaces the monolithic ExecutionContext with a leaner per-mode context, and wires up the mode/shell-session APIs to accept Vec<RunnerMode> so that multiple modes can be requested in a single CLI invocation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4449281 commit 79ac610

File tree

16 files changed

+275
-218
lines changed

16 files changed

+275
-218
lines changed

src/cli/exec/mod.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,19 @@ pub async fn run(
9191
/// result polling. It is used by both `codspeed exec` directly and by `codspeed run` when
9292
/// executing targets defined in codspeed.yaml.
9393
pub async fn execute_with_harness(
94-
config: crate::executor::Config,
94+
mut config: crate::executor::Config,
9595
api_client: &CodSpeedAPIClient,
9696
codspeed_config: &CodSpeedConfig,
9797
setup_cache_dir: Option<&Path>,
9898
) -> Result<()> {
99-
let mut execution_context =
100-
executor::ExecutionContext::new(config, codspeed_config, api_client).await?;
99+
let orchestrator =
100+
executor::Orchestrator::new(&mut config, codspeed_config, api_client).await?;
101101

102-
if !execution_context.is_local() {
102+
if !orchestrator.is_local() {
103103
super::show_banner();
104104
}
105105

106-
debug!("config: {:#?}", execution_context.config);
107-
let executor = executor::get_executor_from_mode(&execution_context.config.mode);
106+
debug!("config: {config:#?}");
108107

109108
let get_exec_harness_installer_url = || {
110109
format!(
@@ -125,13 +124,9 @@ pub async fn execute_with_harness(
125124
poll_results(api_client, upload_result, &poll_opts).await
126125
};
127126

128-
executor::execute_benchmarks(
129-
executor.as_ref(),
130-
&mut execution_context,
131-
setup_cache_dir,
132-
poll_results_fn,
133-
)
134-
.await?;
127+
orchestrator
128+
.execute(&mut config, setup_cache_dir, poll_results_fn)
129+
.await?;
135130

136131
Ok(())
137132
}

src/cli/run/logger.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
use crate::executor::ExecutionContext;
21
use crate::logger::{GROUP_TARGET, OPENED_GROUP_TARGET};
32
use crate::prelude::*;
43
use crate::run_environment::RunEnvironmentProvider;
54
use log::LevelFilter;
65
use simplelog::{CombinedLogger, WriteLogger};
76
use std::fs::copy;
8-
use std::path::PathBuf;
7+
use std::path::{Path, PathBuf};
98
use tempfile::NamedTempFile;
109

1110
pub struct Logger {
@@ -34,11 +33,7 @@ impl Logger {
3433
Ok(Self { log_file_path })
3534
}
3635

37-
pub fn persist_log_to_profile_folder(
38-
&self,
39-
execution_context: &ExecutionContext,
40-
) -> Result<()> {
41-
let profile_folder = execution_context.profile_folder.clone();
36+
pub fn persist_log_to_profile_folder(&self, profile_folder: &Path) -> Result<()> {
4237
let dest_log_file_path = profile_folder.join("runner.log");
4338
debug!("Persisting log file to {}", dest_log_file_path.display());
4439
log::logger().flush();

src/cli/run/mod.rs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl RunArgs {
7070
repository: None,
7171
provider: None,
7272
working_directory: None,
73-
mode: Some(RunnerMode::Simulation),
73+
mode: vec![RunnerMode::Simulation],
7474
simulation_tool: None,
7575
profile_folder: None,
7676
skip_upload: false,
@@ -141,31 +141,23 @@ pub async fn run(
141141

142142
match run_target {
143143
RunTarget::SingleCommand(args) => {
144-
let config = Config::try_from(args)?;
144+
let mut config = Config::try_from(args)?;
145145

146-
// Create execution context
147-
let mut execution_context =
148-
executor::ExecutionContext::new(config, codspeed_config, api_client).await?;
146+
let orchestrator =
147+
executor::Orchestrator::new(&mut config, codspeed_config, api_client).await?;
149148

150-
if !execution_context.is_local() {
149+
if !orchestrator.is_local() {
151150
super::show_banner();
152151
}
153-
debug!("config: {:#?}", execution_context.config);
154-
155-
// Execute benchmarks
156-
let executor = executor::get_executor_from_mode(&execution_context.config.mode);
152+
debug!("config: {config:#?}");
157153

158154
let poll_opts = PollResultsOptions::for_run(output_json);
159155
let poll_results_fn = async |upload_result: &UploadResult| {
160156
poll_results(api_client, upload_result, &poll_opts).await
161157
};
162-
executor::execute_benchmarks(
163-
executor.as_ref(),
164-
&mut execution_context,
165-
setup_cache_dir,
166-
poll_results_fn,
167-
)
168-
.await?;
158+
orchestrator
159+
.execute(&mut config, setup_cache_dir, poll_results_fn)
160+
.await?;
169161
}
170162

171163
RunTarget::ConfigTargets {

src/cli/shared.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,14 @@ pub struct ExecAndRunSharedArgs {
5555

5656
/// The mode to run the benchmarks in.
5757
/// If not provided, the mode will be loaded from the shell session (set via `codspeed use <mode>`).
58-
#[arg(short, long, value_enum, env = "CODSPEED_RUNNER_MODE")]
59-
pub mode: Option<RunnerMode>,
58+
#[arg(
59+
short,
60+
long,
61+
value_enum,
62+
env = "CODSPEED_RUNNER_MODE",
63+
value_delimiter = ','
64+
)]
65+
pub mode: Vec<RunnerMode>,
6066

6167
/// The Valgrind simulation tool to use (callgrind or tracegrind).
6268
#[arg(long, value_enum, env = "CODSPEED_SIMULATION_TOOL", hide = true)]
@@ -98,25 +104,26 @@ pub struct ExecAndRunSharedArgs {
98104
}
99105

100106
impl ExecAndRunSharedArgs {
101-
/// Resolves the runner mode from CLI argument, shell session, or returns an error.
107+
/// Resolves the runner modes from CLI argument, shell session, or returns an error.
102108
///
103109
/// Priority:
104110
/// 1. CLI argument (--mode or -m)
105111
/// 2. Shell session mode (set via `codspeed use <mode>`)
106112
/// 3. Error if neither is available
107-
pub fn resolve_mode(&self) -> Result<RunnerMode> {
108-
if let Some(mode) = &self.mode {
109-
return Ok(mode.clone());
113+
pub fn resolve_modes(&self) -> Result<Vec<RunnerMode>> {
114+
if !self.mode.is_empty() {
115+
return Ok(self.mode.clone());
110116
}
111117

112-
if let Some(mode) = load_shell_session_mode()? {
113-
debug!("Loaded mode from shell session: {mode:?}");
114-
return Ok(mode);
118+
let modes = load_shell_session_mode()?;
119+
120+
if modes.is_empty() {
121+
return Err(anyhow!(
122+
"No runner mode specified. Use --mode <mode> or set the mode for this shell session with `codspeed use <mode>`."
123+
));
115124
}
116125

117-
Err(anyhow!(
118-
"No runner mode specified. Use --mode <mode> or set the mode for this shell session with `codspeed use <mode>`."
119-
))
126+
Ok(modes)
120127
}
121128
}
122129

src/cli/show.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
use crate::prelude::*;
22

33
pub fn run() -> Result<()> {
4-
let shell_session_mode = crate::runner_mode::load_shell_session_mode()?;
4+
let modes = crate::runner_mode::load_shell_session_mode()?;
55

6-
if let Some(mode) = shell_session_mode {
7-
info!("{mode:?}");
8-
} else {
6+
if modes.is_empty() {
97
info!("No mode set for this shell session");
8+
} else {
9+
let modes_str = modes
10+
.iter()
11+
.map(|m| format!("{m:?}"))
12+
.collect::<Vec<_>>()
13+
.join(", ");
14+
info!("{modes_str}");
1015
}
1116

1217
Ok(())

src/cli/use_mode.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ use clap::Args;
66

77
#[derive(Debug, Args)]
88
pub struct UseArgs {
9-
/// Set the CodSpeed runner mode for this shell session. If not provided, the current mode will
10-
/// be displayed.
11-
pub mode: RunnerMode,
9+
/// Set the CodSpeed runner mode(s) for this shell session.
10+
/// Multiple modes can be provided as separate arguments (e.g. `simulation walltime`)
11+
/// or comma-separated (e.g. `simulation,walltime`).
12+
#[arg(value_delimiter = ',', required = true)]
13+
pub mode: Vec<RunnerMode>,
1214
}
1315

1416
pub fn run(args: UseArgs) -> Result<()> {

src/executor/config.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ pub enum SimulationTool {
2727
/// for controlling the execution flow. It is typically constructed from command-line
2828
/// arguments via [`RunArgs`] and combined with [`CodSpeedConfig`] to create an
2929
/// [`ExecutionContext`].
30-
#[derive(Debug)]
30+
#[derive(Debug, Clone)]
3131
pub struct Config {
3232
pub upload_url: Url,
3333
pub token: Option<String>,
3434
pub repository_override: Option<RepositoryOverride>,
3535
pub working_directory: Option<String>,
3636
pub command: String,
3737

38-
pub mode: RunnerMode,
38+
pub modes: Vec<RunnerMode>,
3939
pub instruments: Instruments,
4040
pub enable_perf: bool,
4141
/// Stack unwinding mode for perf (if enabled)
@@ -53,7 +53,7 @@ pub struct Config {
5353
pub go_runner_version: Option<Version>,
5454
}
5555

56-
#[derive(Debug, PartialEq, Clone)]
56+
#[derive(Debug, Clone, PartialEq)]
5757
pub struct RepositoryOverride {
5858
pub owner: String,
5959
pub repository: String,
@@ -81,6 +81,13 @@ impl Config {
8181
pub fn set_token(&mut self, token: Option<String>) {
8282
self.token = token;
8383
}
84+
85+
/// Create a clone of this config pinned to a single mode.
86+
pub fn for_mode(&self, mode: &RunnerMode) -> Config {
87+
let mut c = self.clone();
88+
c.modes = vec![mode.clone()];
89+
c
90+
}
8491
}
8592

8693
#[cfg(test)]
@@ -93,7 +100,7 @@ impl Config {
93100
repository_override: None,
94101
working_directory: None,
95102
command: "".into(),
96-
mode: RunnerMode::Simulation,
103+
modes: vec![RunnerMode::Simulation],
97104
instruments: Instruments::test(),
98105
perf_unwinding_mode: None,
99106
enable_perf: false,
@@ -114,7 +121,7 @@ impl TryFrom<RunArgs> for Config {
114121
type Error = Error;
115122
fn try_from(args: RunArgs) -> Result<Self> {
116123
let instruments = Instruments::try_from(&args)?;
117-
let mode = args.shared.resolve_mode()?;
124+
let modes = args.shared.resolve_modes()?;
118125
let raw_upload_url = args
119126
.shared
120127
.upload_url
@@ -131,7 +138,7 @@ impl TryFrom<RunArgs> for Config {
131138
.map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider))
132139
.transpose()?,
133140
working_directory: args.shared.working_directory,
134-
mode,
141+
modes,
135142
instruments,
136143
perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode,
137144
enable_perf: args.shared.perf_run_args.enable_perf,
@@ -153,7 +160,7 @@ impl Config {
153160
args: crate::cli::exec::ExecArgs,
154161
command: String,
155162
) -> Result<Self> {
156-
let mode = args.shared.resolve_mode()?;
163+
let modes = args.shared.resolve_modes()?;
157164
let raw_upload_url = args
158165
.shared
159166
.upload_url
@@ -170,7 +177,7 @@ impl Config {
170177
.map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider))
171178
.transpose()?,
172179
working_directory: args.shared.working_directory,
173-
mode,
180+
modes,
174181
instruments: Instruments { mongodb: None }, // exec doesn't support MongoDB
175182
perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode,
176183
enable_perf: args.shared.perf_run_args.enable_perf,
@@ -210,7 +217,7 @@ mod tests {
210217
repository: None,
211218
provider: None,
212219
working_directory: None,
213-
mode: Some(RunnerMode::Simulation),
220+
mode: vec![RunnerMode::Simulation],
214221
simulation_tool: None,
215222
profile_folder: None,
216223
skip_upload: false,
@@ -250,7 +257,7 @@ mod tests {
250257
repository: Some("owner/repo".into()),
251258
provider: Some(RepositoryProvider::GitLab),
252259
working_directory: Some("/tmp".into()),
253-
mode: Some(RunnerMode::Simulation),
260+
mode: vec![RunnerMode::Simulation],
254261
simulation_tool: None,
255262
profile_folder: Some("./codspeed.out".into()),
256263
skip_upload: true,
@@ -334,7 +341,7 @@ mod tests {
334341
repository: None,
335342
provider: None,
336343
working_directory: None,
337-
mode: Some(RunnerMode::Simulation),
344+
mode: vec![RunnerMode::Simulation],
338345
simulation_tool: None,
339346
profile_folder: None,
340347
skip_upload: false,

0 commit comments

Comments
 (0)