use crate::util::sysconf;
use glob::glob;
use std::collections::HashSet;
#[derive(Debug, Default)]
pub struct CpuTime {
pub user: f64,
pub system: f64,
pub idle: f64,
pub nice: Option<f64>,
pub iowait: Option<f64>,
pub irq: Option<f64>,
pub softirq: Option<f64>,
pub steal: Option<f64>,
pub guest: Option<f64>,
pub guest_nice: Option<f64>,
}
impl CpuTime {
fn new(fields: &Vec<f64>) -> CpuTime {
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()
};
if vlen >= 8 {
cpu_time.steal = Some(fields[7]);
}
if vlen >= 9 {
cpu_time.guest = Some(fields[8]);
}
if vlen >= 10 {
cpu_time.guest_nice = Some(fields[9]);
}
cpu_time
}
}
pub fn system_time() -> CpuTime {
let fields = cpu_time_fields(true);
CpuTime::new(&fields[0])
}
pub fn per_cpu_time() -> Vec<CpuTime> {
cpu_time_fields(false)
.into_iter()
.map(|times| CpuTime::new(×))
.collect::<Vec<CpuTime>>()
}
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() {
if idx == 0 || !line.contains("cpu") {
continue;
}
let times = cpu_str_to_times(line);
result.push(times);
}
}
result
}
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>>()
}
pub fn cpu_count(logical: bool) -> Option<usize> {
if logical {
cpu_count_logical()
} else {
cpu_count_physical()
}
}
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)
}
fn cpu_count_logical() -> Option<usize> {
let count = sysconf(libc::_SC_NPROCESSORS_ONLN);
if count > 0 {
return Some(count as usize);
}
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() {
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],
);
}
}