workflow_perf_monitor/cpu/
mod.rs

1//! Get cpu usage for current process and specified thread.
2//!
3//! A method named `cpu` on `ThreadStat` and `ProcessStat`
4//! can retrieve cpu usage of thread and process respectively.
5//!
6//! The returning value is unnormalized, that is for multi-processor machine,
7//! the cpu usage will beyond 100%, for example returning 2.8 means 280% cpu usage.
8//! If normalized value is what you expected, divide the returning by processor_numbers.
9//!
10//! ## Example
11//!
12//! ```
13//! # use perf_monitor::cpu::ThreadStat;
14//! let mut stat = ThreadStat::cur().unwrap();
15//! let _ = (0..1_000_000).into_iter().sum::<u64>();
16//! let usage = stat.cpu().unwrap();
17//! println!("current thread cpu usage is {:.2}%", usage * 100f64);
18//! ```
19//!
20//! ## Bottom Layer Interface
21//! | platform | thread | process |
22//! | -- | -- | -- |
23//! | windows |[GetThreadTimes] | [GetProcessTimes] |
24//! | linux & android | [/proc/{pid}/task/{tid}/stat][man5] | [clockgettime] |
25//! | macos & ios | [thread_info] | [getrusage] |
26//!
27//! [GetThreadTimes]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes
28//! [GetProcessTimes]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes
29//! [man5]: https://man7.org/linux/man-pages/man5/proc.5.html
30//! [thread_info]: http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/thread_info.html
31//! [clockgettime]: https://man7.org/linux/man-pages/man2/clock_gettime.2.html
32//! [getrusage]: https://www.man7.org/linux/man-pages/man2/getrusage.2.html
33
34#[cfg(any(target_os = "linux", target_os = "android"))]
35mod android_linux;
36#[cfg(any(target_os = "ios", target_os = "macos"))]
37mod ios_macos;
38#[cfg(target_os = "windows")]
39mod windows;
40
41#[cfg(any(target_os = "linux", target_os = "android"))]
42use android_linux as platform;
43#[cfg(any(target_os = "ios", target_os = "macos"))]
44use ios_macos as platform;
45#[cfg(target_os = "windows")]
46use windows as platform;
47
48#[cfg(any(target_os = "ios", target_os = "macos"))]
49pub use ios_macos::get_thread_basic_info;
50pub use platform::{cpu_time, ThreadId};
51pub use std::io::Result;
52
53use std::{
54    io, mem,
55    time::{Duration, Instant},
56};
57
58/// logical processor number
59pub fn processor_numbers() -> std::io::Result<usize> {
60    std::thread::available_parallelism().map(|x| x.get())
61}
62
63/// A struct to monitor process cpu usage
64pub struct ProcessStat {
65    now: Instant,
66    cpu_time: Duration,
67}
68
69impl ProcessStat {
70    /// return a monitor of current process
71    pub fn cur() -> io::Result<Self> {
72        Ok(ProcessStat {
73            now: Instant::now(),
74            cpu_time: platform::cpu_time()?,
75        })
76    }
77
78    /// return the cpu usage from last invoke,
79    /// or when this struct created if it is the first invoke.
80    pub fn cpu(&mut self) -> io::Result<f64> {
81        let old_time = mem::replace(&mut self.cpu_time, platform::cpu_time()?);
82        let old_now = mem::replace(&mut self.now, Instant::now());
83        let real_time = self.now.saturating_duration_since(old_now).as_secs_f64();
84        let cpu_time = self.cpu_time.saturating_sub(old_time).as_secs_f64();
85        Ok(cpu_time / real_time)
86    }
87}
88
89/// A struct to monitor thread cpu usage
90pub struct ThreadStat {
91    stat: platform::ThreadStat,
92}
93
94impl ThreadStat {
95    /// return a monitor of current thread.
96    pub fn cur() -> Result<Self> {
97        Ok(ThreadStat {
98            stat: platform::ThreadStat::cur()?,
99        })
100    }
101
102    /// return a monitor of specified thread.
103    ///
104    /// `tid` is **NOT** `std::thread::ThreadId`.
105    /// [`ThreadId::current`] can be used to retrieve a valid tid.
106    pub fn build(thread_id: ThreadId) -> Result<Self> {
107        Ok(ThreadStat {
108            stat: platform::ThreadStat::build(thread_id)?,
109        })
110    }
111
112    /// return the cpu usage from last invoke,
113    /// or when this struct created if it is the first invoke.
114    pub fn cpu(&mut self) -> Result<f64> {
115        self.stat.cpu()
116    }
117
118    /// return the cpu_time in user mode and system mode from last invoke,
119    /// or when this struct created if it is the first invoke.
120    pub fn cpu_time(&mut self) -> Result<Duration> {
121        self.stat.cpu_time()
122    }
123}
124
125#[cfg(test)]
126mod test {
127    use super::*;
128
129    // this test should be executed alone.
130    #[test]
131    #[ignore]
132    fn test_process_usage() {
133        let mut stat = ProcessStat::cur().unwrap();
134
135        std::thread::sleep(std::time::Duration::from_secs(1));
136
137        let usage = stat.cpu().unwrap();
138
139        assert!(usage < 0.01);
140
141        let num = processor_numbers().unwrap();
142        for _ in 0..num * 10 {
143            std::thread::spawn(move || loop {
144                let _ = (0..10_000_000).into_iter().sum::<u128>();
145            });
146        }
147
148        let mut stat = ProcessStat::cur().unwrap();
149
150        std::thread::sleep(std::time::Duration::from_secs(1));
151
152        let usage = stat.cpu().unwrap();
153
154        assert!(usage > 0.9 * num as f64)
155    }
156
157    #[test]
158    fn test_thread_usage() {
159        let mut stat = ThreadStat::cur().unwrap();
160
161        std::thread::sleep(std::time::Duration::from_secs(1));
162        let usage = stat.cpu().unwrap();
163        assert!(usage < 0.01);
164
165        let mut x = 1_000_000u64;
166        std::hint::black_box(&mut x);
167        let mut times = 1000u64;
168        std::hint::black_box(&mut times);
169        for i in 0..times {
170            let x = (0..x + i).into_iter().sum::<u64>();
171            std::hint::black_box(x);
172        }
173        let usage = stat.cpu().unwrap();
174        assert!(usage > 0.5)
175    }
176}