@@ -174,12 +174,14 @@ let dyn_of_metadata_result =
174174type full_block_result =
175175 { block : block_result
176176 ; metadata : metadata_result
177+ ; duration : Dune_trace.Event.Cram .times option
177178 }
178179
179180type sh_script =
180181 { script : Path .t
181182 ; cram_to_output : block_result Cram_lexer .block list
182183 ; metadata_file : Path .t option
184+ ; time_file : Path .t option
183185 }
184186
185187let read_exit_codes_and_prefix_maps file =
@@ -210,22 +212,59 @@ let read_exit_codes_and_prefix_maps file =
210212 loop [] (String. split ~on: '\000' s)
211213;;
212214
215+ let read_times =
216+ let make_time s = Float. of_string s |> Option. value_exn |> Time.Span. of_secs in
217+ fun file ->
218+ let open Option.O in
219+ let * file = file in
220+ let + contents = Option. try_with (fun () -> Io. read_file file ~binary: true ) in
221+ String. split_lines contents
222+ |> List. filter_map ~f: (fun s ->
223+ match String. trim s with
224+ | "" -> None
225+ | s ->
226+ (match String. split_on_char s ~sep: '|' with
227+ | [ real; user; system ] ->
228+ Option. try_with (fun () ->
229+ { Dune_trace.Event.Cram. real = make_time real
230+ ; user = make_time user
231+ ; system = make_time system
232+ })
233+ | _ -> None ))
234+ ;;
235+
213236let read_and_attach_exit_codes (sh_script : sh_script )
214237 : full_block_result Cram_lexer. block list
215238 =
216239 let metadata_entries = read_exit_codes_and_prefix_maps sh_script.metadata_file in
217- let rec loop acc entries blocks =
240+ let times = read_times sh_script.time_file in
241+ let next_time = function
242+ | None -> None , None
243+ | Some [] -> None , Some []
244+ | Some (time :: times ) -> Some time, Some times
245+ in
246+ let rec loop acc entries blocks times =
218247 match blocks, entries with
219248 | [] , [] -> List. rev acc
220249 | (Cram_lexer. Comment _ as comment ) :: blocks , _ ->
221- loop (comment :: acc) entries blocks
250+ loop (comment :: acc) entries blocks times
222251 | Command block :: blocks , metadata_entry :: entries ->
223- loop (Command { block; metadata = Present metadata_entry } :: acc) entries blocks
252+ let duration, times = next_time times in
253+ loop
254+ (Command { block; metadata = Present metadata_entry; duration } :: acc)
255+ entries
256+ blocks
257+ times
224258 | Cram_lexer. Command block :: blocks , [] ->
225- loop (Command { block; metadata = Missing_unreachable } :: acc) entries blocks
259+ let duration, times = next_time times in
260+ loop
261+ (Command { block; metadata = Missing_unreachable ; duration } :: acc)
262+ entries
263+ blocks
264+ times
226265 | [] , _ :: _ -> Code_error. raise " more blocks than metadata" []
227266 in
228- loop [] metadata_entries sh_script.cram_to_output
267+ loop [] metadata_entries sh_script.cram_to_output times
229268;;
230269
231270let line_number =
@@ -279,7 +318,7 @@ let sanitize ~parent_script cram_to_output : command_out Cram_lexer.block list =
279318 List. map cram_to_output ~f: (fun (t : full_block_result Cram_lexer.block ) ->
280319 match t with
281320 | Cram_lexer. Comment t -> Cram_lexer. Comment t
282- | Command { block; metadata } ->
321+ | Command { block; metadata; duration = _ } ->
283322 let output =
284323 match metadata with
285324 | Missing_unreachable -> " ***** UNREACHABLE *****"
@@ -338,7 +377,20 @@ let cram_commmands commands =
338377 Buffer. contents buf
339378;;
340379
341- let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber. t =
380+ let timeformat =
381+ lazy
382+ (let format =
383+ let precision = 3 in
384+ [ 'R' ; 'S' ; 'U' ]
385+ |> List. map ~f: (fun what -> sprintf " %%%d%c" precision what)
386+ |> String. concat ~sep: " |"
387+ in
388+ sprintf {| TIMEFORMAT = " %s" | } format)
389+ ;;
390+
391+ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts (shell : Cram_stanza.Shell.t )
392+ : sh_script Fiber. t
393+ =
342394 let script = Path. relative temp_dir " main.sh" in
343395 let oc = Io. open_out ~binary: true script in
344396 Fiber. finalize ~finally: (fun () -> Fiber. return @@ close_out oc)
@@ -350,6 +402,18 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
350402 quote_for_sh path
351403 in
352404 let metadata_file = file " cram.metadata" in
405+ let user_shell_time_file =
406+ match shell with
407+ | Sh -> None
408+ | Bash -> Some (file " time" )
409+ in
410+ let * user_shell_time_file_sh_path =
411+ match user_shell_time_file with
412+ | None -> Fiber. return None
413+ | Some f ->
414+ let + file = sh_path f in
415+ Some file
416+ in
353417 let * metadata_file_sh_path = sh_path metadata_file in
354418 let i = ref 0 in
355419 let loop block =
@@ -370,11 +434,18 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
370434 (* Where we store the output of shell code written by the user *)
371435 let user_shell_code_output_file = file ~ext: " .output" in
372436 let + user_shell_code_output_file_sh_path = sh_path user_shell_code_output_file in
437+ let untimed_command =
438+ sprintf
439+ " . %s > %s 2>&1"
440+ user_shell_code_file_sh_path
441+ user_shell_code_output_file_sh_path
442+ in
373443 fprln
374444 oc
375- " . %s > %s 2>&1"
376- user_shell_code_file_sh_path
377- user_shell_code_output_file_sh_path;
445+ " %s"
446+ (match user_shell_time_file_sh_path with
447+ | None -> untimed_command
448+ | Some time_file -> sprintf " { time %s; } 2>> %s" untimed_command time_file);
378449 fprln
379450 oc
380451 {| printf " %%d\0 %%s\0 " $? " $%s" >> % s| }
@@ -396,10 +467,16 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
396467 | External _ -> ()
397468 | In_build_dir _ -> fprln oc " rm -f %s" script_sh_path)
398469 in
470+ (* Needed for us to capture timing information. Users shouldn't really care,
471+ but if they do, they can always set this again within their command (and
472+ break timings) *)
473+ (match shell with
474+ | Sh -> ()
475+ | Bash -> fprln oc " %s" (Lazy. force timeformat));
399476 let + cram_to_output = Fiber. sequential_map ~f: loop cram_stanzas in
400477 let command_count = ! i in
401478 let metadata_file = Option. some_if (command_count > 0 ) metadata_file in
402- { script; cram_to_output; metadata_file }
479+ { script; cram_to_output; metadata_file; time_file = user_shell_time_file }
403480;;
404481
405482let _display_with_bars s = List. iter (String. split_lines s) ~f: (Printf. eprintf " | %s\n " )
@@ -447,7 +524,7 @@ let run_cram_test
447524 (shell : Cram_stanza.Shell.t )
448525 =
449526 let open Fiber.O in
450- let * sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts in
527+ let * sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts shell in
451528 let env = make_run_env env ~temp_dir ~cwd in
452529 let open Fiber.O in
453530 let sh =
@@ -481,7 +558,19 @@ let run_cram_test
481558 sh
482559 [ Path. to_string sh_script.script ]
483560 >> | function
484- | Ok () -> read_and_attach_exit_codes sh_script |> sanitize ~parent_script: script
561+ | Ok () ->
562+ let detailed_output = read_and_attach_exit_codes sh_script in
563+ Dune_trace. emit Cram (fun () ->
564+ (* CR-someday rgrinberg: a little lame that we don't have a good way
565+ to relate these to the underlying process event. *)
566+ List. filter_map detailed_output ~f: (function
567+ | Comment _ -> None
568+ | Command { duration; block = { command; _ } ; _ } ->
569+ (match duration with
570+ | None -> None
571+ | Some times -> Some { Dune_trace.Event.Cram. command; times }))
572+ |> Dune_trace.Event.Cram. test);
573+ sanitize ~parent_script: script detailed_output
485574 | Error `Timed_out ->
486575 let timeout_loc, timeout = Option. value_exn timeout in
487576 let timeout_set_message =
0 commit comments