1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::{time::Duration, process::Child};

/// Resource usage statistics for a process.
#[derive(Debug, Clone)]
pub struct RUsage {
    /// User CPU time used.
    pub utime: Duration,
    /// System CPU time used.
    pub stime: Duration,
    /// Maximum resident set size.
    pub maxrss: usize,
    /// Integral shared memory size.
    pub ixrss: usize,
    /// Integral unshared data size.
    pub idrss: usize,
    /// Integral unshared stack size.
    pub isrss: usize,
    /// Page reclaims (soft page faults).
    pub minflt: usize,
    /// Page faults (hard page faults).
    pub majflt: usize,
    /// Swaps.
    pub nswap: usize,
    /// Block input operations.
    pub inblock: usize,
    /// Block output operations.
    pub oublock: usize,
    /// IPC messages sent.
    pub msgsnd: usize,
    /// IPC messages received.
    pub msgrcv: usize,
    /// Signals received.
    pub nsignals: usize,
    /// Voluntary context switches.
    pub nvcsw: usize,
    /// Involuntary context switches.
    pub nivcsw: usize,
}

/// Error type for `getrusage` failures.
#[derive(Debug)]
pub enum Error {
    /// The process does not exist.
    NoSuchProcess,
    /// The process exists, but is not a child of the current process.
    NotChild,
    /// The process exists, but its resource usage statistics are unavailable.
    Unavailable,
    /// This platform is not supported. There is only support for linux.
    UnsupportedPlatform,
}

/// A trait for getting resource usage statistics for a process.
pub trait GetRUsage {
    /// Waits for the process to exit and returns its resource usage statistics.
    /// Works only on linux with wait4 syscall available.
    fn wait_for_rusage(&mut self) -> Result<RUsage, Error>;
}

/// Type wrapper for result.
pub type RUsageResult = std::result::Result<RUsage, Error>;

/// Returns an empty `libc::rusage` struct.
#[cfg(target_os = "linux")]
unsafe fn empty_raw_rusage() -> libc::rusage {
    std::mem::zeroed()
}

/// Converts a `libc::timeval` to a `std::time::Duration`.
fn duration_from_timeval(timeval: libc::timeval) -> Duration {
    Duration::new(timeval.tv_sec as u64, (timeval.tv_usec * 1000) as u32)
}

impl GetRUsage for Child {
    fn wait_for_rusage(&mut self) -> Result<RUsage, Error> {
        let pid = self.id() as i32;
        let mut status: i32 = 0;
        #[cfg(target_os = "linux")]
        {
            let mut rusage;
            unsafe {
                rusage = empty_raw_rusage();
                libc::wait4(
                    pid,
                    &mut status as *mut libc::c_int,
                    0i32,
                    &mut rusage as *mut libc::rusage,
                );
            }

            if status == 0 {
                Ok(RUsage { 
                    utime: duration_from_timeval(rusage.ru_utime),
                    stime: duration_from_timeval(rusage.ru_stime),
                    maxrss: rusage.ru_maxrss as usize,
                    ixrss: rusage.ru_ixrss as usize,
                    idrss: rusage.ru_idrss as usize,
                    isrss: rusage.ru_isrss as usize,
                    minflt: rusage.ru_minflt as usize,
                    majflt: rusage.ru_majflt as usize,
                    nswap: rusage.ru_nswap as usize,
                    inblock: rusage.ru_inblock as usize,
                    oublock: rusage.ru_oublock as usize,
                    msgsnd: rusage.ru_msgsnd as usize,
                    msgrcv: rusage.ru_msgrcv as usize,
                    nsignals: rusage.ru_nsignals as usize,
                    nvcsw: rusage.ru_nvcsw as usize,
                    nivcsw: rusage.ru_nivcsw as usize,
                })
            } else if status == libc::ECHILD {
                Err(Error::NoSuchProcess)
            } else if status == libc::EINTR {
                Err(Error::NotChild)
            } else {
                Err(Error::Unavailable)
            }
        }
        #[cfg(not(target_os = "linux"))]
        {
            Err(Error::UnsupportedPlatform)
        }
    }
}

#[cfg(test)]
mod tests {
    use std::process::Command;

    use super::*;

    #[test]
    fn count_utime() {
        let command = Command::new("tree").arg("/home")
            .stdout(std::process::Stdio::null())
            .spawn().expect("failed to execute process")
            .wait_for_rusage().expect("failed to get rusage");
        assert!(command.utime.as_micros() > 0);
    }
}