Skip to content

Commit 6bb3da1

Browse files
committed
fix(runner): forward environment in memory executor
1 parent e608eb1 commit 6bb3da1

4 files changed

Lines changed: 102 additions & 53 deletions

File tree

src/run/runner/helpers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ pub mod introspected_golang;
66
pub mod introspected_nodejs;
77
pub mod profile_folder;
88
pub mod run_command_with_log_pipe;
9+
pub mod run_with_env;
910
pub mod run_with_sudo;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! Forwards the current environment to a command when run with sudo.
2+
3+
use crate::prelude::*;
4+
use crate::run::runner::helpers::command::CommandBuilder;
5+
use std::collections::HashMap;
6+
use std::io::Write;
7+
use std::process::Command;
8+
use tempfile::NamedTempFile;
9+
10+
/// Returns a list of exported environment variables which can be loaded with `source` in a shell.
11+
///
12+
/// Example: `declare -x outputs="out"`
13+
fn get_exported_system_env() -> Result<String> {
14+
let output = Command::new("bash")
15+
.arg("-c")
16+
.arg("export")
17+
.output()
18+
.context("Failed to run `export`")?;
19+
if !output.status.success() {
20+
bail!(
21+
"Failed to get system environment variables: {}",
22+
String::from_utf8_lossy(&output.stderr)
23+
);
24+
}
25+
26+
String::from_utf8(output.stdout).context("Failed to parse export output as UTF-8")
27+
}
28+
29+
/// Wraps a command to run with environment variables forwarded.
30+
///
31+
/// # Returns
32+
/// Returns a tuple of (CommandBuilder, NamedTempFile) where:
33+
/// - CommandBuilder is wrapped with bash to source the environment and run the original command
34+
/// - NamedTempFile is the environment file that must be kept alive until command execution
35+
pub fn wrap_with_env(
36+
mut cmd_builder: CommandBuilder,
37+
extra_env: &HashMap<&'static str, String>,
38+
) -> Result<(CommandBuilder, NamedTempFile)> {
39+
let env_file = create_env_file(extra_env)?;
40+
41+
// Create bash command that sources the env file and runs the original command
42+
let original_command = cmd_builder.as_command_line();
43+
let bash_command = format!(
44+
"source {} && {}",
45+
env_file.path().display(),
46+
original_command
47+
);
48+
cmd_builder.wrap("bash", ["-c", &bash_command]);
49+
50+
Ok((cmd_builder, env_file))
51+
}
52+
53+
pub fn create_env_file(extra_env: &HashMap<&'static str, String>) -> Result<NamedTempFile> {
54+
let system_env = get_exported_system_env()?;
55+
let base_injected_env = extra_env
56+
.iter()
57+
.map(|(k, v)| format!("export {k}={v}"))
58+
.collect::<Vec<_>>()
59+
.join("\n");
60+
61+
// Create and return the environment file
62+
let mut env_file = NamedTempFile::new()?;
63+
env_file.write_all(format!("{system_env}\n{base_injected_env}").as_bytes())?;
64+
Ok(env_file)
65+
}

src/run/runner/memory/executor.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use crate::prelude::*;
22
use crate::run::instruments::mongo_tracer::MongoTracer;
33
use crate::run::runner::executor::Executor;
44
use crate::run::runner::helpers::command::CommandBuilder;
5+
use crate::run::runner::helpers::env::get_base_injected_env;
56
use crate::run::runner::helpers::get_bench_command::get_bench_command;
67
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback;
8+
use crate::run::runner::helpers::run_with_env::wrap_with_env;
79
use crate::run::runner::helpers::run_with_sudo::wrap_with_sudo;
810
use crate::run::runner::shared::fifo::RunnerFifo;
9-
use crate::run::runner::{ExecutorName, RunData};
11+
use crate::run::runner::{ExecutorName, RunData, RunnerMode};
1012
use crate::run::{check_system::SystemInfo, config::Config};
1113
use async_trait::async_trait;
1214
use ipc_channel::ipc;
@@ -18,32 +20,38 @@ use runner_shared::fifo::IntegrationMode;
1820
use std::path::Path;
1921
use std::process::Command;
2022
use std::rc::Rc;
23+
use tempfile::NamedTempFile;
2124

2225
pub struct MemoryExecutor;
2326

2427
impl MemoryExecutor {
2528
fn build_memtrack_command(
2629
config: &Config,
2730
run_data: &RunData,
28-
) -> Result<(MemtrackIpcServer, CommandBuilder)> {
31+
) -> Result<(MemtrackIpcServer, CommandBuilder, NamedTempFile)> {
2932
// FIXME: We only support native languages for now
3033

3134
// Find memtrack binary - check env variable or use default command name
3235
let memtrack_path = std::env::var("CODSPEED_MEMTRACK_BINARY")
3336
.unwrap_or_else(|_| "codspeed-memtrack".to_string());
3437

38+
// Setup memtrack IPC server
39+
let (ipc_server, server_name) = ipc::IpcOneShotServer::new()?;
40+
41+
// Build the memtrack command
3542
let mut cmd_builder = CommandBuilder::new(memtrack_path);
3643
cmd_builder.arg("track");
37-
cmd_builder.arg(get_bench_command(config)?);
3844
cmd_builder.arg("--output");
3945
cmd_builder.arg(run_data.profile_folder.join("results"));
40-
41-
// Setup memtrack IPC server
42-
let (ipc_server, server_name) = ipc::IpcOneShotServer::new()?;
4346
cmd_builder.arg("--ipc-server");
4447
cmd_builder.arg(server_name);
48+
cmd_builder.arg(get_bench_command(config)?);
49+
50+
// Wrap command with environment forwarding
51+
let extra_env = get_base_injected_env(RunnerMode::Memory, &run_data.profile_folder);
52+
let (cmd_builder, env_file) = wrap_with_env(cmd_builder, &extra_env)?;
4553

46-
Ok((ipc_server, cmd_builder))
54+
Ok((ipc_server, cmd_builder, env_file))
4755
}
4856
}
4957

@@ -85,7 +93,7 @@ impl Executor for MemoryExecutor {
8593
// Create the results/ directory inside the profile folder to avoid having memtrack create it with wrong permissions
8694
std::fs::create_dir_all(run_data.profile_folder.join("results"))?;
8795

88-
let (ipc, cmd_builder) = Self::build_memtrack_command(config, run_data)?;
96+
let (ipc, cmd_builder, _env_file) = Self::build_memtrack_command(config, run_data)?;
8997
let cmd = wrap_with_sudo(cmd_builder)?.build();
9098
debug!("cmd: {cmd:?}");
9199

src/run/runner/wall_time/executor.rs

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use crate::run::RunnerMode;
55
use crate::run::instruments::mongo_tracer::MongoTracer;
66
use crate::run::runner::executor::Executor;
77
use crate::run::runner::helpers::command::CommandBuilder;
8-
use crate::run::runner::helpers::env::{get_base_injected_env, is_codspeed_debug_enabled};
8+
use crate::run::runner::helpers::env::get_base_injected_env;
9+
use crate::run::runner::helpers::env::is_codspeed_debug_enabled;
910
use crate::run::runner::helpers::get_bench_command::get_bench_command;
1011
use crate::run::runner::helpers::introspected_golang;
1112
use crate::run::runner::helpers::introspected_nodejs;
1213
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe;
14+
use crate::run::runner::helpers::run_with_env::wrap_with_env;
1315
use crate::run::runner::helpers::run_with_sudo::wrap_with_sudo;
1416
use crate::run::runner::{ExecutorName, RunData};
1517
use crate::run::{check_system::SystemInfo, config::Config};
@@ -69,25 +71,6 @@ impl Drop for HookScriptsGuard {
6971
}
7072
}
7173

72-
/// Returns a list of exported environment variables which can be loaded with `source` in a shell.
73-
///
74-
/// Example: `declare -x outputs="out"`
75-
fn get_exported_system_env() -> Result<String> {
76-
let output = Command::new("bash")
77-
.arg("-c")
78-
.arg("export")
79-
.output()
80-
.context("Failed to run `export`")?;
81-
if !output.status.success() {
82-
bail!(
83-
"Failed to get system environment variables: {}",
84-
String::from_utf8_lossy(&output.stderr)
85-
);
86-
}
87-
88-
String::from_utf8(output.stdout).context("Failed to parse export output as UTF-8")
89-
}
90-
9174
pub struct WallTimeExecutor {
9275
perf: Option<PerfRunner>,
9376
}
@@ -103,19 +86,10 @@ impl WallTimeExecutor {
10386
config: &Config,
10487
run_data: &RunData,
10588
) -> Result<(NamedTempFile, NamedTempFile, CommandBuilder)> {
106-
let bench_cmd = get_bench_command(config)?;
107-
108-
let system_env = get_exported_system_env()?;
109-
let base_injected_env =
110-
get_base_injected_env(RunnerMode::Walltime, &run_data.profile_folder)
111-
.into_iter()
112-
.map(|(k, v)| format!("export {k}={v}",))
113-
.collect::<Vec<_>>()
114-
.join("\n");
115-
89+
// Build additional PATH environment variables
11690
let path_env = std::env::var("PATH").unwrap_or_default();
117-
let path_env = format!(
118-
"export PATH={}:{}:{}",
91+
let path_value = format!(
92+
"{}:{}:{}",
11993
introspected_nodejs::setup()
12094
.map_err(|e| anyhow!("failed to setup NodeJS introspection. {e}"))?
12195
.to_string_lossy(),
@@ -125,18 +99,17 @@ impl WallTimeExecutor {
12599
path_env
126100
);
127101

128-
let combined_env = format!("{system_env}\n{base_injected_env}\n{path_env}");
102+
let mut extra_env = get_base_injected_env(RunnerMode::Walltime, &run_data.profile_folder);
103+
extra_env.insert("PATH", path_value);
129104

130-
let mut env_file = NamedTempFile::new()?;
131-
env_file.write_all(combined_env.as_bytes())?;
105+
// We have to write the benchmark command to a script, to ensure proper formatting
106+
// and to not have to manually escape everything.
107+
let mut script_file = NamedTempFile::new()?;
108+
script_file.write_all(get_bench_command(config)?.as_bytes())?;
132109

133-
let script_file = {
134-
let mut file = NamedTempFile::new()?;
135-
let bash_command = format!("source {} && {}", env_file.path().display(), bench_cmd);
136-
debug!("Bash command: {bash_command}");
137-
file.write_all(bash_command.as_bytes())?;
138-
file
139-
};
110+
let mut bench_cmd = CommandBuilder::new("bash");
111+
bench_cmd.arg(script_file.path());
112+
let (mut bench_cmd, env_file) = wrap_with_env(bench_cmd, &extra_env)?;
140113

141114
let mut cmd_builder = CommandBuilder::new("systemd-run");
142115
if let Some(cwd) = &config.working_directory {
@@ -157,9 +130,11 @@ impl WallTimeExecutor {
157130
cmd_builder.arg("--same-dir");
158131
cmd_builder.arg(format!("--uid={}", nix::unistd::Uid::current().as_raw()));
159132
cmd_builder.arg(format!("--gid={}", nix::unistd::Gid::current().as_raw()));
160-
cmd_builder.args(["--", "bash"]);
161-
cmd_builder.arg(script_file.path());
162-
Ok((env_file, script_file, cmd_builder))
133+
cmd_builder.args(["--"]);
134+
135+
bench_cmd.wrap_with(cmd_builder);
136+
137+
Ok((env_file, script_file, bench_cmd))
163138
}
164139
}
165140

0 commit comments

Comments
 (0)