@@ -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,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+
4576pub 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