You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
8.8 KiB

(*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module F = Format
module L = Logging
module Json = struct
(** do not break lines *)
let pp_seq pp f l = Pp.seq ~sep:"," pp f l
let pp_string f s = F.fprintf f "\"%s\"" (Escape.escape_json s)
let pp_field pp_value f field_name field_value =
Format.fprintf f "%a:%a" pp_string field_name pp_value field_value
(** only what we need for now *)
type t = [`Int of int | `String of string | `Assoc of (string * t) list | `List of t list]
let rec pp_assoc_field f (key, value) = pp_field pp f key value
and pp f = function
| `Int i ->
F.pp_print_int f i
| `String s ->
pp_string f s
| `Assoc fields ->
F.fprintf f "{%a}" (pp_seq pp_assoc_field) fields
| `List items ->
F.fprintf f "[%a]" (pp_seq pp) items
end
module JsonFragment = struct
type t = AssocBegin | AssocEnd | ListBegin | ListItemSeparator | ListEnd
let to_string = function
| AssocBegin ->
"AssocBegin"
| AssocEnd ->
"AssocEnd"
| ListBegin ->
"ListBegin"
| ListItemSeparator ->
"ListItemSeparator"
| ListEnd ->
"ListEnd"
type pp_state = Outside | InAssocFirst | InAssocMiddle | InList
let string_of_state = function
| Outside ->
"Outside"
| InAssocFirst ->
"InAssocFirst"
| InAssocMiddle ->
"InAssocMiddle"
| InList ->
"InList"
(** for some limited (not thread-safe) form of safety, and to know when we need to print separators
*)
let pp_state = ref [Outside]
let pp f json_fragment =
match (json_fragment, !pp_state) with
| AssocBegin, ((Outside | InList) :: _ as state) ->
pp_state := InAssocFirst :: state
| AssocEnd, (InAssocFirst | InAssocMiddle) :: state' ->
F.pp_print_string f "}" ;
pp_state := state'
| ListBegin, ((Outside | InList) :: _ as state) ->
F.pp_print_string f "[" ;
pp_state := InList :: state
| ListItemSeparator, InList :: _ ->
F.pp_print_string f ","
| ListEnd, InList :: state0 ->
F.pp_print_string f "]" ;
pp_state := state0
| _ ->
L.die InternalError "Unexpected json fragment \"%s\" in state [%a]"
(to_string json_fragment)
(Pp.seq (Pp.to_string ~f:string_of_state))
!pp_state
let pp_assoc_field pp_value f key value =
match !pp_state with
| InAssocFirst :: state0 ->
F.pp_print_string f "{" ;
Json.pp_field pp_value f key value ;
pp_state := InAssocMiddle :: state0
| InAssocMiddle :: _ ->
F.pp_print_string f "," ;
Json.pp_field pp_value f key value
| _ ->
L.die InternalError "Unexpected assoc field \"%t\" in state [%a]"
(fun f -> Json.pp_field pp_value f key value)
(Pp.seq (Pp.to_string ~f:string_of_state))
!pp_state
end
type event_type = Begin | Complete | End | Instant
(* TODO(2018): add [Thread] when we have multicore OCaml :) *)
type scope = Global | Process
(* initialised at the start of the program *)
let t0 = Mtime_clock.now ()
let pp_arguments_field f arguments =
match arguments with
| [] ->
()
| _ :: _ ->
JsonFragment.pp_assoc_field Json.pp f "args" (`Assoc arguments)
let pp_categories_field f categories =
match categories with
| [] ->
()
| _ :: _ ->
let pp_categories f categories =
Format.fprintf f "\"%a\"" (Json.pp_seq F.pp_print_string) categories
in
JsonFragment.pp_assoc_field pp_categories f "cat" categories
let pp_duration_field f duration =
JsonFragment.pp_assoc_field Json.pp f "dur" (`Int (Mtime.Span.to_us duration |> Float.to_int))
let pp_event_type_field f event_type =
let pp_event_type f event_type =
match event_type with
| Begin ->
Json.pp_string f "B"
| Complete ->
Json.pp_string f "X"
| End ->
Json.pp_string f "E"
| Instant ->
Json.pp_string f "i"
in
JsonFragment.pp_assoc_field pp_event_type f "ph" event_type
let pp_process_id_field f pid = JsonFragment.pp_assoc_field Pid.pp f "pid" pid
let pp_name_field f name = JsonFragment.pp_assoc_field Json.pp f "name" (`String name)
let pp_scope_field f scope =
let pp_scope f = function Global -> Json.pp_string f "g" | Process -> Json.pp_string f "p" in
JsonFragment.pp_assoc_field pp_scope f "s" scope
let pp_timestamp_field f ts_opt =
let ts = match ts_opt with None -> Mtime_clock.elapsed () | Some t -> Mtime.span t0 t in
JsonFragment.pp_assoc_field Json.pp f "ts" (`Int (Mtime.Span.to_us ts |> Float.to_int))
let log_begin_event f ?timestamp ?categories ?arguments ~name () =
JsonFragment.pp f AssocBegin ;
pp_event_type_field f Begin ;
pp_name_field f name ;
pp_timestamp_field f timestamp ;
pp_process_id_field f (ProcessPoolState.get_pid ()) ;
Option.iter categories ~f:(pp_categories_field f) ;
Option.iter arguments ~f:(pp_arguments_field f) ;
JsonFragment.pp f AssocEnd
let log_end_event f ?timestamp ?arguments () =
JsonFragment.pp f AssocBegin ;
pp_event_type_field f End ;
pp_timestamp_field f timestamp ;
Option.iter arguments ~f:(pp_arguments_field f) ;
pp_process_id_field f (ProcessPoolState.get_pid ()) ;
JsonFragment.pp f AssocEnd
let log_complete_event f ~timestamp ?duration ?categories ?arguments ~name () =
JsonFragment.pp f AssocBegin ;
pp_event_type_field f Complete ;
pp_name_field f name ;
pp_timestamp_field f (Some timestamp) ;
pp_process_id_field f (ProcessPoolState.get_pid ()) ;
Option.iter duration ~f:(pp_duration_field f) ;
Option.iter categories ~f:(pp_categories_field f) ;
Option.iter arguments ~f:(pp_arguments_field f) ;
JsonFragment.pp f AssocEnd
let log_instant_event f ?timestamp ~name scope =
JsonFragment.pp f AssocBegin ;
pp_event_type_field f Instant ;
pp_name_field f name ;
pp_timestamp_field f timestamp ;
pp_process_id_field f (ProcessPoolState.get_pid ()) ;
pp_scope_field f scope ;
JsonFragment.pp f AssocEnd
type logger = F.formatter
let register_gc_stats logger =
let alarms = ref [] in
let gc_alarm = Gc.Expert.Alarm.create (fun () -> alarms := Mtime_clock.now () :: !alarms) in
Epilogues.register ~description:"recording gc alarms" ~f:(fun () ->
Gc.Expert.Alarm.delete gc_alarm ;
List.iter !alarms ~f:(fun timestamp ->
log_instant_event logger ~timestamp ~name:"gc_major" Process ;
JsonFragment.pp logger ListItemSeparator ;
F.fprintf logger "%!" ) )
let logger =
lazy
(let log_file =
let results_dir =
(* if invoked in a sub-dir (e.g., in Buck integrations), log inside the original log
file *)
Sys.getenv Config.infer_top_results_dir_env_var
|> Option.value ~default:Config.results_dir
in
results_dir ^/ Config.trace_events_file
in
let is_toplevel_process = CommandLineOption.is_originator && not !ProcessPoolState.in_child in
( if is_toplevel_process then
let preexisting_logfile = PolyVariantEqual.( = ) (Sys.file_exists log_file) `Yes in
if preexisting_logfile then Unix.unlink log_file ) ;
let out_channel = Pervasives.open_out_gen [Open_append; Open_creat] 0o666 log_file in
let logger = F.formatter_of_out_channel out_channel in
register_gc_stats logger ;
if is_toplevel_process then (
JsonFragment.pp logger ListBegin ;
F.fprintf logger "%!" ;
Epilogues.register_late ~description:"closing perf trace json" ~f:(fun () ->
log_instant_event logger ~name:"end" Global ;
JsonFragment.pp logger ListEnd ;
F.fprintf logger "@." ;
Out_channel.close out_channel ) ) ;
logger)
(* export logging functions that output a list element at a time and flushes so that multiple
processes can write to the same file and not garble each other's output. This should mostly work
as appending to a file is atomic as long as the write is not too big. *)
let log_begin_event f ?timestamp ?categories ?arguments ~name () =
log_begin_event f ?timestamp ?categories ?arguments ~name () ;
JsonFragment.pp f ListItemSeparator ;
F.fprintf f "%!"
let log_end_event f ?timestamp ?arguments () =
log_end_event f ?timestamp ?arguments () ;
JsonFragment.pp f ListItemSeparator ;
F.fprintf f "%!"
let log_complete_event f ~timestamp ?duration ?categories ?arguments ~name () =
log_complete_event f ~timestamp ?duration ?categories ?arguments ~name () ;
JsonFragment.pp f ListItemSeparator ;
F.fprintf f "%!"
let log_instant_event f ?timestamp ~name scope =
log_instant_event f ?timestamp ~name scope ;
JsonFragment.pp f ListItemSeparator ;
F.fprintf f "%!"
let log =
if Config.trace_events then fun f_log ->
let logger = Lazy.force logger in
f_log logger
else fun _ -> ()