Skip to main content

ic_host_process/observation/
mod.rs

1//! Raw native process observations imported from IC Profiler 70907ff.
2//!
3//! Consumers own workload selection, cadence, PID-identity admission, aggregation
4//! and interpretation of unavailable counters. These observations are not atomic.
5
6mod 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/// One observed process; this is not proof of ownership or a live process handle.
16#[derive(Clone, Debug, Default)]
17pub struct ProcessObservation {
18    /// Observed process identifier.
19    pub pid: u32,
20    /// Observed parent identifier.
21    pub ppid: u32,
22    /// Opaque creation identity: Linux start ticks or Darwin start microseconds. Compare only on the same host.
23    pub start_ticks: u64,
24    /// Observed process name.
25    pub name: String,
26    /// Process user CPU nanoseconds.
27    pub user_ns: u64,
28    /// Process system CPU nanoseconds.
29    pub system_ns: u64,
30    /// Linux waited-child user CPU nanoseconds; zero on Darwin.
31    pub waited_user_ns: u64,
32    /// Linux waited-child system CPU nanoseconds; zero on Darwin.
33    pub waited_system_ns: u64,
34    /// Resident bytes; shared pages can occur in multiple processes.
35    pub rss_bytes: u64,
36    /// Linux proportional-set bytes, when requested and available.
37    pub pss_bytes: Option<u64>,
38    /// Linux storage read bytes, when available.
39    pub read_bytes: Option<u64>,
40    /// Linux storage write bytes, when available.
41    pub write_bytes: Option<u64>,
42    /// Linux process and waited-child minor faults; zero on Darwin.
43    pub minor_faults: u64,
44    /// Linux process and waited-child major faults; zero on Darwin.
45    pub major_faults: u64,
46    /// Observed thread count.
47    pub threads: u64,
48}
49
50/// Observe base CPU/RSS/identity facts without Linux I/O or PSS reads.
51#[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/// Read the native opaque creation identity, or None if unavailable.
60#[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/// Observe a process, including Linux I/O and explicitly requested PSS.
69#[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/// Native child-list evidence. It does not select or adopt workload descendants.
85#[derive(Debug, Default)]
86pub struct ChildProcesses {
87    /// Observed child process IDs.
88    pub pids: Vec<u32>,
89    /// The interface or all thread lists were unavailable.
90    pub unavailable: bool,
91    /// A thread list was unreadable or malformed.
92    pub incomplete: bool,
93    /// The Darwin caller-selected PID capacity was reached.
94    pub truncated: bool,
95}
96
97/// Observe native child IDs without changing process ownership.
98/// Darwin's capacity is caller-selected; Linux inspects every task's children file.
99#[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/// Observe the Linux scheduler-affinity CPU count for a PID.
111/// Returns None on another host or native query failure; fallback policy is caller-owned.
112#[must_use]
113#[cfg_attr(
114    not(target_os = "linux"),
115    allow(
116        clippy::missing_const_for_fn,
117        reason = "the Linux implementation queries live scheduler state and cannot be const"
118    )
119)]
120#[allow(
121    unsafe_code,
122    reason = "reviewed initialized sched_getaffinity output imported from Profiler"
123)]
124pub fn affinity_cpus(pid: u32) -> Option<u32> {
125    #[cfg(target_os = "linux")]
126    {
127        let pid = libc::pid_t::try_from(pid).ok()?;
128        // SAFETY: cpu_set_t is a plain-data bitset; all-zero initialization is valid.
129        let mut set: libc::cpu_set_t = unsafe { std::mem::zeroed() };
130        // SAFETY: correct writable output buffer and ABI size; pointer is not retained.
131        if unsafe { libc::sched_getaffinity(pid, std::mem::size_of_val(&set), &raw mut set) } != 0 {
132            return None;
133        }
134        // SAFETY: reads the complete initialized bitset after a successful query.
135        u32::try_from(unsafe { libc::CPU_COUNT(&set) }).ok()
136    }
137    #[cfg(not(target_os = "linux"))]
138    {
139        let _ = pid;
140        None
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    #[test]
147    #[cfg(unix)]
148    fn native_identity_clocks_and_process_facts_are_available() {
149        let pid = std::process::id();
150        let before = super::monotonic_ns();
151        let observation = super::process(pid, true).unwrap();
152        assert_eq!(observation.pid, pid);
153        assert_ne!(observation.rss_bytes, 0);
154        assert_eq!(super::process_identity(pid), Some(observation.start_ticks));
155        assert!(super::monotonic_ns() >= before);
156        assert_ne!(super::thread_cpu_ns(), 0);
157        assert_ne!(super::tid(), 0);
158        #[cfg(target_os = "macos")]
159        assert!(observation.pss_bytes.is_none() && observation.read_bytes.is_none());
160    }
161}