Skip to content

Commit 998b2e3

Browse files
committed
feat(runner): improve error handling for valgrind
1 parent e031110 commit 998b2e3

1 file changed

Lines changed: 94 additions & 3 deletions

File tree

src/run/runner/valgrind/measure.rs

Lines changed: 94 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,33 @@ 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+
(eval $1)
60+
status=$?
61+
echo -n "$status" > "$2"
62+
"#;
63+
64+
let rwx = std::fs::Permissions::from_mode(0o777);
65+
let mut script_file = tempfile::Builder::new()
66+
.suffix(".sh")
67+
.permissions(rwx)
68+
.tempfile()?;
69+
script_file.write_all(WRAPPER_SCRIPT.as_bytes())?;
70+
71+
// Note: We have to convert the file to a path to be able to execute it.
72+
// Otherwise this will fail with 'File is busy' error.
73+
Ok(script_file.into_temp_path())
74+
}
75+
4576
pub fn measure(
4677
config: &Config,
4778
profile_folder: &Path,
@@ -85,8 +116,14 @@ pub fn measure(
85116
.arg(format!("--callgrind-out-file={}", profile_path.to_str().unwrap()).as_str())
86117
.arg(format!("--log-file={}", log_path.to_str().unwrap()).as_str());
87118

88-
// Set the command to execute
89-
cmd.args(["sh", "-c", get_bench_command(config)?.as_str()]);
119+
// Set the command to execute:
120+
let script_path = create_run_script()?;
121+
let cmd_status_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
122+
cmd.args([
123+
script_path.to_str().unwrap(),
124+
get_bench_command(config)?.as_str(),
125+
cmd_status_path.to_str().unwrap(),
126+
]);
90127

91128
// TODO: refactor and move this to the `Instruments` struct
92129
if let Some(mongo_tracer) = mongo_tracer {
@@ -96,9 +133,63 @@ pub fn measure(
96133
debug!("cmd: {:?}", cmd);
97134
let status = run_command_with_log_pipe(cmd)
98135
.map_err(|e| anyhow!("failed to execute the benchmark process. {}", e))?;
136+
let cmd_status = {
137+
let content = std::fs::read_to_string(&cmd_status_path)?;
138+
content.parse::<u32>()?
139+
};
140+
141+
debug!(
142+
"Valgrind exit code = {:?}, Valgrind signal = {:?}, Program exit code = {}",
143+
status.code(),
144+
status.signal(),
145+
cmd_status
146+
);
147+
148+
// Check the valgrind exit code
99149
if !status.success() {
100-
bail!("failed to execute the benchmark process");
150+
let valgrind_log = profile_folder.join("valgrind.log");
151+
let valgrind_log = std::fs::read_to_string(&valgrind_log).unwrap_or_default();
152+
debug!("valgrind.log: {}", valgrind_log);
153+
154+
bail!("failed to execute valgrind");
155+
}
156+
157+
// Check the exit code which was written to the file by the wrapper script.
158+
if cmd_status != 0 {
159+
bail!(
160+
"failed to execute the benchmark process, exit code: {}",
161+
cmd_status
162+
);
101163
}
102164

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

0 commit comments

Comments
 (0)