use anyhow::Result;
use std::collections::HashMap;
use std::fs;
use users::get_user_by_uid;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ProcessInfo {
pub pid: u32,
pub name: String,
pub user: String,
pub uid: u32,
pub connections: usize,
pub read_bytes: u64,
pub write_bytes: u64,
pub read_bytes_sec: f64,
pub write_bytes_sec: f64,
pub cpu_percent: f64,
pub mem_percent: f64,
pub state: String,
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct ProcessDelta {
pub pid: u32,
pub name: String,
pub user: String,
pub connections: usize,
pub read_bytes_sec: f64,
pub write_bytes_sec: f64,
pub cpu_percent: f64,
pub mem_percent: f64,
pub state: String,
}
pub struct ProcessCollector {
last_io: HashMap<u32, (u64, u64)>,
last_cpu: HashMap<u32, (u64, u64, std::time::Instant)>,
last_time: std::time::Instant,
total_memory_kb: u64,
clock_tick: u64,
}
impl Default for ProcessCollector {
fn default() -> Self {
Self::new()
}
}
impl ProcessCollector {
pub fn new() -> Self {
Self {
last_io: HashMap::new(),
last_cpu: HashMap::new(),
last_time: std::time::Instant::now(),
total_memory_kb: Self::get_total_memory(),
clock_tick: Self::get_clock_tick(),
}
}
fn get_total_memory() -> u64 {
if let Ok(content) = fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if line.starts_with("MemTotal:") {
return line
.split(':')
.nth(1)
.and_then(|s| s.split_whitespace().next())
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
}
}
0
}
fn get_clock_tick() -> u64 {
unsafe { libc::sysconf(libc::_SC_CLK_TCK) as u64 }.max(1)
}
fn get_process_name(pid: u32) -> String {
fs::read_to_string(format!("/proc/{}/comm", pid))
.unwrap_or_default()
.trim()
.to_string()
}
fn get_process_user(pid: u32) -> (String, u32) {
if let Ok(content) = fs::read_to_string(format!("/proc/{}/status", pid)) {
for line in content.lines() {
if line.starts_with("Uid:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if let Ok(uid) = parts[1].parse::<u32>() {
let user_name = get_user_by_uid(uid)
.map(|u| u.name().to_string_lossy().to_string())
.unwrap_or_else(|| uid.to_string());
return (user_name, uid);
}
}
}
}
}
("unknown".to_string(), 0)
}
fn get_process_io(pid: u32) -> (u64, u64) {
if let Ok(content) = fs::read_to_string(format!("/proc/{}/io", pid)) {
let mut read_bytes = 0u64;
let mut write_bytes = 0u64;
for line in content.lines() {
if line.starts_with("read_bytes:") {
read_bytes = line
.split(':')
.nth(1)
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
} else if line.starts_with("write_bytes:") {
write_bytes = line
.split(':')
.nth(1)
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
}
}
return (read_bytes, write_bytes);
}
(0, 0)
}
fn get_socket_connections(pid: u32) -> usize {
let fd_path = format!("/proc/{}/fd", pid);
if let Ok(fd_dir) = fs::read_dir(&fd_path) {
return fd_dir
.filter_map(|e| e.ok())
.filter_map(|e| {
if let Ok(link) = fs::read_link(e.path()) {
let link_str = link.to_string_lossy().to_string();
if link_str.starts_with("socket:[") {
return Some(());
}
}
None
})
.count();
}
0
}
fn get_all_pids() -> Vec<u32> {
let mut pids = Vec::new();
if let Ok(entries) = fs::read_dir("/proc") {
for entry in entries.flatten() {
if let Ok(pid) = entry.file_name().to_string_lossy().parse::<u32>() {
pids.push(pid);
}
}
}
pids
}
fn get_process_stat(pid: u32) -> (u64, u64, u64, String) {
if let Ok(content) = fs::read_to_string(format!("/proc/{}/stat", pid)) {
let parts: Vec<&str> = content.split_whitespace().collect();
if parts.len() >= 17 {
let utime: u64 = parts[13].parse().unwrap_or(0);
let stime: u64 = parts[14].parse().unwrap_or(0);
let rss: u64 = parts[23].parse().unwrap_or(0);
let state = parts.get(2).unwrap_or(&"?").to_string();
return (utime, stime, rss, state);
}
}
(0, 0, 0, "?".to_string())
}
fn format_state(state: &str) -> &'static str {
match state {
"R" => "Running",
"S" => "Sleeping",
"D" => "Disk Sleep",
"T" => "Stopped",
"t" => "Tracing Stop",
"X" => "Dead",
"Z" => "Zombie",
"P" => "Parked",
"I" => "Idle",
_ => "Unknown",
}
}
pub fn collect(&mut self) -> Result<Vec<ProcessInfo>> {
let now = std::time::Instant::now();
let elapsed = now.duration_since(self.last_time).as_secs_f64();
let mut processes = Vec::new();
let mut current_io = HashMap::new();
let mut current_cpu = HashMap::new();
for pid in Self::get_all_pids() {
let name = Self::get_process_name(pid);
let (user, uid) = Self::get_process_user(pid);
let (read_bytes, write_bytes) = Self::get_process_io(pid);
let connections = Self::get_socket_connections(pid);
let (utime, stime, rss, state_code) = Self::get_process_stat(pid);
let state = Self::format_state(&state_code);
let (read_sec, write_sec) = if elapsed > 0.0 {
if let Some(&(last_read, last_write)) = self.last_io.get(&pid) {
(
(read_bytes.saturating_sub(last_read)) as f64 / elapsed,
(write_bytes.saturating_sub(last_write)) as f64 / elapsed,
)
} else {
(0.0, 0.0)
}
} else {
(0.0, 0.0)
};
let page_size_kb = 4.0_f64;
let mem_percent = if self.total_memory_kb > 0 {
(rss as f64 * page_size_kb / self.total_memory_kb as f64) * 100.0
} else {
0.0
};
let cpu_percent =
if let Some((last_utime, last_stime, last_time)) = self.last_cpu.get(&pid) {
let time_elapsed = now.duration_since(*last_time).as_secs_f64();
if time_elapsed > 0.0 && self.clock_tick > 0 {
let delta_utime = utime.saturating_sub(*last_utime) as f64;
let delta_stime = stime.saturating_sub(*last_stime) as f64;
let delta_ticks = delta_utime + delta_stime;
let cpu_time = delta_ticks / self.clock_tick as f64;
(cpu_time / time_elapsed) * 100.0
} else {
0.0
}
} else {
0.0
};
current_io.insert(pid, (read_bytes, write_bytes));
current_cpu.insert(pid, (utime, stime, now));
processes.push(ProcessInfo {
pid,
name,
user,
uid,
connections,
read_bytes,
write_bytes,
read_bytes_sec: read_sec,
write_bytes_sec: write_sec,
cpu_percent: cpu_percent.min(6400.0),
mem_percent: mem_percent.min(100.0),
state: state.to_string(),
});
}
self.last_io = current_io;
self.last_cpu = current_cpu;
self.last_time = now;
Ok(processes)
}
pub fn collect_delta(&mut self) -> Result<Vec<ProcessDelta>> {
let processes = self.collect()?;
Ok(processes
.into_iter()
.map(|p| ProcessDelta {
pid: p.pid,
name: p.name,
user: p.user,
connections: p.connections,
read_bytes_sec: p.read_bytes_sec,
write_bytes_sec: p.write_bytes_sec,
cpu_percent: p.cpu_percent,
mem_percent: p.mem_percent,
state: p.state,
})
.collect())
}
}