module Timing : sig
type running
type finished
val with_timing_lwt : label:string -> f:(running -> 'a Lwt.t) -> (finished * 'a) Lwt.t
val with_timer_lwt :
?should_print:bool -> timer:string -> f:(unit -> 'a Lwt.t) -> running -> 'a Lwt.t
val with_timer : ?should_print:bool -> timer:string -> f:(unit -> 'a) -> running -> 'a
val get_total_wall_duration : finished -> float
val to_json : abridged:bool -> finished -> Hh_json.json
val to_json_legacy : abridged:bool -> finished -> Hh_json.json
val print_summary_timing_table : finished -> unit
val merge : from:finished -> into:running -> unit
end = struct
type time_measurement = {
start_age: float;
duration: float;
}
type processor_info = {
cpu_user: float;
cpu_nice_user: float;
cpu_system: float;
cpu_idle: float;
cpu_usage: float;
}
type worker_wall_times = {
worker_idle: time_measurement;
worker_read_request: time_measurement;
worker_run: time_measurement;
worker_send_response: time_measurement;
worker_done: time_measurement;
worker_gc_minor: time_measurement;
worker_gc_major: time_measurement;
}
type result = {
timer_name: string;
user: time_measurement;
system: time_measurement;
worker_user: time_measurement;
worker_system: time_measurement;
worker_wall_times: worker_wall_times;
wall: time_measurement;
processor_totals: processor_info;
flow_cpu_usage: float;
sub_results: result list;
sample_count: int;
}
type worker_wall_start_times = {
worker_idle_start: float;
worker_read_request_start: float;
worker_run_start: float;
worker_send_response_start: float;
worker_done_start: float;
worker_gc_minor_start: float;
worker_gc_major_start: float;
}
type running_timer = {
timer: string;
user_start: float;
system_start: float;
worker_user_start: float;
worker_system_start: float;
worker_wall_start_times: worker_wall_start_times;
wall_start: float;
processor_info_start: Sys_utils.processor_info;
mutable sub_results_rev: result list;
}
type running = running_timer ref
type finished = result
let times () =
Unix.(
let tm = times () in
(tm.tms_utime +. tm.tms_cutime, tm.tms_stime +. tm.tms_cstime))
let worker_times () =
let worker_user_time =
match Measure.get_sum "worker_user_time" with
| None -> 0.0
| Some time -> time
in
let worker_system_time =
match Measure.get_sum "worker_system_time" with
| None -> 0.0
| Some time -> time
in
(worker_user_time, worker_system_time)
let time_measurement start end_ = { start_age = start; duration = end_ -. start }
let (worker_wall_start_times, worker_wall_times) =
let get_run () = Base.Option.value ~default:0.0 (Measure.get_sum "worker_wall_time") in
let get_read () = Base.Option.value ~default:0.0 (Measure.get_sum "worker_read_request") in
let get_send () = Base.Option.value ~default:0.0 (Measure.get_sum "worker_send_response") in
let get_idle () = Base.Option.value ~default:0.0 (Measure.get_sum "worker_idle") in
let get_done () = Base.Option.value ~default:0.0 (Measure.get_sum "worker_done") in
let get_gc_minor () =
Base.Option.value ~default:0.0 (Measure.get_sum "worker_gc_minor_wall_time")
in
let get_gc_major () =
Base.Option.value ~default:0.0 (Measure.get_sum "worker_gc_major_wall_time")
in
let worker_wall_start_times () =
{
worker_idle_start = get_idle ();
worker_read_request_start = get_read ();
worker_run_start = get_run ();
worker_send_response_start = get_send ();
worker_done_start = get_done ();
worker_gc_minor_start = get_gc_minor ();
worker_gc_major_start = get_gc_major ();
}
in
let worker_wall_times start =
{
worker_idle = time_measurement start.worker_idle_start (get_idle ());
worker_read_request = time_measurement start.worker_read_request_start (get_read ());
worker_run = time_measurement start.worker_run_start (get_run ());
worker_send_response = time_measurement start.worker_send_response_start (get_send ());
worker_done = time_measurement start.worker_done_start (get_done ());
worker_gc_minor = time_measurement start.worker_gc_minor_start (get_gc_minor ());
worker_gc_major = time_measurement start.worker_gc_major_start (get_gc_major ());
}
in
(worker_wall_start_times, worker_wall_times)
let legacy_top_timer_name = "Profiling"
let start_timer ~timer =
let wall_start = Unix.gettimeofday () in
let (user_start, system_start) = times () in
let (worker_user_start, worker_system_start) = worker_times () in
let worker_wall_start_times = worker_wall_start_times () in
let processor_info_start = Sys_utils.processor_info () in
{
timer;
user_start;
system_start;
worker_user_start;
worker_system_start;
worker_wall_start_times;
wall_start;
processor_info_start;
sub_results_rev = [];
}
let flow_start_time = Unix.gettimeofday ()
let make_processor_info start end_ =
let cpu_user = end_.Sys_utils.cpu_user -. start.Sys_utils.cpu_user in
let cpu_nice_user = end_.Sys_utils.cpu_nice_user -. start.Sys_utils.cpu_nice_user in
let cpu_system = end_.Sys_utils.cpu_system -. start.Sys_utils.cpu_system in
let cpu_idle = end_.Sys_utils.cpu_idle -. start.Sys_utils.cpu_idle in
let cpu_busy = cpu_user +. cpu_nice_user +. cpu_system in
let cpu_usage =
if cpu_busy = 0. then
0.
else
cpu_busy /. (cpu_busy +. cpu_idle)
in
{ cpu_user; cpu_nice_user; cpu_system; cpu_idle; cpu_usage }
let stop_timer running_timer =
let wall_end = Unix.gettimeofday () in
let (user_end, system_end) = times () in
let (worker_user_end, worker_system_end) = worker_times () in
let processor_info_end = Sys_utils.processor_info () in
let user =
{ start_age = running_timer.user_start; duration = user_end -. running_timer.user_start }
in
let system =
{
start_age = running_timer.system_start;
duration = system_end -. running_timer.system_start;
}
in
let worker_user =
{
start_age = running_timer.worker_user_start;
duration = worker_user_end -. running_timer.worker_user_start;
}
in
let worker_system =
{
start_age = running_timer.worker_system_start;
duration = worker_system_end -. running_timer.worker_system_start;
}
in
let worker_wall_times = worker_wall_times running_timer.worker_wall_start_times in
let wall =
{
start_age = running_timer.wall_start -. flow_start_time;
duration = wall_end -. running_timer.wall_start;
}
in
let processor_totals =
make_processor_info
running_timer.processor_info_start.Sys_utils.proc_totals
processor_info_end.Sys_utils.proc_totals
in
let flow_cpu_time =
user.duration +. system.duration +. worker_user.duration +. worker_system.duration
in
let total_cpu_time =
processor_totals.cpu_user
+. processor_totals.cpu_nice_user
+. processor_totals.cpu_system
+. processor_totals.cpu_idle
in
let flow_cpu_usage =
if total_cpu_time = 0. then
0.
else
flow_cpu_time /. total_cpu_time
in
{
timer_name = running_timer.timer;
user;
system;
worker_user;
worker_system;
worker_wall_times;
wall;
processor_totals;
flow_cpu_usage;
sub_results = List.rev running_timer.sub_results_rev;
sample_count = 1;
}
let with_timing_lwt ~label ~f =
let total_timer = start_timer ~timer:label in
let running = ref total_timer in
let%lwt ret = f running in
let finished_timer = stop_timer total_timer in
Lwt.return (finished_timer, ret)
let prepare_timer ~timer ~running =
let parent_timer = !running in
let running_timer = start_timer ~timer in
running := running_timer;
(parent_timer, running_timer)
let finalize_timer ~should_print ~timer ~running_timer ~parent_timer ~running =
let finished_timer = stop_timer running_timer in
parent_timer.sub_results_rev <- finished_timer :: parent_timer.sub_results_rev;
running := parent_timer;
if should_print then
let stats =
Printf.sprintf
"start_wall_age: %f; wall_duration: %f; cpu_usage: %f; flow_cpu_usage: %f"
finished_timer.wall.start_age
finished_timer.wall.duration
finished_timer.processor_totals.cpu_usage
finished_timer.flow_cpu_usage
in
Hh_logger.info "TimingEvent `%s`: %s" timer stats
let with_timer_lwt ?(should_print = false) ~timer ~f running =
let (parent_timer, running_timer) = prepare_timer ~timer ~running in
Lwt.finalize f (fun () ->
finalize_timer ~should_print ~timer ~running_timer ~parent_timer ~running;
Lwt.return_unit)
let with_timer ?(should_print = false) ~timer ~f running =
let (parent_timer, running_timer) = prepare_timer ~timer ~running in
try
let result = f () in
finalize_timer ~should_print ~timer ~running_timer ~parent_timer ~running;
result
with exn ->
let exn = Exception.wrap exn in
finalize_timer ~should_print ~timer ~running_timer ~parent_timer ~running;
Exception.reraise exn
let get_total_wall_duration finished_timer = finished_timer.wall.duration
let combine_time_measurements =
List.fold_left
(fun acc t ->
{ start_age = acc.start_age +. t.start_age; duration = acc.duration +. t.duration })
{ start_age = 0.0; duration = 0.0 }
let json_of_time_measurement { start_age; duration } =
Hh_json.(
JSON_Object
[
("start_age", JSON_Number (Dtoa.ecma_string_of_float start_age));
("duration", JSON_Number (Dtoa.ecma_string_of_float duration));
])
let total_cpu_time info = info.cpu_user +. info.cpu_nice_user +. info.cpu_system +. info.cpu_idle
let json_of_processor_info ~abridged info =
Hh_json.(
if abridged then
let total = total_cpu_time info in
JSON_Object
[
("total", JSON_Number (Dtoa.ecma_string_of_float total));
("usage", JSON_Number (Dtoa.ecma_string_of_float info.cpu_usage));
]
else
JSON_Object
[
("user", JSON_Number (Dtoa.ecma_string_of_float info.cpu_user));
("nice", JSON_Number (Dtoa.ecma_string_of_float info.cpu_nice_user));
("system", JSON_Number (Dtoa.ecma_string_of_float info.cpu_system));
("idle", JSON_Number (Dtoa.ecma_string_of_float info.cpu_idle));
("usage", JSON_Number (Dtoa.ecma_string_of_float info.cpu_usage));
])
let merge_dupes =
let merge_time_measurement a b =
{ start_age = a.start_age; duration = a.duration +. b.duration }
in
let merge_worker_wall_times a b =
{
worker_idle = merge_time_measurement a.worker_idle b.worker_idle;
worker_read_request = merge_time_measurement a.worker_read_request b.worker_read_request;
worker_run = merge_time_measurement a.worker_run b.worker_run;
worker_send_response = merge_time_measurement a.worker_send_response b.worker_send_response;
worker_done = merge_time_measurement a.worker_done b.worker_done;
worker_gc_minor = merge_time_measurement a.worker_gc_minor b.worker_gc_minor;
worker_gc_major = merge_time_measurement a.worker_gc_major b.worker_gc_major;
}
in
let weighted_average values =
let (weight_sum, acc) =
List.fold_left
(fun (weight_sum, acc) (weight, value) ->
assert (weight >= 0.);
(weight_sum +. weight, acc +. (weight *. value)))
(0., 0.)
values
in
if weight_sum > 0. then
acc /. weight_sum
else
0.
in
let merge_processor_totals a b =
{
cpu_user = a.cpu_user +. b.cpu_user;
cpu_nice_user = a.cpu_nice_user +. b.cpu_nice_user;
cpu_system = a.cpu_system +. b.cpu_system;
cpu_idle = a.cpu_idle +. b.cpu_idle;
cpu_usage =
weighted_average [(total_cpu_time a, a.cpu_usage); (total_cpu_time b, b.cpu_usage)];
}
in
fun ~dupes result ->
List.fold_left
(fun result dupe ->
{
timer_name = result.timer_name;
wall = merge_time_measurement result.wall dupe.wall;
user = merge_time_measurement result.user dupe.user;
system = merge_time_measurement result.system dupe.system;
worker_user = merge_time_measurement result.worker_user dupe.worker_user;
worker_system = merge_time_measurement result.worker_system dupe.worker_system;
worker_wall_times =
merge_worker_wall_times result.worker_wall_times dupe.worker_wall_times;
processor_totals = merge_processor_totals result.processor_totals dupe.processor_totals;
flow_cpu_usage =
weighted_average
[
(total_cpu_time result.processor_totals, result.flow_cpu_usage);
(total_cpu_time dupe.processor_totals, dupe.flow_cpu_usage);
];
sub_results = result.sub_results @ dupe.sub_results;
sample_count = result.sample_count + 1;
})
result
dupes
let rec json_of_result ~abridged ~max_depth ~dupes result =
let {
timer_name;
wall;
user;
system;
worker_user;
worker_system;
worker_wall_times;
processor_totals;
flow_cpu_usage;
sub_results;
sample_count;
} =
merge_dupes ~dupes result
in
Hh_json.(
let cpu = [user; system; worker_user; worker_system] in
let common_fields =
[
("wall", json_of_time_measurement wall);
("cpu", json_of_time_measurement (combine_time_measurements cpu));
("flow_cpu_usage", JSON_Number (Dtoa.ecma_string_of_float flow_cpu_usage));
("processor_totals", json_of_processor_info ~abridged processor_totals);
]
in
let fields =
if abridged then
if sample_count > 1 then
("samples", JSON_Number (string_of_int sample_count)) :: common_fields
else
common_fields
else
let sub_results =
if max_depth > 0 then
json_of_results ~abridged ~max_depth:(max_depth - 1) sub_results
else
JSON_Object []
in
common_fields
@ [
("wall", json_of_time_measurement wall);
("user", json_of_time_measurement user);
("system", json_of_time_measurement system);
("worker_user", json_of_time_measurement worker_user);
("worker_system", json_of_time_measurement worker_system);
( "worker_wall_times",
JSON_Object
[
("run", json_of_time_measurement worker_wall_times.worker_run);
("read", json_of_time_measurement worker_wall_times.worker_read_request);
("send", json_of_time_measurement worker_wall_times.worker_send_response);
("idle", json_of_time_measurement worker_wall_times.worker_idle);
("done", json_of_time_measurement worker_wall_times.worker_done);
("gc_minor", json_of_time_measurement worker_wall_times.worker_gc_minor);
("gc_major", json_of_time_measurement worker_wall_times.worker_gc_major);
] );
("sub_results", sub_results);
("samples", JSON_Number (string_of_int sample_count));
]
in
(timer_name, JSON_Object fields))
and json_of_results ~abridged ~max_depth results =
let (results_rev, dupes) =
List.fold_left
(fun (results, dupes) result ->
match SMap.find_opt result.timer_name dupes with
| None -> (result :: results, SMap.add result.timer_name [] dupes)
| Some prev_dupes -> (results, SMap.add result.timer_name (result :: prev_dupes) dupes))
([], SMap.empty)
results
in
let json_results =
List.fold_left
(fun acc result ->
let json_result =
json_of_result ~abridged ~max_depth ~dupes:(SMap.find result.timer_name dupes) result
in
json_result :: acc)
[]
results_rev
in
Hh_json.JSON_Object json_results
let to_json ~abridged result =
let max_depth =
if abridged then
1
else
100
in
json_of_results ~abridged ~max_depth [result]
let to_json_legacy ~abridged result =
let results_rev =
List.fold_left
(fun acc sub_result ->
let prefix = sub_result.timer_name ^ ":" in
List.fold_left
(fun acc sub_sub_result ->
{ sub_sub_result with timer_name = prefix ^ sub_sub_result.timer_name } :: acc)
(sub_result :: acc)
sub_result.sub_results)
[{ result with timer_name = legacy_top_timer_name }]
result.sub_results
in
let results = json_of_results ~abridged ~max_depth:0 (List.rev results_rev) in
Hh_json.JSON_Object [("results", results)]
let print_summary_timing_table =
let sum_cpu result =
result.user.duration
+. result.system.duration
+. result.worker_user.duration
+. result.worker_system.duration
in
let sum_cpu_start_age result =
result.user.start_age
+. result.system.start_age
+. result.worker_user.start_age
+. result.worker_system.start_age
in
let print_summary_single_raw
key (result_wall, result_cpu, (run, read, send, idle, done_, gc_minor, gc_major)) total =
let run = run -. gc_minor -. gc_major in
let worker_total = idle +. done_ +. read +. run +. send +. gc_minor +. gc_major in
let worker_total =
if worker_total = 0.0 then
1.0
else
worker_total
in
let worker_idle_pct = idle /. worker_total *. 100. in
let worker_read_pct = read /. worker_total *. 100. in
let worker_run_pct = run /. worker_total *. 100. in
let worker_send_pct = send /. worker_total *. 100. in
let worker_done_pct = done_ /. worker_total *. 100. in
let worker_gc_minor_pct = gc_minor /. worker_total *. 100. in
let worker_gc_major_pct = gc_major /. worker_total *. 100. in
Printf.eprintf
"%7.3f (%5.1f%%) %9.3f (%5.1f%%) %3d%% %3d%% %3d%% %3d%% %3d%% %3d%% %3d%% %s\n%!"
result_wall
(100.0 *. result_wall /. total.wall.duration)
result_cpu
(100.0 *. result_cpu /. sum_cpu total)
(worker_run_pct |> int_of_float)
(worker_read_pct |> int_of_float)
(worker_send_pct |> int_of_float)
(worker_idle_pct |> int_of_float)
(worker_done_pct |> int_of_float)
(worker_gc_minor_pct |> int_of_float)
(worker_gc_major_pct |> int_of_float)
key
in
let print_summary_single key result total =
let worker_wall_times =
( result.worker_wall_times.worker_run.duration,
result.worker_wall_times.worker_read_request.duration,
result.worker_wall_times.worker_send_response.duration,
result.worker_wall_times.worker_idle.duration,
result.worker_wall_times.worker_done.duration,
result.worker_wall_times.worker_gc_minor.duration,
result.worker_wall_times.worker_gc_major.duration )
in
print_summary_single_raw key (result.wall.duration, sum_cpu result, worker_wall_times) total
in
let print_unknown ~indent last_end (wall_start_age, cpu_start_age, worker_wall_start) total =
let (run_start, read_start, send_start, idle_start, done_start, gc_minor_start, gc_major_start)
=
worker_wall_start
in
let ( wall_end,
cpu_end,
(run_end, read_end, send_end, idle_end, done_end, gc_minor_end, gc_major_end) ) =
last_end
in
let unknown_wall = wall_start_age -. wall_end in
if unknown_wall /. total.wall.duration > 0.01 then
let unknown_cpu = cpu_start_age -. cpu_end in
let unknown_worker =
( run_start -. run_end,
read_start -. read_end,
send_start -. send_end,
idle_start -. idle_end,
done_start -. done_end,
gc_minor_start -. gc_minor_end,
gc_major_start -. gc_major_end )
in
print_summary_single_raw
(indent ^ "<Unknown>")
(unknown_wall, unknown_cpu, unknown_worker)
total
in
let worker_wall_times_to_tuples worker_wall_times =
let {
worker_run = { start_age = run_start; duration = run_duration };
worker_read_request = { start_age = read_start; duration = read_duration };
worker_send_response = { start_age = send_start; duration = send_duration };
worker_idle = { start_age = idle_start; duration = idle_duration };
worker_done = { start_age = done_start; duration = done_duration };
worker_gc_minor = { start_age = gc_minor_start; duration = gc_minor_duration };
worker_gc_major = { start_age = gc_major_start; duration = gc_major_duration };
} =
worker_wall_times
in
let worker_last =
(run_start, read_start, send_start, idle_start, done_start, gc_minor_start, gc_major_start)
in
let worker_remaining =
( run_duration,
read_duration,
send_duration,
idle_duration,
done_duration,
gc_minor_duration,
gc_major_duration )
in
let worker_end =
( run_start +. run_duration,
read_start +. read_duration,
send_start +. send_duration,
idle_start +. idle_duration,
done_start +. done_duration,
gc_minor_start +. gc_minor_duration,
gc_major_start +. gc_major_duration )
in
(worker_last, worker_remaining, worker_end)
in
let rec print_result_rows
~indent ~total (last_end, (wall_remaining, cpu_remaining, worker_remaining)) result =
let (result_worker_starts, result_worker_durations, result_worker_end) =
worker_wall_times_to_tuples result.worker_wall_times
in
print_unknown
~indent
last_end
(result.wall.start_age, sum_cpu_start_age result, result_worker_starts)
total;
print_summary_single (indent ^ result.timer_name) result total;
if result.sub_results <> [] then (
let new_indent = indent ^ " " in
let (last_end, remaining) =
List.fold_left
(print_result_rows ~indent:new_indent ~total)
( (result.wall.start_age, sum_cpu_start_age result, result_worker_starts),
(result.wall.duration, sum_cpu result, result_worker_durations) )
result.sub_results
in
print_unknown
~indent:new_indent
last_end
( result.wall.start_age +. result.wall.duration,
sum_cpu_start_age result +. sum_cpu result,
result_worker_end )
total;
print_summary_single_raw (new_indent ^ "<Unknown total>") remaining total
);
let last_end =
( result.wall.start_age +. result.wall.duration,
sum_cpu_start_age result +. sum_cpu result,
result_worker_end )
in
let remaining =
let wall_remaining = wall_remaining -. result.wall.duration in
let cpu_remaining = cpu_remaining -. sum_cpu result in
let worker_remaining =
let (run, read, send, idle, done_, gc_minor, gc_major) = worker_remaining in
( run -. result.worker_wall_times.worker_run.duration,
read -. result.worker_wall_times.worker_read_request.duration,
send -. result.worker_wall_times.worker_send_response.duration,
idle -. result.worker_wall_times.worker_idle.duration,
done_ -. result.worker_wall_times.worker_done.duration,
gc_minor -. result.worker_wall_times.worker_gc_minor.duration,
gc_major -. result.worker_wall_times.worker_gc_major.duration )
in
(wall_remaining, cpu_remaining, worker_remaining)
in
(last_end, remaining)
in
fun total ->
let label = Printf.sprintf "%s Timings" total.timer_name in
let header =
" WALL TIME CPU TIME RUN/READ/SEND/IDLE/DONE/GC m/GC M SECTION"
in
let header_len = String.length header + 8 in
let whitespace_len = header_len - String.length label in
Printf.eprintf
"%s%s%s\n%!"
(String.make ((whitespace_len + 1) / 2) '=')
label
(String.make (whitespace_len / 2) '=');
Printf.eprintf "%s\n%!" header;
Printf.eprintf "%s\n%!" (String.make header_len '-');
print_summary_single "<Total>" total total;
let indent = " " in
let (worker_last, worker_remaining, worker_end) =
worker_wall_times_to_tuples total.worker_wall_times
in
let last_end = (total.wall.start_age, sum_cpu_start_age total, worker_last) in
let remaining = (total.wall.duration, sum_cpu total, worker_remaining) in
let (last_end, remaining) =
List.fold_left (print_result_rows ~indent ~total) (last_end, remaining) total.sub_results
in
let () =
let start =
( total.wall.start_age +. total.wall.duration,
sum_cpu_start_age total +. sum_cpu total,
worker_end )
in
print_unknown ~indent last_end start total
in
print_summary_single_raw "<Unknown total>" remaining total
let merge ~from ~into = !into.sub_results_rev <- from :: !into.sub_results_rev
end
module Memory : sig
type running
type finished
val with_memory_lwt : label:string -> f:(running -> 'a Lwt.t) -> (finished * 'a) Lwt.t
val legacy_sample_memory : metric:string -> value:float -> running -> unit
val sample_memory : group:string -> metric:string -> value:float -> running -> unit
val add_memory :
group:string ->
metric:string ->
start:float ->
delta:float ->
hwm_delta:float ->
running ->
unit
val to_json : abridged:bool -> finished -> Hh_json.json
val print_summary_memory_table : finished -> unit
val merge : from:finished -> into:running -> unit
end = struct
type memory_result = {
start: float;
delta: float;
high_water_mark_delta: float;
is_legacy: bool;
}
and running' = {
running_groups_rev: string list;
running_results: memory_result SMap.t SMap.t;
running_sub_results_rev: finished list;
}
and running = running' ref
and finished = {
finished_label: string;
finished_groups: string list;
finished_results: memory_result SMap.t SMap.t;
finished_sub_results: finished list;
}
let legacy_group = "LEGACY"
let with_memory_lwt ~label ~f =
let running_memory =
ref { running_groups_rev = []; running_results = SMap.empty; running_sub_results_rev = [] }
in
let%lwt ret = f running_memory in
let finished_memory =
{
finished_label = label;
finished_groups = List.rev !running_memory.running_groups_rev;
finished_results = !running_memory.running_results;
finished_sub_results = List.rev !running_memory.running_sub_results_rev;
}
in
Lwt.return (finished_memory, ret)
let get_group_map ~group running_memory =
match SMap.find_opt group !running_memory.running_results with
| None ->
running_memory :=
{
!running_memory with
running_groups_rev = group :: !running_memory.running_groups_rev;
running_results = SMap.add group SMap.empty !running_memory.running_results;
};
SMap.empty
| Some group -> group
let get_metric ~group ~metric running_memory =
get_group_map ~group running_memory |> SMap.find_opt metric
let set_metric ~group ~metric entry running_memory =
let group_map = get_group_map ~group running_memory |> SMap.add metric entry in
running_memory :=
{
!running_memory with
running_results = SMap.add group group_map !running_memory.running_results;
}
let legacy_sample_memory ~metric ~value running_memory =
let legacy_metric =
{ start = 0.0; delta = value; high_water_mark_delta = value; is_legacy = true }
in
set_metric ~group:legacy_group ~metric legacy_metric running_memory
let start_sampling ~group ~metric ~value running_memory =
let new_metric =
{ start = value; delta = 0.0; high_water_mark_delta = 0.0; is_legacy = false }
in
set_metric ~group ~metric new_metric running_memory
let sample_memory ~group ~metric ~value running_memory =
match get_metric ~group ~metric running_memory with
| None -> start_sampling ~group ~metric ~value running_memory
| Some old_metric ->
let new_metric =
{
old_metric with
delta = value -. old_metric.start;
high_water_mark_delta = max (value -. old_metric.start) old_metric.high_water_mark_delta;
}
in
set_metric ~group ~metric new_metric running_memory
let add_memory ~group ~metric ~start ~delta ~hwm_delta running_memory =
let new_metric = { start; delta; high_water_mark_delta = hwm_delta; is_legacy = false } in
set_metric ~group ~metric new_metric running_memory
let rec to_json ~abridged finished_memory =
Hh_json.(
let object_props =
SMap.fold
(fun group_name group props ->
if group_name = legacy_group then
SMap.fold
(fun k v props -> (k, JSON_Number (Dtoa.ecma_string_of_float v.delta)) :: props)
group
props
else
let group_json =
SMap.fold
(fun k v props ->
( k,
JSON_Object
[
("start", JSON_Number (Dtoa.ecma_string_of_float v.start));
("delta", JSON_Number (Dtoa.ecma_string_of_float v.delta));
( "hwm_delta",
JSON_Number (Dtoa.ecma_string_of_float v.high_water_mark_delta) );
] )
:: props)
group
[]
in
(group_name, JSON_Object group_json) :: props)
finished_memory.finished_results
[]
in
let object_props =
if abridged then
object_props
else
let sub_results =
JSON_Object
(List.map
(fun result -> (result.finished_label, to_json ~abridged:false result))
finished_memory.finished_sub_results)
in
("sub_results", sub_results) :: object_props
in
JSON_Object object_props)
let print_summary_memory_table =
let pretty_num f =
let abs_f = abs_float f in
if abs_f > 1000000000.0 then
Printf.sprintf "%+7.2fG" (f /. 1000000000.0)
else if abs_f > 1000000.0 then
Printf.sprintf "%+7.2fM" (f /. 1000000.0)
else if abs_f > 1000.0 then
Printf.sprintf "%+7.2fK" (f /. 1000.0)
else
Printf.sprintf "%+7.2f " f
in
let pretty_pct num denom =
if denom = 0.0 then
"(--N/A--)"
else
let fraction = num /. denom in
if fraction >= 10.0 then
Printf.sprintf "(%+6.1fx)" fraction
else
Printf.sprintf "(%+6.1f%%)" (fraction *. 100.0)
in
let print_summary_single ~indent key result =
let indent = String.make indent ' ' in
Printf.eprintf
"%s %s %s %s %s %s%s\n%!"
(pretty_num result.start)
(pretty_num result.delta)
(pretty_pct result.delta result.start)
(pretty_num result.high_water_mark_delta)
(pretty_pct result.high_water_mark_delta result.start)
indent
key
in
let header_without_section = " START DELTA HWM DELTA " in
let pre_section_whitespace = String.make (String.length header_without_section) ' ' in
let print_group ~indent finished_results group_name =
Base.Option.iter (SMap.find_opt group_name finished_results) ~f:(fun group ->
let indent_str = String.make (String.length header_without_section + indent - 2) ' ' in
Printf.eprintf "%s== %s ==\n%!" indent_str group_name;
SMap.iter (print_summary_single ~indent:(indent + 2)) group)
in
let print_header label =
let label = Printf.sprintf "%s Memory Stats" label in
let header = header_without_section ^ "SECTION" in
let header_len = String.length header + 8 in
let whitespace_len = header_len - String.length label in
Printf.eprintf
"%s%s%s\n%!"
(String.make ((whitespace_len + 1) / 2) '=')
label
(String.make (whitespace_len / 2) '=');
Printf.eprintf "%s\n%!" header;
Printf.eprintf "%s\n%!" (String.make header_len '-')
in
let rec print_finished ~indent results =
if (not (SMap.is_empty results.finished_results)) || results.finished_sub_results <> [] then (
let header_indent = String.make indent '=' in
Printf.eprintf
"%s%s %s %s\n%!"
pre_section_whitespace
header_indent
results.finished_label
header_indent;
let indent = indent + 2 in
List.iter (print_group ~indent results.finished_results) results.finished_groups;
List.iter (fun sub_result -> print_finished ~indent sub_result) results.finished_sub_results
)
in
fun memory ->
if SMap.cardinal memory.finished_results > 0 || memory.finished_sub_results <> [] then (
print_header memory.finished_label;
print_finished ~indent:2 memory
)
let merge ~from ~into =
into := { !into with running_sub_results_rev = from :: !into.running_sub_results_rev }
end
type running = {
running_timing: Timing.running;
running_memory: Memory.running;
}
type finished = {
finished_timing: Timing.finished;
finished_memory: Memory.finished;
}
let print_summary profile =
Printf.eprintf "\n%!";
Memory.print_summary_memory_table profile.finished_memory;
Printf.eprintf "\n%!";
Timing.print_summary_timing_table profile.finished_timing;
Printf.eprintf "\n%!"
let with_profiling_lwt ~label ~should_print_summary f =
let%lwt (finished_timing, (finished_memory, ret)) =
Timing.with_timing_lwt ~label ~f:(fun running_timing ->
Memory.with_memory_lwt ~label ~f:(fun running_memory ->
let profile = { running_timing; running_memory } in
f profile))
in
let finished_profile = { finished_timing; finished_memory } in
if should_print_summary then print_summary finished_profile;
Lwt.return (finished_profile, ret)
let get_profiling_duration profile = Timing.get_total_wall_duration profile.finished_timing
let with_timer_lwt ?should_print ~timer ~f profile =
Timing.with_timer_lwt ?should_print ~timer ~f profile.running_timing
let with_timer ?should_print ~timer ~f profile =
Timing.with_timer ?should_print ~timer ~f profile.running_timing
let legacy_sample_memory ~metric ~value profile =
Memory.legacy_sample_memory ~metric ~value profile.running_memory
let total_memory_group = "TOTAL"
let sample_memory ?group ~metric ~value profile =
Memory.sample_memory ~group:total_memory_group ~metric ~value profile.running_memory;
Base.Option.iter group ~f:(fun group ->
Memory.sample_memory ~group ~metric ~value profile.running_memory)
let add_memory ?group ~metric ~start ~delta ~hwm_delta profile =
Memory.add_memory
~group:total_memory_group
~metric
~start
~delta
~hwm_delta
profile.running_memory;
Base.Option.iter group ~f:(fun group ->
Memory.add_memory ~group ~metric ~start ~delta ~hwm_delta profile.running_memory)
let to_json_properties profile =
[
("timing", Timing.to_json ~abridged:false profile.finished_timing);
("memory", Memory.to_json ~abridged:false profile.finished_memory);
]
let to_legacy_json_properties profile =
[
("timing", Timing.to_json_legacy ~abridged:false profile.finished_timing);
("memory", Memory.to_json ~abridged:false profile.finished_memory);
]
let get_timing_json_string profile =
Timing.to_json ~abridged:false profile.finished_timing |> Hh_json.json_to_string
let get_abridged_timing_json_string profile =
Timing.to_json ~abridged:true profile.finished_timing |> Hh_json.json_to_string
let get_abridged_legacy_timing_json_string profile =
Timing.to_json_legacy ~abridged:true profile.finished_timing |> Hh_json.json_to_string
let get_memory_json_string profile =
Memory.to_json ~abridged:false profile.finished_memory |> Hh_json.json_to_string
let get_abridged_memory_json_string profile =
Memory.to_json ~abridged:true profile.finished_memory |> Hh_json.json_to_string
let merge ~from ~into =
Timing.merge ~from:from.finished_timing ~into:into.running_timing;
Memory.merge ~from:from.finished_memory ~into:into.running_memory