ic_host_process/observation/
mod.rs1mod clock;
7#[cfg(target_os = "macos")]
8mod macos;
9pub mod procfs;
10
11pub use clock::{monotonic_ns, thread_cpu_ns, tid};
12#[cfg(not(target_os = "macos"))]
13use std::path::Path;
14
15#[derive(Clone, Debug, Default)]
17pub struct ProcessObservation {
18 pub pid: u32,
20 pub ppid: u32,
22 pub start_ticks: u64,
24 pub name: String,
26 pub user_ns: u64,
28 pub system_ns: u64,
30 pub waited_user_ns: u64,
32 pub waited_system_ns: u64,
34 pub rss_bytes: u64,
36 pub pss_bytes: Option<u64>,
38 pub read_bytes: Option<u64>,
40 pub write_bytes: Option<u64>,
42 pub minor_faults: u64,
44 pub major_faults: u64,
46 pub threads: u64,
48}
49
50#[must_use]
52pub fn basic_process(pid: u32) -> Option<ProcessObservation> {
53 #[cfg(target_os = "macos")]
54 return macos::process(pid);
55 #[cfg(not(target_os = "macos"))]
56 procfs::basic_process(Path::new("/proc"), pid)
57}
58
59#[must_use]
61pub fn process_identity(pid: u32) -> Option<u64> {
62 #[cfg(target_os = "macos")]
63 return macos::identity(pid);
64 #[cfg(not(target_os = "macos"))]
65 procfs::process_identity(Path::new("/proc"), pid)
66}
67
68#[must_use]
70pub fn process(pid: u32, memory_detail: bool) -> Option<ProcessObservation> {
71 #[cfg(target_os = "macos")]
72 {
73 let _ = memory_detail;
74 basic_process(pid)
75 }
76 #[cfg(not(target_os = "macos"))]
77 {
78 let mut result = basic_process(pid)?;
79 procfs::read_details(Path::new("/proc"), &mut result, memory_detail);
80 Some(result)
81 }
82}
83
84#[derive(Debug, Default)]
86pub struct ChildProcesses {
87 pub pids: Vec<u32>,
89 pub unavailable: bool,
91 pub incomplete: bool,
93 pub truncated: bool,
95}
96
97#[must_use]
100pub fn children(pid: u32, capacity: usize) -> ChildProcesses {
101 #[cfg(target_os = "macos")]
102 return macos::children(pid, capacity);
103 #[cfg(not(target_os = "macos"))]
104 {
105 let _ = capacity;
106 procfs::children(Path::new("/proc"), pid)
107 }
108}
109
110#[must_use]
113#[cfg_attr(
114 not(target_os = "linux"),
115 expect(
116 clippy::missing_const_for_fn,
117 reason = "the Linux implementation queries live scheduler state and cannot be const"
118 )
119)]
120pub fn affinity_cpus(pid: u32) -> Option<u32> {
121 #[cfg(target_os = "linux")]
122 {
123 let pid = rustix::thread::Pid::from_raw(i32::try_from(pid).ok()?);
125 rustix::thread::sched_getaffinity(pid)
126 .ok()
127 .map(|set| set.count())
128 }
129 #[cfg(not(target_os = "linux"))]
130 {
131 let _ = pid;
132 None
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 #[test]
139 #[cfg(target_os = "linux")]
140 fn affinity_preserves_current_thread_selection_and_invalid_pid_refusal() {
141 let current = super::affinity_cpus(0).unwrap();
142 assert!(current > 0);
143 assert_eq!(
144 super::affinity_cpus(u32::try_from(super::tid()).unwrap()),
145 Some(current)
146 );
147 assert_eq!(super::affinity_cpus(u32::MAX), None);
148 }
149
150 #[test]
151 #[cfg(unix)]
152 fn native_identity_clocks_and_process_facts_are_available() {
153 let pid = std::process::id();
154 let before = super::monotonic_ns();
155 let observation = super::process(pid, true).unwrap();
156 assert_eq!(observation.pid, pid);
157 assert_ne!(observation.rss_bytes, 0);
158 assert_eq!(super::process_identity(pid), Some(observation.start_ticks));
159 assert!(super::monotonic_ns() >= before);
160 assert_ne!(super::thread_cpu_ns(), 0);
161 assert_ne!(super::tid(), 0);
162 #[cfg(target_os = "macos")]
163 assert!(observation.pss_bytes.is_none() && observation.read_bytes.is_none());
164 }
165}