command_rusage/
lib.rs

1use std::{time::Duration, process::Child};
2
3/// Resource usage statistics for a process.
4#[derive(Debug, Clone)]
5pub struct RUsage {
6    /// User CPU time used.
7    pub utime: Duration,
8    /// System CPU time used.
9    pub stime: Duration,
10    /// Maximum resident set size.
11    pub maxrss: usize,
12    /// Integral shared memory size.
13    pub ixrss: usize,
14    /// Integral unshared data size.
15    pub idrss: usize,
16    /// Integral unshared stack size.
17    pub isrss: usize,
18    /// Page reclaims (soft page faults).
19    pub minflt: usize,
20    /// Page faults (hard page faults).
21    pub majflt: usize,
22    /// Swaps.
23    pub nswap: usize,
24    /// Block input operations.
25    pub inblock: usize,
26    /// Block output operations.
27    pub oublock: usize,
28    /// IPC messages sent.
29    pub msgsnd: usize,
30    /// IPC messages received.
31    pub msgrcv: usize,
32    /// Signals received.
33    pub nsignals: usize,
34    /// Voluntary context switches.
35    pub nvcsw: usize,
36    /// Involuntary context switches.
37    pub nivcsw: usize,
38}
39
40/// Error type for `getrusage` failures.
41#[derive(Debug)]
42pub enum Error {
43    /// The process does not exist.
44    NoSuchProcess,
45    /// The process exists, but is not a child of the current process.
46    NotChild,
47    /// The process exists, but its resource usage statistics are unavailable.
48    Unavailable,
49    /// This platform is not supported. There is only support for linux.
50    UnsupportedPlatform,
51}
52
53/// A trait for getting resource usage statistics for a process.
54pub trait GetRUsage {
55    /// Waits for the process to exit and returns its resource usage statistics.
56    /// Works only on linux with wait4 syscall available.
57    fn wait_for_rusage(&mut self) -> Result<RUsage, Error>;
58}
59
60/// Type wrapper for result.
61pub type RUsageResult = std::result::Result<RUsage, Error>;
62
63/// Returns an empty `libc::rusage` struct.
64#[cfg(target_os = "linux")]
65unsafe fn empty_raw_rusage() -> libc::rusage {
66    std::mem::zeroed()
67}
68
69/// Converts a `libc::timeval` to a `std::time::Duration`.
70fn duration_from_timeval(timeval: libc::timeval) -> Duration {
71    Duration::new(timeval.tv_sec as u64, (timeval.tv_usec * 1000) as u32)
72}
73
74impl GetRUsage for Child {
75    fn wait_for_rusage(&mut self) -> Result<RUsage, Error> {
76        let pid = self.id() as i32;
77        let mut status: i32 = 0;
78        #[cfg(target_os = "linux")]
79        {
80            let mut rusage;
81            unsafe {
82                rusage = empty_raw_rusage();
83                libc::wait4(
84                    pid,
85                    &mut status as *mut libc::c_int,
86                    0i32,
87                    &mut rusage as *mut libc::rusage,
88                );
89            }
90
91            if status == 0 {
92                Ok(RUsage { 
93                    utime: duration_from_timeval(rusage.ru_utime),
94                    stime: duration_from_timeval(rusage.ru_stime),
95                    maxrss: rusage.ru_maxrss as usize,
96                    ixrss: rusage.ru_ixrss as usize,
97                    idrss: rusage.ru_idrss as usize,
98                    isrss: rusage.ru_isrss as usize,
99                    minflt: rusage.ru_minflt as usize,
100                    majflt: rusage.ru_majflt as usize,
101                    nswap: rusage.ru_nswap as usize,
102                    inblock: rusage.ru_inblock as usize,
103                    oublock: rusage.ru_oublock as usize,
104                    msgsnd: rusage.ru_msgsnd as usize,
105                    msgrcv: rusage.ru_msgrcv as usize,
106                    nsignals: rusage.ru_nsignals as usize,
107                    nvcsw: rusage.ru_nvcsw as usize,
108                    nivcsw: rusage.ru_nivcsw as usize,
109                })
110            } else if status == libc::ECHILD {
111                Err(Error::NoSuchProcess)
112            } else if status == libc::EINTR {
113                Err(Error::NotChild)
114            } else {
115                Err(Error::Unavailable)
116            }
117        }
118        #[cfg(not(target_os = "linux"))]
119        {
120            Err(Error::UnsupportedPlatform)
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use std::process::Command;
128
129    use super::*;
130
131    #[test]
132    fn count_utime() {
133        let command = Command::new("tree").arg("/home")
134            .stdout(std::process::Stdio::null())
135            .spawn().expect("failed to execute process")
136            .wait_for_rusage().expect("failed to get rusage");
137        assert!(command.utime.as_micros() > 0);
138    }
139}