use crate::*;
use std::{fs::{read_dir, DirEntry, File},
io::{self, prelude::*}};
use sysconf::raw::{sysconf, SysconfVariable};
#[derive(Debug)]
pub struct Info {
pub cmdline: String,
pub start: i64,
pub pid: i32,
}
fn get_proc_info(filter: Option<&str>,
entry: &DirEntry,
clocktick: i64,
time_ref: i64)
-> Option<Info> {
let pid = i32::from_str(&entry.file_name().to_string_lossy()).ok()?;
let mut stat = String::new();
File::open(entry.path().join("stat")).ok()?.read_to_string(&mut stat).ok()?;
let (cmd_start, cmd_end) = (stat.find('(')? + 1, stat.rfind(')')?);
if filter.map_or(false, |f| f != &stat[cmd_start..cmd_end]) {
return None;
}
let start_time = i64::from_str(stat[cmd_end + 1..].split(' ').nth(20)?).ok()?;
let mut cmdline = String::new();
File::open(entry.path().join("cmdline")).ok()?.read_to_string(&mut cmdline).ok()?;
cmdline = cmdline.replace("\0", " ").trim().into();
Some(Info { cmdline, start: time_ref + (start_time / clocktick) as i64, pid })
}
pub fn get_all_info(filter: Option<&str>) -> Result<Vec<Info>, io::Error> {
let clocktick = sysconf(SysconfVariable::ScClkTck).unwrap() as i64;
let mut uptimestr = String::new();
File::open("/proc/uptime")?.read_to_string(&mut uptimestr)?;
let uptime = i64::from_str(uptimestr.split('.').next().unwrap()).unwrap();
let time_ref = epoch_now() - uptime;
let mut ret: Vec<Info> = Vec::new();
for entry in read_dir("/proc/")? {
if let Some(i) = get_proc_info(filter, &entry?, clocktick, time_ref) {
ret.push(i)
}
}
Ok(ret)
}
#[cfg(test)]
mod tests {
use chrono::DateTime;
use regex::Regex;
use std::{collections::BTreeMap, process::Command};
use crate::proces::*;
fn parse_ps_time(s: &str) -> i64 {
DateTime::parse_from_str(&format!("{} +0000", s), "%b %d %T %Y %z") .expect(&format!("Cannot parse {}", s))
.timestamp()
}
#[test] #[rustfmt::skip]
fn start_time() {
let mut info: BTreeMap<i32,(String,Option<i64>,Option<i64>)> = get_all_info(None)
.unwrap()
.iter()
.fold(BTreeMap::new(), |mut a,i| {a.insert(i.pid, (i.cmdline.clone(),Some(i.start),None)); a});
let ps_start = epoch_now();
let re = Regex::new("^ *([0-9]+) [A-Za-z]+ ([a-zA-Z0-9: ]+)$").unwrap();
let cmd = Command::new("ps").env("TZ", "UTC")
.env("LC_ALL", "C") .args(&["-o",
"pid,lstart", "-ax", "-h"]) .output()
.expect("failed to execute ps");
for lineres in cmd.stdout.lines() {
if let Ok(line) = lineres {
match re.captures(&line) {
Some(c) => {
let pid = c.get(1).unwrap().as_str().parse::<i32>().unwrap();
let time = parse_ps_time(c.get(2).unwrap().as_str());
if let Some((comm, t, None)) =
info.insert(pid, ("?".into(), None, Some(time)))
{
info.insert(pid, (comm, t, Some(time)));
}
},
None => assert!(false, "Couldn't parse {}", line),
}
}
}
assert!(info.len() > 10, "Only {} processes found", info.len());
let mut e: u32 = 0;
for (pid, times) in info {
match times {
(ref c,Some(t), None) => {e+=1; println!("WARN {:>20} {:>7} {}: disappeared after rust run", c, pid, fmt_time(t));},
(ref c,None, Some(t)) if t >= ps_start -1 => {e+=1; println!("WARN {:>20} {:>7} {}: appeared right after rust run", c, pid, fmt_time(t));},
(ref c,None, Some(t)) => {e+=10;println!("ERR {:>20} {:>7} {}: seen by ps but not by rust", c, pid, fmt_time(t));},
(ref c,Some(tr), Some(tp)) if tr == tp => {e+=0; println!("OK {:>20} {:>7} {}: same time", c, pid, fmt_time(tr));},
(ref c,Some(tr), Some(tp)) if (tr-tp).abs() < 2 => {e+=0; println!("IGN {:>20} {:>7} {}: {} secs diff {}", c, pid, fmt_time(tr), tr-tp, fmt_time(tp));},
(ref c,Some(tr), Some(tp)) if (tr-tp).abs() < 5 => {e+=1; println!("WARN {:>20} {:>7} {}: {} secs diff {}", c, pid, fmt_time(tr), tr-tp, fmt_time(tp));},
(ref c,Some(tr), Some(tp)) => {e+=5; println!("ERR {:>20} {:>7} {}: {} secs diff {}", c, pid, fmt_time(tr), tr-tp, fmt_time(tp));},
(ref c,None, None) => {e+=10;println!("ERR {:>20} {:>7}: no times", c, pid);},
}
}
assert!(e < 10, "Got failure score of {}", e);
}
}