-
Notifications
You must be signed in to change notification settings - Fork 0
/
AutoLevity_Hooks.thy
170 lines (153 loc) · 5.74 KB
/
AutoLevity_Hooks.thy
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
(*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*)
theory AutoLevity_Hooks
imports
AutoLevity_Base
AutoLevity_Theory_Report
begin
(*
* This file installs the dependency tracing hooks so that they
* will be active in the current Isabelle session. These are the
* toplevel command hooks for tracing dependencies, and a shutdown
* hook for writing out the dependencies when the session finishes.
*)
(* Install toplevel command hooks. *)
setup \<open>
case getenv "AUTOLEVITY" of
"1" => (tracing ("Setting up AUTOLEVITY=1");
AutoLevity_Base.setup_command_hook {trace_apply = false})
| "2" => (tracing ("Setting up AUTOLEVITY=2");
AutoLevity_Base.setup_command_hook {trace_apply = true})
| _ => I
\<close>
(* Collect all the traces and write them to a combined file. *)
ML \<open>
structure AutoLevity_Combined_Report = struct
(* Session-qualified theory name -> JSON ouput lines *)
fun levity_report_for thy_name: string list option =
let
val thy = Thy_Info.get_theory thy_name;
val thy_short_name = Context.theory_name thy;
val trans = AutoLevity_Base.get_transactions ();
in if Symtab.defined trans thy_short_name then
AutoLevity_Theory_Report.get_reports_for_thy thy
|> AutoLevity_Theory_Report.string_reports_of
|> SOME
else
NONE
end;
(* This has the usual race condition, but should be very unlikely in practice *)
fun local_temp_file path = let
val dir = Path.dir path;
val file = Path.file_name path;
fun try_path () = let
(* avoid colliding with other processes *)
val pid = Posix.ProcEnv.getpid () |> Posix.Process.pidToWord |> SysWord.toInt;
(* timestamp *)
val now = Time.now () |> Time.toMilliseconds;
(* serialized pseudorandom state within this process *)
val rand = Random.random_range 100000 999999;
val temp_id = Library.space_implode "-" (map string_of_int [pid, now, rand]);
val temp_path = Path.append dir (Path.basic (file ^ ".tmp" ^ temp_id));
in if File.exists temp_path
then
((* again, this should be very unlikely in practice *)
warning ("local_temp_file: (unlikely) failed attempt: " ^
Path.implode temp_path);
try_path ())
else
(File.append temp_path ""; (* create empty file *)
temp_path)
end;
in try_path () end;
(* Output all traces in this session to given path.
Wraps each theory in the session in a JSON dictionary as follows:
{
"session": <escaped session name>,
"content": [
<theory "foo" content>,
<theory "bar" content>,
...
]
}
*)
fun levity_report_all output_path: unit =
let
val this_session = Session.get_name ();
(* Use a temp file for atomic output. Note that we don't use
File.tmp_path or similar because the standard /tmp/isabelle-*
tmpdir might not be on the same file system as output_path,
whereas we can only rename atomically on the same file system *)
val temp_path = local_temp_file output_path;
(* Wrap individual theory reports in a JSON dictionary as follows:
{
"theory": <escaped theory name>,
"content": <theory content as JSON dictionary>
}
Note that this closes and re-opens the output file
over and over. But it's better than building the entire
combined output in memory. *)
fun dump_all _ [] = ()
| dump_all (first: bool) (thy_name::remaining) = let
val _ = @{print} ("Reporting on " ^ thy_name ^ "...")
val separator = if first then "" else ", "; (* from previous item *)
val json_start = [separator, "{", quote "theory", ":", JSON_string_encode thy_name, ",\n",
quote "content", ":"];
val json_end = ["}\n"];
in case levity_report_for thy_name of
NONE => (
@{print} ("No transaction record for " ^ thy_name);
dump_all first remaining
)
| SOME thy_report => (
File.append_list temp_path (json_start @ thy_report @ json_end);
dump_all false remaining
)
end;
val thy_names = Thy_Info.get_names ();
val _ = File.write_list temp_path
["{", quote "session", ":", JSON_string_encode this_session, ",\n",
quote "content", ":", "[\n"];
val _ = dump_all true thy_names;
val _ = File.append_list temp_path ["]\n", "}"];
val _ = OS.FileSys.rename {
old = Path.implode (Path.expand temp_path),
new = Path.implode (Path.expand output_path)
};
in
@{print} ("Report written to: " ^ Path.implode output_path);
()
end;
(* Wrapper that outputs to the Isabelle build log directory *)
fun levity_session_log () =
let
val this_session = Session.get_name ();
val fname = this_session ^ ".lev";
val output_path = Path.make ["log", fname]
|> Path.append (Path.explode (getenv "ISABELLE_OUTPUT"));
val session_traces = AutoLevity_Base.get_transactions ();
in
if Symtab.is_empty session_traces
(* AutoLevity does not collect traces for interactive PIDE sessions,
* so don't overwrite the levity log *)
then warning ("No traces for session: " ^ this_session)
else levity_report_all output_path
end
handle exn =>
(* from Pure/Tools/build.ML *)
(List.app (fn msg => writeln (encode_lines (YXML.content_of msg)))
(Runtime.exn_message_list exn);
Exn.reraise exn);
end
\<close>
(* Do the output when the Isabelle session finishes.
The session shutdown hook requires a patch to Isabelle, so we wrap
this code to be a no-op on vanilla Isabelle installations. *)
ML \<open>
try (ML_Context.eval ML_Compiler.flags @{here})
(ML_Lex.read_text ("Session.register_shutdown_hook AutoLevity_Combined_Report.levity_session_log", @{here}))
\<close>
end