From 42a155668e75c8c26477d813140656e67d8c5bd3 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Tue, 14 Jan 2020 12:01:06 +0100 Subject: [PATCH] sys: change PidStat API Rather than standalone functions, `PidStat` now provides a `read_for_pid` and `parse` method. Both are public. One side effect is that the documentation of PidStat's contents are on the same page as the ways to retrieve it. Additionally, we deprecate `read_proc_starttime` as it was reading the entire contents to then filter out one value, in order to promote caching. The standalone `read_proc_pid_stat` method is now also deprecated. Once we add pid-fds the API will feel cleaner this way. Signed-off-by: Wolfgang Bumiller --- proxmox-sys/src/linux/procfs.rs | 165 +++++++++++++++++--------------- 1 file changed, 86 insertions(+), 79 deletions(-) diff --git a/proxmox-sys/src/linux/procfs.rs b/proxmox-sys/src/linux/procfs.rs index 74bb778c..23d73a76 100644 --- a/proxmox-sys/src/linux/procfs.rs +++ b/proxmox-sys/src/linux/procfs.rs @@ -26,6 +26,7 @@ lazy_static! { static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64; } +/// Selected contents of the `/proc/PID/stat` file. pub struct PidStat { pub pid: Pid, pub ppid: Pid, @@ -38,84 +39,94 @@ pub struct PidStat { pub rss: i64, } -pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result { - let stat = parse_proc_pid_stat(std::str::from_utf8(&std::fs::read(format!( - "/proc/{}/stat", - pid - ))?)?)?; - if stat.pid.as_raw() != pid { - bail!( - "unexpected pid for process: found pid {} in /proc/{}/stat", - stat.pid.as_raw(), +impl PidStat { + /// Retrieve the `stat` file contents of a process. + pub fn read_for_pid(pid: Pid) -> Result { + let stat = Self::parse(std::str::from_utf8(&std::fs::read(format!( + "/proc/{}/stat", pid - ); + ))?)?)?; + if stat.pid != pid { + bail!( + "unexpected pid for process: found pid {} in /proc/{}/stat", + stat.pid.as_raw(), + pid + ); + } + Ok(stat) + } + + /// Parse the contents of a `/proc/PID/stat` file. + pub fn parse(statstr: &str) -> Result { + // It starts with the pid followed by a '('. + let cmdbeg = statstr + .find('(') + .ok_or_else(|| format_err!("missing '(' in /proc/PID/stat"))?; + + if !statstr[..=cmdbeg].ends_with(" (") { + bail!("bad /proc/PID/stat line before the '('"); + } + + let pid: u32 = statstr[..(cmdbeg - 1)] + .parse() + .map_err(|e| format_err!("bad pid in /proc/PID/stat: {}", e))?; + let pid = Pid::from_raw(pid as i32); + + // After the '(' we have an arbitrary command name, then ')' and the remaining values + let cmdend = statstr + .rfind(')') + .ok_or_else(|| format_err!("missing ')' in /proc/PID/stat"))?; + let mut parts = statstr[cmdend + 1..].trim_start().split_ascii_whitespace(); + + // helpers: + fn required<'a>(value: Option<&'a str>, what: &'static str) -> Result<&'a str, Error> { + value.ok_or_else(|| format_err!("missing '{}' in /proc/PID/stat", what)) + } + + fn req_num(value: Option<&str>, what: &'static str) -> Result + where + T: FromStr, + ::Err: Into, + { + required(value, what)?.parse::().map_err(|e| e.into()) + } + + fn req_byte(value: Option<&str>, what: &'static str) -> Result { + let value = required(value, what)?; + if value.len() != 1 { + bail!("invalid '{}' in /proc/PID/stat", what); + } + Ok(value.as_bytes()[0]) + } + + let out = PidStat { + pid, + status: req_byte(parts.next(), "status")?, + ppid: Pid::from_raw(req_num::(parts.next(), "ppid")? as i32), + utime: req_num::(parts.nth(9), "utime")?, + stime: req_num::(parts.next(), "stime")?, + num_threads: req_num::(parts.nth(4), "num_threads")?, + starttime: req_num::(parts.nth(1), "start_time")?, + vsize: req_num::(parts.next(), "vsize")?, + rss: req_num::(parts.next(), "rss")? * 4096, + }; + + let _ = req_num::(parts.next(), "it_real_value")?; + // and more... + + Ok(out) } - Ok(stat) } -fn parse_proc_pid_stat(statstr: &str) -> Result { - // It starts with the pid followed by a '('. - let cmdbeg = statstr - .find('(') - .ok_or_else(|| format_err!("missing '(' in /proc/PID/stat"))?; - - if !statstr[..=cmdbeg].ends_with(" (") { - bail!("bad /proc/PID/stat line before the '('"); - } - - let pid: u32 = statstr[..(cmdbeg - 1)] - .parse() - .map_err(|e| format_err!("bad pid in /proc/PID/stat: {}", e))?; - let pid = Pid::from_raw(pid as i32); - - // After the '(' we have an arbitrary command name, then ')' and the remaining values - let cmdend = statstr - .rfind(')') - .ok_or_else(|| format_err!("missing ')' in /proc/PID/stat"))?; - let mut parts = statstr[cmdend + 1..].trim_start().split_ascii_whitespace(); - - // helpers: - fn required<'a>(value: Option<&'a str>, what: &'static str) -> Result<&'a str, Error> { - value.ok_or_else(|| format_err!("missing '{}' in /proc/PID/stat", what)) - } - - fn req_num(value: Option<&str>, what: &'static str) -> Result - where - T: FromStr, - ::Err: Into, - { - required(value, what)?.parse::().map_err(|e| e.into()) - } - - fn req_byte(value: Option<&str>, what: &'static str) -> Result { - let value = required(value, what)?; - if value.len() != 1 { - bail!("invalid '{}' in /proc/PID/stat", what); - } - Ok(value.as_bytes()[0]) - } - - let out = PidStat { - pid, - status: req_byte(parts.next(), "status")?, - ppid: Pid::from_raw(req_num::(parts.next(), "ppid")? as i32), - utime: req_num::(parts.nth(9), "utime")?, - stime: req_num::(parts.next(), "stime")?, - num_threads: req_num::(parts.nth(4), "num_threads")?, - starttime: req_num::(parts.nth(1), "start_time")?, - vsize: req_num::(parts.next(), "vsize")?, - rss: req_num::(parts.next(), "rss")? * 4096, - }; - - let _ = req_num::(parts.next(), "it_real_value")?; - // and more... - - Ok(out) +/// Read `/proc/PID/stat` for a pid. +#[deprecated(note = "use `PidStat::read_for_pid`")] +pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result { + PidStat::read_for_pid(Pid::from_raw(pid)) } #[test] fn test_read_proc_pid_stat() { - let stat = parse_proc_pid_stat( + let stat = PidStat::parse( "28900 (zsh) S 22489 28900 28900 34826 10252 4194304 6851 5946551 0 2344 6 3 25205 1413 \ 20 0 1 0 287592 12496896 1910 18446744073709551615 93999319244800 93999319938061 \ 140722897984224 0 0 0 2 3686404 134295555 1 0 0 17 10 0 0 0 0 0 93999320079088 \ @@ -134,19 +145,15 @@ fn test_read_proc_pid_stat() { assert_eq!(stat.rss, 1910 * 4096); } +#[deprecated(note = "use `PidStat`")] pub fn read_proc_starttime(pid: libc::pid_t) -> Result { - let info = read_proc_pid_stat(pid)?; - - Ok(info.starttime) + PidStat::read_for_pid(Pid::from_raw(pid)).map(|stat| stat.starttime) } pub fn check_process_running(pid: libc::pid_t) -> Option { - if let Ok(info) = read_proc_pid_stat(pid) { - if info.status != b'Z' { - return Some(info); - } - } - None + PidStat::read_for_pid(Pid::from_raw(pid)) + .ok() + .filter(|stat| stat.status != b'Z') } pub fn check_process_running_pstart(pid: libc::pid_t, pstart: u64) -> Option {