mirror of
https://git.proxmox.com/git/proxmox
synced 2025-05-03 03:15:43 +00:00
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 <w.bumiller@proxmox.com>
This commit is contained in:
parent
e3b41dc815
commit
42a155668e
@ -26,6 +26,7 @@ lazy_static! {
|
|||||||
static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64;
|
static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Selected contents of the `/proc/PID/stat` file.
|
||||||
pub struct PidStat {
|
pub struct PidStat {
|
||||||
pub pid: Pid,
|
pub pid: Pid,
|
||||||
pub ppid: Pid,
|
pub ppid: Pid,
|
||||||
@ -38,84 +39,94 @@ pub struct PidStat {
|
|||||||
pub rss: i64,
|
pub rss: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result<PidStat, Error> {
|
impl PidStat {
|
||||||
let stat = parse_proc_pid_stat(std::str::from_utf8(&std::fs::read(format!(
|
/// Retrieve the `stat` file contents of a process.
|
||||||
"/proc/{}/stat",
|
pub fn read_for_pid(pid: Pid) -> Result<Self, Error> {
|
||||||
pid
|
let stat = Self::parse(std::str::from_utf8(&std::fs::read(format!(
|
||||||
))?)?)?;
|
"/proc/{}/stat",
|
||||||
if stat.pid.as_raw() != pid {
|
|
||||||
bail!(
|
|
||||||
"unexpected pid for process: found pid {} in /proc/{}/stat",
|
|
||||||
stat.pid.as_raw(),
|
|
||||||
pid
|
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<PidStat, Error> {
|
||||||
|
// 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<T>(value: Option<&str>, what: &'static str) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
T: FromStr,
|
||||||
|
<T as FromStr>::Err: Into<Error>,
|
||||||
|
{
|
||||||
|
required(value, what)?.parse::<T>().map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn req_byte(value: Option<&str>, what: &'static str) -> Result<u8, Error> {
|
||||||
|
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::<u32>(parts.next(), "ppid")? as i32),
|
||||||
|
utime: req_num::<u64>(parts.nth(9), "utime")?,
|
||||||
|
stime: req_num::<u64>(parts.next(), "stime")?,
|
||||||
|
num_threads: req_num::<u64>(parts.nth(4), "num_threads")?,
|
||||||
|
starttime: req_num::<u64>(parts.nth(1), "start_time")?,
|
||||||
|
vsize: req_num::<u64>(parts.next(), "vsize")?,
|
||||||
|
rss: req_num::<i64>(parts.next(), "rss")? * 4096,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = req_num::<u64>(parts.next(), "it_real_value")?;
|
||||||
|
// and more...
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
}
|
}
|
||||||
Ok(stat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_proc_pid_stat(statstr: &str) -> Result<PidStat, Error> {
|
/// Read `/proc/PID/stat` for a pid.
|
||||||
// It starts with the pid followed by a '('.
|
#[deprecated(note = "use `PidStat::read_for_pid`")]
|
||||||
let cmdbeg = statstr
|
pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result<PidStat, Error> {
|
||||||
.find('(')
|
PidStat::read_for_pid(Pid::from_raw(pid))
|
||||||
.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<T>(value: Option<&str>, what: &'static str) -> Result<T, Error>
|
|
||||||
where
|
|
||||||
T: FromStr,
|
|
||||||
<T as FromStr>::Err: Into<Error>,
|
|
||||||
{
|
|
||||||
required(value, what)?.parse::<T>().map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn req_byte(value: Option<&str>, what: &'static str) -> Result<u8, Error> {
|
|
||||||
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::<u32>(parts.next(), "ppid")? as i32),
|
|
||||||
utime: req_num::<u64>(parts.nth(9), "utime")?,
|
|
||||||
stime: req_num::<u64>(parts.next(), "stime")?,
|
|
||||||
num_threads: req_num::<u64>(parts.nth(4), "num_threads")?,
|
|
||||||
starttime: req_num::<u64>(parts.nth(1), "start_time")?,
|
|
||||||
vsize: req_num::<u64>(parts.next(), "vsize")?,
|
|
||||||
rss: req_num::<i64>(parts.next(), "rss")? * 4096,
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = req_num::<u64>(parts.next(), "it_real_value")?;
|
|
||||||
// and more...
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_proc_pid_stat() {
|
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 \
|
"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 \
|
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 \
|
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);
|
assert_eq!(stat.rss, 1910 * 4096);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated(note = "use `PidStat`")]
|
||||||
pub fn read_proc_starttime(pid: libc::pid_t) -> Result<u64, Error> {
|
pub fn read_proc_starttime(pid: libc::pid_t) -> Result<u64, Error> {
|
||||||
let info = read_proc_pid_stat(pid)?;
|
PidStat::read_for_pid(Pid::from_raw(pid)).map(|stat| stat.starttime)
|
||||||
|
|
||||||
Ok(info.starttime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_process_running(pid: libc::pid_t) -> Option<PidStat> {
|
pub fn check_process_running(pid: libc::pid_t) -> Option<PidStat> {
|
||||||
if let Ok(info) = read_proc_pid_stat(pid) {
|
PidStat::read_for_pid(Pid::from_raw(pid))
|
||||||
if info.status != b'Z' {
|
.ok()
|
||||||
return Some(info);
|
.filter(|stat| stat.status != b'Z')
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_process_running_pstart(pid: libc::pid_t, pstart: u64) -> Option<PidStat> {
|
pub fn check_process_running_pstart(pid: libc::pid_t, pstart: u64) -> Option<PidStat> {
|
||||||
|
Loading…
Reference in New Issue
Block a user