Skip to content

Commit 22373e8

Browse files
feat: implement codspeed use to set mode for shell session
1 parent 6dd5c9c commit 22373e8

8 files changed

Lines changed: 159 additions & 20 deletions

File tree

src/cli/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub(crate) mod exec;
33
pub(crate) mod run;
44
mod setup;
55
mod shared;
6+
mod use_mode;
67

78
pub(crate) use shared::*;
89

@@ -83,6 +84,8 @@ enum Commands {
8384
Auth(auth::AuthArgs),
8485
/// Pre-install the codspeed executors
8586
Setup,
87+
/// Set the codspeed mode for the rest of the shell session
88+
Use(use_mode::UseArgs),
8689
}
8790

8891
pub async fn run() -> Result<()> {
@@ -132,6 +135,7 @@ pub async fn run() -> Result<()> {
132135
}
133136
Commands::Auth(args) => auth::run(args, &api_client, cli.config_name.as_deref()).await?,
134137
Commands::Setup => setup::setup(setup_cache_dir).await?,
138+
Commands::Use(args) => use_mode::run(args)?,
135139
}
136140
Ok(())
137141
}

src/cli/run/mod.rs

Lines changed: 1 addition & 1 deletion
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: RunnerMode::Simulation,
73+
mode: Some(RunnerMode::Simulation),
7474
profile_folder: None,
7575
skip_upload: false,
7676
skip_run: false,

src/cli/shared.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::VERSION;
22
use crate::prelude::*;
33
use crate::run_environment::interfaces::RepositoryProvider;
4-
use crate::runner_mode::RunnerMode;
4+
use crate::runner_mode::{RunnerMode, load_shell_session_mode};
55
use clap::Args;
66
use clap::ValueEnum;
77
use std::path::PathBuf;
@@ -53,8 +53,9 @@ pub struct ExecAndRunSharedArgs {
5353
pub working_directory: Option<String>,
5454

5555
/// The mode to run the benchmarks in.
56+
/// If not provided, the mode will be loaded from the shell session (set via `codspeed use <mode>`).
5657
#[arg(short, long, value_enum, env = "CODSPEED_RUNNER_MODE")]
57-
pub mode: RunnerMode,
58+
pub mode: Option<RunnerMode>,
5859

5960
/// Profile folder to use for the run.
6061
#[arg(long)]
@@ -91,6 +92,29 @@ pub struct ExecAndRunSharedArgs {
9192
pub perf_run_args: PerfRunArgs,
9293
}
9394

95+
impl ExecAndRunSharedArgs {
96+
/// Resolves the runner mode from CLI argument, shell session, or returns an error.
97+
///
98+
/// Priority:
99+
/// 1. CLI argument (--mode or -m)
100+
/// 2. Shell session mode (set via `codspeed use <mode>`)
101+
/// 3. Error if neither is available
102+
pub fn resolve_mode(&self) -> Result<RunnerMode> {
103+
if let Some(mode) = &self.mode {
104+
return Ok(mode.clone());
105+
}
106+
107+
if let Some(mode) = load_shell_session_mode()? {
108+
debug!("Loaded mode from shell session: {mode:?}");
109+
return Ok(mode);
110+
}
111+
112+
Err(anyhow!(
113+
"No runner mode specified. Use --mode <mode> or set the mode for this shell session with `codspeed use <mode>`."
114+
))
115+
}
116+
}
117+
94118
#[derive(Debug, Copy, Clone, PartialEq, ValueEnum, Default)]
95119
pub enum UnwindingMode {
96120
/// Use the frame pointer for unwinding. Requires the binary to be compiled with frame pointers enabled.

src/cli/use_mode.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//! Named like this because `use` is a keyword
2+
3+
use crate::prelude::*;
4+
use crate::runner_mode::RunnerMode;
5+
use clap::Args;
6+
7+
#[derive(Debug, Args)]
8+
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: Option<RunnerMode>,
12+
}
13+
14+
pub fn run(args: UseArgs) -> Result<()> {
15+
if let Some(mode) = &args.mode {
16+
crate::runner_mode::register_shell_session_mode(mode)?;
17+
debug!(
18+
"Registered codspeed use mode '{:?}' for this shell session (parent PID)",
19+
args.mode
20+
);
21+
} else {
22+
let shell_session_mode = crate::runner_mode::load_shell_session_mode()?;
23+
24+
if let Some(mode) = shell_session_mode {
25+
info!("{mode:?}");
26+
} else {
27+
info!("No mode set for this shell session");
28+
}
29+
}
30+
Ok(())
31+
}

src/executor/config.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ impl TryFrom<RunArgs> for Config {
100100
type Error = Error;
101101
fn try_from(args: RunArgs) -> Result<Self> {
102102
let instruments = Instruments::try_from(&args)?;
103+
let mode = args.shared.resolve_mode()?;
103104
let raw_upload_url = args
104105
.shared
105106
.upload_url
@@ -116,7 +117,7 @@ impl TryFrom<RunArgs> for Config {
116117
.map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider))
117118
.transpose()?,
118119
working_directory: args.shared.working_directory,
119-
mode: args.shared.mode,
120+
mode,
120121
instruments,
121122
perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode,
122123
enable_perf: args.shared.perf_run_args.enable_perf,
@@ -137,6 +138,7 @@ impl Config {
137138
args: crate::cli::exec::ExecArgs,
138139
command: String,
139140
) -> Result<Self> {
141+
let mode = args.shared.resolve_mode()?;
140142
let raw_upload_url = args
141143
.shared
142144
.upload_url
@@ -153,7 +155,7 @@ impl Config {
153155
.map(|repo| RepositoryOverride::from_arg(repo, args.shared.provider))
154156
.transpose()?,
155157
working_directory: args.shared.working_directory,
156-
mode: args.shared.mode,
158+
mode,
157159
instruments: Instruments { mongodb: None }, // exec doesn't support MongoDB
158160
perf_unwinding_mode: args.shared.perf_run_args.perf_unwinding_mode,
159161
enable_perf: args.shared.perf_run_args.enable_perf,
@@ -192,7 +194,7 @@ mod tests {
192194
repository: None,
193195
provider: None,
194196
working_directory: None,
195-
mode: RunnerMode::Simulation,
197+
mode: Some(RunnerMode::Simulation),
196198
profile_folder: None,
197199
skip_upload: false,
198200
skip_run: false,
@@ -231,7 +233,7 @@ mod tests {
231233
repository: Some("owner/repo".into()),
232234
provider: Some(RepositoryProvider::GitLab),
233235
working_directory: Some("/tmp".into()),
234-
mode: RunnerMode::Simulation,
236+
mode: Some(RunnerMode::Simulation),
235237
profile_folder: Some("./codspeed.out".into()),
236238
skip_upload: true,
237239
skip_run: true,
@@ -314,7 +316,7 @@ mod tests {
314316
repository: None,
315317
provider: None,
316318
working_directory: None,
317-
mode: RunnerMode::Simulation,
319+
mode: Some(RunnerMode::Simulation),
318320
profile_folder: None,
319321
skip_upload: false,
320322
skip_run: false,

src/project_config/merger.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ impl ConfigMerger {
5252
}
5353
}
5454

55-
// Note: mode field has a required default value from clap, so we can't
56-
// distinguish between "user set it" vs "default value". For now, we
57-
// always use the CLI value. This will be addressed in a future PR
58-
// when we make mode optional in CLI args.
59-
6055
merged
6156
}
6257

@@ -157,7 +152,7 @@ mod tests {
157152
repository: None,
158153
provider: None,
159154
working_directory: Some("./cli-dir".to_string()),
160-
mode: RunnerMode::Walltime,
155+
mode: Some(RunnerMode::Walltime),
161156
profile_folder: None,
162157
skip_upload: false,
163158
skip_run: false,
@@ -189,7 +184,7 @@ mod tests {
189184
repository: None,
190185
provider: None,
191186
working_directory: None,
192-
mode: RunnerMode::Walltime,
187+
mode: Some(RunnerMode::Walltime),
193188
profile_folder: None,
194189
skip_upload: false,
195190
skip_run: false,
@@ -211,8 +206,8 @@ mod tests {
211206

212207
// Config working_directory should be used
213208
assert_eq!(merged.working_directory, Some("./config-dir".to_string()));
214-
// Mode stays as CLI default (can't override due to clap default)
215-
assert_eq!(merged.mode, RunnerMode::Walltime);
209+
// Mode stays as CLI value
210+
assert_eq!(merged.mode, Some(RunnerMode::Walltime));
216211
}
217212

218213
#[test]
@@ -223,7 +218,7 @@ mod tests {
223218
repository: None,
224219
provider: None,
225220
working_directory: Some("./dir".to_string()),
226-
mode: RunnerMode::Simulation,
221+
mode: Some(RunnerMode::Simulation),
227222
profile_folder: None,
228223
skip_upload: false,
229224
skip_run: false,
@@ -240,7 +235,7 @@ mod tests {
240235

241236
// Should be identical to CLI
242237
assert_eq!(merged.working_directory, Some("./dir".to_string()));
243-
assert_eq!(merged.mode, RunnerMode::Simulation);
238+
assert_eq!(merged.mode, Some(RunnerMode::Simulation));
244239
}
245240

246241
#[test]
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
use clap::ValueEnum;
2-
use serde::{Deserialize, Serialize};
2+
use serde::Deserialize;
3+
use serde::Serialize;
4+
5+
mod shell_session;
6+
7+
pub(crate) use shell_session::load_shell_session_mode;
8+
pub(crate) use shell_session::register_shell_session_mode;
39

410
#[derive(ValueEnum, Clone, Debug, Serialize, Deserialize, PartialEq)]
511
#[serde(rename_all = "lowercase")]

src/runner_mode/shell_session.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use super::RunnerMode;
2+
use crate::prelude::*;
3+
use libc::pid_t;
4+
use std::path::Path;
5+
use std::path::PathBuf;
6+
use std::sync::OnceLock;
7+
use sysinfo::Pid;
8+
use sysinfo::ProcessRefreshKind;
9+
use sysinfo::RefreshKind;
10+
use sysinfo::System;
11+
12+
static SYSTEM: OnceLock<System> = OnceLock::new();
13+
14+
/// Get the root directory where the use mode is stored
15+
/// If available, uses `$XDG_RUNTIME_DIR/codspeed_use_mode`
16+
/// Otherwise, falls back to `std::env::temp_dir()/codspeed_use_mode`
17+
fn get_use_mode_root_dir() -> PathBuf {
18+
let base_dir = if let Some(xdg_runtime_dir) = std::env::var_os("XDG_RUNTIME_DIR") {
19+
PathBuf::from(xdg_runtime_dir)
20+
} else {
21+
std::env::temp_dir()
22+
};
23+
24+
base_dir.join("codspeed_use_mode")
25+
}
26+
27+
fn get_parent_pid(pid: pid_t) -> Option<pid_t> {
28+
let s = SYSTEM.get_or_init(|| {
29+
System::new_with_specifics(
30+
RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing()),
31+
)
32+
});
33+
34+
let current_pid = Pid::from_u32(pid as u32);
35+
36+
s.process(current_pid)
37+
.and_then(|p| p.parent())
38+
.map(|pid| pid.as_u32() as pid_t)
39+
}
40+
41+
fn get_mode_file_path(base_dir: &Path, pid: pid_t) -> PathBuf {
42+
base_dir.join(pid.to_string())
43+
}
44+
45+
pub(crate) fn register_shell_session_mode(mode: &RunnerMode) -> Result<()> {
46+
let use_mode_dir = get_use_mode_root_dir();
47+
std::fs::create_dir_all(&use_mode_dir)?;
48+
49+
let Some(parent_pid) = get_parent_pid(std::process::id() as pid_t) else {
50+
return Err(anyhow!("Could not determine parent PID"));
51+
};
52+
53+
let mode_file_path = get_mode_file_path(&use_mode_dir, parent_pid);
54+
55+
std::fs::write(mode_file_path, serde_json::to_string(mode)?)?;
56+
Ok(())
57+
}
58+
59+
pub(crate) fn load_shell_session_mode() -> Result<Option<RunnerMode>> {
60+
// Go up the process tree until we find a registered mode
61+
let mut current_pid = std::process::id() as pid_t;
62+
63+
while let Some(parent_pid) = get_parent_pid(current_pid) {
64+
let use_mode_dir = get_use_mode_root_dir();
65+
let mode_file_path = get_mode_file_path(&use_mode_dir, parent_pid);
66+
67+
if mode_file_path.exists() {
68+
let mode_str = std::fs::read_to_string(mode_file_path)?;
69+
let mode: RunnerMode = serde_json::from_str(&mode_str)?;
70+
return Ok(Some(mode));
71+
}
72+
73+
current_pid = parent_pid;
74+
}
75+
76+
Ok(None)
77+
}

0 commit comments

Comments
 (0)