Summary: This adds an option `--trace-events` that generates a Chrome trace event[1] to quickly visualise the performance of infer. Reviewed By: mbouaziz Differential Revision: D9831599 fbshipit-source-id: 96a33c627master
parent
eccfb4de10
commit
6837629654
@ -0,0 +1,285 @@
|
||||
(*
|
||||
* 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 _ -> ()
|
@ -0,0 +1,44 @@
|
||||
(*
|
||||
* 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 Json : sig
|
||||
(** only what we need for now *)
|
||||
type t = [`Int of int | `String of string | `Assoc of (string * t) list | `List of t list]
|
||||
end
|
||||
|
||||
type scope = Global | Process
|
||||
|
||||
type logger
|
||||
|
||||
val log_begin_event :
|
||||
logger
|
||||
-> ?timestamp:Mtime.t
|
||||
-> ?categories:string list
|
||||
-> ?arguments:(string * Json.t) list
|
||||
-> name:string
|
||||
-> unit
|
||||
-> unit
|
||||
|
||||
val log_end_event :
|
||||
logger -> ?timestamp:Mtime.t -> ?arguments:(string * Json.t) list -> unit -> unit
|
||||
|
||||
val log_complete_event :
|
||||
logger
|
||||
-> timestamp:Mtime.t
|
||||
-> ?duration:Mtime.Span.t
|
||||
-> ?categories:string list
|
||||
-> ?arguments:(string * Json.t) list
|
||||
-> name:string
|
||||
-> unit
|
||||
-> unit
|
||||
[@@warning "-32"]
|
||||
|
||||
val log_instant_event : logger -> ?timestamp:Mtime.t -> name:string -> scope -> unit
|
||||
|
||||
val log : (logger -> unit) -> unit
|
Loading…
Reference in new issue