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(test)]
mod tests {
use super::{after_comm, starttime_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);
}
}