Skip to content

Commit dc84227

Browse files
committed
feature: trace events for cram commands
Signed-off-by: Rudi Grinberg <me@rgrinberg.com>
1 parent f47ac1d commit dc84227

9 files changed

Lines changed: 252 additions & 13 deletions

File tree

bin/common.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,7 @@ let init_with_root ~(root : Workspace_root.t) (builder : Builder.t) =
13161316
; Log
13171317
; File_watcher
13181318
; Diagnostics
1319+
; Cram
13191320
]
13201321
| Some s ->
13211322
String.split ~on:',' s

doc/changes/added/13092.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Add timing information for every command executed by cram (#13092,
2+
@rgrinberg)

src/dune_rules/cram/cram_exec.ml

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,14 @@ let dyn_of_metadata_result =
174174
type full_block_result =
175175
{ block : block_result
176176
; metadata : metadata_result
177+
; duration : Dune_trace.Event.Cram.times option
177178
}
178179

179180
type 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

185187
let 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+
213236
let 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

231270
let 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

405482
let _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 =

src/dune_trace/category.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type t =
1717
| File_watcher
1818
| Diagnostics
1919
| Log
20+
| Cram
2021

2122
let all =
2223
[ Rpc
@@ -35,6 +36,7 @@ let all =
3536
; File_watcher
3637
; Diagnostics
3738
; Log
39+
; Cram
3840
]
3941
;;
4042

@@ -55,6 +57,7 @@ let to_string = function
5557
| File_watcher -> "file_watcher"
5658
| Diagnostics -> "diagnostics"
5759
| Log -> "log"
60+
| Cram -> "cram"
5861
;;
5962

6063
let of_string =
@@ -87,5 +90,6 @@ module Set = Bit_set.Make (struct
8790
| File_watcher -> 13
8891
| Diagnostics -> 14
8992
| Log -> 15
93+
| Cram -> 16
9094
;;
9195
end)

src/dune_trace/category.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type t =
1515
| File_watcher
1616
| Diagnostics
1717
| Log
18+
| Cram
1819

1920
val to_string : t -> string
2021
val of_string : string -> t option

src/dune_trace/dune_trace.mli

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module Category : sig
1818
| File_watcher
1919
| Diagnostics
2020
| Log
21+
| Cram
2122

2223
val of_string : string -> t option
2324
end
@@ -126,6 +127,21 @@ module Event : sig
126127
val accept : success:bool -> error:string option -> t
127128
val close : id:int -> t
128129
end
130+
131+
module Cram : sig
132+
type times =
133+
{ real : Time.Span.t
134+
; system : Time.Span.t
135+
; user : Time.Span.t
136+
}
137+
138+
type command =
139+
{ command : string list
140+
; times : times
141+
}
142+
143+
val test : command list -> t
144+
end
129145
end
130146

131147
module Out : sig

src/dune_trace/event.ml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,34 @@ let log { Log.Message.level; message; args } =
432432
in
433433
Event.instant ~args ~name now Log
434434
;;
435+
436+
module Cram = struct
437+
type times =
438+
{ real : Time.Span.t
439+
; system : Time.Span.t
440+
; user : Time.Span.t
441+
}
442+
443+
type command =
444+
{ command : string list
445+
; times : times
446+
}
447+
448+
let test commands =
449+
let now = Time.now () in
450+
let args =
451+
[ ( "commands"
452+
, List.map commands ~f:(fun { command; times = { real; user; system } } ->
453+
Arg.record
454+
[ "command", Arg.list (List.map command ~f:Arg.string)
455+
; "real", Event.make_dur real
456+
; "user", Event.make_dur user
457+
; "system", Event.make_dur system
458+
]
459+
|> Arg.list)
460+
|> Arg.list )
461+
]
462+
in
463+
Event.instant ~args ~name:"cram" now Cram
464+
;;
465+
end

test/blackbox-tests/dune.jq

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
def logs($m):
22
select(.cat == "log" and (.args.message | contains($m))) | .args;
3+
4+
def redactCommandTimes:
5+
walk(if type == "object" then
6+
with_entries(
7+
if .key | IN("dur", "real", "user", "system") then
8+
.value = "redacted"
9+
else . end)
10+
else . end);

0 commit comments

Comments
 (0)