Skip to content

Commit ec5dff7

Browse files
Merge pull request #19 from ocaml-multicore/better-traces-output
Better traces
2 parents 8a518dd + 13b71b0 commit ec5dff7

20 files changed

+23744
-22
lines changed

.ocamlformat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
profile = default
2-
version = 0.24.1
2+
version = 0.25.1

dscheck.opam

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ depends: [
1010
"ocaml" {>= "5.0.0"}
1111
"dune" {>= "2.9"}
1212
"containers"
13+
"tsort"
1314
"oseq"
1415
"alcotest" {>= "1.6.0" & with-test}
1516
"odoc" {with-doc}

dune-project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
(package
1212
(name dscheck)
1313
(synopsis "Traced Atomics")
14-
(depends (ocaml (>= 5.0.0)) dune containers oseq (alcotest (and (>= 1.6.0) :with-test))))
14+
(depends (ocaml (>= 5.0.0)) dune containers tsort oseq (alcotest (and (>= 1.6.0) :with-test))))
1515

1616

gen_traces.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
set -eux pipefail
2+
3+
dscheck_trace_file="tests/traces/ms_queue" dune exec tests/test_michael_scott_queue.exe
4+
dscheck_trace_file="tests/traces/naive_counter" dune exec tests/test_naive_counter.exe
5+
dscheck_trace_file="tests/traces/list" dune exec tests/test_list.exe
6+
dscheck_trace_file="tests/traces/conditional_nested" dune exec tests/test_conditional_nested.exe
7+
dscheck_trace_file="tests/traces/conditional_ssb" dune exec tests/test_conditional_ssb.exe

src/trace_tracker.ml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
module Op = struct
2+
type t = { proc : int; variable : int; step : int }
3+
4+
let is_dependent t1 t2 = t1.variable == t2.variable
5+
6+
let compare_proc_step t1 t2 =
7+
let c1 = Int.compare t1.proc t2.proc in
8+
if c1 <> 0 then c1 else Int.compare t1.step t2.step
9+
10+
let to_str t = Printf.sprintf "(%d,%c)" t.proc (Char.chr (t.variable + 96))
11+
end
12+
13+
module Trace = struct
14+
module Key = struct
15+
type t = (Op.t * Op.t option) list
16+
17+
let compare t1 t2 =
18+
List.compare
19+
(fun (op1, dep1) (op2, dep2) ->
20+
let c1 = Op.compare_proc_step op1 op2 in
21+
if c1 <> 0 then c1 else Option.compare Op.compare_proc_step dep1 dep2)
22+
t1 t2
23+
end
24+
25+
type t = Op.t List.t
26+
27+
let of_schedule_for_checks schedule_for_checks : t =
28+
let steps = Hashtbl.create 10 in
29+
List.map
30+
(fun (proc, _, variable) ->
31+
Option.map
32+
(fun variable : Op.t ->
33+
let current =
34+
Hashtbl.find_opt steps proc |> Option.value ~default:0
35+
in
36+
Hashtbl.replace steps proc (current + 1);
37+
let step = Hashtbl.find steps proc in
38+
39+
{ proc; variable; step })
40+
variable)
41+
schedule_for_checks
42+
|> List.filter_map Fun.id
43+
44+
let to_string t = List.map Op.to_str t |> String.concat ","
45+
46+
let tag_with_deps (t : t) : Key.t =
47+
let next_dep op t = List.find_opt (Op.is_dependent op) t in
48+
let rec attach_deps = function
49+
| [] -> []
50+
| hd :: [] -> [ (hd, None) ]
51+
| hd :: tl -> (hd, next_dep hd tl) :: attach_deps tl
52+
in
53+
let tagged = attach_deps t in
54+
List.sort (fun (op1, _) (op2, _) -> Op.compare_proc_step op1 op2) tagged
55+
56+
let deps_to_str (key : Key.t) : string =
57+
List.map
58+
(fun (op, dep) ->
59+
Op.to_str op ^ "-"
60+
^ (Option.map Op.to_str dep |> Option.value ~default:"none"))
61+
key
62+
|> String.concat ","
63+
end
64+
65+
module TraceMap = Map.Make (Trace.Key)
66+
67+
type t = Trace.t TraceMap.t
68+
69+
let traces = ref TraceMap.empty
70+
71+
let add_trace trace =
72+
let trace = Trace.of_schedule_for_checks trace in
73+
let key = Trace.tag_with_deps trace in
74+
traces :=
75+
TraceMap.update key
76+
(function Some v -> Some v | None -> Some trace)
77+
!traces
78+
79+
let print traces channel =
80+
Printf.fprintf channel "----\n";
81+
TraceMap.iter
82+
(fun _ trace -> Printf.fprintf channel "%s\n" (Trace.to_string trace))
83+
traces;
84+
Printf.fprintf channel "----\n";
85+
flush channel
86+
87+
let print_traces chan = print !traces chan
88+
let clear_traces () = traces := TraceMap.empty
89+
let get_traces () = !traces
90+
91+
let get_deps_str traces =
92+
TraceMap.to_seq traces |> List.of_seq
93+
|> List.map (fun (_, value) -> value)
94+
|> List.map Trace.tag_with_deps
95+
|> List.map Trace.deps_to_str |> String.concat "\n"
96+
97+
let equal t1 t2 =
98+
TraceMap.compare
99+
(fun _ _ ->
100+
0
101+
(* any values under the same key are known to be equivalent, even if the exact sequence is not identical *))
102+
t1 t2
103+
== 0

src/trace_tracker.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type t
2+
3+
val add_trace : (int * 'a * int option) list -> unit
4+
val clear_traces : unit -> unit
5+
val get_traces : unit -> t
6+
val print_traces : out_channel -> unit
7+
val print : t -> out_channel -> unit
8+
val equal : t -> t -> bool
9+
val get_deps_str : t -> string

src/tracedAtomic.ml

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ type _ Effect.t +=
1212
| FetchAndAdd : (int t * int) -> int Effect.t
1313

1414
module IntSet = Set.Make (Int)
15-
1615
module IntMap = Map.Make (Int)
1716

1817
let _string_of_set s = IntSet.fold (fun y x -> string_of_int y ^ "," ^ x) s ""
@@ -193,11 +192,54 @@ type state_cell = {
193192
mutable backtrack : IntSet.t;
194193
}
195194

196-
let num_runs = ref 0
195+
let num_states = ref 0
196+
let num_interleavings = ref 0
197197

198198
(* we stash the current state in case a check fails and we need to log it *)
199199
let schedule_for_checks = ref []
200200

201+
let var_name i =
202+
match i with
203+
| None -> ""
204+
| Some i ->
205+
let c = Char.chr (i + 96) in
206+
Printf.sprintf "%c" c
207+
208+
let print_execution_sequence chan =
209+
let highest_proc =
210+
List.fold_left
211+
(fun highest (curr_proc, _, _) ->
212+
if curr_proc > highest then curr_proc else highest)
213+
(-1) !schedule_for_checks
214+
in
215+
216+
let bar =
217+
List.init ((highest_proc * 20) + 20) (fun _ -> "-") |> String.concat ""
218+
in
219+
Printf.fprintf chan "\nsequence %d\n" !num_interleavings;
220+
Printf.fprintf chan "%s\n" bar;
221+
List.init (highest_proc + 1) (fun proc ->
222+
Printf.fprintf chan "P%d\t\t\t" proc)
223+
|> ignore;
224+
Printf.fprintf chan "\n%s\n" bar;
225+
226+
List.iter
227+
(fun s ->
228+
match s with
229+
| last_run_proc, last_run_op, last_run_ptr ->
230+
let last_run_ptr = var_name last_run_ptr in
231+
let tabs =
232+
List.init last_run_proc (fun _ -> "\t\t\t") |> String.concat ""
233+
in
234+
Printf.fprintf chan "%s%s %s\n" tabs
235+
(atomic_op_str last_run_op)
236+
last_run_ptr)
237+
!schedule_for_checks;
238+
Printf.fprintf chan "%s\n%!" bar
239+
240+
let interleavings_chan = (ref None : out_channel option ref)
241+
let record_traces_flag = ref false
242+
201243
let do_run init_func init_schedule =
202244
init_func ();
203245
(*set up run *)
@@ -214,6 +256,15 @@ let do_run init_func init_schedule =
214256
| [] ->
215257
if !finished_processes == num_processes then (
216258
tracing := false;
259+
260+
num_interleavings := !num_interleavings + 1;
261+
if !record_traces_flag then
262+
Trace_tracker.add_trace !schedule_for_checks;
263+
264+
(match !interleavings_chan with
265+
| None -> ()
266+
| Some chan -> print_execution_sequence chan);
267+
217268
!final_func ();
218269
tracing := true)
219270
| (process_id_to_run, next_op, next_ptr) :: schedule ->
@@ -231,8 +282,8 @@ let do_run init_func init_schedule =
231282
run_trace init_schedule ();
232283
finished_processes := 0;
233284
tracing := false;
234-
num_runs := !num_runs + 1;
235-
if !num_runs mod 1000 == 0 then Printf.printf "run: %d\n%!" !num_runs;
285+
num_states := !num_states + 1;
286+
(* if !num_states mod 1000 == 0 then Printf.printf "run: %d\n%!" !num_states; *)
236287
let procs =
237288
CCVector.mapi
238289
(fun i p -> { proc_id = i; op = p.next_op; obj_ptr = p.next_repr })
@@ -304,31 +355,45 @@ let check f =
304355
let tracing_at_start = !tracing in
305356
tracing := false;
306357
if not (f ()) then (
307-
Printf.printf "Found assertion violation at run %d:\n" !num_runs;
308-
List.iter
309-
(fun s ->
310-
match s with
311-
| last_run_proc, last_run_op, last_run_ptr ->
312-
let last_run_ptr =
313-
Option.map string_of_int last_run_ptr |> Option.value ~default:""
314-
in
315-
Printf.printf "Process %d: %s %s\n" last_run_proc
316-
(atomic_op_str last_run_op)
317-
last_run_ptr)
318-
!schedule_for_checks;
358+
Printf.printf "Found assertion violation at run %d:\n" !num_interleavings;
359+
print_execution_sequence stdout;
319360
assert false);
320361
tracing := tracing_at_start
321362

322363
let reset_state () =
323364
finished_processes := 0;
324365
atomics_counter := 1;
325-
num_runs := 0;
366+
num_states := 0;
367+
num_interleavings := 0;
326368
schedule_for_checks := [];
369+
Trace_tracker.clear_traces ();
327370
CCVector.clear processes
328371

329-
let trace func =
372+
let dscheck_trace_file_env = Sys.getenv_opt "dscheck_trace_file"
373+
374+
let dpor func =
330375
reset_state ();
331376
let empty_state = do_run func [ (0, Start, None) ] :: [] in
332377
let empty_clock = IntMap.empty in
333378
let empty_last_access = IntMap.empty in
334379
explore func empty_state empty_clock empty_last_access
380+
381+
let trace ?interleavings ?(record_traces = false) func =
382+
record_traces_flag := record_traces || Option.is_some dscheck_trace_file_env;
383+
interleavings_chan := interleavings;
384+
385+
dpor func;
386+
387+
(* print reports *)
388+
(match !interleavings_chan with
389+
| None -> ()
390+
| Some chan ->
391+
Printf.fprintf chan "\nexplored %d interleavings and %d states\n"
392+
!num_interleavings !num_states);
393+
394+
match dscheck_trace_file_env with
395+
| None -> ()
396+
| Some path ->
397+
let chan = open_out path in
398+
Trace_tracker.print_traces chan;
399+
close_out chan

src/tracedAtomic.mli

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,16 @@ val incr : int t -> unit
4646
val decr : int t -> unit
4747
(** [decr r] atomically decrements the value of [r] by [1]. *)
4848

49-
val trace : (unit -> unit) -> unit
50-
(** start the simulation trace *)
49+
val trace :
50+
?interleavings:out_channel -> ?record_traces:bool -> (unit -> unit) -> unit
51+
(** [trace ?interleavings ?record_traces f] starts the simulation trace.
52+
53+
If [interleavings] output channel is provided, DSCheck will continously
54+
print the visited interleavings there.
55+
56+
[record_traces] enables [Trace_tracker], which is typically used for
57+
testing DSCheck itself.
58+
*)
5159

5260
val spawn : (unit -> unit) -> unit
5361
(** spawn [f] as a new 'thread' *)

tests/dune

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,28 @@
1212
(name test_michael_scott_queue)
1313
(libraries dscheck alcotest)
1414
(modules test_michael_scott_queue michael_scott_queue))
15+
16+
(test
17+
(name test_trace)
18+
(libraries dscheck alcotest)
19+
(modules test_trace))
20+
21+
(rule
22+
(with-stdout-to
23+
report_trace.output
24+
(run ./test_trace.exe)))
25+
26+
(rule
27+
(alias runtest)
28+
(action
29+
(diff report_trace.expected report_trace.output)))
30+
31+
(test
32+
(name test_conditional_nested)
33+
(libraries dscheck alcotest)
34+
(modules test_conditional_nested))
35+
36+
(test
37+
(name test_conditional_ssb)
38+
(libraries dscheck alcotest)
39+
(modules test_conditional_ssb))

tests/report_trace.expected

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
sequence 1
3+
----------------------------------------
4+
P0 P1
5+
----------------------------------------
6+
start
7+
fetch_and_add a
8+
start
9+
fetch_and_add a
10+
fetch_and_add b
11+
----------------------------------------
12+
13+
sequence 2
14+
----------------------------------------
15+
P0 P1
16+
----------------------------------------
17+
start
18+
start
19+
fetch_and_add a
20+
fetch_and_add a
21+
fetch_and_add b
22+
----------------------------------------
23+
24+
sequence 3
25+
----------------------------------------
26+
P0 P1
27+
----------------------------------------
28+
start
29+
start
30+
fetch_and_add a
31+
fetch_and_add a
32+
fetch_and_add b
33+
----------------------------------------
34+
35+
explored 3 interleavings and 12 states
36+
----
37+
(1,a),(0,a),(1,b)
38+
(0,a),(1,a),(1,b)
39+
----

0 commit comments

Comments
 (0)