cpu-monitor 0.1.1

A library to get cpu usage over a given duration
Documentation
//! Parsers for the contents of the `/proc` directory.
//!
use nom;
use std::io::{self, BufReader, BufRead};
use std::fs::File;
use std::time::Duration;
use std::ops;
use std;

/// The stats from `/proc/stat`.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Stat {
    /// Total stats, sum of all cpus.
    pub cpu_totals: StatCpu,
    /// For each cpu, the number of *units* spent in different contexts.
    pub cpus: Vec<StatCpu>,
    /// Number of context switches since the system booted.
    pub context_switches: u64,
    /// Timestamp (in seconds since epoch) that system booted.
    pub boot_time: u64,
    /// The total number of processes and threads created since system booted.
    pub processes: u64,
    /// The total number of processes running on the cpu.
    pub procs_running: u64,
    /// The total number of processes waiting to run on the cpu.
    pub procs_blocked: u64,
    // todo `softirq`
}

impl ops::Sub for Stat {
    type Output = StatDelta;

    fn sub(mut self, rhs: Self) -> Self::Output {
        assert_eq!(self.cpus.len(), rhs.cpus.len(), "different number of cpus");
        self.cpus.iter_mut()
            .zip(rhs.cpus.into_iter())
            .for_each(|(this, rhs)| *this = *this - rhs);
        let cpu_totals = self.cpu_totals - rhs.cpu_totals;
        let context_switches = self.context_switches.checked_sub(rhs.context_switches).unwrap();
        let processes = self.processes.checked_sub(rhs.processes).unwrap();
        let max = ::std::i64::MAX as u64;
        if self.procs_running > max || rhs.procs_running > max {
            panic!("overflow");
        }
        let procs_running = (self.procs_running as i64)
            .checked_sub(rhs.procs_running as i64).unwrap();
        if self.procs_blocked > max || rhs.procs_blocked > max {
            panic!("overflow");
        }
        let procs_blocked = (self.procs_blocked as i64)
            .checked_sub(rhs.procs_blocked as i64).unwrap();
        StatDelta {
            cpu_totals,
            cpus: self.cpus,
            context_switches,
            processes,
            procs_running,
            procs_blocked,
        }
    }
}

impl<'a> ops::Sub<&'a Stat> for Stat {
    type Output = StatDelta;

    fn sub(mut self, rhs: &'a Self) -> Self::Output {
        assert_eq!(self.cpus.len(), rhs.cpus.len(), "different number of cpus");
        self.cpus.iter_mut()
            .zip(rhs.cpus.iter())
            .for_each(|(this, rhs)| *this = *this - *rhs);
        let cpu_totals = self.cpu_totals - rhs.cpu_totals;
        let context_switches = self.context_switches.checked_sub(rhs.context_switches).unwrap();
        let processes = self.processes.checked_sub(rhs.processes).unwrap();
        let max = ::std::i64::MAX as u64;
        if self.procs_running > max || rhs.procs_running > max {
            panic!("overflow");
        }
        let procs_running = (self.procs_running as i64)
            .checked_sub(rhs.procs_running as i64).unwrap();
        if self.procs_blocked > max || rhs.procs_blocked > max {
            panic!("overflow");
        }
        let procs_blocked = (self.procs_blocked as i64)
            .checked_sub(rhs.procs_blocked as i64).unwrap();
        StatDelta {
            cpu_totals,
            cpus: self.cpus,
            context_switches,
            processes,
            procs_running,
            procs_blocked,
        }
    }
}
impl Stat {
    pub fn from_system() -> io::Result<Self> {
        Stat::from_iter(BufReader::new(File::open("/proc/stat")?).lines())
    }

    fn from_iter(mut iter: impl Iterator<Item=io::Result<String>>) -> io::Result<Stat> {
        let cpu_totals = parse_line(&mut iter, |s| StatCpu::from_str(&s))?;
        let mut cpus = Vec::new();
        let mut next_line;
        loop {
            next_line = iter.next().ok_or(io::ErrorKind::InvalidData.into()).and_then(|v| v)?;
            if let Some(cpu_info) = StatCpu::from_str(&next_line) {
                cpus.push(cpu_info);
            } else {
                break;
            }
        }
        // skip interrupts (in next_line)

        let context_switches = parse_line(&mut iter, |s| parse_single("ctxt", s))?;
        let boot_time = parse_line(&mut iter, |s| parse_single("btime", s))?;
        let processes = parse_line(&mut iter, |s| parse_single("processes", s))?;
        let procs_running = parse_line(&mut iter, |s| parse_single("procs_running", s))?;
        let procs_blocked = parse_line(&mut iter, |s| parse_single("procs_blocked", s))?;
        // todo softirq
        Ok(Stat {
            cpu_totals,
            cpus,
            context_switches,
            boot_time,
            processes,
            procs_running,
            procs_blocked
        })
    }
}

/// The change in the stat values over a time period
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StatDelta {
    /// The number of *units* over the time period.
    pub cpu_totals: StatCpu,
    /// For each cpu, the number of *units* spent in different contexts.
    pub cpus: Vec<StatCpu>,
    /// Number of context switches.
    pub context_switches: u64,
    /// The total number of processes and threads created over the time period.
    pub processes: u64,
    /// The change in number of processes running on the cpu.
    pub procs_running: i64,
    /// The change in number of processes waiting to run on the cpu.
    pub procs_blocked: i64,
    // todo `softirq`
}

/// Helper to flatten all the i/o errors
fn parse_line<I, F, Val>(iter: &mut I, mapper: F) -> io::Result<Val>
where I: Iterator<Item=io::Result<String>>,
      F: Fn(String) -> Option<Val>
{
    iter.next()
        .ok_or(io::ErrorKind::InvalidData.into())
        .and_then(|val| val) // flatten
        .and_then(|val| mapper(val).ok_or(io::ErrorKind::InvalidData.into()))
}

/// Info about the number of *units* in the various cpu contexts.
///
/// *units* could be anything, for example cpu cycles, or hundredths of a second. The numbers only
/// really make sense as a proportion of the total.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StatCpu {
    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,
}

impl ops::Sub for StatCpu {
    type Output = StatCpu;

    fn sub(self, rhs: Self) -> Self::Output {
        StatCpu {
            user: self.user.checked_sub(rhs.user).unwrap(),
            nice: self.nice.checked_sub(rhs.nice).unwrap(),
            system: self.system.checked_sub(rhs.system).unwrap(),
            idle: self.idle.checked_sub(rhs.idle).unwrap(),
            iowait: self.iowait.checked_sub(rhs.iowait).unwrap(),
            irq: self.irq.checked_sub(rhs.irq).unwrap(),
            softirq: self.softirq.checked_sub(rhs.softirq).unwrap(),
            steal: self.steal.checked_sub(rhs.steal).unwrap(),
            guest: self.guest.checked_sub(rhs.guest).unwrap(),
        }
    }
}

impl StatCpu {
    fn from_str(input: &str) -> Option<StatCpu> {
        parse_cpu_line(input).map(|(_, answer)| answer).ok()
    }

    pub fn total(&self) -> u64 {
        self.user
            .checked_add(self.nice).unwrap()
            .checked_add(self.system).unwrap()
            .checked_add(self.idle).unwrap()
            .checked_add(self.iowait).unwrap()
            .checked_add(self.irq).unwrap()
            .checked_add(self.softirq).unwrap()
            .checked_add(self.steal).unwrap()
            .checked_add(self.guest).unwrap()
    }

}

named!(parse_cpu_line<&str, StatCpu>, do_parse!(
    tag!("cpu") >>
    opt!(nom::digit0) >>
    call!(nom::space0) >>
    user: call!(parse_u64) >>
    call!(nom::space0) >>
    nice: call!(parse_u64) >>
    call!(nom::space0) >>
    system: call!(parse_u64) >>
    call!(nom::space0) >>
    idle: call!(parse_u64) >>
    call!(nom::space0) >>
    iowait: call!(parse_u64) >>
    call!(nom::space0) >>
    irq: call!(parse_u64) >>
    call!(nom::space0) >>
    softirq: call!(parse_u64) >>
    call!(nom::space0) >>
    steal: call!(parse_u64) >>
    call!(nom::space0) >>
    guest: call!(parse_u64) >>
    (StatCpu { user, nice, system, idle, iowait, irq, softirq, steal, guest })
));


fn parse_single(name: &str, input: impl AsRef<str>) -> Option<u64> {
    let result = do_parse!(input.as_ref(),
        tag!(name) >>
        tag!(" ") >>
        val: call!(parse_u64) >>
        (val)
    );
    result.map(|(_, val)| val).ok()
}

/// Parse a u64.
///
/// Will return successfully if there is no more input.
fn parse_u64(mut input: &str) -> nom::IResult<&str, u64> {
    let mut num = input.get(0..1)
        .and_then(|s| s.chars().next())
        .and_then(|d| d.to_digit(10))
        .map(|d| d as u64)
        .ok_or(nom::Err::Error(error_position!(input, nom::ErrorKind::Custom(0))))?;
    loop {
        input = &input[1..]; // digits are all 1 utf8 byte.
        match input.get(0..1)
            .and_then(|s| s.chars().next())
            .and_then(|d| d.to_digit(10))
            .map(|d| d as u64)
        {
            Some(d) => { num = num * 10 + d },
            None => break,
        }
    }
    Ok((input, num))
}

pub struct DiskStats {
    inner: Vec<DiskStat>
}

impl DiskStats {
    pub fn from_system() -> io::Result<Self> {
        let mut reader = BufReader::new(File::open("/proc/diskstats")?);
        let mut disk_stats = Vec::new();

        unimplemented!()
    }

    pub fn iter(&self) -> impl Iterator<Item=&DiskStat> {
        self.inner.iter()
    }
}

impl IntoIterator for DiskStats {
    type IntoIter = std::vec::IntoIter<DiskStat>;
    type Item = DiskStat;
    fn into_iter(self) -> Self::IntoIter {
        self.inner.into_iter()
    }
}

pub struct DiskStat {
    pub major: u64,
    pub minor: u64,
    pub name: String,
    pub reads_completed: u64,
    pub reads_merged: u64,
    pub sectors_read: u64,
    // in ms
    pub time_reading: Duration,
    pub writes_completed: u64,
    pub writes_merged: u64,
    pub sectors_written: u64,
    // in ms
    pub time_writing: Duration,
    pub io_in_progress: u64,
    // in ms
    pub time_io: Duration,
    // in ms
    pub time_io_weighted: Duration,
}

#[cfg(test)]
mod tests {
    use std::io::{self, BufRead};
    use super::Stat;

    #[test]
    fn parse_single() {
        let input = "processes 2453";
        assert_eq!(super::parse_single("processes", input).unwrap(), 2453);
    }

    #[test]
    fn parse_u64() {
        assert!(super::parse_u64("a123").is_err());
        assert_eq!(super::parse_u64("12 "),
                   Result::Ok((" ", 12)));
        assert_eq!(super::parse_u64("12"),
                   Result::Ok(("", 12)));
    }

    #[test]
    fn proc_stat() {
        let raw = "\
cpu  17501 2 6293 8212469 20141 1955 805 0 0 0
cpu0 4713 0 1720 2049410 8036 260 255 0 0 0
cpu1 3866 0 1325 2054893 3673 928 307 0 0 0
cpu2 4966 1 1988 2051243 5596 516 141 0 0 0
cpu3 3955 0 1258 2056922 2835 250 100 0 0 0
intr 1015182 8 8252 0 0 0 0 0 0 1 113449 0 0 198907 0 0 0 18494 0 0 1 0 0 0 29 22 7171 46413 13 0 413 167 528 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 2238717
btime 1535128607
processes 2453
procs_running 1
procs_blocked 0
softirq 4257581 64 299604 69 2986 36581 0 3497229 283111 0 137937
";
        let _stat = Stat::from_iter(io::Cursor::new(raw).lines()).unwrap();
    }
}