2022-10-22 18:54:46 +02:00
|
|
|
use log::info;
|
|
|
|
use procfs::process::{FDInfo, Io, Process, Stat, Status};
|
2022-01-14 07:20:53 +01:00
|
|
|
use procfs::{ProcError, ProcessCgroup};
|
|
|
|
use std::thread;
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
|
|
|
|
pub enum ProcessTask {
|
|
|
|
Process(Process),
|
2022-07-11 18:18:06 +02:00
|
|
|
Task { stat: Box<Stat>, owner: u32 },
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ProcessTask {
|
2022-07-11 18:18:06 +02:00
|
|
|
pub fn stat(&self) -> Result<Stat, ProcError> {
|
2022-01-14 07:20:53 +01:00
|
|
|
match self {
|
2023-01-24 12:23:42 +01:00
|
|
|
ProcessTask::Process(x) => x.stat(),
|
2022-07-11 18:18:06 +02:00
|
|
|
ProcessTask::Task { stat: x, owner: _ } => Ok(*x.clone()),
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn cmdline(&self) -> Result<Vec<String>, ProcError> {
|
|
|
|
match self {
|
|
|
|
ProcessTask::Process(x) => x.cmdline(),
|
|
|
|
_ => Err(ProcError::Other("not supported".to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn cgroups(&self) -> Result<Vec<ProcessCgroup>, ProcError> {
|
|
|
|
match self {
|
|
|
|
ProcessTask::Process(x) => x.cgroups(),
|
|
|
|
_ => Err(ProcError::Other("not supported".to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn fd(&self) -> Result<Vec<FDInfo>, ProcError> {
|
|
|
|
match self {
|
2023-01-24 12:23:42 +01:00
|
|
|
ProcessTask::Process(x) => x.fd()?.collect(),
|
2022-01-14 07:20:53 +01:00
|
|
|
_ => Err(ProcError::Other("not supported".to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn loginuid(&self) -> Result<u32, ProcError> {
|
|
|
|
match self {
|
|
|
|
ProcessTask::Process(x) => x.loginuid(),
|
|
|
|
_ => Err(ProcError::Other("not supported".to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn owner(&self) -> u32 {
|
|
|
|
match self {
|
2022-07-11 18:18:06 +02:00
|
|
|
ProcessTask::Process(x) => x.uid().unwrap_or(0),
|
2022-01-14 07:20:53 +01:00
|
|
|
ProcessTask::Task { stat: _, owner: x } => *x,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn wchan(&self) -> Result<String, ProcError> {
|
|
|
|
match self {
|
|
|
|
ProcessTask::Process(x) => x.wchan(),
|
|
|
|
_ => Err(ProcError::Other("not supported".to_string())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ProcessInfo {
|
|
|
|
pub pid: i32,
|
|
|
|
pub ppid: i32,
|
|
|
|
pub curr_proc: ProcessTask,
|
|
|
|
pub curr_io: Option<Io>,
|
|
|
|
pub prev_io: Option<Io>,
|
2022-10-22 18:54:46 +02:00
|
|
|
pub curr_stat: Option<Stat>,
|
|
|
|
pub prev_stat: Option<Stat>,
|
2022-01-14 07:20:53 +01:00
|
|
|
pub curr_status: Option<Status>,
|
|
|
|
pub interval: Duration,
|
|
|
|
}
|
|
|
|
|
2022-10-22 18:54:46 +02:00
|
|
|
pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
|
2022-01-14 07:20:53 +01:00
|
|
|
let mut base_procs = Vec::new();
|
|
|
|
let mut ret = Vec::new();
|
|
|
|
|
2022-10-22 18:54:46 +02:00
|
|
|
// Take an initial snapshot of process I/O and CPU info, so we can calculate changes over time
|
2022-01-14 07:20:53 +01:00
|
|
|
if let Ok(all_proc) = procfs::process::all_processes() {
|
2022-07-11 18:18:06 +02:00
|
|
|
for proc in all_proc.flatten() {
|
2022-01-14 07:20:53 +01:00
|
|
|
let io = proc.io().ok();
|
2022-10-22 18:54:46 +02:00
|
|
|
let stat = proc.stat().ok();
|
2022-01-14 07:20:53 +01:00
|
|
|
let time = Instant::now();
|
2022-10-22 18:54:46 +02:00
|
|
|
base_procs.push((proc.pid(), io, stat, time));
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-22 18:54:46 +02:00
|
|
|
// wait a bit...
|
2022-01-14 07:20:53 +01:00
|
|
|
thread::sleep(interval);
|
|
|
|
|
2022-10-22 18:54:46 +02:00
|
|
|
// now get process info again, build up results
|
|
|
|
for (pid, prev_io, prev_stat, prev_time) in base_procs {
|
2022-07-11 18:18:06 +02:00
|
|
|
let curr_proc_pid = pid;
|
2022-10-22 18:54:46 +02:00
|
|
|
let curr_proc = if let Ok(p) = Process::new(curr_proc_pid) {
|
|
|
|
p
|
|
|
|
} else {
|
|
|
|
info!("failed to retrieve info for pid={curr_proc_pid}, process probably died between snapshots");
|
|
|
|
continue;
|
2022-07-11 18:18:06 +02:00
|
|
|
};
|
|
|
|
|
2022-01-14 07:20:53 +01:00
|
|
|
let curr_io = curr_proc.io().ok();
|
2022-10-22 18:54:46 +02:00
|
|
|
let curr_stat = curr_proc.stat().ok();
|
2022-01-14 07:20:53 +01:00
|
|
|
let curr_status = curr_proc.status().ok();
|
|
|
|
let curr_time = Instant::now();
|
|
|
|
let interval = curr_time - prev_time;
|
2023-01-24 12:23:42 +01:00
|
|
|
let ppid = curr_proc.stat().map(|p| p.ppid).unwrap_or_default();
|
2022-01-14 07:20:53 +01:00
|
|
|
let curr_proc = ProcessTask::Process(curr_proc);
|
|
|
|
|
|
|
|
let proc = ProcessInfo {
|
|
|
|
pid,
|
|
|
|
ppid,
|
|
|
|
curr_proc,
|
|
|
|
curr_io,
|
|
|
|
prev_io,
|
2022-10-22 18:54:46 +02:00
|
|
|
curr_stat,
|
|
|
|
prev_stat,
|
2022-01-14 07:20:53 +01:00
|
|
|
curr_status,
|
|
|
|
interval,
|
|
|
|
};
|
|
|
|
|
|
|
|
ret.push(proc);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ProcessInfo {
|
|
|
|
/// PID of process
|
|
|
|
pub fn pid(&self) -> i32 {
|
|
|
|
self.pid
|
|
|
|
}
|
|
|
|
|
2023-04-05 20:12:01 +02:00
|
|
|
/// PPID of process
|
|
|
|
pub fn ppid(&self) -> i32 {
|
|
|
|
self.ppid
|
|
|
|
}
|
|
|
|
|
2022-01-14 07:20:53 +01:00
|
|
|
/// Name of command
|
|
|
|
pub fn name(&self) -> String {
|
|
|
|
self.command()
|
|
|
|
.split(' ')
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.first()
|
|
|
|
.map(|x| x.to_string())
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Full name of command, with arguments
|
|
|
|
pub fn command(&self) -> String {
|
|
|
|
if let Ok(cmd) = &self.curr_proc.cmdline() {
|
|
|
|
if !cmd.is_empty() {
|
2022-11-04 21:11:17 +01:00
|
|
|
cmd.join(" ").replace(['\n', '\t'], " ")
|
2022-01-14 07:20:53 +01:00
|
|
|
} else {
|
2022-07-11 18:18:06 +02:00
|
|
|
match self.curr_proc.stat() {
|
|
|
|
Ok(p) => p.comm,
|
|
|
|
Err(_) => "".to_string(),
|
|
|
|
}
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
} else {
|
2022-07-11 18:18:06 +02:00
|
|
|
match self.curr_proc.stat() {
|
|
|
|
Ok(p) => p.comm,
|
|
|
|
Err(_) => "".to_string(),
|
|
|
|
}
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the status of the process
|
|
|
|
pub fn status(&self) -> String {
|
2023-01-24 12:23:42 +01:00
|
|
|
if let Ok(p) = self.curr_proc.stat() {
|
|
|
|
match p.state {
|
|
|
|
'S' => "Sleeping",
|
|
|
|
'R' => "Running",
|
|
|
|
'D' => "Disk sleep",
|
|
|
|
'Z' => "Zombie",
|
|
|
|
'T' => "Stopped",
|
|
|
|
't' => "Tracing",
|
|
|
|
'X' => "Dead",
|
|
|
|
'x' => "Dead",
|
|
|
|
'K' => "Wakekill",
|
|
|
|
'W' => "Waking",
|
|
|
|
'P' => "Parked",
|
|
|
|
_ => "Unknown",
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
"Unknown"
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
2023-01-24 12:23:42 +01:00
|
|
|
.into()
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// CPU usage as a percent of total
|
|
|
|
pub fn cpu_usage(&self) -> f64 {
|
2022-10-22 18:54:46 +02:00
|
|
|
if let Some(cs) = &self.curr_stat {
|
|
|
|
if let Some(ps) = &self.prev_stat {
|
|
|
|
let curr_time = cs.utime + cs.stime;
|
|
|
|
let prev_time = ps.utime + ps.stime;
|
|
|
|
|
2023-02-27 08:53:01 +01:00
|
|
|
let usage_ms = (curr_time - prev_time) * 1000 / procfs::ticks_per_second();
|
2022-10-22 18:54:46 +02:00
|
|
|
let interval_ms =
|
|
|
|
self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis());
|
|
|
|
usage_ms as f64 * 100.0 / interval_ms as f64
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Memory size in number of bytes
|
|
|
|
pub fn mem_size(&self) -> u64 {
|
2022-07-11 18:18:06 +02:00
|
|
|
match self.curr_proc.stat() {
|
2023-02-27 08:53:01 +01:00
|
|
|
Ok(p) => p.rss_bytes(),
|
2022-07-11 18:18:06 +02:00
|
|
|
Err(_) => 0,
|
|
|
|
}
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Virtual memory size in bytes
|
|
|
|
pub fn virtual_size(&self) -> u64 {
|
2023-01-24 12:23:42 +01:00
|
|
|
self.curr_proc.stat().map(|p| p.vsize).unwrap_or_default()
|
2022-01-14 07:20:53 +01:00
|
|
|
}
|
|
|
|
}
|