@@ -9,8 +9,12 @@ use crate::run::{config::Config, instruments::mongo_tracer::MongoTracer};
99use lazy_static:: lazy_static;
1010use std:: env;
1111use std:: fs:: canonicalize;
12+ use std:: io:: Write ;
13+ use std:: os:: unix:: fs:: PermissionsExt ;
14+ use std:: os:: unix:: process:: ExitStatusExt ;
1215use std:: path:: Path ;
1316use std:: { env:: consts:: ARCH , process:: Command } ;
17+ use tempfile:: TempPath ;
1418
1519lazy_static ! {
1620 static ref VALGRIND_BASE_ARGS : Vec <String > = {
@@ -42,6 +46,63 @@ lazy_static! {
4246 } ;
4347}
4448
49+ /// Creates the command that executes the specified command safely.
50+ ///
51+ /// # Returns
52+ ///
53+ /// A tuple containing:
54+ /// 1. The command to execute
55+ /// 2. The path to the wrapper shell script file
56+ /// 3. The path to the file containing the exit code
57+ fn safe_run_cmd ( to_execute : & str ) -> anyhow:: Result < ( String , TempPath , TempPath ) > {
58+ const WRAPPER_SCRIPT : & str = r#"
59+ #!/bin/sh
60+
61+ safe_run()
62+ {
63+ (eval $1)
64+ status=$?
65+ echo -n "$status" > "$2"
66+ }
67+
68+ # Args:
69+ # 1. Cmd that should be executed
70+ # 2. File to write the status code to
71+ if [ "$#" -ne 2 ]; then
72+ echo "Usage: $0 <cmd> <file>"
73+ exit 1
74+ fi
75+
76+ safe_run "$1" "$2"
77+ "# ;
78+
79+ // The command is wrapped in a shell script, which executes it in a
80+ // subprocess and then writes the exit code to a file. The process will
81+ // always exit with status code 0, unless valgrind fails.
82+ let script_path = {
83+ let rwx = std:: fs:: Permissions :: from_mode ( 0o777 ) ;
84+ let mut script_file = tempfile:: Builder :: new ( )
85+ . suffix ( ".sh" )
86+ . permissions ( rwx)
87+ . tempfile ( ) ?;
88+ script_file. write_all ( WRAPPER_SCRIPT . as_bytes ( ) ) ?;
89+
90+ // Note: We have to convert the fiel to a path to be able to execute it.
91+ // Otherwise this will fail with 'File is busy' error.
92+ script_file. into_temp_path ( )
93+ } ;
94+
95+ let cmd_status_path = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) . into_temp_path ( ) ;
96+ let run_cmd = format ! (
97+ "{} \" {}\" \" {}\" " ,
98+ script_path. to_str( ) . unwrap( ) ,
99+ to_execute,
100+ cmd_status_path. to_str( ) . unwrap( )
101+ ) ;
102+
103+ Ok ( ( run_cmd, script_path, cmd_status_path) )
104+ }
105+
45106pub fn measure (
46107 config : & Config ,
47108 profile_folder : & Path ,
@@ -84,9 +145,9 @@ pub fn measure(
84145 )
85146 . arg ( format ! ( "--callgrind-out-file={}" , profile_path. to_str( ) . unwrap( ) ) . as_str ( ) )
86147 . arg ( format ! ( "--log-file={}" , log_path. to_str( ) . unwrap( ) ) . as_str ( ) ) ;
87-
88- // Set the command to execute
89- cmd. args ( [ "sh" , "-c" , get_bench_command ( config ) ? . as_str ( ) ] ) ;
148+ // Set the command to execute:
149+ let ( run_cmd , _script , cmd_status_file ) = safe_run_cmd ( get_bench_command ( config ) ? . as_str ( ) ) ? ;
150+ cmd. args ( [ "sh" , "-c" , & run_cmd ] ) ;
90151
91152 // TODO: refactor and move this to the `Instruments` struct
92153 if let Some ( mongo_tracer) = mongo_tracer {
@@ -96,9 +157,62 @@ pub fn measure(
96157 debug ! ( "cmd: {:?}" , cmd) ;
97158 let status = run_command_with_log_pipe ( cmd)
98159 . map_err ( |e| anyhow ! ( "failed to execute the benchmark process. {}" , e) ) ?;
160+ let cmd_status = {
161+ let content = std:: fs:: read_to_string ( & cmd_status_file) ?;
162+ content. parse :: < u32 > ( ) ?
163+ } ;
164+
165+ debug ! (
166+ "Valgrind exit code = {:?}, Valgrind signal = {:?}, Program exit code = {}" ,
167+ status. code( ) ,
168+ status. signal( ) ,
169+ cmd_status
170+ ) ;
171+
172+ // Check the valgrind exit code
99173 if !status. success ( ) {
100- bail ! ( "failed to execute the benchmark process" ) ;
174+ let valgrind_log = profile_folder. join ( "valgrind.log" ) ;
175+ let valgrind_log = std:: fs:: read_to_string ( & valgrind_log) . unwrap_or_default ( ) ;
176+ debug ! ( "valgrind.log: {}" , valgrind_log) ;
177+
178+ bail ! ( "failed to execute valgrind" ) ;
179+ }
180+
181+ // Check the exit code which was written to the file by the wrapper script.
182+ if cmd_status != 0 {
183+ bail ! (
184+ "failed to execute the benchmark process, exit code: {}" ,
185+ cmd_status
186+ ) ;
101187 }
102188
103189 Ok ( ( ) )
104190}
191+
192+ #[ cfg( test) ]
193+ mod tests {
194+ use super :: * ;
195+
196+ fn safe_run ( to_execute : & str ) -> String {
197+ let ( run_cmd, _script, out_status) = safe_run_cmd ( to_execute) . unwrap ( ) ;
198+
199+ let mut cmd = Command :: new ( "sh" ) ;
200+ cmd. args ( [ "-c" , & run_cmd] ) ;
201+
202+ assert ! ( cmd. output( ) . unwrap( ) . status. success( ) ) ;
203+
204+ std:: fs:: read_to_string ( out_status) . unwrap ( )
205+ }
206+
207+ #[ test]
208+ fn test_run_wrapper_script ( ) {
209+ let output = safe_run ( "ls" ) ;
210+ assert_eq ! ( output, "0" ) ;
211+
212+ let output = safe_run ( "exit 1" ) ;
213+ assert_eq ! ( output, "1" ) ;
214+
215+ let output = safe_run ( "exit 42" ) ;
216+ assert_eq ! ( output, "42" ) ;
217+ }
218+ }
0 commit comments