rpsutil 0.0.3

System monitoring
Documentation
use crate::util::sysconf;
use glob::glob;
use std::collections::HashSet;

/// Struct to represent CPU times.
/// All values are in seconds.
#[derive(Debug, Default)]
pub struct CpuTime {
    /// Time spent in user mode.
    ///
    /// Linux: also includes **guest** time.
    pub user: f64,

    /// Time spent in system mode.
    pub system: f64,

    /// Time spent in the idle task.
    pub idle: f64,

    /// Time spent in user mode with low priority (nice).
    ///
    /// Linux: also includes **guest_nice** time.
    ///
    /// Available: Linux
    pub nice: Option<f64>,

    /// Time spent waiting for I/O to complete.
    ///
    /// Available: Linux
    pub iowait: Option<f64>,

    /// Time spent for servicing hardware interrupts.
    ///
    /// Available: Linux
    pub irq: Option<f64>,

    /// Time spent for servicing software interrupts.
    ///
    /// Available: Linux
    pub softirq: Option<f64>,

    /// Time spent by other operating systems running in a virtualized environment.
    ///
    /// Available: Linux 2.6.11+
    pub steal: Option<f64>,

    /// Time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
    ///
    /// Available: Linux 2.6.24+
    pub guest: Option<f64>,

    /// Time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel).
    ///
    /// Available: Linux 3.2.0+
    pub guest_nice: Option<f64>,
    // * interrupt (Windows): time spent for servicing hardware interrupts ( similar to “irq” on UNIX)
    // * dpc (Windows): time spent servicing deferred procedure calls (DPCs); DPCs are interrupts that run at a lower priority than standard interrupts.
}

impl CpuTime {
    // Accepts a list of f64 values as struct values.
    // Availability: Linux
    fn new(fields: &Vec<f64>) -> CpuTime {
        // This is currently an implementation for Linux
        let vlen = fields.len();

        let mut cpu_time = CpuTime {
            user: fields[0],
            nice: Some(fields[1]),
            system: fields[2],
            idle: fields[3],
            iowait: Some(fields[4]),
            irq: Some(fields[5]),
            softirq: Some(fields[6]),

            ..Default::default()
        };

        // Linux >= 2.6.11
        if vlen >= 8 {
            cpu_time.steal = Some(fields[7]);
        }

        // Linux >= 2.6.24
        if vlen >= 9 {
            cpu_time.guest = Some(fields[8]);
        }

        // Linux >= 3.2.0
        if vlen >= 10 {
            cpu_time.guest_nice = Some(fields[9]);
        }

        cpu_time
    }
}

/// Return whole system CPU times as `CpuTime`.
///
/// Availability: Linux
///
/// Examples:
/// ```
/// let system_time = rpsutil::cpu::system_time();
/// assert!(system_time.user > 0.0);
/// assert!(system_time.system > 0.0);
/// assert!(system_time.idle > 0.0);
/// ```
pub fn system_time() -> CpuTime {
    let fields = cpu_time_fields(true);
    CpuTime::new(&fields[0])
}

/// Return per CPU times as a vector of `CpuTime`.
///
/// Availability: Linux
///
/// Examples:
/// ```
/// let cpu_times = rpsutil::cpu::per_cpu_time();
/// assert!(cpu_times.len() >= 2);
/// ```
pub fn per_cpu_time() -> Vec<CpuTime> {
    cpu_time_fields(false)
        .into_iter()
        .map(|times| CpuTime::new(&times))
        .collect::<Vec<CpuTime>>()
}

// Returns a vector or CPU times vectors.
// Availability: Linux
fn cpu_time_fields(system: bool) -> Vec<Vec<f64>> {
    let mut result: Vec<Vec<f64>> = vec![];
    let contents = std::fs::read_to_string("/proc/stat").unwrap();
    let lines: Vec<&str> = contents.trim().split('\n').collect();

    if system {
        let times = cpu_str_to_times(lines[0]);
        result.push(times);
    } else {
        for (idx, line) in lines.into_iter().enumerate() {
            // skip the first line or any other value that is not cpu related
            if idx == 0 || !line.contains("cpu") {
                continue;
            }
            let times = cpu_str_to_times(line);
            result.push(times);
        }
    }

    result
}

// convert a &str that contains the cpu times into a list of f64.
// Availability: Linux
fn cpu_str_to_times(val: &str) -> Vec<f64> {
    let v: Vec<&str> = val.split_whitespace().collect();
    v[1..]
        .iter()
        .map(|x| x.parse::<f64>().unwrap() / sysconf(libc::_SC_CLK_TCK) as f64)
        .collect::<Vec<f64>>()
}

/// Return the number of CPUs in the system or `None` if undetermined.
/// If the param is set to `true`, the result will be the number of logical CPUs, `false` will return the number of physical CPUs.
///
/// Examples:
/// ```
/// // Get the number of logical CPUs.
/// let logical = rpsutil::cpu::cpu_count(true);
///
/// // Get the number of physical CPUs.
/// let physical = rpsutil::cpu::cpu_count(false);
/// ```
pub fn cpu_count(logical: bool) -> Option<usize> {
    if logical {
        cpu_count_logical()
    } else {
        cpu_count_physical()
    }
}

// Returns the number of physical CPUs. Return `None` if undetermined.
// Availability: Linux
fn cpu_count_physical() -> Option<usize> {
    let mut set: HashSet<String> = HashSet::new();
    let path = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_id";
    for entry in glob(path).expect(&format!("Failed to parse {}", path)) {
        match entry {
            Ok(filepath) => {
                let core_id = std::fs::read_to_string(filepath).unwrap();
                set.insert(core_id);
            }
            Err(err) => println!("Error: {}", err),
        }
    }

    let result = set.len();
    if result == 0 {
        return None;
    }

    Some(result)
}

// Returns the number of physical CPUs. Return `None` if undetermined.
// Availability: Linux
fn cpu_count_logical() -> Option<usize> {
    // method 1
    let count = sysconf(libc::_SC_NPROCESSORS_ONLN);

    if count > 0 {
        return Some(count as usize);
    }

    // method 2
    let times = cpu_time_fields(false);
    let num = times.len();

    if num == 0 {
        return None;
    }

    Some(num)
}

#[cfg(test)]
mod tests {
    #[test]
    #[cfg(linux)]
    fn test_cpu_time_struct() {
        // Linux
        let fields = vec![
            58217.46, 23.51, 11603.8, 3228050.26, 999.97, 0.0, 3788.2, 0.0, 0.0, 0.0,
        ];
        let cpu_time = super::CpuTime::new(&fields);
        assert_eq!(cpu_time.user, fields[0]);
        assert_eq!(cpu_time.system, fields[2]);
        assert_eq!(cpu_time.idle, fields[3]);
    }

    #[test]
    fn test_system_cpu_time_fields() {
        let times = super::cpu_time_fields(true);
        assert_eq!(times.len(), 1);
    }

    #[test]
    fn test_per_cpu_time_fields() {
        let times = super::cpu_time_fields(false);
        assert!(times.len() > 1);
    }

    #[test]
    #[cfg(linux)]
    fn test_cpu_str_to_times() {
        let cpu_str = "cpu  5734950 2292 1141826 318806461 99775 0 371504 0 0 0";
        let time_list = super::cpu_str_to_times(cpu_str);
        assert_eq!(
            time_list,
            vec![57349.50, 22.92, 11418.26, 3188064.61, 997.75, 0.0, 3715.04, 0.0, 0.0, 0.0],
        );
    }
}