| use tokio::fs::{self}; |
| |
| /// Parse the contents of a /proc/[pid]/task/[tid]/stat file into a human-readable format |
| fn parse_stat(contents: &str) -> String { |
| let fields: Vec<&str> = contents.split_whitespace().collect(); |
| if fields.len() < 52 { |
| return format!("Invalid stat format: {}", contents); |
| } |
| |
| let cmd_start = contents.find('(').unwrap_or(0); |
| let cmd_end = contents.rfind(')').unwrap_or(contents.len()); |
| let comm = &contents[cmd_start + 1..cmd_end]; |
| |
| let mut result = String::new(); |
| result.push_str(&format!("PID: {}\n", fields[0])); |
| result.push_str(&format!("Command: {}\n", comm)); |
| result.push_str(&format!( |
| "State: {} ({})\n", |
| fields[2], |
| match fields[2] { |
| "R" => "Running", |
| "S" => "Sleeping (interruptible)", |
| "D" => "Waiting in uninterruptible disk sleep", |
| "Z" => "Zombie", |
| "T" => "Stopped", |
| "t" => "Tracing stop", |
| "W" => "Paging", |
| "X" | "x" => "Dead", |
| "K" => "Wakekill", |
| "P" => "Parked", |
| _ => "Unknown", |
| } |
| )); |
| result.push_str(&format!("Parent PID: {}\n", fields[3])); |
| result.push_str(&format!("Process Group: {}\n", fields[4])); |
| result.push_str(&format!("Session ID: {}\n", fields[5])); |
| result.push_str(&format!("TTY: {}\n", fields[6])); |
| result.push_str(&format!("Foreground Process Group: {}\n", fields[7])); |
| result.push_str(&format!("Kernel Flags: {}\n", fields[8])); |
| result.push_str(&format!("Minor Faults: {}\n", fields[9])); |
| result.push_str(&format!("Children Minor Faults: {}\n", fields[10])); |
| result.push_str(&format!("Major Faults: {}\n", fields[11])); |
| result.push_str(&format!("Children Major Faults: {}\n", fields[12])); |
| result.push_str(&format!("User Mode Time: {} ticks\n", fields[13])); |
| result.push_str(&format!("System Mode Time: {} ticks\n", fields[14])); |
| result.push_str(&format!("Children User Mode Time: {} ticks\n", fields[15])); |
| result.push_str(&format!( |
| "Children System Mode Time: {} ticks\n", |
| fields[16] |
| )); |
| result.push_str(&format!("Priority: {}\n", fields[17])); |
| result.push_str(&format!("Nice Value: {}\n", fields[18])); |
| result.push_str(&format!("Number of Threads: {}\n", fields[19])); |
| result.push_str(&format!("Real-time Priority: {}\n", fields[39])); |
| result.push_str(&format!( |
| "Policy: {} ({})\n", |
| fields[40], |
| match fields[40] { |
| "0" => "SCHED_NORMAL/OTHER", |
| "1" => "SCHED_FIFO", |
| "2" => "SCHED_RR", |
| "3" => "SCHED_BATCH", |
| "5" => "SCHED_IDLE", |
| "6" => "SCHED_DEADLINE", |
| _ => "Unknown", |
| } |
| )); |
| result.push_str(&format!( |
| "Aggregated Block I/O Delays: {} ticks\n", |
| fields[41] |
| )); |
| result.push_str(&format!("Guest Time: {} ticks\n", fields[42])); |
| result.push_str(&format!("Children Guest Time: {} ticks\n", fields[43])); |
| |
| result |
| } |
| |
| /// Get detailed information about the system's processes and related /proc data |
| pub async fn get_proc_info() -> Result<String, std::io::Error> { |
| let static_proc_files = vec![ |
| "/proc/uptime", |
| "/proc/cpuinfo", |
| "/proc/stat", |
| "/proc/meminfo", |
| "/proc/interrupts", |
| "/proc/softirqs", |
| "/proc/latency", |
| "/proc/buddyinfo", |
| "/proc/slabinfo", |
| "/proc/vmstat", |
| "/proc/loadavg", |
| "/proc/cmdline", |
| "/proc/version", |
| "/proc/net/sockstat", |
| "/proc/net/snmp", |
| "/proc/net/netlink", |
| "/proc/net/netstat", |
| "/proc/net/dev", |
| "/proc/net/packet", |
| "/proc/net/tcp", |
| "/proc/net/tcp6", |
| "/proc/net/udp", |
| "/proc/net/udp6", |
| "/proc/net/raw", |
| "/proc/net/raw6", |
| "/proc/net/icmp", |
| "/proc/net/icmp6", |
| "/proc/net/udplite", |
| "/proc/net/udplite6", |
| "/proc/net/unix", |
| "/proc/net/softnet_stat", |
| "/proc/tty/drivers", |
| "/proc/sys/kernel/pid_max", |
| "/proc/sys/kernel/random/boot_id", |
| "/proc/mounts", |
| "/proc/modules", |
| ]; |
| |
| let mut result = String::new(); |
| |
| async fn dump_file(result: &mut String, path: &str) -> Result<(), std::io::Error> { |
| match fs::read_to_string(path).await { |
| Ok(contents) => { |
| result.push_str(&format!("=== {} ===\n", path)); |
| |
| if path.ends_with("/stat") && path.contains("/task/") { |
| result.push_str(&parse_stat(&contents)); |
| } else { |
| result.push_str(&contents); |
| } |
| |
| result.push_str("\n\n"); |
| } |
| Err(e) => { |
| if let Ok(metadata) = fs::metadata(path).await { |
| if metadata.is_dir() && path.ends_with("/fd") { |
| result.push_str(&format!("=== {} (directory) ===\n", path)); |
| if let Ok(mut rd) = fs::read_dir(path).await { |
| while let Ok(Some(entry)) = rd.next_entry().await { |
| let fd_path = entry.path(); |
| match fs::read_link(&fd_path).await { |
| Ok(link) => { |
| result.push_str(&format!( |
| "{} -> {}\n", |
| fd_path.display(), |
| link.display() |
| )); |
| } |
| Err(_) => { |
| result.push_str(&format!( |
| "{} (unreadable symlink)\n", |
| fd_path.display() |
| )); |
| } |
| } |
| } |
| } |
| result.push('\n'); |
| } else { |
| result.push_str(&format!("=== {} ERROR: {} ===\n\n", path, e)); |
| } |
| } else { |
| result.push_str(&format!("=== {} ERROR: {} ===\n\n", path, e)); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| for path in &static_proc_files { |
| dump_file(&mut result, path).await?; |
| } |
| |
| let mut proc_dir = fs::read_dir("/proc").await?; |
| while let Ok(Some(entry)) = proc_dir.next_entry().await { |
| let file_type = entry.file_type().await?; |
| if file_type.is_dir() { |
| if let Ok(pid) = entry.file_name().to_string_lossy().parse::<u32>() { |
| let pid_paths = vec![ |
| format!("/proc/{}/cmdline", pid), |
| format!("/proc/{}/statm", pid), |
| format!("/proc/{}/cgroup", pid), |
| format!("/proc/{}/task/{}/stat", pid, pid), |
| format!("/proc/{}/task/{}/status", pid, pid), |
| format!("/proc/{}/task/{}/wchan", pid, pid), |
| format!("/proc/{}/task/{}/syscall", pid, pid), |
| format!("/proc/{}/task/{}/fd", pid, pid), |
| ]; |
| |
| for p in pid_paths { |
| dump_file(&mut result, &p).await?; |
| } |
| } |
| } |
| } |
| |
| Ok(result) |
| } |