profile/
stop_watch.rs

1//! Like `std::time::Instant`, but also measures memory & CPU cycles.
2
3#![allow(clippy::print_stderr)]
4
5use std::{
6    fmt,
7    time::{Duration, Instant},
8};
9
10use crate::MemoryUsage;
11
12pub struct StopWatch {
13    time: Instant,
14    #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
15    counter: Option<perf_event::Counter>,
16    memory: MemoryUsage,
17}
18
19pub struct StopWatchSpan {
20    pub time: Duration,
21    pub instructions: Option<u64>,
22    pub memory: MemoryUsage,
23}
24
25impl StopWatch {
26    pub fn start() -> StopWatch {
27        #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
28        let counter = {
29            // When debugging rust-analyzer using rr, the perf-related syscalls cause it to abort.
30            // We allow disabling perf by setting the env var `RA_DISABLE_PERF`.
31
32            use std::sync::OnceLock;
33            static PERF_ENABLED: OnceLock<bool> = OnceLock::new();
34
35            if *PERF_ENABLED.get_or_init(|| std::env::var_os("RA_DISABLE_PERF").is_none()) {
36                let mut counter = perf_event::Builder::new()
37                    .build()
38                    .map_err(|err| eprintln!("Failed to create perf counter: {err}"))
39                    .ok();
40                if let Some(counter) = &mut counter
41                    && let Err(err) = counter.enable()
42                {
43                    eprintln!("Failed to start perf counter: {err}")
44                }
45                counter
46            } else {
47                None
48            }
49        };
50        let memory = MemoryUsage::now();
51        let time = Instant::now();
52        StopWatch {
53            time,
54            #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
55            counter,
56            memory,
57        }
58    }
59
60    pub fn elapsed(&mut self) -> StopWatchSpan {
61        let time = self.time.elapsed();
62
63        #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
64        let instructions = self.counter.as_mut().and_then(|it| {
65            it.read().map_err(|err| eprintln!("Failed to read perf counter: {err}")).ok()
66        });
67        #[cfg(all(target_os = "linux", target_env = "ohos"))]
68        let instructions = None;
69        #[cfg(not(target_os = "linux"))]
70        let instructions = None;
71
72        let memory = MemoryUsage::now() - self.memory;
73        StopWatchSpan { time, instructions, memory }
74    }
75}
76
77impl fmt::Display for StopWatchSpan {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{:.2?}", self.time)?;
80        if let Some(mut instructions) = self.instructions {
81            let mut prefix = "";
82            if instructions > 10000 {
83                instructions /= 1000;
84                prefix = "k";
85            }
86            if instructions > 10000 {
87                instructions /= 1000;
88                prefix = "m";
89            }
90            if instructions > 10000 {
91                instructions /= 1000;
92                prefix = "g";
93            }
94            write!(f, ", {instructions}{prefix}instr")?;
95        }
96        write!(f, ", {}", self.memory)?;
97        Ok(())
98    }
99}