Source file profile.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
[@@@ocaml.warning "+a-18-40-42-48"]
type file = string
module Int = Misc.Stdlib.Int
module String = Misc.Stdlib.String
module Counters = struct
type t = int String.Map.t
let create () = String.Map.empty
let get name t = String.Map.find_opt name t |> Option.value ~default:0
let set name count t = String.Map.add name count t
let incr name t =
String.Map.update name
(fun count_opt ->
let count = Option.value ~default:0 count_opt in
Some (succ count))
t
let is_empty = String.Map.is_empty
let union = String.Map.union (fun _ count1 count2 -> Some (count1 + count2))
let to_string t =
t
|> String.Map.bindings
|> List.map (fun (name, count) -> Printf.sprintf "%s = %d" name count)
|> String.concat "; "
|> Printf.sprintf "[%s]"
end
external time_include_children: bool -> float = "caml_sys_time_include_children"
let cpu_time () = time_include_children true
module Measure = struct
type t = {
time : float;
allocated_words : float;
top_heap_words : int;
counters : Counters.t;
}
let create ?(counters = Counters.create ()) () =
let stat = Gc.quick_stat () in
{
time = cpu_time ();
allocated_words = stat.minor_words +. stat.major_words;
top_heap_words = stat.top_heap_words;
counters = counters;
}
let zero = { time = 0.; allocated_words = 0.; top_heap_words = 0; counters = Counters.create () }
end
module Measure_diff = struct
let timestamp = let r = ref (-1) in fun () -> incr r; !r
type t = {
timestamp : int;
duration : float;
allocated_words : float;
top_heap_words_increase : int;
counters : Counters.t;
}
let zero () = {
timestamp = timestamp ();
duration = 0.;
allocated_words = 0.;
top_heap_words_increase = 0;
counters = Counters.create ();
}
let accumulate t (m1 : Measure.t) (m2 : Measure.t) = {
timestamp = t.timestamp;
duration = t.duration +. (m2.time -. m1.time);
allocated_words =
t.allocated_words +. (m2.allocated_words -. m1.allocated_words);
top_heap_words_increase =
t.top_heap_words_increase + (m2.top_heap_words - m1.top_heap_words);
counters = Counters.union t.counters m2.counters
}
let of_diff m1 m2 =
accumulate (zero ()) m1 m2
end
type hierarchy =
| E of (string, Measure_diff.t * hierarchy) Hashtbl.t
[@@unboxed]
let create () = E (Hashtbl.create 2)
let hierarchy = ref (create ())
let initial_measure = ref None
let reset () = hierarchy := create (); initial_measure := None
let record_call_internal ?(accumulate = false) ?counter_f name f =
let E prev_hierarchy = !hierarchy in
let start_measure = Measure.create () in
if !initial_measure = None then initial_measure := Some start_measure;
let this_measure_diff, this_table =
if accumulate
then
match Hashtbl.find prev_hierarchy name with
| exception Not_found -> Measure_diff.zero (), Hashtbl.create 2
| measure_diff, E table ->
Hashtbl.remove prev_hierarchy name;
measure_diff, table
else Measure_diff.zero (), Hashtbl.create 2
in
hierarchy := E this_table;
let counters = ref (Counters.create ()) in
Misc.try_finally (
match counter_f with
| Some counter_f ->
fun () ->
let result = f () in
if List.mem `Counters !Clflags.profile_columns then
counters := counter_f result;
result
| None -> f
)
~always:(fun () ->
hierarchy := E prev_hierarchy;
let end_measure = Measure.create ~counters:(!counters) () in
let measure_diff =
Measure_diff.accumulate this_measure_diff start_measure end_measure in
Hashtbl.add prev_hierarchy name (measure_diff, E this_table))
let record_call = record_call_internal ?counter_f:None
let record_call_with_counters ?accumulate ~counter_f =
record_call_internal ?accumulate ~counter_f
let record ?accumulate pass f x = record_call ?accumulate pass (fun () -> f x)
let record_with_counters ?accumulate ~counter_f pass f x =
record_call_internal ?accumulate ~counter_f pass (fun () -> f x)
let file_prefix = "file="
type display = {
to_string : max:float -> width:int -> string;
worth_displaying : max:float -> bool;
}
let time_display precision v : display =
let to_string_without_unit v ~width = Printf.sprintf "%0*.*f" width precision v in
let to_string ~max:_ ~width =
to_string_without_unit v ~width:(width - 1) ^ "s" in
let worth_displaying ~max:_ =
float_of_string (to_string_without_unit v ~width:0) <> 0. in
{ to_string; worth_displaying }
let memory_word_display =
let bytes_of_words words = words *. float_of_int (Sys.word_size / 8) in
let to_string_without_unit v ~width scale =
let precision = 3 and precision_power = 1e3 in
let v_rescaled = bytes_of_words v /. scale in
let v_rounded =
floor (v_rescaled *. precision_power +. 0.5) /. precision_power in
let v_str = Printf.sprintf "%.*f" precision v_rounded in
let index_of_dot = String.index v_str '.' in
let v_str_truncated =
String.sub v_str 0
(if index_of_dot >= precision
then index_of_dot
else precision + 1)
in
Printf.sprintf "%*s" width v_str_truncated
in
let choose_memory_scale =
let units = [|"B"; "kB"; "MB"; "GB"|] in
fun words ->
let bytes = bytes_of_words words in
let scale = ref (Array.length units - 1) in
while !scale > 0 && bytes < 1024. ** float_of_int !scale do
decr scale
done;
1024. ** float_of_int !scale, units.(!scale)
in
fun ?previous v : display ->
let to_string ~max ~width =
let scale, scale_str = choose_memory_scale max in
let width = width - String.length scale_str in
to_string_without_unit v ~width scale ^ scale_str
in
let worth_displaying ~max =
let scale, _ = choose_memory_scale max in
float_of_string (to_string_without_unit v ~width:0 scale) <> 0.
&& match previous with
| None -> true
| Some p ->
to_string_without_unit p ~width:0 scale
<> to_string_without_unit v ~width:0 scale
in
{ to_string; worth_displaying }
let counters_display counters =
let to_string ~max:_ ~width:_ = Counters.to_string counters in
let worth_displaying ~max:_ = not (Counters.is_empty counters) in
0., { to_string; worth_displaying }
let profile_list (E table) =
let l = Hashtbl.fold (fun k d l -> (k, d) :: l) table [] in
List.sort (fun (_, (p1, _)) (_, (p2, _)) ->
compare p1.Measure_diff.timestamp p2.Measure_diff.timestamp) l
let compute_other_category (E table : hierarchy) (total : Measure_diff.t) =
let r = ref total in
Hashtbl.iter (fun _pass ((p2 : Measure_diff.t), _) ->
let p1 = !r in
r := {
timestamp = p1.timestamp;
duration = p1.duration -. p2.duration;
allocated_words = p1.allocated_words -. p2.allocated_words;
top_heap_words_increase =
p1.top_heap_words_increase - p2.top_heap_words_increase;
counters = Counters.create ();
}
) table;
!r
type row = R of string * (float * display) list * row list
let rec rows_of_hierarchy ~nesting make_row name measure_diff hierarchy env =
let rows =
rows_of_hierarchy_list
~nesting:(nesting + 1) make_row hierarchy measure_diff env in
let values, env =
make_row env measure_diff ~toplevel_other:(nesting = 0 && name = "other") in
R (name, values, rows), env
and rows_of_hierarchy_list ~nesting make_row hierarchy total env =
let list = profile_list hierarchy in
let list =
if list <> [] || nesting = 0
then list @ [ "other", (compute_other_category hierarchy total, create ()) ]
else []
in
let env = ref env in
List.map (fun (name, (measure_diff, hierarchy)) ->
let a, env' =
rows_of_hierarchy ~nesting make_row name measure_diff hierarchy !env in
env := env';
a
) list
let rows_of_hierarchy hierarchy measure_diff initial_measure columns timings_precision =
let make_row prev_top_heap_words (p : Measure_diff.t) ~toplevel_other =
let top_heap_words =
prev_top_heap_words
+ p.top_heap_words_increase
- if toplevel_other
then initial_measure.Measure.top_heap_words
else 0
in
let make value ~f = value, f value in
List.map (function
| `Time ->
make p.duration ~f:(time_display timings_precision)
| `Alloc ->
make p.allocated_words ~f:memory_word_display
| `Top_heap ->
make (float_of_int p.top_heap_words_increase) ~f:memory_word_display
| `Abs_top_heap ->
make (float_of_int top_heap_words)
~f:(memory_word_display ~previous:(float_of_int prev_top_heap_words))
| `Counters -> counters_display p.counters
) columns,
top_heap_words
in
rows_of_hierarchy_list ~nesting:0 make_row hierarchy measure_diff
initial_measure.top_heap_words
let max_by_column ~n_columns rows =
let a = Array.make n_columns 0. in
let rec loop (R (_, values, rows)) =
List.iteri (fun i (v, _) -> a.(i) <- Float.max a.(i) v) values;
List.iter loop rows
in
List.iter loop rows;
a
let width_by_column ~n_columns ~display_cell rows =
let a = Array.make n_columns 1 in
let rec loop (R (_, values, rows)) =
List.iteri (fun i cell ->
let _, str = display_cell i cell ~width:0 in
a.(i) <- Int.max a.(i) (String.length str)
) values;
List.iter loop rows;
in
List.iter loop rows;
a
let output_rows
~(output_row : prefix:string -> cell_strings:string list -> name:string -> unit)
~(new_prefix : prev:string -> curr_name:string -> string)
~(always_output_ancestors : bool)
~(pad_empty : bool)
rows
=
let n_columns =
match rows with
| [] -> 0
| R (_, values, _) :: _ -> List.length values
in
let maxs = max_by_column ~n_columns rows in
let display_cell i (_, c) ~width =
let display_cell = c.worth_displaying ~max:maxs.(i) in
display_cell, if display_cell
then c.to_string ~max:maxs.(i) ~width
else if pad_empty then String.make width '-' else ""
in
let widths = width_by_column ~n_columns ~display_cell rows in
let rec loop (R (name, values, rows)) ~prefix ~output_stack =
let worth_displaying, cell_strings =
values
|> List.mapi (fun i cell -> display_cell i cell ~width:widths.(i))
|> List.split
in
let should_output_row = List.exists (fun b -> b) worth_displaying in
let output_current () =
let cell_strings = if should_output_row then cell_strings else [] in
output_row ~prefix ~cell_strings ~name
in
output_stack := output_current :: (if always_output_ancestors then !output_stack else []);
if should_output_row then
(List.rev !output_stack |> List.iter (fun f -> f ()); output_stack := []);
List.iter (loop ~prefix:(new_prefix ~prev:prefix ~curr_name:name) ~output_stack) rows;
if !output_stack <> [] then output_stack := List.tl !output_stack
in
List.iter (loop ~prefix:"" ~output_stack:(ref [])) rows
let output_columns output_rows_f columns ~timings_precision =
match columns with
| [] -> ()
| _ :: _ ->
let initial_measure =
match !initial_measure with
| Some v -> v
| None -> Measure.zero
in
let total = Measure_diff.of_diff Measure.zero (Measure.create ()) in
output_rows_f (rows_of_hierarchy !hierarchy total initial_measure columns timings_precision)
let print ppf =
output_rows
~output_row:(fun ~prefix ~cell_strings ~name ->
Format.fprintf ppf "%s%s %s@\n" prefix (String.concat " " cell_strings) name)
~new_prefix:(fun ~prev ~curr_name:_ -> " " ^ prev)
~always_output_ancestors:true
~pad_empty:true
|> output_columns
let column_mapping = [
`Time, "time";
`Alloc, "alloc";
`Top_heap, "top-heap";
`Abs_top_heap, "absolute-top-heap";
`Counters, "counters"
]
let output_to_csv ppf columns =
let sanitise_for_csv =
String.map (fun c -> if Char.equal c ',' then '_' else c) in
let to_csv cell_strings =
cell_strings |> List.map sanitise_for_csv |> String.concat ","
in
let string_columns = List.map (fun col -> List.assoc col column_mapping) columns in
Format.fprintf ppf "%s@\n" (to_csv ("pass name" :: string_columns));
let add_suffix pass =
let suffix = if String.starts_with ~prefix:file_prefix pass then "/" else "" in
pass ^ suffix
in
let output_row_f =
output_rows
~output_row:(fun ~prefix ~cell_strings ~name ->
Format.fprintf ppf "%s@\n" (to_csv ((prefix ^ add_suffix name) :: cell_strings)))
~new_prefix:(fun ~prev ~curr_name ->
Format.sprintf "%s%s/" prev (add_suffix curr_name))
~always_output_ancestors:false ~pad_empty:false
in
output_columns output_row_f columns
let all_columns = List.map fst column_mapping
let column_names = List.map snd column_mapping
let options_doc =
Printf.sprintf
" Print performance information for each pass\
\n The columns are: %s."
(String.concat " " column_names)
let generate = "generate"
let transl = "transl"
let typing = "typing"