Skip to main content

aios_collector/
proc_reader.rs

1//! `/proc` 文件系统读取器
2//!
3//! "how" — 如何从 Linux /proc 获取进程级资源信息。
4//!
5//! 读取:
6//! - `/proc/[pid]/stat` — 进程状态、线程数
7//! - `/proc/[pid]/status` — VmRSS, VmSwap
8//! - `/proc/[pid]/oom_score` — LMK 打分
9//! - `/proc/[pid]/io` — 磁盘 I/O 累计
10//! - `/proc/[pid]/cmdline` — 进程命令行 (用于推断包名)
11
12use aios_spec::{ProcState, ProcStateEvent};
13use std::collections::HashMap;
14use std::fs;
15use std::path::Path;
16
17/// `/proc` 读取器
18pub struct ProcReader;
19
20/// 一次 /proc 轮询的结果
21#[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    /// 扫描所有进程, 返回当前快照列表
37    ///
38    /// 遍历 `/proc/[0-9]*/` 目录, 读取每个进程的状态。
39    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            // 只处理数字目录 (PID)
53            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    /// 读取单个进程的信息
67    fn read_process(pid: u32) -> Option<ProcSnapshot> {
68        let base = format!("/proc/{}", pid);
69
70        // 读取 stat — 第3字段为状态, 第20字段为线程数
71        let (state, threads) = Self::read_stat(&base)?;
72
73        // 读取 VmRSS, VmSwap, Uid
74        let (uid, vm_rss_kb, vm_swap_kb) = Self::read_status(&base)?;
75
76        // 读取 OOM score
77        let oom_score = Self::read_oom_score(&base).unwrap_or(0);
78
79        // 读取 I/O
80        let (io_read_mb, io_write_mb) = Self::read_io(&base).unwrap_or((0, 0));
81
82        // 读取 cmdline (包名推断)
83        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        // /proc/[pid]/stat 格式: pid (comm) state ... threads ...
102        // 第3字段是 state, 需要跳过 comm 中的括号
103        let comm_end = content.rfind(')')?;
104        let rest = &content[comm_end + 2..]; // skip ") "
105        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        // state 已消耗, 其后的 16 个字段: ppid..nice
116        // num_threads 是剩余部分的第 17 个元素 (0-indexed: 16)
117        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                // Format: "Uid:\t1000\t1000\t1000\t1000"
132                uid = line.split_whitespace().nth(1)?.parse().ok()?;
133            } else if line.starts_with("VmRSS:") {
134                // Format: "VmRSS:\t  123456 kB"
135                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        // 转换为 MB
165        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        // cmdline 是 null-separated, 取第一个参数
171        let first_arg = content.split('\0').next()?;
172        if first_arg.is_empty() {
173            return None;
174        }
175
176        // 尝试从命令行中提取 Android 包名
177        // 格式通常为 "com.example.app" 或 "/system/bin/surfaceflinger"
178        if first_arg.contains('.') && !first_arg.starts_with('/') {
179            Some(first_arg.to_string())
180        } else {
181            // 对于 native 进程, 返回进程名
182            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    /// 将 ProcSnapshot 转换为 aios-spec 的 ProcStateEvent
190    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
207/// 计算两次快照之间的增量变化
208pub fn diff_snapshots(
209    prev: &HashMap<u32, ProcSnapshot>,
210    curr: &HashMap<u32, ProcSnapshot>,
211) -> Vec<ProcSnapshot> {
212    // 对于新出现的或者状态变化的进程, 返回当前快照
213    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, // 新进程
224            }
225        })
226        .cloned()
227        .collect()
228}