tmai_core/tmux/
process.rs1use parking_lot::RwLock;
2use std::collections::HashMap;
3use std::time::{Duration, Instant};
4
5const CHILD_CACHE_OFFSET: u32 = 1_000_000_000;
9
10const ENV_CACHE_OFFSET: u32 = 2_000_000_000;
12
13#[derive(Debug, Clone)]
15pub struct ProcessInfo {
16 pub cmdline: String,
18 pub last_update: Instant,
20}
21
22pub struct ProcessCache {
24 cache: RwLock<HashMap<u32, ProcessInfo>>,
26 ttl: Duration,
28}
29
30impl ProcessCache {
31 pub fn new() -> Self {
33 Self {
34 cache: RwLock::new(HashMap::new()),
35 ttl: Duration::from_secs(5),
36 }
37 }
38
39 pub fn with_ttl(ttl: Duration) -> Self {
41 Self {
42 cache: RwLock::new(HashMap::new()),
43 ttl,
44 }
45 }
46
47 pub fn get_cmdline(&self, pid: u32) -> Option<String> {
49 {
51 let cache = self.cache.read();
52 if let Some(info) = cache.get(&pid) {
53 if info.last_update.elapsed() < self.ttl {
54 return Some(info.cmdline.clone());
55 }
56 }
57 }
58
59 let cmdline = self.read_cmdline(pid)?;
61
62 {
64 let mut cache = self.cache.write();
65 cache.insert(
66 pid,
67 ProcessInfo {
68 cmdline: cmdline.clone(),
69 last_update: Instant::now(),
70 },
71 );
72 }
73
74 Some(cmdline)
75 }
76
77 fn read_cmdline(&self, pid: u32) -> Option<String> {
79 let path = format!("/proc/{}/cmdline", pid);
80 std::fs::read_to_string(&path)
81 .ok()
82 .map(|s| s.replace('\0', " ").trim().to_string())
83 }
84
85 pub fn get_child_cmdline(&self, pid: u32) -> Option<String> {
87 let cache_key = pid + CHILD_CACHE_OFFSET;
89 {
90 let cache = self.cache.read();
91 if let Some(info) = cache.get(&cache_key) {
92 if info.last_update.elapsed() < self.ttl {
93 return Some(info.cmdline.clone());
94 }
95 }
96 }
97
98 let children_path = format!("/proc/{}/task/{}/children", pid, pid);
100 let children = std::fs::read_to_string(&children_path).ok()?;
101
102 let child_pid: u32 = children.split_whitespace().next()?.parse().ok()?;
104 let cmdline = self.read_cmdline(child_pid)?;
105
106 {
108 let mut cache = self.cache.write();
109 cache.insert(
110 cache_key,
111 ProcessInfo {
112 cmdline: cmdline.clone(),
113 last_update: Instant::now(),
114 },
115 );
116 }
117
118 Some(cmdline)
119 }
120
121 pub fn cleanup(&self) {
123 let mut cache = self.cache.write();
124 cache.retain(|_, info| info.last_update.elapsed() < self.ttl);
125 }
126
127 pub fn get_env_var(&self, pid: u32, var_name: &str) -> Option<String> {
132 let _cache_key = pid + ENV_CACHE_OFFSET;
133
134 let environ_path = format!("/proc/{}/environ", pid);
136 let content = std::fs::read(&environ_path).ok()?;
137
138 let prefix = format!("{}=", var_name);
139
140 for entry in content.split(|&b| b == 0) {
142 if let Ok(entry_str) = std::str::from_utf8(entry) {
143 if let Some(value) = entry_str.strip_prefix(&prefix) {
144 return Some(value.to_string());
145 }
146 }
147 }
148
149 None
150 }
151
152 pub fn clear(&self) {
154 let mut cache = self.cache.write();
155 cache.clear();
156 }
157
158 pub fn len(&self) -> usize {
160 self.cache.read().len()
161 }
162
163 pub fn is_empty(&self) -> bool {
165 self.cache.read().is_empty()
166 }
167}
168
169impl Default for ProcessCache {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_cache_creation() {
181 let cache = ProcessCache::new();
182 assert!(cache.is_empty());
183 }
184
185 #[test]
186 fn test_cache_with_ttl() {
187 let cache = ProcessCache::with_ttl(Duration::from_secs(10));
188 assert!(cache.is_empty());
189 }
190
191 #[test]
192 fn test_cache_clear() {
193 let cache = ProcessCache::new();
194 cache.clear();
196 assert!(cache.is_empty());
197 }
198}