use std::fs;
use std::io::{Error, ErrorKind, Result};
use std::{thread, time};
#[derive(Debug, Clone)]
pub struct CpuTimes {
pub user: u64,
pub nice: u64,
pub system: u64,
pub idle: u64,
pub iowait: u64,
pub irq: u64,
pub softirq: u64,
pub steal: u64,
pub guest: u64,
pub guest_nice: u64,
}
impl CpuTimes {
fn total_time(&self) -> u64 {
self.user
+ self.nice
+ self.system
+ self.idle
+ self.iowait
+ self.irq
+ self.softirq
+ self.steal
}
fn cpu_percent_since(&self, past_cpu_times: &CpuTimes) -> CpuTimesPercent {
if self.total_time() > past_cpu_times.total_time() {
let diff_total = (self.total_time() - past_cpu_times.total_time()) as f64;
CpuTimesPercent {
user: delta_percentage(self.user, past_cpu_times.user, diff_total),
nice: delta_percentage(self.nice, past_cpu_times.nice, diff_total),
system: delta_percentage(self.system, past_cpu_times.system, diff_total),
idle: delta_percentage(self.idle, past_cpu_times.idle, diff_total),
iowait: delta_percentage(self.iowait, past_cpu_times.iowait, diff_total),
irq: delta_percentage(self.irq, past_cpu_times.irq, diff_total),
softirq: delta_percentage(self.softirq, past_cpu_times.softirq, diff_total),
steal: delta_percentage(self.steal, past_cpu_times.steal, diff_total),
guest: delta_percentage(self.guest, past_cpu_times.guest, diff_total),
guest_nice: delta_percentage(
self.guest_nice,
past_cpu_times.guest_nice,
diff_total,
),
}
} else {
CpuTimesPercent {
user: 0.,
nice: 0.,
system: 0.,
idle: 0.,
iowait: 0.,
irq: 0.,
softirq: 0.,
steal: 0.,
guest: 0.,
guest_nice: 0.,
}
}
}
}
#[derive(Debug)]
pub struct CpuTimesPercent {
pub user: f64,
pub nice: f64,
pub system: f64,
pub idle: f64,
pub iowait: f64,
pub irq: f64,
pub softirq: f64,
pub steal: f64,
pub guest: f64,
pub guest_nice: f64,
}
impl CpuTimesPercent {
fn busy_times(&self) -> f64 {
self.user + self.nice + self.system + self.irq + self.softirq + self.steal
}
}
#[derive(Clone, Debug)]
pub struct CpuPercentCollector {
last_statement_cpu: CpuTimes,
last_statement_percpu: Vec<CpuTimes>,
}
impl CpuPercentCollector {
pub fn new() -> Result<CpuPercentCollector> {
let last_statement_cpu = cpu_times()?;
let last_statement_percpu = cpu_times_percpu()?;
Ok(CpuPercentCollector {
last_statement_cpu,
last_statement_percpu,
})
}
pub fn cpu_times_percent(&mut self) -> Result<CpuTimesPercent> {
let current_cpu_times = cpu_times()?;
let cpu_percent_since = current_cpu_times.cpu_percent_since(&self.last_statement_cpu);
self.last_statement_cpu = current_cpu_times;
Ok(cpu_percent_since)
}
pub fn cpu_times_percent_percpu(&mut self) -> Result<Vec<CpuTimesPercent>> {
let current_cpu_times_percpu = cpu_times_percpu()?;
let mut cpu_times_percent_vector: Vec<CpuTimesPercent> = Vec::new();
let current_cpu_times_percpu_copy = current_cpu_times_percpu.clone();
for (iter, cpu_times) in current_cpu_times_percpu.iter().enumerate() {
cpu_times_percent_vector
.push(cpu_times.cpu_percent_since(&self.last_statement_percpu[iter]));
}
self.last_statement_percpu = current_cpu_times_percpu_copy;
Ok(cpu_times_percent_vector)
}
}
fn info_cpu_line(cpu_line: &str) -> Result<Vec<u64>> {
let mut fields: Vec<&str> = cpu_line.split_whitespace().collect();
if fields.len() < 2 {
return Err(Error::new(
ErrorKind::InvalidData,
format!("Wrong line use from /proc/stat : {}", cpu_line),
));
}
if fields.len() < 10 {
return Err(Error::new(
ErrorKind::InvalidData,
format!("Wrong format of /proc/stat line : {}, Maybe the kernel version is too old (Linux 2.6.33)", cpu_line),
));
}
fields.remove(0);
let mut values: Vec<u64> = Vec::new();
for elt in fields {
let value = match elt.parse::<u64>() {
Ok(v) => v,
Err(_) => {
return Err(Error::new(
ErrorKind::InvalidData,
format!("failed to parse {}", elt),
));
}
};
values.push(value);
}
Ok(values)
}
fn cpu_line_to_cpu_times(cpu_info: &[u64]) -> CpuTimes {
let user = cpu_info[0];
let nice = cpu_info[1];
let system = cpu_info[2];
let idle = cpu_info[3];
let iowait = cpu_info[4];
let irq = cpu_info[5];
let softirq = cpu_info[6];
let steal = cpu_info[7];
let guest = cpu_info[8];
let guest_nice = cpu_info[9];
CpuTimes {
user,
nice,
system,
idle,
iowait,
irq,
softirq,
steal,
guest,
guest_nice,
}
}
pub fn cpu_times() -> Result<CpuTimes> {
let data = fs::read_to_string("/proc/stat")?;
let lines: Vec<&str> = data.lines().collect();
let cpu_info = match info_cpu_line(lines[0]) {
Ok(cpu_info) => cpu_info,
Err(error) => return Err(error),
};
Ok(cpu_line_to_cpu_times(&cpu_info))
}
pub fn cpu_times_percpu() -> Result<Vec<CpuTimes>> {
let data = fs::read_to_string("/proc/stat")?;
let mut lines: Vec<&str> = data.lines().collect();
let mut cpu_times_vector: Vec<CpuTimes> = Vec::new();
lines.remove(0);
for line in lines {
if line.starts_with("cpu") {
let cpu_info = match info_cpu_line(line) {
Ok(cpu_info) => cpu_info,
Err(error) => return Err(error),
};
let cpu_time = cpu_line_to_cpu_times(&cpu_info);
cpu_times_vector.push(cpu_time);
}
}
Ok(cpu_times_vector)
}
pub fn cpu_percent(interval: f64) -> Result<f64> {
Ok(cpu_times_percent(interval)?.busy_times())
}
pub fn cpu_percent_percpu(interval: f64) -> Result<Vec<f64>> {
let cpu_percent_percpu = cpu_times_percent_percpu(interval)?;
let mut cpu_percents: Vec<f64> = Vec::new();
for cpu_percent in cpu_percent_percpu {
cpu_percents.push(cpu_percent.busy_times());
}
Ok(cpu_percents)
}
pub fn cpu_times_percent(interval: f64) -> Result<CpuTimesPercent> {
let mut cpu_percent_last_call = CpuPercentCollector::new()?;
let interval = (test_interval(interval)? * 1000.) as u64;
let block_time = time::Duration::from_millis(interval);
thread::sleep(block_time);
cpu_percent_last_call.cpu_times_percent()
}
pub fn cpu_times_percent_percpu(interval: f64) -> Result<Vec<CpuTimesPercent>> {
let mut cpu_percent_last_call = CpuPercentCollector::new()?;
let interval = (test_interval(interval)? * 1000.) as u64;
let block_time = time::Duration::from_millis(interval);
thread::sleep(block_time);
cpu_percent_last_call.cpu_times_percent_percpu()
}
fn delta_percentage(current_value: u64, past_value: u64, total_diff: f64) -> f64 {
if past_value <= current_value {
let percentage = (100. * (current_value - past_value) as f64) / total_diff;
if percentage > 100. {
100.
} else {
percentage
}
} else {
0.
}
}
fn test_interval(interval: f64) -> Result<f64> {
if interval <= 0. {
Err(Error::new(
ErrorKind::InvalidData,
format!("Interval must be greater than 0 : {}", interval),
))
} else {
Ok(interval)
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn info_cpu_line_test() {
let input = "cpu0 61286 322 19182 1708940 323 0 322 0 0 0 ";
let result = match info_cpu_line(input) {
Ok(r) => r,
Err(e) => panic!("{}", e),
};
assert_eq!(
result,
vec![61286, 322, 19182, 1_708_940, 323, 0, 322, 0, 0, 0]
);
}
#[test]
fn cpu_line_to_cpu_times_test() {
let input = vec![62972, 178, 18296, 349_198, 163, 0, 493, 0, 0, 0];
let result = cpu_line_to_cpu_times(&input);
assert_eq!(result.user, 62972);
assert_eq!(result.nice, 178);
assert_eq!(result.system, 18296);
assert_eq!(result.idle, 349_198);
assert_eq!(result.iowait, 163);
assert_eq!(result.irq, 0);
assert_eq!(result.softirq, 493);
assert_eq!(result.steal, 0);
assert_eq!(result.guest, 0);
assert_eq!(result.guest_nice, 0);
}
#[test]
fn cpu_time_percent_test() {
let input1 = vec![62972, 178, 18296, 349_198, 163, 0, 493, 0, 0, 0];
let input2 = vec![61286, 322, 19182, 1_708_940, 323, 0, 322, 0, 0, 0];
let result1 = cpu_line_to_cpu_times(&input1);
let result2 = cpu_line_to_cpu_times(&input2);
let percent = result2.cpu_percent_since(&result1);
assert!(percent.user >= 0.);
assert!(percent.user <= 100.);
assert!(percent.nice >= 0.);
assert!(percent.nice <= 100.);
assert!(percent.system >= 0.);
assert!(percent.system <= 100.);
assert!(percent.idle >= 0.);
assert!(percent.idle <= 100.);
assert!(percent.iowait >= 0.);
assert!(percent.iowait <= 100.);
assert!(percent.irq >= 0.);
assert!(percent.irq <= 100.);
assert!(percent.softirq >= 0.);
assert!(percent.softirq <= 100.);
assert!(percent.guest >= 0.);
assert!(percent.guest <= 100.);
assert!(percent.guest_nice >= 0.);
assert!(percent.guest_nice <= 100.);
assert!(percent.steal >= 0.);
assert!(percent.steal <= 100.);
}
}