claudectl_core/
process.rs1use crate::session::{ClaudeSession, SessionStatus};
2
3pub fn fetch_and_enrich(sessions: &mut [ClaudeSession]) {
6 if sessions.is_empty() {
7 return;
8 }
9
10 let pids: Vec<String> = sessions.iter().map(|s| s.pid.to_string()).collect();
11 let pid_arg = pids.join(",");
12
13 let output = std::process::Command::new("ps")
14 .args(["-o", "pid=,tty=,%cpu=,rss=,command=", "-p", &pid_arg])
15 .env_clear()
16 .output();
17
18 let output = match output {
19 Ok(o) => o,
20 Err(e) => {
21 crate::logger::log("ERROR", &format!("ps command failed: {e}"));
22 for s in sessions.iter_mut() {
24 s.status = SessionStatus::Finished;
25 s.cpu_percent = 0.0;
26 }
27 return;
28 }
29 };
30
31 let stdout = String::from_utf8_lossy(&output.stdout);
32
33 let mut alive_pids = std::collections::HashSet::new();
35
36 for line in stdout.lines() {
37 let trimmed = line.trim();
38 let fields: Vec<&str> = trimmed.split_whitespace().collect();
39 if fields.len() < 5 {
40 continue;
41 }
42 let Ok(pid) = fields[0].parse::<u32>() else {
43 continue;
44 };
45 let tty = fields[1].to_string();
46 let cpu = fields[2].parse::<f32>().unwrap_or(0.0);
47 let rss_kb = fields[3].parse::<f64>().unwrap_or(0.0);
48 let mem_mb = rss_kb / 1024.0;
49 let command = fields[4..].join(" ");
50
51 if !command.contains("claude") {
55 continue;
56 }
57
58 alive_pids.insert(pid);
59
60 for session in sessions.iter_mut() {
61 if session.pid == pid {
62 session.tty = tty.clone();
63 session.mem_mb = mem_mb;
64
65 session.cpu_history.push(cpu);
67 if session.cpu_history.len() > 3 {
68 session.cpu_history.remove(0);
69 }
70 session.cpu_percent =
71 session.cpu_history.iter().sum::<f32>() / session.cpu_history.len() as f32;
72
73 if let Some(idx) = command.find("claude") {
75 let after_claude = &command[idx + 6..];
76 session.command_args = after_claude.trim().to_string();
77 }
78
79 let cmd_parts: Vec<&str> = command.split_whitespace().collect();
81 extract_session_meta(&cmd_parts, session);
82
83 break;
84 }
85 }
86 }
87
88 for session in sessions.iter_mut() {
96 if alive_pids.contains(&session.pid) {
97 if session.status == crate::session::SessionStatus::Finished {
98 session.status = crate::session::SessionStatus::Unknown;
99 }
100 } else {
101 session.status = crate::session::SessionStatus::Finished;
102 session.cpu_percent = 0.0;
103 }
104 }
105}
106
107fn extract_session_meta(cmd: &[&str], session: &mut ClaudeSession) {
108 let mut i = 0;
109 while i < cmd.len() {
110 match cmd[i] {
111 "--name" | "-n" if i + 1 < cmd.len() => {
112 session.session_name = cmd[i + 1].to_string();
113 i += 2;
114 continue;
115 }
116 "--resume" | "-r" if i + 1 < cmd.len() => {
117 let val = cmd[i + 1];
118 if !looks_like_uuid(val) {
119 session.session_name = val.to_string();
120 }
121 i += 2;
122 continue;
123 }
124 _ => {}
125 }
126 i += 1;
127 }
128}
129
130fn looks_like_uuid(s: &str) -> bool {
131 s.len() == 36
132 && s.chars().all(|c| c.is_ascii_hexdigit() || c == '-')
133 && s.matches('-').count() == 4
134}