Skip to content

Commit 5668cde

Browse files
committed
feat(runner): improve error handling for valgrind
1 parent d6b498f commit 5668cde

1 file changed

Lines changed: 99 additions & 3 deletions

File tree

src/run/runner/valgrind/measure.rs

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ use crate::run::{config::Config, instruments::mongo_tracer::MongoTracer};
99
use lazy_static::lazy_static;
1010
use std::env;
1111
use std::fs::canonicalize;
12+
use std::io::Write;
13+
use std::os::unix::fs::PermissionsExt;
14+
use std::os::unix::process::ExitStatusExt;
1215
use std::path::Path;
1316
use std::{env::consts::ARCH, process::Command};
17+
use tempfile::TempPath;
1418

1519
lazy_static! {
1620
static ref VALGRIND_BASE_ARGS: Vec<String> = {
@@ -42,6 +46,38 @@ lazy_static! {
4246
};
4347
}
4448

49+
/// Creates the shell script on disk and returns the path to it.
50+
fn create_run_script() -> anyhow::Result<TempPath> {
51+
// The command is wrapped in a shell script, which executes it in a
52+
// subprocess and then writes the exit code to a file. The process will
53+
// always exit with status code 0, unless valgrind fails.
54+
//
55+
// Args:
56+
// 1. The command to execute
57+
// 2. The path to the file where the exit code will be written
58+
const WRAPPER_SCRIPT: &str = r#"#!/bin/sh
59+
safe_run()
60+
{
61+
(eval $1)
62+
status=$?
63+
echo -n "$status" > "$2"
64+
}
65+
66+
safe_run "$1" "$2"
67+
"#;
68+
69+
let rwx = std::fs::Permissions::from_mode(0o777);
70+
let mut script_file = tempfile::Builder::new()
71+
.suffix(".sh")
72+
.permissions(rwx)
73+
.tempfile()?;
74+
script_file.write_all(WRAPPER_SCRIPT.as_bytes())?;
75+
76+
// Note: We have to convert the file to a path to be able to execute it.
77+
// Otherwise this will fail with 'File is busy' error.
78+
Ok(script_file.into_temp_path())
79+
}
80+
4581
pub fn measure(
4682
config: &Config,
4783
profile_folder: &Path,
@@ -85,8 +121,14 @@ pub fn measure(
85121
.arg(format!("--callgrind-out-file={}", profile_path.to_str().unwrap()).as_str())
86122
.arg(format!("--log-file={}", log_path.to_str().unwrap()).as_str());
87123

88-
// Set the command to execute
89-
cmd.args(["sh", "-c", get_bench_command(config)?.as_str()]);
124+
// Set the command to execute:
125+
let script_path = create_run_script()?;
126+
let cmd_status_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
127+
cmd.args([
128+
script_path.to_str().unwrap(),
129+
get_bench_command(config)?.as_str(),
130+
cmd_status_path.to_str().unwrap(),
131+
]);
90132

91133
// TODO: refactor and move this to the `Instruments` struct
92134
if let Some(mongo_tracer) = mongo_tracer {
@@ -96,9 +138,63 @@ pub fn measure(
96138
debug!("cmd: {:?}", cmd);
97139
let status = run_command_with_log_pipe(cmd)
98140
.map_err(|e| anyhow!("failed to execute the benchmark process. {}", e))?;
141+
let cmd_status = {
142+
let content = std::fs::read_to_string(&cmd_status_path)?;
143+
content.parse::<u32>()?
144+
};
145+
146+
debug!(
147+
"Valgrind exit code = {:?}, Valgrind signal = {:?}, Program exit code = {}",
148+
status.code(),
149+
status.signal(),
150+
cmd_status
151+
);
152+
153+
// Check the valgrind exit code
99154
if !status.success() {
100-
bail!("failed to execute the benchmark process");
155+
let valgrind_log = profile_folder.join("valgrind.log");
156+
let valgrind_log = std::fs::read_to_string(&valgrind_log).unwrap_or_default();
157+
debug!("valgrind.log: {}", valgrind_log);
158+
159+
bail!("failed to execute valgrind");
160+
}
161+
162+
// Check the exit code which was written to the file by the wrapper script.
163+
if cmd_status != 0 {
164+
bail!(
165+
"failed to execute the benchmark process, exit code: {}",
166+
cmd_status
167+
);
101168
}
102169

103170
Ok(())
104171
}
172+
173+
#[cfg(test)]
174+
mod tests {
175+
use super::*;
176+
177+
fn safe_run(to_execute: &str) -> (u32, u32) {
178+
let script_path = create_run_script().unwrap();
179+
let out_status = tempfile::NamedTempFile::new().unwrap().into_temp_path();
180+
181+
let mut cmd = Command::new(script_path.to_str().unwrap());
182+
cmd.args([to_execute, out_status.to_str().unwrap()]);
183+
184+
let script_status = cmd.status().unwrap().code().unwrap() as u32;
185+
let out_status = std::fs::read_to_string(out_status)
186+
.unwrap()
187+
.parse::<u32>()
188+
.unwrap();
189+
190+
(script_status, out_status)
191+
}
192+
193+
#[test]
194+
fn test_run_wrapper_script() {
195+
assert_eq!(safe_run("ls"), (0, 0));
196+
assert_eq!(safe_run("exit 0"), (0, 0));
197+
assert_eq!(safe_run("exit 1"), (0, 1));
198+
assert_eq!(safe_run("exit 255"), (0, 255));
199+
}
200+
}

0 commit comments

Comments
 (0)