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