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