source/profiling.ml (109 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
module Worker = Hack_parallel.Std.Worker
module GlobalState = struct
type t = {
mutable profiling_output: string option;
mutable memory_profiling_output: string option;
}
let global_state = { profiling_output = None; memory_profiling_output = None }
let initialize ?profiling_output ?memory_profiling_output () =
Option.iter profiling_output ~f:(fun output ->
PyrePath.remove_if_exists (PyrePath.create_absolute output);
global_state.profiling_output <- Some output);
Option.iter memory_profiling_output ~f:(fun output ->
PyrePath.remove_if_exists (PyrePath.create_absolute output);
global_state.memory_profiling_output <- Some output);
()
let get () = global_state
let restore old_state =
global_state.profiling_output <- old_state.profiling_output;
global_state.memory_profiling_output <- old_state.memory_profiling_output
end
module Event = struct
type event_type =
| Duration of int
| Counter of string option
[@@deriving yojson]
type t = {
name: string;
worker_id: int;
pid: int;
event_type: event_type;
timestamp: int;
tags: (string * string) list;
}
[@@deriving yojson]
let now_in_microseconds () =
let now_in_nanoseconds = Mtime_clock.now () |> Mtime.to_uint64_ns |> Int.of_int64_trunc in
now_in_nanoseconds / 1000
let create ?(timestamp = now_in_microseconds ()) ?(tags = []) ~event_type name =
let name = String.filter ~f:Char.is_print name in
let pid = Unix.getpid () |> Pid.to_int in
let worker_id = Worker.current_worker_id () in
{ name; worker_id; pid; event_type; timestamp; tags }
end
let log_to_path path ~event_creator =
let path = PyrePath.create_absolute path in
let line = event_creator () |> Event.to_yojson |> Yojson.Safe.to_string in
File.append ~lines:[line] path
(* Taking a constructor instead of an event here so that events can be created lazily *)
let log_performance_event event_creator =
Option.iter GlobalState.global_state.profiling_output ~f:(log_to_path ~event_creator)
let log_memory_event event_creator =
Option.iter GlobalState.global_state.memory_profiling_output ~f:(log_to_path ~event_creator)
type 'a result_with_tags = {
result: 'a;
tags: unit -> (string * string) list;
}
let track_duration_event_with_dynamic_tags ~f name =
let timer = Timer.start () in
let { result; tags } = f () in
let duration = Timer.stop_in_us timer in
let create_event () =
let tags = tags () in
Event.create name ~tags ~event_type:(Duration duration)
in
log_performance_event create_event;
result
let track_duration_event ?(tags = []) ~f name =
let f () = { result = f (); tags = (fun () -> tags) } in
track_duration_event_with_dynamic_tags ~f name
let track_shared_memory_usage ?name () =
let create_event () =
let used_heap_size = SharedMemory.heap_size () in
let wasted_heap_size = SharedMemory.wasted_heap_size () in
let { SharedMemory.nonempty_slots = nonempty_hash_slots; used_slots = used_hash_slots; _ } =
SharedMemory.hash_stats ()
in
let { SharedMemory.used_slots = used_dependency_slots; _ } = SharedMemory.dep_stats () in
let create_tag name counter = name, string_of_int counter in
Event.create
"Shared Memory Usage"
~event_type:(Event.Counter name)
~tags:
[
create_tag "used_heap_size" used_heap_size;
create_tag "wasted_heap_size" wasted_heap_size;
create_tag "nonempty_hash_slots" nonempty_hash_slots;
create_tag "used_hash_slots" used_hash_slots;
create_tag "used_dependency_slots" used_dependency_slots;
]
in
log_memory_event create_event
let with_before_and_after_shared_memory_tracking f ~name =
track_shared_memory_usage () ~name:(Format.sprintf "Before %s" name);
let result = f () in
track_shared_memory_usage () ~name:(Format.sprintf "After %s" name);
result
let track_duration_and_shared_memory ?tags ~f name =
with_before_and_after_shared_memory_tracking (fun () -> track_duration_event name ?tags ~f) ~name
let track_duration_and_shared_memory_with_dynamic_tags ~f name =
with_before_and_after_shared_memory_tracking
(fun () -> track_duration_event_with_dynamic_tags name ~f)
~name