use std::collections::HashMap;
use std::process::Command;
#[derive(Debug)]
pub struct ProcInfo {
pub pid: u32,
pub ppid: u32,
pub rss_kb: u64,
pub cpu_pct: f64,
pub command: String,
}
pub fn get_process_info() -> HashMap<u32, ProcInfo> {
let mut map = HashMap::new();
let output = Command::new("ps")
.args(["-eo", "pid,ppid,rss,%cpu,command"])
.output()
.ok();
if let Some(output) = output {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 {
if let (Ok(pid), Ok(ppid), Ok(rss)) = (
parts[0].parse::<u32>(),
parts[1].parse::<u32>(),
parts[2].parse::<u64>(),
) {
let cpu = parts[3].parse::<f64>().unwrap_or(0.0);
let command = parts[4..].join(" ");
map.insert(pid, ProcInfo {
pid,
ppid,
rss_kb: rss,
cpu_pct: cpu,
command,
});
}
}
}
}
map
}
pub fn get_children_map(procs: &HashMap<u32, ProcInfo>) -> HashMap<u32, Vec<u32>> {
let mut children: HashMap<u32, Vec<u32>> = HashMap::new();
for proc in procs.values() {
children.entry(proc.ppid).or_default().push(proc.pid);
}
children
}
pub fn has_active_descendant(
pid: u32,
children_map: &HashMap<u32, Vec<u32>>,
process_info: &HashMap<u32, ProcInfo>,
cpu_threshold: f64,
) -> bool {
let mut stack = vec![pid];
while let Some(p) = stack.pop() {
if let Some(kids) = children_map.get(&p) {
for &kid in kids {
if process_info.get(&kid).is_some_and(|p| p.cpu_pct > cpu_threshold) {
return true;
}
stack.push(kid);
}
}
}
false
}
pub fn get_listening_ports() -> HashMap<u32, Vec<u16>> {
let mut map: HashMap<u32, Vec<u16>> = HashMap::new();
let output = Command::new("lsof")
.args(["-i", "-P", "-n", "-sTCP:LISTEN"])
.output()
.ok();
if let Some(output) = output {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
let is_tcp_listen = parts.len() >= 9
&& parts[7] == "TCP"
&& line.contains("(LISTEN)");
if is_tcp_listen {
if let Ok(pid) = parts[1].parse::<u32>() {
if let Some(addr) = parts.get(8) {
if let Some(port_str) = addr.rsplit(':').next() {
if let Ok(port) = port_str.parse::<u16>() {
map.entry(pid).or_default().push(port);
}
}
}
}
}
}
}
map
}
pub fn collect_git_stats(cwd: &str) -> (u32, u32) {
let output = Command::new("git")
.args(["-C", cwd, "status", "--porcelain"])
.output()
.ok();
let mut added = 0u32;
let mut modified = 0u32;
if let Some(output) = output {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.len() < 2 {
continue;
}
let status_code = &line[..2];
if status_code.contains('?') || status_code.contains('A') {
added += 1;
} else if status_code.contains('M') {
modified += 1;
}
}
}
}
(added, modified)
}