aios_collector/
proc_reader.rs1use aios_spec::{ProcState, ProcStateEvent};
13use std::collections::HashMap;
14use std::fs;
15use std::path::Path;
16
17pub struct ProcReader;
19
20#[derive(Debug, Clone)]
22pub struct ProcSnapshot {
23 pub pid: u32,
24 pub uid: u32,
25 pub package_name: Option<String>,
26 pub vm_rss_kb: u64,
27 pub vm_swap_kb: u64,
28 pub threads: u32,
29 pub oom_score: i32,
30 pub io_read_mb: u64,
31 pub io_write_mb: u64,
32 pub state: ProcState,
33}
34
35impl ProcReader {
36 pub fn scan_all() -> Vec<ProcSnapshot> {
40 let mut snapshots = Vec::new();
41 let proc_dir = Path::new("/proc");
42
43 let entries = match fs::read_dir(proc_dir) {
44 Ok(e) => e,
45 Err(_) => return snapshots,
46 };
47
48 for entry in entries.flatten() {
49 let name = entry.file_name();
50 let name_str = name.to_string_lossy();
51
52 let pid: u32 = match name_str.parse() {
54 Ok(p) => p,
55 Err(_) => continue,
56 };
57
58 if let Some(snap) = Self::read_process(pid) {
59 snapshots.push(snap);
60 }
61 }
62
63 snapshots
64 }
65
66 fn read_process(pid: u32) -> Option<ProcSnapshot> {
68 let base = format!("/proc/{}", pid);
69
70 let (state, threads) = Self::read_stat(&base)?;
72
73 let (uid, vm_rss_kb, vm_swap_kb) = Self::read_status(&base)?;
75
76 let oom_score = Self::read_oom_score(&base).unwrap_or(0);
78
79 let (io_read_mb, io_write_mb) = Self::read_io(&base).unwrap_or((0, 0));
81
82 let package_name = Self::read_cmdline(&base);
84
85 Some(ProcSnapshot {
86 pid,
87 uid,
88 package_name,
89 vm_rss_kb,
90 vm_swap_kb,
91 threads,
92 oom_score,
93 io_read_mb,
94 io_write_mb,
95 state,
96 })
97 }
98
99 fn read_stat(base: &str) -> Option<(ProcState, u32)> {
100 let content = fs::read_to_string(format!("{}/stat", base)).ok()?;
101 let comm_end = content.rfind(')')?;
104 let rest = &content[comm_end + 2..]; let mut parts = rest.split_whitespace();
106
107 let state_char = parts.next()?;
108 let state = match state_char {
109 "R" => ProcState::Running,
110 "S" | "D" | "I" => ProcState::Sleeping,
111 "Z" | "X" => ProcState::Zombie,
112 _ => ProcState::Unknown,
113 };
114
115 let threads_str = parts.nth(16)?;
118 let threads: u32 = threads_str.parse().ok()?;
119
120 Some((state, threads))
121 }
122
123 fn read_status(base: &str) -> Option<(u32, u64, u64)> {
124 let content = fs::read_to_string(format!("{}/status", base)).ok()?;
125 let mut uid = 0u32;
126 let mut vm_rss = 0u64;
127 let mut vm_swap = 0u64;
128
129 for line in content.lines() {
130 if line.starts_with("Uid:") {
131 uid = line.split_whitespace().nth(1)?.parse().ok()?;
133 } else if line.starts_with("VmRSS:") {
134 let val: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
136 vm_rss = val;
137 } else if line.starts_with("VmSwap:") {
138 let val: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
139 vm_swap = val;
140 }
141 }
142
143 Some((uid, vm_rss, vm_swap))
144 }
145
146 fn read_oom_score(base: &str) -> Option<i32> {
147 let content = fs::read_to_string(format!("{}/oom_score", base)).ok()?;
148 content.trim().parse().ok()
149 }
150
151 fn read_io(base: &str) -> Option<(u64, u64)> {
152 let content = fs::read_to_string(format!("{}/io", base)).ok()?;
153 let mut read_bytes = 0u64;
154 let mut write_bytes = 0u64;
155
156 for line in content.lines() {
157 if line.starts_with("read_bytes:") {
158 read_bytes = line.split_whitespace().nth(1)?.parse().ok()?;
159 } else if line.starts_with("write_bytes:") {
160 write_bytes = line.split_whitespace().nth(1)?.parse().ok()?;
161 }
162 }
163
164 Some((read_bytes / (1024 * 1024), write_bytes / (1024 * 1024)))
166 }
167
168 fn read_cmdline(base: &str) -> Option<String> {
169 let content = fs::read_to_string(format!("{}/cmdline", base)).ok()?;
170 let first_arg = content.split('\0').next()?;
172 if first_arg.is_empty() {
173 return None;
174 }
175
176 if first_arg.contains('.') && !first_arg.starts_with('/') {
179 Some(first_arg.to_string())
180 } else {
181 let name = std::path::Path::new(first_arg)
183 .file_name()
184 .map(|n| n.to_string_lossy().to_string());
185 name
186 }
187 }
188
189 pub fn to_event(snapshot: &ProcSnapshot, timestamp_ms: i64) -> ProcStateEvent {
191 ProcStateEvent {
192 timestamp_ms,
193 pid: snapshot.pid,
194 uid: snapshot.uid,
195 package_name: snapshot.package_name.clone(),
196 vm_rss_kb: snapshot.vm_rss_kb,
197 vm_swap_kb: snapshot.vm_swap_kb,
198 threads: snapshot.threads,
199 oom_score: snapshot.oom_score,
200 io_read_mb: snapshot.io_read_mb,
201 io_write_mb: snapshot.io_write_mb,
202 state: snapshot.state.clone(),
203 }
204 }
205}
206
207pub fn diff_snapshots(
209 prev: &HashMap<u32, ProcSnapshot>,
210 curr: &HashMap<u32, ProcSnapshot>,
211) -> Vec<ProcSnapshot> {
212 curr.values()
214 .filter(|c| {
215 match prev.get(&c.pid) {
216 Some(p) => {
217 p.vm_rss_kb != c.vm_rss_kb
218 || p.vm_swap_kb != c.vm_swap_kb
219 || p.oom_score != c.oom_score
220 || p.threads != c.threads
221 || p.state != c.state
222 },
223 None => true, }
225 })
226 .cloned()
227 .collect()
228}