package ppx_module_timer

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file ppx_module_timer_runtime.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
open! Base
module Gc = Caml.Gc

external __MODULE__ : string = "%loc_MODULE"

let am_recording_environment_variable = "PPX_MODULE_TIMER"

let get_am_recording_environment_variable () =
  (* avoid Caml.Sys.getenv_opt to preserve 4.04.x compatibility *)
  match Caml.Sys.getenv am_recording_environment_variable with
  | value -> Some value
  | exception _ -> None
;;

let am_recording = Option.is_some (get_am_recording_environment_variable ())

module Startup_time = struct
  module Gc_events = struct
    type t =
      { minor_collections : int
      ; major_collections : int
      ; compactions : int
      }
    [@@deriving sexp_of]
  end

  type t =
    { module_name : string
    ; startup_time_in_nanoseconds : Int63.t
    ; gc_events : Gc_events.t
    }
  [@@deriving sexp_of]
end

let startup_times_in_reverse_chronological_order = ref []
let currently_running_module_name = ref ""
let currently_running_module_start = ref Int63.zero
let currently_running_module_gc_stats = ref (Gc.quick_stat ())

let reset_currently_running_module () =
  currently_running_module_name := "";
  currently_running_module_start := Int63.zero
;;

let record_start module_name =
  if am_recording
  then (
    assert (String.is_empty !currently_running_module_name);
    currently_running_module_name := module_name;
    currently_running_module_gc_stats := Gc.quick_stat ();
    (* call [Time_now] as late as possible before running the module body *)
    currently_running_module_start := Time_now.nanoseconds_since_unix_epoch ())
;;

let record_until module_name =
  if am_recording
  then (
    (* compute [Time_now] as soon as possible after running the module body *)
    let until = Time_now.nanoseconds_since_unix_epoch () in
    let start = !currently_running_module_start in
    let gc_stats_after = Gc.quick_stat () in
    let gc_stats_before = !currently_running_module_gc_stats in
    let startup_time_in_nanoseconds = Int63.( - ) until start in
    assert (String.equal !currently_running_module_name module_name);
    let gc_events : Startup_time.Gc_events.t =
      { minor_collections =
          gc_stats_after.minor_collections - gc_stats_before.minor_collections
      ; major_collections =
          gc_stats_after.major_collections - gc_stats_before.major_collections
      ; compactions = gc_stats_after.compactions - gc_stats_before.compactions
      }
    in
    let startup_time : Startup_time.t =
      { module_name; startup_time_in_nanoseconds; gc_events }
    in
    startup_times_in_reverse_chronological_order :=
      startup_time :: !startup_times_in_reverse_chronological_order;
    reset_currently_running_module ())
;;

let string_of_span_in_ns nanos = Int63.to_string nanos ^ "ns"

let char_is_digit_or_underscore = function
  | '0' .. '9'
  | '_' -> true
  | _ -> false
;;

let span_in_ns_of_string string =
  match String.chop_suffix string ~suffix:"ns" with
  | Some prefix
    when String.for_all prefix ~f:char_is_digit_or_underscore ->
    Some (Int63.of_string prefix)
  | _ -> None
;;

let gc_events_suffix_string
      ({ minor_collections; major_collections; compactions } : Startup_time.Gc_events.t)
  =
  let to_list description count =
    if count = 0 then [] else [ Int.to_string count ^ " " ^ description ]
  in
  let strings =
    to_list "minor collections" minor_collections
    @ to_list "major collections" major_collections
    @ to_list "compactions" compactions
  in
  if List.is_empty strings then "" else "; GC: " ^ String.concat strings ~sep:", "
;;

let print_with_left_column_right_justified list =
  let left_column_width =
    List.fold list ~init:0 ~f:(fun width (left, _, _) ->
      Int.max width (String.length left))
  in
  List.iter list ~f:(fun (left, right, gc_events) ->
    Stdio.printf
      "%*s %s%s\n"
      left_column_width
      left
      right
      (gc_events_suffix_string gc_events))
;;

let default_print_recorded_startup_times startup_times =
  let startup_times =
    match
      get_am_recording_environment_variable () |> Option.bind ~f:span_in_ns_of_string
    with
    | None -> startup_times
    | Some override ->
      Stdio.print_endline "ppx_module_timer: overriding time measurements for testing";
      List.mapi startup_times ~f:(fun index (startup_time : Startup_time.t) ->
        let startup_time_in_nanoseconds =
          Int63.( * ) override (Int63.of_int (index + 1))
        in
        { startup_time with startup_time_in_nanoseconds })
  in
  List.map
    startup_times
    ~f:(fun ({ module_name; startup_time_in_nanoseconds; gc_events } : Startup_time.t) ->
      string_of_span_in_ns startup_time_in_nanoseconds, module_name, gc_events)
  |> print_with_left_column_right_justified
;;

let print_recorded_startup_times = ref default_print_recorded_startup_times

let () =
  Caml.at_exit (fun () ->
    !print_recorded_startup_times
      (List.rev !startup_times_in_reverse_chronological_order))
;;
OCaml

Innovation. Community. Security.