linux_proc-0.1.1/Cargo.toml.orig010064400017500001750000000011321340572361000150010ustar0000000000000000[package] name = "linux_proc" description = "A library to help reading the contents of `/proc` on linux" readme = "README.md" version = "0.1.1" authors = ["Richard Dodd "] include = ["Cargo.toml", "README.md", "src/**/*.rs"] repository = "https://github.com/derekdreery/linux_proc" keywords = ["linux", "proc", "system", "info", "top"] categories = ["command-line-interface", "filesystem", "os::unix-apis", "parser-implementations"] license = "MIT/Apache-2.0" edition = "2018" [badges] travis-ci = { repository = "derekdreery/linux_proc" } [dev-dependencies] quicli = "0.3" linux_proc-0.1.1/Cargo.toml0000644000000021460000000000000112550ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "linux_proc" version = "0.1.1" authors = ["Richard Dodd "] include = ["Cargo.toml", "README.md", "src/**/*.rs"] description = "A library to help reading the contents of `/proc` on linux" readme = "README.md" keywords = ["linux", "proc", "system", "info", "top"] categories = ["command-line-interface", "filesystem", "os::unix-apis", "parser-implementations"] license = "MIT/Apache-2.0" repository = "https://github.com/derekdreery/linux_proc" [dev-dependencies.quicli] version = "0.3" [badges.travis-ci] repository = "derekdreery/linux_proc" linux_proc-0.1.1/README.md010064400017500001750000000001331334132235000133640ustar0000000000000000# `linux_proc` Parsers and data structures for reading the contents of "/proc" directory. linux_proc-0.1.1/src/diskstats.rs010064400017500001750000000122141340424627700152730ustar0000000000000000//! Bindings to `/proc/diskstats`. use std::collections::HashMap; use std::fs::File; use std::io; use std::time::Duration; use crate::{util, Error}; pub struct DiskStats { inner: HashMap, } impl DiskStats { const PATH: &'static str = "/proc/diskstats"; /// Parse the contents of `/proc/diskstats`. pub fn from_system() -> io::Result { DiskStats::from_reader(File::open(Self::PATH)?) } fn from_reader(reader: impl io::Read) -> io::Result { let mut reader = util::LineParser::new(reader); let mut inner = HashMap::new(); loop { match reader.parse_line(DiskStat::from_str) { Ok(disk_stat) => { if inner.insert(disk_stat.name.clone(), disk_stat).is_some() { panic!("Duplicate device name in /proc/diskstats"); } } Err(ref e) if e.kind() == io::ErrorKind::UnexpectedEof => break, Err(e) => return Err(e), } } Ok(DiskStats { inner }) } pub fn iter(&self) -> impl Iterator { self.inner.values() } } impl std::ops::Deref for DiskStats { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner } } impl IntoIterator for DiskStats { type IntoIter = std::collections::hash_map::IntoIter; type Item = (String, 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, 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, } macro_rules! err_msg { ($inner:expr, $msg:expr) => { $inner.ok_or_else(|| Error::from($msg)) }; } impl DiskStat { fn from_str(input: &str) -> Result { let (input, major) = err_msg!(util::parse_u64(input), "major number")?; let (input, minor) = err_msg!(util::parse_u64(input), "minor number")?; let (input, name) = err_msg!(util::parse_token(input), "device name")?; let name = name.to_owned(); let (input, reads_completed) = err_msg!(util::parse_u64(input), "reads completed successfully")?; let (input, reads_merged) = err_msg!(util::parse_u64(input), "reads merged")?; let (input, sectors_read) = err_msg!(util::parse_u64(input), "sectors read")?; let (input, time_reading) = err_msg!(util::parse_u64(input), "time spent reading (ms)")?; let time_reading = Duration::from_millis(time_reading); let (input, writes_completed) = err_msg!(util::parse_u64(input), "writes completed successfully")?; let (input, writes_merged) = err_msg!(util::parse_u64(input), "writes merged")?; let (input, sectors_written) = err_msg!(util::parse_u64(input), "sectors written")?; let (input, time_writing) = err_msg!(util::parse_u64(input), "time writing")?; let time_writing = Duration::from_millis(time_writing); let (input, io_in_progress) = err_msg!(util::parse_u64(input), "I/Os currently in progress")?; let (input, time_io) = err_msg!(util::parse_u64(input), "time spent doing I/Os (ms)")?; let time_io = Duration::from_millis(time_io); let (_input, time_io_weighted) = err_msg!( util::parse_u64(input), "weighted time spent doing I/Os (ms)" )?; let time_io_weighted = Duration::from_millis(time_io_weighted); // We don't check remaining content as future linux may add extra columns. Ok(DiskStat { major, minor, name, reads_completed, reads_merged, sectors_read, time_reading, writes_completed, writes_merged, sectors_written, time_writing, io_in_progress, time_io, time_io_weighted, }) } } #[cfg(test)] mod tests { use super::DiskStats; use std::io; #[test] fn proc_diskstats() { let raw = "\ 8 16 sdb 213 0 18712 564 0 0 0 0 0 217 794 8 17 sdb1 48 0 4688 157 0 0 0 0 0 164 227 8 18 sdb2 44 0 4656 204 0 0 0 0 0 167 254 8 19 sdb3 44 0 4656 187 0 0 0 0 0 164 234 8 0 sda 446866 32893 8168064 20164 339296 376515 86758441 4343530 0 250860 4704740 8 1 sda1 143 30 11462 24 1 0 8 0 0 50 64 8 2 sda2 46 0 4992 0 0 0 0 0 0 17 17 8 3 sda3 6 0 36 0 0 0 0 0 0 4 4 8 5 sda5 446599 32863 8148758 20140 331949 376515 86758433 4337104 0 233207 4686390 8 32 sdc 7354 0 1580168 91987 7 0 56 0 0 91374 96127 8 33 sdc1 7279 0 1575472 91310 7 0 56 0 0 90670 95424 11 0 sr0 0 0 0 0 0 0 0 0 0 0 0 "; let _stat = DiskStats::from_reader(io::Cursor::new(raw)).unwrap(); } } linux_proc-0.1.1/src/lib.rs010064400017500001750000000012611340572005200140150ustar0000000000000000//! Parsers for the contents of the `/proc` directory. //! pub mod diskstats; pub mod stat; pub mod uptime; mod util; use std::fmt; /// A very simple error handler. pub struct Error(String); impl From for Error { fn from(f: String) -> Error { Error(f) } } impl<'a> From<&'a str> for Error { fn from(f: &str) -> Error { Error(f.into()) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl std::error::Error for Error {} linux_proc-0.1.1/src/stat.rs010064400017500001750000000161221340424627700142370ustar0000000000000000//! Bindings to `/proc/stat`. use crate::{util, Error}; use std::{fs::File, io}; macro_rules! parse_single { ($name:expr) => { |input| { let (input, name) = util::parse_token(input).ok_or(Error::from("cannot read name"))?; if name != $name { return Err(Error::from(format!( "incorrect name, expected: {}, actual: {}", $name, name ))); } let (input, value) = util::parse_u64(input).ok_or(Error::from("cannot read value"))?; let input = util::consume_space(input); if !input.is_empty() { return Err(Error::from("trailing content")); } Ok(value) } }; } /// 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, /// 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 Stat { const PATH: &'static str = "/proc/stat"; /// Parse the contents of `/proc/stat`. pub fn from_system() -> io::Result { Stat::from_reader(File::open(Self::PATH)?) } fn from_reader(reader: impl io::Read) -> io::Result { let mut reader = util::LineParser::new(reader); let cpu_totals = reader.parse_line(StatCpu::from_str)?; let mut cpus = Vec::new(); loop { if let Ok(cpu_info) = reader.parse_line(StatCpu::from_str) { cpus.push(cpu_info); } else { break; } } reader.parse_line(util::parse_dummy)?; let context_switches = reader.parse_line(parse_single!("ctxt"))?; let boot_time = reader.parse_line(parse_single!("btime"))?; let processes = reader.parse_line(parse_single!("processes"))?; let procs_running = reader.parse_line(parse_single!("procs_running"))?; let procs_blocked = reader.parse_line(parse_single!("procs_blocked"))?; // todo softirq Ok(Stat { cpu_totals, cpus, context_switches, boot_time, processes, procs_running, procs_blocked, }) } } /// 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: Option, pub guest: Option, pub guest_nice: Option, } macro_rules! err_msg { ($inner:expr, $msg:expr) => { $inner.ok_or_else(|| Error::from($msg)) }; } impl StatCpu { fn from_str(input: &str) -> Result { let (input, cpunum) = err_msg!(util::parse_token(input), "first token")?; if !cpunum.starts_with("cpu") { return Err("starts with cpu".into()); } let (input, user) = err_msg!(util::parse_u64(input), "user")?; let (input, nice) = err_msg!(util::parse_u64(input), "nice")?; let (input, system) = err_msg!(util::parse_u64(input), "system")?; let (input, idle) = err_msg!(util::parse_u64(input), "idle")?; let (input, iowait) = err_msg!(util::parse_u64(input), "iowait")?; let (input, irq) = err_msg!(util::parse_u64(input), "irq")?; let (input, softirq) = err_msg!(util::parse_u64(input), "softirq")?; // Following are optional fields let (input, steal) = match util::parse_u64(input) { Some((i, steal)) => (i, Some(steal)), None => (input, None), }; let (input, guest) = match util::parse_u64(input) { Some((i, guest)) => (i, Some(guest)), None => (input, None), }; let (_, guest_nice) = match util::parse_u64(input) { Some((i, guest_nice)) => (i, Some(guest_nice)), None => (input, None), }; // We don't check remaining content as future linux may add extra columns. Ok(StatCpu { user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, }) } /// Convenience function to add up all cpu values. 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_or(0)) .unwrap() .checked_add(self.guest.unwrap_or(0)) .unwrap() .checked_add(self.guest_nice.unwrap_or(0)) .unwrap() } } #[test] fn test_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_reader(io::Cursor::new(raw)).unwrap(); } linux_proc-0.1.1/src/uptime.rs010064400017500001750000000033071340572331100145560ustar0000000000000000//! Bindings to `/proc/uptime`. use std::collections::HashMap; use std::fs::File; use std::io; use std::time::Duration; use crate::{util, Error}; pub struct Uptime { /// The time the system has been up for. pub up: Duration, /// The time any core has been idle for. This may be more than the uptime if the system has /// multiple cores. pub idle: Duration, } impl Uptime { const PATH: &'static str = "/proc/uptime"; /// Parse the contents of `/proc/uptime`. pub fn from_system() -> io::Result { Uptime::from_reader(File::open(Self::PATH)?) } pub fn from_reader(reader: impl io::Read) -> io::Result { let mut reader = util::LineParser::new(reader); let uptime = reader.parse_line(Self::from_str)?; Ok(uptime) } pub fn from_str(input: &str) -> Result { let (input, up_secs) = util::parse_u64(input).ok_or("expected number")?; let input = util::expect_bytes(".", input).ok_or("expected \".\"")?; let (input, up_nanos) = util::parse_nanos(input).ok_or("expected number")?; let (input, idle_secs) = util::parse_u64(input).ok_or("expected number")?; let input = util::expect_bytes(".", input).ok_or("expected \".\"")?; let (_input, idle_nanos) = util::parse_nanos(input).ok_or("expected number")?; Ok(Uptime { up: Duration::new(up_secs, up_nanos), idle: Duration::new(idle_secs, idle_nanos), }) } } #[cfg(test)] mod tests { use super::Uptime; use std::io; #[test] fn proc_uptime() { let raw = "\ 1640919.14 2328903.47 "; let _stat = Uptime::from_reader(io::Cursor::new(raw)).unwrap(); } } linux_proc-0.1.1/src/util.rs010064400017500001750000000121511340572272500142350ustar0000000000000000use crate::Error; use std::{self, io}; // todo use `!`. /// A helper to facilitate paring line by line while reusing a string buffer. pub struct LineParser { reader: io::BufReader, buffer: String, } impl LineParser where R: io::Read, { pub fn new(reader: R) -> LineParser { LineParser { reader: io::BufReader::new(reader), buffer: String::with_capacity(100), } } /// If the parse fails, the line is available for trying different parsers. pub fn parse_line(&mut self, parser: F) -> io::Result where F: Fn(&str) -> Result, E: std::error::Error + Send + Sync + 'static, { // Only fetch next line if we consumed the previous if self.buffer.is_empty() { let read = io::BufRead::read_line(&mut self.reader, &mut self.buffer)?; if read == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); } } let parsed = parser(&self.buffer) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, Box::new(e)))?; // we've succeeded so clear the buffer. self.buffer.clear(); Ok(parsed) } } pub fn parse_u64(input: &str) -> Option<(&str, u64)> { let input = consume_space(input); let mut chars = input.chars(); let (mut next_idx, mut acc) = match chars.next() { Some(ch) => match ch.to_digit(10) { Some(val) => (ch.len_utf8(), val as u64), None => return None, }, None => return None, }; for ch in chars { match ch.to_digit(10) { Some(val) => { acc = acc * 10 + val as u64; next_idx += ch.len_utf8(); } None => break, } } Some((&input[next_idx..], acc)) } #[test] fn test_parse_u64() { assert_eq!(parse_u64(""), None); assert_eq!(parse_u64(" "), None); assert_eq!(parse_u64("12 "), Some((" ", 12))); assert_eq!(parse_u64("12"), Some(("", 12))); assert_eq!(parse_u64("a12"), None); assert_eq!(parse_u64(" 12"), Some(("", 12))); assert_eq!(parse_u64("a 12"), None); assert_eq!(parse_u64(" 12a"), Some(("a", 12))); } pub fn consume_space(input: &str) -> &str { for (idx, ch) in input.char_indices() { if !ch.is_whitespace() && ch != '\n' && ch != '\r' { return &input[idx..]; } } return &input[input.len()..]; } #[test] fn test_consume_space() { assert_eq!(consume_space(""), ""); assert_eq!(consume_space(" "), ""); assert_eq!(consume_space(" a"), "a"); assert_eq!(consume_space(" a "), "a "); assert_eq!(consume_space("a "), "a "); } /// Consumes any space before the token, but not after. pub fn parse_token(input: &str) -> Option<(&str, &str)> { let token = consume_space(input); if token.is_empty() { return None; } let mut end = 0; for (idx, ch) in token.char_indices() { if ch.is_whitespace() { break; } end = idx + ch.len_utf8(); } Some((&token[end..], &token[..end])) } #[test] fn test_parse_token() { assert_eq!(parse_token(""), None); assert_eq!(parse_token(" "), None); assert_eq!(parse_token("token "), Some((" ", "token"))); assert_eq!(parse_token("token"), Some(("", "token"))); assert_eq!(parse_token(" token"), Some(("", "token"))); assert_eq!(parse_token(" token "), Some((" ", "token"))); } // todo should be ! not Error. pub fn parse_dummy(_input: &str) -> Result<(), Error> { Ok(()) } pub fn expect_bytes<'a>(expected: &str, input: &'a str) -> Option<&'a str> { let input = consume_space(input); if input.starts_with(expected) { Some(&input[expected.len()..]) } else { None } } #[test] fn test_expect_bytes() { assert_eq!(expect_bytes("", ""), Some("")); assert_eq!(expect_bytes("a", ""), None); assert_eq!(expect_bytes("abc", "abcde"), Some("de")); assert_eq!(expect_bytes("a", "b"), None); } /// Parses numbers after a decimal point, where the first column is 1_000_000_000. pub fn parse_nanos(input: &str) -> Option<(&str, u32)> { let input = consume_space(input); let mut chars = input.chars(); let (mut next_idx, mut acc) = match chars.next() { Some(ch) => match ch.to_digit(10) { Some(val) => (ch.len_utf8(), (val as u32) * 100_000_000), None => return None, }, None => return None, }; let mut multer = 10_000_000u32; for ch in chars { match ch.to_digit(10) { Some(val) => { acc += (val as u32) * multer; next_idx += ch.len_utf8(); multer /= 10; } None => break, } if multer == 1 { panic!("too many numbers"); } } Some((&input[next_idx..], acc)) } #[test] fn test_parse_nanos() { assert_eq!(parse_nanos(""), None); assert_eq!(parse_nanos("1"), Some(("", 100_000_000))); assert_eq!(parse_nanos(" 12"), Some(("", 120_000_000))); assert_eq!(parse_nanos("012"), Some(("", 12_000_000))); assert_eq!(parse_nanos(".12"), None); } linux_proc-0.1.1/.cargo_vcs_info.json0000644000000001120000000000000132460ustar00{ "git": { "sha1": "6652eb4bc70d4e300d8ef89cd7a0d174208167aa" } }