Skip to content

Commit 673a4a9

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

File tree

9 files changed

+191
-15
lines changed

9 files changed

+191
-15
lines changed

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: 80 additions & 14 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 : Time.Span.t 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,49 @@ let read_exit_codes_and_prefix_maps file =
210212
loop [] (String.split ~on:'\000' s)
211213
;;
212214

215+
let read_times file =
216+
let open Option.O in
217+
let* file = file in
218+
let+ contents = Option.try_with (fun () -> Io.read_file file ~binary:true) in
219+
String.split_lines contents
220+
|> List.filter_map ~f:(fun s ->
221+
match String.trim s with
222+
| "" -> None
223+
| s -> Float.of_string s >>| Time.Span.of_secs)
224+
;;
225+
213226
let read_and_attach_exit_codes (sh_script : sh_script)
214227
: full_block_result Cram_lexer.block list
215228
=
216229
let metadata_entries = read_exit_codes_and_prefix_maps sh_script.metadata_file in
217-
let rec loop acc entries blocks =
230+
let times = read_times sh_script.time_file in
231+
let next_time = function
232+
| None -> None, None
233+
| Some [] -> None, Some []
234+
| Some (time :: times) -> Some time, Some times
235+
in
236+
let rec loop acc entries blocks times =
218237
match blocks, entries with
219238
| [], [] -> List.rev acc
220-
| (Cram_lexer.Comment _ as comment) :: blocks, _ ->
221-
loop (comment :: acc) entries blocks
239+
| (Cram_lexer.Comment _ as comment) :: blocks, __ ->
240+
loop (comment :: acc) entries blocks times
222241
| Command block :: blocks, metadata_entry :: entries ->
223-
loop (Command { block; metadata = Present metadata_entry } :: acc) entries blocks
242+
let duration, times = next_time times in
243+
loop
244+
(Command { block; metadata = Present metadata_entry; duration } :: acc)
245+
entries
246+
blocks
247+
times
224248
| Cram_lexer.Command block :: blocks, [] ->
225-
loop (Command { block; metadata = Missing_unreachable } :: acc) entries blocks
249+
let duration, times = next_time times in
250+
loop
251+
(Command { block; metadata = Missing_unreachable; duration } :: acc)
252+
entries
253+
blocks
254+
times
226255
| [], _ :: _ -> Code_error.raise "more blocks than metadata" []
227256
in
228-
loop [] metadata_entries sh_script.cram_to_output
257+
loop [] metadata_entries sh_script.cram_to_output times
229258
;;
230259

231260
let line_number =
@@ -279,7 +308,7 @@ let sanitize ~parent_script cram_to_output : command_out Cram_lexer.block list =
279308
List.map cram_to_output ~f:(fun (t : full_block_result Cram_lexer.block) ->
280309
match t with
281310
| Cram_lexer.Comment t -> Cram_lexer.Comment t
282-
| Command { block; metadata } ->
311+
| Command { block; metadata; duration = _ } ->
283312
let output =
284313
match metadata with
285314
| Missing_unreachable -> "***** UNREACHABLE *****"
@@ -338,7 +367,9 @@ let cram_commmands commands =
338367
Buffer.contents buf
339368
;;
340369

341-
let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
370+
let create_sh_script cram_stanzas ~temp_dir ~setup_scripts (shell : Cram_stanza.Shell.t)
371+
: sh_script Fiber.t
372+
=
342373
let script = Path.relative temp_dir "main.sh" in
343374
let oc = Io.open_out ~binary:true script in
344375
Fiber.finalize ~finally:(fun () -> Fiber.return @@ close_out oc)
@@ -350,6 +381,18 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
350381
quote_for_sh path
351382
in
352383
let metadata_file = file "cram.metadata" in
384+
let user_shell_time_file =
385+
match shell with
386+
| Sh -> None
387+
| Bash -> Some (file "time")
388+
in
389+
let* user_shell_time_file_sh_path =
390+
match user_shell_time_file with
391+
| None -> Fiber.return None
392+
| Some f ->
393+
let+ file = sh_path f in
394+
Some file
395+
in
353396
let* metadata_file_sh_path = sh_path metadata_file in
354397
let i = ref 0 in
355398
let loop block =
@@ -370,11 +413,18 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
370413
(* Where we store the output of shell code written by the user *)
371414
let user_shell_code_output_file = file ~ext:".output" in
372415
let+ user_shell_code_output_file_sh_path = sh_path user_shell_code_output_file in
416+
let untimed_command =
417+
sprintf
418+
". %s > %s 2>&1"
419+
user_shell_code_file_sh_path
420+
user_shell_code_output_file_sh_path
421+
in
373422
fprln
374423
oc
375-
". %s > %s 2>&1"
376-
user_shell_code_file_sh_path
377-
user_shell_code_output_file_sh_path;
424+
"%s"
425+
(match user_shell_time_file_sh_path with
426+
| None -> untimed_command
427+
| Some time_file -> sprintf "{ time %s; } 2>> %s" untimed_command time_file);
378428
fprln
379429
oc
380430
{|printf "%%d\0%%s\0" $? "$%s" >> %s|}
@@ -396,10 +446,14 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
396446
| External _ -> ()
397447
| In_build_dir _ -> fprln oc "rm -f %s" script_sh_path)
398448
in
449+
(* Needed for us to capture timing information. Users shouldn't really care,
450+
but if they do, they can always set this again within their command (and
451+
break timings) *)
452+
fprln oc "TIMEFORMAT=\"%%3R\"";
399453
let+ cram_to_output = Fiber.sequential_map ~f:loop cram_stanzas in
400454
let command_count = !i in
401455
let metadata_file = Option.some_if (command_count > 0) metadata_file in
402-
{ script; cram_to_output; metadata_file }
456+
{ script; cram_to_output; metadata_file; time_file = user_shell_time_file }
403457
;;
404458

405459
let _display_with_bars s = List.iter (String.split_lines s) ~f:(Printf.eprintf "| %s\n")
@@ -447,7 +501,7 @@ let run_cram_test
447501
(shell : Cram_stanza.Shell.t)
448502
=
449503
let open Fiber.O in
450-
let* sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts in
504+
let* sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts shell in
451505
let env = make_run_env env ~temp_dir ~cwd in
452506
let open Fiber.O in
453507
let sh =
@@ -481,7 +535,19 @@ let run_cram_test
481535
sh
482536
[ Path.to_string sh_script.script ]
483537
>>| function
484-
| Ok () -> read_and_attach_exit_codes sh_script |> sanitize ~parent_script:script
538+
| Ok () ->
539+
let detailed_output = read_and_attach_exit_codes sh_script in
540+
Dune_trace.emit Cram (fun () ->
541+
(* CR-someday rgrinberg: a little lame that we don't have a good way
542+
to relate these to the underlying process event. *)
543+
List.filter_map detailed_output ~f:(function
544+
| Comment _ -> None
545+
| Command { duration; block = { command; _ }; _ } ->
546+
(match duration with
547+
| None -> None
548+
| Some duration -> Some { Dune_trace.Event.Cram.command; dur = duration }))
549+
|> Dune_trace.Event.Cram.test);
550+
sanitize ~parent_script:script detailed_output
485551
| Error `Timed_out ->
486552
let timeout_loc, timeout = Option.value_exn timeout in
487553
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: 10 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,15 @@ 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 command =
133+
{ command : string list
134+
; dur : Time.Span.t
135+
}
136+
137+
val test : command list -> t
138+
end
129139
end
130140

131141
module Out : sig

src/dune_trace/event.ml

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

test/blackbox-tests/test-cases/cram/bash-shell.t

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Demonstrate the shell field in the cram stanza
55
> EOF
66

77
$ printShell() {
8-
> dune trace cat | jq '.[] | select(.cat == "process" and (.args.categories | index("cram"))) | .args | .prog | split("/") | last'
8+
> dune trace cat | jq 'select(.cat == "process" and (.args.categories | index("cram"))) | .args | .prog | split("/") | last'
99
> }
1010

1111
$ cat >foo.t <<'EOF'
@@ -30,3 +30,15 @@ Demonstrate the shell field in the cram stanza
3030
[1]
3131
$ printShell
3232
"bash"
33+
34+
$ dune trace cat | jq 'select(.cat == "cram") | .args | (.. | .dur? // empty) |= "redacted"'
35+
{
36+
"commands": [
37+
{
38+
"command": [
39+
"echo foo"
40+
],
41+
"dur": "redacted"
42+
}
43+
]
44+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Demonstrate command level trace events
2+
3+
$ cat > dune-project <<EOF
4+
> (lang dune 3.22)
5+
> EOF
6+
7+
$ cat > dune <<EOF
8+
> (cram
9+
> (shell bash))
10+
> EOF
11+
12+
$ cat >foo.t <<'EOF'
13+
> $ echo a
14+
> > echo b
15+
> Break
16+
> $ echo c
17+
> EOF
18+
19+
$ dune runtest foo.t
20+
File "foo.t", line 1, characters 0-0:
21+
Error: Files _build/default/foo.t and _build/default/foo.t.corrected differ.
22+
[1]
23+
24+
$ dune trace cat | jq 'select(.cat == "cram") | .args | (.. | .dur? // empty) |= "redacted"'
25+
{
26+
"commands": [
27+
{
28+
"command": [
29+
"echo a",
30+
"echo b"
31+
],
32+
"dur": "redacted"
33+
},
34+
{
35+
"command": [
36+
"echo c"
37+
],
38+
"dur": "redacted"
39+
}
40+
]
41+
}
42+
43+
We have to override TIMEFORMAT to get this timing information:
44+
45+
$ cat >timeformat.t <<'EOF'
46+
> $ echo $TIMEFORMAT
47+
> EOF
48+
49+
$ dune runtest timeformat.t
50+
File "timeformat.t", line 1, characters 0-0:
51+
Error: Files _build/default/timeformat.t and
52+
_build/default/timeformat.t.corrected differ.
53+
[1]
54+
$ dune promotion show timeformat.t
55+
$ echo $TIMEFORMAT
56+
%3R
57+

0 commit comments

Comments
 (0)