Source file intf.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
(** For a tutorial on using [Core_profiler] please look at:
[http://docs/programming/performance/core_profiler.html]
This interface file defines the [Profiler_intf] interface, which has three
implementations:
(1) By opening [Core_profiler_disabled.Std] you get an implementation of the interface
where the profiling functions are no-ops.
(2) By opening [Core_profiler.Std_online] you get an implementation where the
profiling stats like mean, stddev etc are maintained online, i.e. in-process with the
process that is being profiled. Running the online profiler causes the program to
print a table of stats periodically to stdout as your program runs.
(3) By opening [Core_profiler.Std_offline] you get the implementation where the
profiling data is held in-memory in a buffer and is written out to a file at process
exit and the stats can be analyzed offline. For most programs this is the preferred
approach to profiling.
Broadly, there are two ways to collect metrics using [Core_profiler]:
(1) One can collect time-stamped metrics at various points in your programs using
[Probe]s. The metrics are integers that represent some application specific quantity,
for example the length of some list, the number of allocated words on the heap etc.
(2) Alternately, one can use [Timer]s to collect time stamps without recording any
associated metric. [Timer]s are a special kind of [Probe] that are useful when one
only wants to measure *when something happened* and there is no associated quantity to
measure. [Timer]s are strictly less general than [Probe]s, but are slightly more
efficient. *)
open! Core
module type Probe = sig
type t
type probe = t
(** The [*_args] below are instantiated differently for [Timer]s and [Probe]s. See
[Profiler_intf] below. *)
type 'a create_args
type 'a record_args
(** Create a timer or probe that isn't in a group. *)
val create : name:string -> t create_args
(** [record] a particular data sample. *)
val record : t record_args
(** A [Group] provides a way of grouping multiple probes (or timers). Once grouped, one
can measure stats between members of a group -- i.e. the time it takes to get from
one probe to the other in the group, or the change in a metric between two probes in
a group.
[Core_profiler] supports a path query syntax where one can ask for stats about
*paths* in the time series of data. The idea of a group is best explained by an
example, which you can find in the tutorial. *)
module Group : sig
type t
val create : name:string -> t create_args
(** [sources] should be a list of probes, specifying the edges that we care about.
(i.e., we care about (s, this) for all s in [sources]). When using online
profiling, if no sources are specified no, stats will be collected.
For offline profiling sources are less relevant. All the probe information is
collected in the output and sources provides a default configuration to the
profiler_tool. If no sources are specified, the offline tool will fall back to
the default of "all two-probe direct paths" of a group. *)
val add_probe : t -> ?sources:probe array -> name:string -> unit -> probe
(** Resetting a group avoids path/delta calculation across the reset. This shouldn't
be necessary in simple cases if you specify the edges you care about, via
[sources] or otherwise. In more complex cases with cycles, you will need to call
this at the start or end of the function you are instrumenting. *)
val reset : t -> unit
end
end
type 'a timer_create_args = 'a
type 'a timer_record_args = 'a -> unit
type 'a probe_create_args = units:Profiler_units.t -> 'a
type 'a probe_record_args = 'a -> int -> unit
(** All three profilers -- the disabled one, the online one and the offline one --
implement [Profiler_intf]. *)
module type Profiler_intf = sig
module Profiler : sig
(** [is_enabled] can be used to "guard" expensive computations done while recording a
metric. For example:
{[
Probe.record len <some computation>
]}
If <some computation> is just a variable reference it is free (when using
[Core_profiler_disabled]). However, if it involves some actual work, it is better
to write:
{[
if Profiler.is_enabled then Probe.record len <some computation>
]}
When using online or offline profiling, the boolean is constant [true] and with
disabled profiling, the boolean is [false]. *)
val is_enabled : bool
(** [configure] lets one set various profiler parameters programmatically.
- [don't_require_core_profiler_env] : To protect against Core_profiler being
enabled in production, it will check the environment variable [CORE_PROFILER]
whenever you try to create the first [Timer] or [Probe]. Setting
[don't_require_core_profiler_env] disables raising an exception if the
[CORE_PROFILER] environment variable is not set.
You need to call this before any [Timer] or [Probe] has been created. If you
set [don't_require_core_profiler_env] after a [Timer] or [Probe] has been
created, then it will raise an exception if the value you are trying to set
disagrees with that which was read from the environment.
- [offline_profiler_data_file] : This specifies the name of the data file to use.
By default this is "profiler.dat".
- [online_print_time_interval_secs] : This is the rate at which stats should be
printed by the online profiler. Stats may not be printed at this rate is one
does not call [at] or [safe_to_delay] periodically.
- [online_print_by_default] : Setting this to [false] disables printing stats
every time interval. One can print stats by explicitly calling [dump_stats].
The environment variable [CORE_PROFILER] can be used to configure the
app. Also see [core_profiler_env_help_string] below. *)
val configure
: ?don't_require_core_profiler_env:unit
-> ?offline_profiler_data_file:string
-> ?online_print_time_interval_secs:int
-> ?online_print_by_default:bool
-> unit
-> unit
(** There are several slow operations that may happen occasionally when calling
[record]: allocation, [Time_stamp_counter] calibration, etc. [safe_to_delay]
checks if they will be necessary soon, and does them in advance. If possible,
call this (fairly regularly) from a time-insensitive point in code (or at least,
outside any deltas / groups) to reduce the number of spurious jumps in time
deltas. If you know for certain that you will be using [Core_profiler], you also
probably want to call this at startup, to perform the first allocation. *)
val safe_to_delay : unit -> unit
(** In the online profiler, [dump_stats] prints a table of stats -- this is the same
table that is printed periodically and this function gives the user the option to
disable the automatic printing and take control of the printing process.
In the offline profiler, [dump_stats] writes out all the collected stats so
far. This normally happens [at_exit] and this function lets the programmer dump
the stats earlier. *)
val dump_stats : unit -> unit
end
(** A [Timer] contains only a time stamp and no extra information; however, it is useful
because (in [Offline]) the current time is recorded when measurements are made. *)
module Timer :
Probe
with type 'a create_args := 'a timer_create_args
and type 'a record_args := 'a timer_record_args
(** A [Probe] records some integer value that is passed to [at] along with a
timestamp. *)
module Probe :
Probe
with type 'a create_args := 'a probe_create_args
and type 'a record_args := 'a probe_record_args
(** [Delta_probe] is an optimized two-probe group to track changes to some counter. *)
module Delta_probe : sig
type t
type state
val create : name:string -> units:Profiler_units.t -> t
(** To measure changes in a value, one can call [start] followed by a call [stop]
after some time. The call to [stop] will record the delta. Calls to
[start]/[stop] must be interleaved for each [t].
Calling [pause] in place of [stop] causes [t] to accumulate, but not record, the
delta. [start] and [pause] can then be interleaved multiple times. Afterwards,
calling [record] will record the sum of the deltas between each [start]/[pause],
and reset [t].
Valid sequences should satisfy this regular expression:
{v
start;(pause;start;)*((pause;record;)|stop;)
v}
Calling these functions out of order will cause bad data to be recorded. This API
does not raise exceptions, so one will not be warned of errors.
For each [t], there are two valid sequences of calls. The first is calling
[start] then [stop]. The second is calling [start] then [pause] an arbitrary
number of times, and ending with [record]. *)
val start : t -> int -> unit
val stop : t -> int -> unit
val pause : t -> int -> unit
val record : t -> unit
(** These are non-stateful and can be used in Async, wherein multiple jobs might call
[stateless_start] before the corresponding [stop_async] is called. One can use
[stateless_start] and [stateless_stop] to wrap async functions roughly like the
following. This function cannot be provided as part of the [Core_profiler]
library because we'd like the library to be usable in [Async] and hence now depend
on it.
{[
let wrap_async t f x =
let state = stateless_start t (Gc.minor_words ()) in
try_with ~run:`Now (fun () -> f x)
>>= fun res ->
stateless_stop t state (Gc.minor_words ());
match res with
| Ok x -> return x
| Error ex -> Exn.reraise ex "Core_profiler wrap_async"
]}
The stateless API does not support pausing. This is because state would require
memory allocation if it supported accumulating the counter. *)
val stateless_start : t -> int -> state
val stateless_stop : t -> state -> int -> unit
end
(** [Delta_timer] is an optimized two-probe group to track time differences between
calls to [start] and [stop]. *)
module Delta_timer : sig
type t
type state
val create : name:string -> t
val start : t -> unit
val stop : t -> unit
val pause : t -> unit
val record : t -> unit
val stateless_start : t -> state
val stateless_stop : t -> state -> unit
(** Typically partially applied (the first two arguments) to produce a 'wrapped'
function. This behaves like the identity function on functions, except it times
the inner function. *)
val wrap_sync : t -> ('a -> 'b) -> 'a -> 'b
val wrap_sync2 : t -> ('a -> 'b -> 'c) -> 'a -> 'b -> 'c
val wrap_sync3 : t -> ('a -> 'b -> 'c -> 'd) -> 'a -> 'b -> 'c -> 'd
val wrap_sync4 : t -> ('a -> 'b -> 'c -> 'd -> 'e) -> 'a -> 'b -> 'c -> 'd -> 'e
end
end
let core_profiler_env_help_string =
"\n\
\ Assign a value to environment variable CORE_PROFILER in order to proceed,\n\
\ or replace references to [Core_profiler] with [Core_profiler_disabled].\n\n\
\ The environment variable [CORE_PROFILER] must be set to run a program that uses the\n\
\ [Core_profiler] library. This check is meant to protect us from accidentally\n\
\ deploying binaries with profiling into production. The variable can contain zero \
or\n\
\ more name-value pairs.\n\n\
\ Syntax:\n\
\ CORE_PROFILER=[name=value(,name=value)+] <command to run>\n\n\
\ i.e. commas are separators and invalid names will simply be ignored.\n\n\
\ The valid names are:\n\n\
\ OUTPUT_FILE=<filename>\n\
\ This determines the output filename for the offline profiler.\n\n\
\ PRINT_INTERVAL=<int>\n\
\ This determines the integer number of seconds between outputing online stats\n\
\ summaries. Setting print interval does not affect the offline profiler.\n\n\
\ PRINT_ENABLED=<true|false>\n\
\ Setting this to false disables printing in the online profiler. One can use this to\n\
\ control printing by calling the [Profiler.dump_stats] function.\n\n\
\ Example:\n\
\ CORE_PROFILER=PRINT_INTERVAL=3 myprog.exe\n\n\
\ Example:\n\
\ CORE_PROFILER= myprog.exe\n"
;;