1use std::{
2 fs,
3 io::{self, Write as _},
4 sync::{Mutex, atomic::AtomicU32},
5 time::{Duration, Instant},
6};
7
8use flux_config as config;
9use itertools::Itertools;
10use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
11use rustc_middle::ty::TyCtxt;
12use serde::Serialize;
13
14use crate::FixpointQueryKind;
15
16const BOLD: anstyle::Style = anstyle::Style::new().bold();
17const GREY: anstyle::Style = anstyle::AnsiColor::BrightBlack.on_default();
18
19pub fn print_summary(total_time: Duration) -> io::Result<()> {
20 let mut stderr = anstream::Stderr::always(std::io::stderr());
21 writeln!(
22 &mut stderr,
23 "{BOLD}summary.{BOLD:#} {} functions processed: {} checked; {} trusted; {} ignored. {} constraints solved. Finished in {}{GREY:#}",
24 METRICS.get(Metric::FnTotal),
25 METRICS.get(Metric::FnChecked),
26 METRICS.get(Metric::FnTrusted),
27 METRICS.get(Metric::FnIgnored),
28 METRICS.get(Metric::CsTotal),
29 fmt_duration(total_time),
30 )
31}
32
33static METRICS: Metrics = Metrics::new();
34
35#[repr(u8)]
36pub enum Metric {
37 FnTotal,
39 FnTrusted,
41 FnIgnored,
43 FnChecked,
45 FnCached,
47 FnTrivial,
49 CsTotal,
51 CsError,
53}
54
55struct Metrics {
56 counts: [AtomicU32; 8],
57}
58
59impl Metrics {
60 const fn new() -> Self {
61 Self {
62 counts: [
63 AtomicU32::new(0),
64 AtomicU32::new(0),
65 AtomicU32::new(0),
66 AtomicU32::new(0),
67 AtomicU32::new(0),
68 AtomicU32::new(0),
69 AtomicU32::new(0),
70 AtomicU32::new(0),
71 ],
72 }
73 }
74
75 fn incr(&self, metric: Metric, val: u32) {
76 self.counts[metric as usize].fetch_add(val, std::sync::atomic::Ordering::Relaxed);
77 }
78
79 fn get(&self, metric: Metric) -> u32 {
80 self.counts[metric as usize].load(std::sync::atomic::Ordering::Relaxed)
81 }
82}
83
84pub fn incr_metric(metric: Metric, val: u32) {
85 METRICS.incr(metric, val);
86}
87
88pub fn incr_metric_if(cond: bool, metric: Metric) {
89 if cond {
90 incr_metric(metric, 1);
91 }
92}
93
94static TIMINGS: Mutex<Vec<Entry>> = Mutex::new(Vec::new());
95
96pub enum TimingKind {
97 Total,
99 CheckFn(LocalDefId),
101 FixpointQuery(DefId, FixpointQueryKind),
103}
104
105#[derive(Serialize)]
106struct TimingsDump {
107 total: ms,
109 functions: Vec<FuncTiming>,
111 queries: Vec<QueryTiming>,
113}
114
115#[derive(Serialize)]
116struct FuncTiming {
117 def_path: String,
118 time_ms: ms,
119}
120
121#[derive(Serialize)]
122struct QueryTiming {
123 task_key: String,
124 time_ms: ms,
125}
126
127fn snd<A, B: Copy>(&(_, b): &(A, B)) -> B {
128 b
129}
130
131pub fn print_and_dump_timings(tcx: TyCtxt) -> io::Result<()> {
132 if !config::timings() {
133 return Ok(());
134 }
135
136 let timings = std::mem::take(&mut *TIMINGS.lock().unwrap());
137 let mut functions = vec![];
138 let mut queries = vec![];
139 let mut total = Duration::from_secs(0);
140 for timing in timings {
141 match timing.kind {
142 TimingKind::CheckFn(local_def_id) => {
143 let def_path = tcx.def_path_str(local_def_id);
144 functions.push((def_path, timing.duration));
145 }
146 TimingKind::FixpointQuery(def_id, kind) => {
147 let key = kind.task_key(tcx, def_id);
148 queries.push((key, timing.duration));
149 }
150 TimingKind::Total => {
151 total = timing.duration;
153 }
154 }
155 }
156 functions.sort_by_key(snd);
157 functions.reverse();
158
159 queries.sort_by_key(snd);
160 queries.reverse();
161
162 print_report(&functions, total);
163 dump_timings(
164 tcx,
165 TimingsDump {
166 total: ms(total),
167 functions: functions
168 .into_iter()
169 .map(|(def_path, time)| FuncTiming { def_path, time_ms: ms(time) })
170 .collect(),
171 queries: queries
172 .into_iter()
173 .map(|(task_key, time)| QueryTiming { task_key, time_ms: ms(time) })
174 .collect(),
175 },
176 )
177}
178
179fn print_report(functions: &[(String, Duration)], total: Duration) {
180 let stats = stats(&functions.iter().map(snd).collect_vec());
181 eprintln!();
182 eprintln!("───────────────────── Timing Report ────────────────────────");
183 eprintln!("Total running time: {:>40}", fmt_duration(total));
184 eprintln!("Functions checked: {:>40}", stats.count);
185 eprintln!("Min: {:>40}", fmt_duration(stats.min));
186 eprintln!("Max: {:>40}", fmt_duration(stats.max));
187 eprintln!("Mean: {:>40}", fmt_duration(stats.mean));
188 eprintln!("Std. Dev.: {:>40}", fmt_duration(stats.standard_deviation));
189
190 let top5 = functions.iter().take(5).cloned().collect_vec();
191 if !top5.is_empty() {
192 eprintln!("────────────────────────────────────────────────────────────");
193 eprintln!("Top 5 Functions ");
194 for (def_path, duration) in top5 {
195 let len = def_path.len();
196 if len > 46 {
197 eprintln!(
198 "• …{} {:>width$}",
199 &def_path[len - 46..],
200 fmt_duration(duration),
201 width = 10
202 );
203 } else {
204 eprintln!(
205 "• {def_path} {:>width$}",
206 fmt_duration(duration),
207 width = 60 - def_path.len() - 3
208 );
209 }
210 }
211 }
212 eprintln!("────────────────────────────────────────────────────────────");
213}
214
215fn dump_timings(tcx: TyCtxt, timings: TimingsDump) -> io::Result<()> {
216 let crate_name = tcx.crate_name(LOCAL_CRATE);
217 fs::create_dir_all(config::log_dir())?;
218 let path = config::log_dir().join(format!("{crate_name}-timings.json"));
219 let mut file = fs::File::create(path)?;
220 serde_json::to_writer(&mut file, &timings)?;
221 Ok(())
222}
223
224pub fn time_it<R>(kind: TimingKind, f: impl FnOnce() -> R) -> R {
225 if !config::timings() {
226 return f();
227 }
228 let start = Instant::now();
229 let r = f();
230 TIMINGS
231 .lock()
232 .unwrap()
233 .push(Entry { duration: start.elapsed(), kind });
234 r
235}
236
237fn stats(durations: &[Duration]) -> TimingStats {
238 let count = durations.len() as u32;
239 if count == 0 {
240 return TimingStats::default();
241 }
242 let sum: Duration = durations.iter().sum();
243 let mean = sum / count;
244
245 let meanf = mean.as_millis() as f64;
246 let mut sum_of_squares = 0.0;
247 let mut max = Duration::ZERO;
248 let mut min = Duration::MAX;
249 for duration in durations {
250 let diff = duration.as_millis() as f64 - meanf;
251 sum_of_squares += diff * diff;
252 max = max.max(*duration);
253 min = min.min(*duration);
254 }
255 let standard_deviation = Duration::from_millis((sum_of_squares / count as f64).sqrt() as u64);
256
257 TimingStats { count, max, min, mean, standard_deviation }
258}
259
260#[derive(Default)]
261struct TimingStats {
262 count: u32,
263 max: Duration,
264 min: Duration,
265 mean: Duration,
266 standard_deviation: Duration,
267}
268
269struct Entry {
270 duration: Duration,
271 kind: TimingKind,
272}
273
274#[allow(non_camel_case_types)]
275#[derive(Clone, Copy, Serialize)]
276#[serde(into = "u128")]
277struct ms(Duration);
278
279impl From<ms> for u128 {
280 fn from(value: ms) -> Self {
281 value.0.as_millis()
282 }
283}
284
285fn fmt_duration(duration: Duration) -> String {
286 let nanos = duration.as_nanos();
287
288 if nanos < 1_000 {
289 format!("{nanos}ns")
290 } else if nanos < 1_000_000 {
291 format!("{:.2}µs", nanos as f64 / 1_000.0)
292 } else if nanos < 1_000_000_000 {
293 format!("{:.2}ms", nanos as f64 / 1_000_000.0)
294 } else if nanos < 60_000_000_000 {
295 format!("{:.2}s", nanos as f64 / 1_000_000_000.0)
296 } else {
297 let seconds = duration.as_secs();
298 let minutes = seconds / 60;
299 let seconds_remainder = seconds % 60;
300
301 if minutes < 60 {
302 format!("{minutes}m {seconds_remainder}s")
303 } else {
304 let hours = minutes / 60;
305 let minutes_remainder = minutes % 60;
306
307 if hours < 24 {
308 format!("{hours}h {minutes_remainder}m {seconds_remainder}s")
309 } else {
310 let days = hours / 24;
311 let hours_remainder = hours % 24;
312 format!("{days}d {hours_remainder}h {minutes_remainder}m {seconds_remainder}s",)
313 }
314 }
315 }
316}