ic_host_process/observation/procfs/
mod.rs1#![allow(
3 unsafe_code,
4 reason = "reviewed sysconf observations imported from Profiler"
5)]
6use super::{ChildProcesses, ProcessObservation};
7use std::{collections::BTreeSet, fs, path::Path, sync::OnceLock};
8
9fn ticks_per_second() -> u64 {
10 static VALUE: OnceLock<u64> = OnceLock::new();
11 *VALUE.get_or_init(|| {
12 #[cfg(unix)]
13 return positive_sysconf(libc::_SC_CLK_TCK, 100);
14 #[cfg(not(unix))]
15 100
16 })
17}
18
19fn page_size() -> u64 {
20 static VALUE: OnceLock<u64> = OnceLock::new();
21 *VALUE.get_or_init(|| {
22 #[cfg(unix)]
23 return positive_sysconf(libc::_SC_PAGESIZE, 4096);
24 #[cfg(not(unix))]
25 4096
26 })
27}
28
29#[cfg(unix)]
30fn positive_sysconf(name: libc::c_int, fallback: u64) -> u64 {
31 let value = unsafe { libc::sysconf(name) };
33 if value > 0 {
34 u64::try_from(value).ok().unwrap_or_default()
35 } else {
36 fallback
37 }
38}
39
40#[must_use]
42pub fn process_identity(proc_root: &Path, pid: u32) -> Option<u64> {
43 let stat = fs::read_to_string(proc_root.join(pid.to_string()).join("stat")).ok()?;
44 let (_, fields) = stat_parts(&stat)?;
45 let mut fields = fields.split_whitespace();
46 fields.next()?; fields.next()?.parse::<u64>().ok()?; fields.nth(17)?.parse().ok() }
50
51#[must_use]
53pub fn basic_process(proc_root: &Path, pid: u32) -> Option<ProcessObservation> {
54 let stat = fs::read_to_string(proc_root.join(pid.to_string()).join("stat")).ok()?;
55 let (name, fields) = parse_stat(&stat)?;
56 let number = |index: usize| fields.get(index)?.parse::<u64>().ok();
57 let ns = |index| number(index).unwrap_or(0).saturating_mul(1_000_000_000) / ticks_per_second();
58 Some(ProcessObservation {
59 pid,
60 ppid: u32::try_from(number(1)?).ok()?,
61 start_ticks: number(19)?,
62 name: name.to_owned(),
63 user_ns: ns(11),
64 system_ns: ns(12),
65 waited_user_ns: ns(13),
66 waited_system_ns: ns(14),
67 rss_bytes: number(21).unwrap_or(0).saturating_mul(page_size()),
68 pss_bytes: None,
69 read_bytes: None,
70 write_bytes: None,
71 minor_faults: number(7)
72 .unwrap_or(0)
73 .saturating_add(number(8).unwrap_or(0)),
74 major_faults: number(9)
75 .unwrap_or(0)
76 .saturating_add(number(10).unwrap_or(0)),
77 threads: number(17).unwrap_or(0),
78 })
79}
80
81fn stat_parts(stat: &str) -> Option<(&str, &str)> {
82 let begin = stat.find('(')?;
83 let end = stat.rfind(')')?;
84 Some((stat.get(begin + 1..end)?, stat.get(end + 1..)?))
85}
86
87fn parse_stat(stat: &str) -> Option<(&str, [&str; 22])> {
88 let (name, fields) = stat_parts(stat)?;
89 let mut fields = fields.split_whitespace();
90 Some((name, std::array::from_fn(|_| fields.next().unwrap_or(""))))
93}
94
95fn field_value(text: &str, name: &str) -> Option<u64> {
96 text.lines()
97 .filter_map(|line| {
98 let mut fields = line.split_whitespace();
99 if fields.next()?.trim_end_matches(':') != name {
100 return None;
101 }
102 fields.next()?.parse().ok()
103 })
104 .next_back()
105}
106
107pub fn read_details(proc_root: &Path, result: &mut ProcessObservation, memory_detail: bool) {
110 let io = fs::read_to_string(proc_root.join(result.pid.to_string()).join("io")).ok();
111 result.read_bytes = io
112 .as_deref()
113 .and_then(|text| field_value(text, "read_bytes"));
114 result.write_bytes = io
115 .as_deref()
116 .and_then(|text| field_value(text, "write_bytes"));
117 if memory_detail {
118 result.pss_bytes =
119 fs::read_to_string(proc_root.join(result.pid.to_string()).join("smaps_rollup"))
120 .ok()
121 .and_then(|text| field_value(&text, "Pss"))
122 .map(|kb| kb.saturating_mul(1024));
123 }
124}
125#[must_use]
127pub fn children(proc_root: &Path, pid: u32) -> ChildProcesses {
128 let mut result = ChildProcesses::default();
129 let tasks = proc_root.join(pid.to_string()).join("task");
133 let Ok(entries) = fs::read_dir(&tasks) else {
134 result.unavailable = true;
135 return result;
136 };
137 let mut children = BTreeSet::new();
138 let mut saw_task = false;
139 for entry in entries {
140 let Ok(entry) = entry else {
141 result.incomplete = true;
142 continue;
143 };
144 if entry
145 .file_name()
146 .to_str()
147 .and_then(|s| s.parse::<u32>().ok())
148 .is_none()
149 {
150 continue;
151 }
152 saw_task = true;
153 match fs::read_to_string(entry.path().join("children")) {
154 Ok(value) => {
155 for child in value.split_whitespace() {
156 if let Ok(child) = child.parse::<u32>() {
157 children.insert(child);
158 } else {
159 result.incomplete = true;
160 }
161 }
162 }
163 Err(_) => {
164 result.incomplete = true;
167 }
168 }
169 }
170 if !saw_task {
171 result.unavailable = true;
172 }
173 result.pids = children.into_iter().collect();
174 result
175}
176
177#[cfg(all(test, unix))]
178mod tests;