pub(crate) fn after_comm(stat: &str) -> Option<&str> {
stat.rsplit_once(')').map(|(_, after)| after)
}
pub(crate) fn starttime_from_stat(stat: &str) -> Option<u64> {
after_comm(stat)?
.split_whitespace()
.nth(19)?
.parse::<u64>()
.ok()
}
pub(crate) fn read_starttime(pid: u32) -> Option<u64> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
starttime_from_stat(&stat)
}
#[cfg(feature = "process-control")]
pub(crate) fn ppid_from_stat(stat: &str) -> Option<u32> {
after_comm(stat)?
.split_whitespace()
.nth(1)?
.parse::<u32>()
.ok()
}
#[cfg(feature = "process-control")]
pub(crate) fn comm_from_stat(stat: &str) -> Option<String> {
let open = stat.find('(')?;
let close = stat.rfind(')')?;
let name = stat.get(open + 1..close)?;
if name.is_empty() {
None
} else {
Some(name.to_string())
}
}
#[cfg(feature = "process-control")]
pub(crate) fn read_stat_meta(pid: u32) -> Option<StatMeta> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
Some(StatMeta {
ppid: ppid_from_stat(&stat),
comm: comm_from_stat(&stat),
starttime: starttime_from_stat(&stat),
})
}
#[cfg(feature = "process-control")]
pub(crate) struct StatMeta {
pub ppid: Option<u32>,
pub comm: Option<String>,
pub starttime: Option<u64>,
}
pub(crate) fn state_from_stat(stat: &str) -> Option<char> {
after_comm(stat)?.split_whitespace().next()?.chars().next()
}
pub(crate) fn read_state(pid: u32) -> Option<char> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
state_from_stat(&stat)
}
#[cfg(all(test, feature = "process-control"))]
mod meta_tests {
use super::{comm_from_stat, ppid_from_stat};
const TRICKY_COMM: &str = "1234 (weird ) proc :) S 1 1234 1234 0 -1 4194304 100 0 0 0 50 25 0 0 20 0 1 0 987654321 1000 2000";
#[test]
fn ppid_reads_field_4_past_a_tricky_comm() {
assert_eq!(ppid_from_stat(TRICKY_COMM), Some(1));
}
#[test]
fn ppid_yields_none_when_absent_or_unparsable() {
assert_eq!(ppid_from_stat("1234 no-paren S 1"), None);
assert_eq!(ppid_from_stat("1234 (proc) S"), None);
assert_eq!(ppid_from_stat("1234 (proc) S not_a_pid"), None);
}
#[test]
fn comm_keeps_a_name_with_inner_parens_and_spaces() {
assert_eq!(
comm_from_stat(TRICKY_COMM).as_deref(),
Some("weird ) proc :")
);
assert_eq!(
comm_from_stat("1234 (worker) S 1").as_deref(),
Some("worker")
);
}
#[test]
fn comm_yields_none_without_a_paren_pair_or_when_empty() {
assert_eq!(comm_from_stat("1234 no-paren S 1"), None);
assert_eq!(comm_from_stat("1234 () S 1"), None);
assert_eq!(comm_from_stat("1234 )weird( S 1"), None);
}
}
#[cfg(test)]
mod tests {
use super::{after_comm, starttime_from_stat, state_from_stat};
const TRICKY_COMM: &str = "1234 (weird ) proc :) S 1 1234 1234 0 -1 4194304 100 0 0 0 50 25 0 0 20 0 1 0 987654321 1000 2000";
#[test]
fn after_comm_skips_a_comm_with_inner_parens_and_spaces() {
let fields: Vec<&str> = after_comm(TRICKY_COMM)
.expect("the line has a closing ')'")
.split_whitespace()
.collect();
assert_eq!(fields[0], "S", "index 0 must be field 3 (state)");
assert_eq!(fields[11], "50", "index 11 must be field 14 (utime)");
assert_eq!(fields[12], "25", "index 12 must be field 15 (stime)");
assert_eq!(
fields[19], "987654321",
"index 19 must be field 22 (starttime)"
);
}
#[test]
fn starttime_reads_field_22_past_a_tricky_comm() {
assert_eq!(starttime_from_stat(TRICKY_COMM), Some(987654321));
}
#[test]
fn no_closing_paren_yields_none() {
assert_eq!(after_comm("1234 no-paren S 1"), None);
assert_eq!(starttime_from_stat("1234 no-paren S 1"), None);
}
#[test]
fn too_few_fields_yields_none() {
assert_eq!(starttime_from_stat("1234 (proc) S 1 1234"), None);
}
#[test]
fn a_non_numeric_starttime_yields_none() {
let stat = "1 (p) S 1 1 1 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 not_a_number";
assert_eq!(starttime_from_stat(stat), None);
}
#[test]
fn state_reads_field_3_past_a_tricky_comm() {
assert_eq!(state_from_stat(TRICKY_COMM), Some('S'));
}
#[test]
fn state_reads_a_zombie() {
assert_eq!(state_from_stat("1234 (proc) Z 1 1234 1234 0 -1"), Some('Z'));
}
#[test]
fn state_yields_none_without_a_closing_paren_or_a_field() {
assert_eq!(state_from_stat("1234 no-paren R 1"), None);
assert_eq!(state_from_stat("1234 (proc)"), None);
}
}