Source file vernacControl.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
type 'e profile_state_gen = {
events : 'e;
sums : NewProfile.sums;
counters : NewProfile.Counters.t;
}
type profile_state = NewProfile.MiniJson.t list list profile_state_gen
let empty_profstate = {
events = [];
sums = NewProfile.empty_sums;
counters = NewProfile.Counters.zero;
}
(** Partially interpreted control flags.
[ControlTime {duration}] means "Time" where the
partial interpretation has already take [duration].
In [ControlRedirect], [truncate] tells whether we should truncate
the file before printing (ie [false] if not the first phase).
Alternatively we could do [ControlRedirect of out_channel],
but that risks leaking open channels.
[ControlFail {st}] means "Fail" where the partial interpretation
did not fail and produced state [st].
[ControlSucceed {st}] means "Succeed" where the partial
interpretation succeeded and produced state [st].
*)
type 'state control_entry =
| ControlTime of { duration: System.duration }
| ControlInstructions of { instructions: System.instruction_count }
| ControlProfile of { to_file : string option; profstate : profile_state }
| ControlRedirect of { fname : string; truncate : bool}
| ControlTimeout of { remaining : float }
| ControlFail of { st : 'state }
| ControlSucceed of { st : 'state }
type 'state control_entries = 'state control_entry list
let check_timeout_f n =
if n <= 0. then CErrors.user_err Pp.(str "Timeout must be > 0.")
let with_measure measure add fmt flag init f =
let result = measure f () in
let result = match result with
| Ok (v,d) -> Ok (v, add init d)
| Error (e,d) -> Error (e, add init d)
in
begin match result with
| Ok (v,result) -> Some (flag result, v)
| Error (e,_) ->
Feedback.msg_notice @@ fmt result;
Exninfo.iraise e
end
let measure_profile f () =
let start_cnt = NewProfile.Counters.get() in
let events, sums, v = NewProfile.with_profiling (fun () ->
try Ok (f ())
with e -> Error (Exninfo.capture e))
in
let counters = NewProfile.Counters.(get() - start_cnt) in
let prof = { events=events; sums; counters } in
match v with
| Ok v -> Ok (v,prof)
| Error e -> Error (e, prof)
let add_profile a b = {
events = if CList.is_empty b.events then a.events else b.events :: a.events;
sums = NewProfile.sums_union a.sums b.sums;
counters = NewProfile.Counters.(a.counters + b.counters);
}
let fmt_profiling counters (sums:NewProfile.sums) =
let open Pp in
let sums = CString.Map.bindings sums in
let sums = List.sort (fun (_,(t1,_)) (_,(t2,_)) -> Float.compare t2 t1) sums in
let longest = List.fold_left (fun longest (name,_) -> max longest (String.length name)) 0 sums in
let pr_one (name,(time,cnt)) =
hov 1
(str name ++ str ":" ++ brk (1 + longest - String.length name, 0) ++
str (Format.asprintf "%a" NewProfile.pptime time) ++
pr_comma () ++ int cnt ++ str " calls")
in
v 0 (
NewProfile.Counters.print counters ++ spc() ++ spc() ++
prlist_with_sep spc pr_one sums)
let rec output_events fmt = function
| [] -> ()
| [[last]] -> Format.fprintf fmt "%a\n" NewProfile.MiniJson.pr last
| [] :: rest -> assert false
| (current :: next) :: rest ->
Format.fprintf fmt "%a,\n" NewProfile.MiniJson.pr current;
match next with
| [] -> output_events fmt rest
| _::_ -> output_events fmt (next :: rest)
let fmt_profile to_file v =
let {events;sums;counters} = match v with
| Ok (_,x) -> x
| Error (_,x) -> x
in
to_file |> Option.iter (fun to_file ->
let to_file = System.get_output_path (to_file ^ ".json") in
let f = open_out to_file in
let fmt = Format.formatter_of_out_channel f in
NewProfile.format_header fmt;
output_events fmt events;
NewProfile.format_footer fmt;
close_out f
);
fmt_profiling counters sums
let with_timeout ~timeout:n f =
check_timeout_f n;
let start = Unix.gettimeofday () in
begin match Control.timeout n f () with
| None -> Exninfo.iraise (Exninfo.capture CErrors.Timeout)
| Some v ->
let stop = Unix.gettimeofday () in
let remaining = n -. (stop -. start) in
if remaining <= 0. then Exninfo.iraise (Exninfo.capture CErrors.Timeout)
else Some (ControlTimeout { remaining }, v)
end
let real_error_loc ~cmdloc ~eloc =
if Loc.finer eloc cmdloc then eloc
else cmdloc
let with_fail f : (Loc.t option * Pp.t, 'a) result =
try
let x = f () in
Error x
with
| e ->
let _, info as exn = Exninfo.capture e in
if CErrors.is_anomaly e && e != CErrors.Timeout then Exninfo.iraise exn;
Ok (Loc.get_loc info, CErrors.iprint exn)
type ('st0,'st) with_local_state = { with_local_state : 'a. 'st0 -> (unit -> 'a) -> 'st * 'a }
let trivial_state = { with_local_state = fun () f -> (), f () }
let with_fail ~loc ~with_local_state st0 f =
let transient_st, res = with_local_state.with_local_state st0 (fun () -> with_fail f) in
match res with
| Error v ->
Some (ControlFail { st = transient_st }, v)
| Ok (eloc, msg) ->
let loc = if !Flags.test_mode then real_error_loc ~cmdloc:loc ~eloc else None in
if not !Flags.quiet || !Flags.test_mode
then Feedback.msg_notice ?loc Pp.(str "The command has indeed failed with message:" ++ fnl () ++ msg);
None
let with_succeed ~with_local_state st0 f =
let transient_st, v = with_local_state.with_local_state st0 f in
Some (ControlSucceed { st = transient_st }, v)
let under_one_control ~loc ~with_local_state control f =
match control with
| ControlTime { duration } ->
with_measure System.measure_duration System.duration_add System.fmt_transaction_result
(fun duration -> ControlTime {duration})
duration
f
| ControlInstructions {instructions} ->
with_measure System.count_instructions System.instruction_count_add System.fmt_instructions_result
(fun instructions -> ControlInstructions {instructions})
instructions
f
| ControlProfile {to_file; profstate} ->
with_measure measure_profile add_profile (fun v -> fmt_profile to_file v)
(fun profstate -> ControlProfile {to_file; profstate})
profstate
f
| ControlRedirect { fname; truncate } ->
let v = Topfmt.with_output_to_file ~truncate fname f () in
Some (ControlRedirect {fname; truncate=false}, v)
| ControlTimeout {remaining} -> with_timeout ~timeout:remaining f
| ControlFail {st} -> with_fail ~loc ~with_local_state st f
| ControlSucceed {st} -> with_succeed ~with_local_state st f
let rec under_control ~loc ~with_local_state controls ~noop f =
match controls with
| [] -> [], f ()
| control :: rest ->
let f () = under_control ~loc ~with_local_state rest ~noop f in
match under_one_control ~loc ~with_local_state control f with
| Some (control, (rest,v)) -> control :: rest, v
| None -> [], noop
let ignore_state = { with_local_state = fun _ f -> (), f () }
let rec after_last_phase ~loc = function
| [] -> false
| control :: rest ->
let rest () = after_last_phase ~loc rest in
match under_one_control ~loc ~with_local_state:ignore_state control rest with
| None -> true
| Some (control,noop) ->
match control with
| ControlTime {duration} ->
Feedback.msg_notice @@ System.fmt_transaction_result (Ok ((),duration));
noop
| ControlInstructions {instructions} ->
Feedback.msg_notice @@ System.fmt_instructions_result (Ok ((),instructions));
noop
| ControlProfile {to_file; profstate} ->
Feedback.msg_notice @@ fmt_profile to_file (Ok ((),profstate));
noop
| ControlRedirect _ -> noop
| ControlTimeout _ -> noop
| ControlFail _ -> CErrors.user_err Pp.(str "The command has not failed!")
| ControlSucceed _ -> true
(** A global default timeout, controlled by option "Set Default Timeout n".
Use "Unset Default Timeout" to deactivate it. *)
let default_timeout = ref None
let check_timeout n =
if n <= 0 then CErrors.user_err Pp.(str "Timeout must be > 0.")
let () = let open Goptions in
declare_int_option
{ optstage = Summary.Stage.Synterp;
optdepr = None;
optkey = ["Default";"Timeout"];
optread = (fun () -> !default_timeout);
optwrite = (fun n -> Option.iter check_timeout n; default_timeout := n) }
let has_timeout ctrl = ctrl |> List.exists (function
| Vernacexpr.ControlTimeout _ -> true
| _ -> false)
let add_default_timeout control =
match !default_timeout with
| None -> control
| Some n ->
if has_timeout control then control
else Vernacexpr.ControlTimeout n :: control
let from_syntax_one : Vernacexpr.control_flag -> unit control_entry = function
| ControlTime -> ControlTime { duration = System.empty_duration }
| ControlInstructions -> ControlInstructions { instructions = Ok 0L }
| ControlProfile to_file -> ControlProfile {to_file; profstate = empty_profstate}
| ControlRedirect s -> ControlRedirect { fname = s; truncate = true }
| ControlTimeout timeout ->
ControlTimeout { remaining = float_of_int timeout }
| ControlFail -> ControlFail { st = () }
| ControlSucceed -> ControlSucceed { st = () }
let from_syntax control = List.map from_syntax_one (add_default_timeout control)