Skip to content

Commit 615a649

Browse files
authored
bench: add GC stats to bench (ocaml#8063)
Signed-off-by: Ali Caglayan <[email protected]>
1 parent a0364ad commit 615a649

File tree

7 files changed

+337
-44
lines changed

7 files changed

+337
-44
lines changed

bench/bench.ml

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -90,32 +90,83 @@ let prepare_workspace () =
9090
Format.eprintf "cloning %s/%s@." pkg.org pkg.name;
9191
Package.clone pkg)
9292

93-
let dune_build () =
93+
let dune_build ~name =
9494
let stdin_from = Process.(Io.null In) in
9595
let stdout_to = Process.Io.make_stdout Swallow in
9696
let stderr_to = Process.Io.make_stderr Swallow in
97+
let gc_dump = Temp.create File ~prefix:"gc_stat" ~suffix:name in
9798
let open Fiber.O in
99+
(* Build with timings and gc stats *)
98100
let+ times =
99101
Process.run_with_times dune ~display:Quiet ~stdin_from ~stdout_to ~stderr_to
100-
[ "build"; "@install"; "--release" ]
102+
[ "build"
103+
; "@install"
104+
; "--release"
105+
; "--dump-gc-stats"
106+
; Path.to_string gc_dump
107+
]
101108
in
102-
times.elapsed_time
109+
(* Read the gc stats from the dump file *)
110+
Dune_lang.Parser.parse_string ~mode:Single ~fname:(Path.to_string gc_dump)
111+
(Io.read_file gc_dump)
112+
|> Dune_lang.Decoder.parse Dune_util.Gc.decode Univ_map.empty
113+
|> Metrics.make times
103114

104115
let run_bench () =
105116
let open Fiber.O in
106-
let* clean = dune_build () in
117+
let* clean = dune_build ~name:"clean" in
107118
let+ zero =
108-
let open Fiber.O in
109119
let rec zero acc n =
110120
if n = 0 then Fiber.return (List.rev acc)
111121
else
112-
let* time = dune_build () in
122+
let* time = dune_build ~name:("zero" ^ string_of_int n) in
113123
zero (time :: acc) (pred n)
114124
in
115125
zero [] 5
116126
in
117127
(clean, zero)
118128

129+
let display what units clean zero =
130+
{ Output.name = what
131+
; metrics =
132+
[ ("[Clean] " ^ what, clean, units); ("[Null] " ^ what, zero, units) ]
133+
}
134+
135+
let results clean zero size =
136+
(* tagging data for json conversion *)
137+
let tag data = Metrics.map ~f:(fun t -> `Float t) ~g:(fun t -> `Int t) data in
138+
let clean = tag clean in
139+
let zero =
140+
List.map zero ~f:tag |> Metrics.unzip
141+
|> Metrics.map ~f:(fun x -> `List x) ~g:(fun x -> `List x)
142+
in
143+
(* bench results *)
144+
[ display "Build Time" "Seconds" clean.elapsed_time zero.elapsed_time
145+
; { Output.name = "Misc"
146+
; metrics = [ ("Size of _boot/dune.exe", `Int size, "Bytes") ]
147+
}
148+
; display "User CPU Time" "Seconds" clean.user_cpu_time zero.user_cpu_time
149+
; display "System CPU Time" "Seconds" clean.system_cpu_time
150+
zero.system_cpu_time
151+
; display "Minor Words" "Approx. Words" clean.minor_words zero.minor_words
152+
; display "Major Words" "Approx. Words" clean.major_words zero.major_words
153+
; display "Minor Collections" "Collections" clean.minor_collections
154+
zero.minor_collections
155+
; display "Major Collections" "Collections" clean.major_collections
156+
zero.major_collections
157+
; display "Heap Words" "Words" clean.heap_words zero.heap_words
158+
; display "Heap Chunks" "Chunks" clean.heap_chunks zero.heap_chunks
159+
; display "Live Words" "Words" clean.live_words zero.live_words
160+
; display "Live Blocks" "Blocks" clean.live_blocks zero.live_blocks
161+
; display "Free Words" "Words" clean.free_words zero.free_words
162+
; display "Free Blocks" "Blocks" clean.free_blocks zero.free_blocks
163+
; display "Largest Free" "Words" clean.largest_free zero.largest_free
164+
; display "Fragments" "Fragments" clean.fragments zero.fragments
165+
; display "Compactions" "Compactions" clean.compactions zero.compactions
166+
; display "Top Heap Words" "Words" clean.top_heap_words zero.top_heap_words
167+
; display "Stack Size" "Words" clean.stack_size zero.stack_size
168+
]
169+
119170
let () =
120171
Dune_util.Log.init ~file:No_log_file ();
121172
let dir = Temp.create Dir ~prefix:"dune" ~suffix:"bench" in
@@ -140,24 +191,12 @@ let () =
140191
let* () = prepare_workspace () in
141192
run_bench ())
142193
in
143-
let zero = List.map zero ~f:(fun t -> `Float t) in
144194
let size =
145195
let stat : Unix.stats = Path.stat_exn dune in
146196
stat.st_size
147197
in
148-
let results =
149-
[ { Output.name = "Build times"
150-
; metrics =
151-
[ ("Clean build time", `Float clean, "secs")
152-
; ("Null build time", `List zero, "secs")
153-
]
154-
}
155-
; { Output.name = "Misc"
156-
; metrics = [ ("Size of _boot/dune.exe", `Int size, "bytes") ]
157-
}
158-
]
159-
in
160-
let version = 2 in
198+
let results = results clean zero size in
199+
let version = 3 in
161200
let output = { Output.config = []; version; results } in
162201
print_string (Json.to_string (Output.to_json output));
163202
flush stdout

bench/bench.mli

Whitespace-only changes.

bench/dune

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
(executable
22
(name bench)
3-
(modules bench)
4-
(libraries dune_stats chrome_trace stdune fiber dune_engine dune_util))
3+
(modules bench metrics)
4+
(libraries
5+
dune_stats
6+
chrome_trace
7+
stdune
8+
fiber
9+
dune_lang
10+
dune_engine
11+
dune_util))
512

613
(rule
714
(alias bench)

bench/metrics.ml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
open Stdune
2+
3+
type ('float, 'int) t =
4+
{ elapsed_time : 'float
5+
; user_cpu_time : 'float
6+
; system_cpu_time : 'float
7+
; minor_words : 'float
8+
; promoted_words : 'float
9+
; major_words : 'float
10+
; minor_collections : 'int
11+
; major_collections : 'int
12+
; heap_words : 'int
13+
; heap_chunks : 'int
14+
; live_words : 'int
15+
; live_blocks : 'int
16+
; free_words : 'int
17+
; free_blocks : 'int
18+
; largest_free : 'int
19+
; fragments : 'int
20+
; compactions : 'int
21+
; top_heap_words : 'int
22+
; stack_size : 'int
23+
}
24+
25+
let make (times : Proc.Times.t) (gc : Gc.stat) =
26+
(* We default to 0 for the other processor times since they are rarely None in
27+
pracice. *)
28+
let { Proc.Resource_usage.user_cpu_time; system_cpu_time } =
29+
Option.value times.resource_usage
30+
~default:{ user_cpu_time = 0.; system_cpu_time = 0. }
31+
in
32+
{ elapsed_time = times.elapsed_time
33+
; user_cpu_time
34+
; system_cpu_time
35+
; minor_words = gc.minor_words
36+
; promoted_words = gc.promoted_words
37+
; major_words = gc.major_words
38+
; minor_collections = gc.minor_collections
39+
; major_collections = gc.major_collections
40+
; heap_words = gc.heap_words
41+
; heap_chunks = gc.heap_chunks
42+
; live_words = gc.live_words
43+
; live_blocks = gc.live_blocks
44+
; free_words = gc.free_words
45+
; free_blocks = gc.free_blocks
46+
; largest_free = gc.largest_free
47+
; fragments = gc.fragments
48+
; compactions = gc.compactions
49+
; top_heap_words = gc.top_heap_words
50+
; stack_size = gc.stack_size
51+
}
52+
53+
let map ~f ~g (metrics : ('float, 'int) t) : ('float_, 'int_) t =
54+
{ elapsed_time = f metrics.elapsed_time
55+
; user_cpu_time = f metrics.user_cpu_time
56+
; system_cpu_time = f metrics.system_cpu_time
57+
; minor_words = f metrics.minor_words
58+
; promoted_words = f metrics.promoted_words
59+
; major_words = f metrics.major_words
60+
; minor_collections = g metrics.minor_collections
61+
; major_collections = g metrics.major_collections
62+
; heap_words = g metrics.heap_words
63+
; heap_chunks = g metrics.heap_chunks
64+
; live_words = g metrics.live_words
65+
; live_blocks = g metrics.live_blocks
66+
; free_words = g metrics.free_words
67+
; free_blocks = g metrics.free_blocks
68+
; largest_free = g metrics.largest_free
69+
; fragments = g metrics.fragments
70+
; compactions = g metrics.compactions
71+
; top_heap_words = g metrics.top_heap_words
72+
; stack_size = g metrics.stack_size
73+
}
74+
75+
(** Turns a list of records into a record of lists. *)
76+
let unzip (metrics : ('float, 'int) t list) : ('float list, 'int list) t =
77+
List.fold_left metrics
78+
~init:
79+
{ elapsed_time = []
80+
; user_cpu_time = []
81+
; system_cpu_time = []
82+
; minor_words = []
83+
; promoted_words = []
84+
; major_words = []
85+
; minor_collections = []
86+
; major_collections = []
87+
; heap_words = []
88+
; heap_chunks = []
89+
; live_words = []
90+
; live_blocks = []
91+
; free_words = []
92+
; free_blocks = []
93+
; largest_free = []
94+
; fragments = []
95+
; compactions = []
96+
; top_heap_words = []
97+
; stack_size = []
98+
} ~f:(fun acc x ->
99+
{ elapsed_time = x.elapsed_time :: acc.elapsed_time
100+
; user_cpu_time = x.user_cpu_time :: acc.user_cpu_time
101+
; system_cpu_time = x.system_cpu_time :: acc.system_cpu_time
102+
; minor_words = x.minor_words :: acc.minor_words
103+
; promoted_words = x.promoted_words :: acc.promoted_words
104+
; major_words = x.major_words :: acc.major_words
105+
; minor_collections = x.minor_collections :: acc.minor_collections
106+
; major_collections = x.major_collections :: acc.major_collections
107+
; heap_words = x.heap_words :: acc.heap_words
108+
; heap_chunks = x.heap_chunks :: acc.heap_chunks
109+
; live_words = x.live_words :: acc.live_words
110+
; live_blocks = x.live_blocks :: acc.live_blocks
111+
; free_words = x.free_words :: acc.free_words
112+
; free_blocks = x.free_blocks :: acc.free_blocks
113+
; largest_free = x.largest_free :: acc.largest_free
114+
; fragments = x.fragments :: acc.fragments
115+
; compactions = x.compactions :: acc.compactions
116+
; top_heap_words = x.top_heap_words :: acc.top_heap_words
117+
; stack_size = x.stack_size :: acc.stack_size
118+
})
119+
|> map ~f:List.rev ~g:List.rev

bench/metrics.mli

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
open Stdune
2+
3+
(** [('float, 'int) t] is a record of metrics about the current process. It
4+
includes timing information and information available from [Gc.stat]. It is
5+
polymorphic in the type of field values to allow for the definition of
6+
[unzip] functions which make serialisation easier. *)
7+
type ('float, 'int) t =
8+
{ elapsed_time : 'float
9+
(** Real time elapsed since the process started and the process
10+
finished. *)
11+
; user_cpu_time : 'float
12+
(** The amount of CPU time spent in user mode during the process. Other
13+
processes and blocked time are not included. *)
14+
; system_cpu_time : 'float
15+
(** The amount of CPU time spent in kernel mode during the process.
16+
Similar to user time, other processes and time spent blocked by
17+
other processes are not counted. *)
18+
; minor_words : 'float
19+
(** Number of words allocated in the minor heap since the program was
20+
started. *)
21+
; promoted_words : 'float
22+
(** Number of words that have been promoted from the minor to the major
23+
heap since the program was started. *)
24+
; major_words : 'float
25+
(** Number of words allocated in the major heap since the program was
26+
started. *)
27+
; minor_collections : 'int
28+
(** Number of minor collections since the program was started. *)
29+
; major_collections : 'int
30+
(** Number of major collection cycles completed since the program was
31+
started. *)
32+
; heap_words : 'int (** Total size of the major heap, in words. *)
33+
; heap_chunks : 'int
34+
(** Number of contiguous pieces of memory that make up the major heap. *)
35+
; live_words : 'int
36+
(** Number of words of live data in the major heap, including the header
37+
words. *)
38+
; live_blocks : 'int (** Number of live blocks in the major heap. *)
39+
; free_words : 'int (** Number of words in the free list. *)
40+
; free_blocks : 'int (** Number of blocks in the free list. *)
41+
; largest_free : 'int
42+
(** Size (in words) of the largest block in the free list. *)
43+
; fragments : 'int
44+
(** Number of wasted words due to fragmentation. These are 1-words free
45+
blocks placed between two live blocks. They are not available for
46+
allocation. *)
47+
; compactions : 'int
48+
(** Number of heap compactions since the program was started. *)
49+
; top_heap_words : 'int
50+
(** Maximum size reached by the major heap, in words. *)
51+
; stack_size : 'int (** Current size of the stack, in words. *)
52+
}
53+
54+
(** [make t gc] creates a new metrics record from the given [t] and [gc]
55+
information. *)
56+
val make : Proc.Times.t -> Gc.stat -> (float, int) t
57+
58+
(** [map ~f ~g m] applies [f] to the float fields and [g] to the int fields of
59+
[m]. *)
60+
val map :
61+
f:('float -> 'float_)
62+
-> g:('int -> 'int_)
63+
-> ('float, 'int) t
64+
-> ('float_, 'int_) t
65+
66+
(** [unzip m] takes a list of metrics [m] and returns a records with the lists
67+
of values for each field. This is particularly convenient when serialising
68+
to json. *)
69+
val unzip : ('float, 'int) t list -> ('float list, 'int list) t

0 commit comments

Comments
 (0)