Skip to main content

joule_profiler_source_procfs/
counters.rs

1use crate::snapshot::{GlobalSnapshot, ProcSnapshot};
2
3/// A min/max accumulator for a single metric.
4#[derive(Debug, Clone, Copy, Default)]
5pub struct MinMax(pub Option<u64>, Option<u64>);
6
7impl MinMax {
8    /// Updates the min/max bounds with the provided value.
9    pub fn update(&mut self, value: u64) {
10        self.0 = Some(self.0.map_or(value, |m| m.min(value)));
11        self.1 = Some(self.1.map_or(value, |m| m.max(value)));
12    }
13
14    pub fn reset(&mut self) {
15        self.0 = None;
16        self.1 = None;
17    }
18
19    pub fn min(&self) -> Option<u64> {
20        self.0
21    }
22
23    pub fn max(&self) -> Option<u64> {
24        self.1
25    }
26}
27
28/// Accumulated memory and I/O counters for a process hierarchy over a phase.
29///
30/// Memory fields track min/max over all snapshots in the phase.
31/// I/O fields track cumulative byte counts: begin is set at phase start.
32/// All metrics are in bytes.
33#[derive(Debug, Default, Clone, Copy)]
34pub struct ProcCounters {
35    /// Virtual memory size.
36    pub vm_size: MinMax,
37
38    /// Resident set size.
39    pub rss: MinMax,
40
41    /// Proportional set size.
42    pub pss: MinMax,
43
44    /// Shared memory, clean + dirty.
45    pub shared: MinMax,
46
47    /// Anonymous memory.
48    pub anon: MinMax,
49
50    /// Cumulative bytes read at the beginning of a phase.
51    pub begin_read_bytes: u64,
52
53    /// Cumulative bytes written at the beginning of a phase.
54    pub begin_write_bytes: u64,
55
56    /// Highest cumulative bytes read observed during this phase.
57    pub end_read_bytes: u64,
58
59    /// Highest cumulative bytes written observed during this phase.
60    pub end_write_bytes: u64,
61}
62
63impl ProcCounters {
64    /// Merges a [`ProcSnapshot`] into the counters.
65    ///
66    /// I/O fields take the max of the current end and the snapshot value,
67    /// since `/proc/{pid}/io` counters are monotonically increasing.
68    pub fn update(&mut self, snapshot: &ProcSnapshot) {
69        self.vm_size.update(snapshot.vm_size);
70        self.rss.update(snapshot.rss);
71        self.pss.update(snapshot.pss);
72        self.shared.update(snapshot.shared);
73        self.anon.update(snapshot.anon);
74        self.end_read_bytes = self.end_read_bytes.max(snapshot.read_bytes);
75        self.end_write_bytes = self.end_write_bytes.max(snapshot.write_bytes);
76    }
77
78    /// Resets memory min/max for the next phase and carries I/O end values forward as the new begin.
79    pub fn reset(&mut self) {
80        self.vm_size.reset();
81        self.rss.reset();
82        self.pss.reset();
83        self.shared.reset();
84        self.anon.reset();
85        self.begin_read_bytes = self.end_read_bytes;
86        self.begin_write_bytes = self.end_write_bytes;
87    }
88}
89
90/// Computes used memory as `MemTotal - MemAvailable`.
91///
92/// If `MemAvailable` is present in `/proc/meminfo`, it is used directly (preferred).
93/// Otherwise, falls back to `MemTotal - (MemFree + Cached)`, which is less accurate
94/// but universally available.
95///
96/// Note: min/max are inverted relative to `available` since higher availability
97/// means lower usage.
98pub fn compute_mem_used(
99    mem_total: u64,
100    mem_available: Option<MinMax>,
101    mem_free: MinMax,
102    cached: MinMax,
103) -> MinMax {
104    if let Some(available) = mem_available {
105        MinMax(
106            Some(mem_total.saturating_sub(available.max().unwrap_or_default())),
107            Some(mem_total.saturating_sub(available.min().unwrap_or_default())),
108        )
109    } else {
110        MinMax(
111            Some(mem_total.saturating_sub(
112                mem_free.max().unwrap_or_default() + cached.max().unwrap_or_default(),
113            )),
114            Some(mem_total.saturating_sub(
115                mem_free.min().unwrap_or_default() + cached.min().unwrap_or_default(),
116            )),
117        )
118    }
119}
120
121/// Accumulated system-wide memory counters over a phase.
122///
123/// `mem_available` and `anon` are `Option` because they may not be present
124/// in `/proc/meminfo` on all kernel configurations. They are initialized
125/// lazily on the first snapshot that contains them.
126#[derive(Debug, Default, Clone, Copy)]
127pub struct GlobalCounters {
128    /// Available memory (`MemAvailable`). None if not exposed by the kernel.
129    pub mem_available: Option<MinMax>,
130
131    /// Free memory (`MemFree`).
132    pub mem_free: MinMax,
133
134    /// Page cache (`Cached`).
135    pub cached: MinMax,
136
137    /// Anonymous pages (`AnonPages`). None if not exposed by the kernel.
138    pub anon: Option<MinMax>,
139
140    /// Free swap (`SwapFree`).
141    pub swap_free: MinMax,
142}
143
144impl GlobalCounters {
145    /// Merges a [`GlobalSnapshot`] into the counters.
146    pub fn update(&mut self, snapshot: &GlobalSnapshot) {
147        self.mem_free.update(snapshot.mem_free);
148        self.cached.update(snapshot.cached);
149        self.swap_free.update(snapshot.swap_free);
150        if let Some(v) = snapshot.mem_available {
151            self.mem_available.get_or_insert_default().update(v);
152        }
153        if let Some(v) = snapshot.anon {
154            self.anon.get_or_insert_default().update(v);
155        }
156    }
157
158    /// Resets all counters for the next phase.
159    pub fn reset(&mut self) {
160        self.mem_free.reset();
161        self.cached.reset();
162        self.swap_free.reset();
163        if let Some(v) = &mut self.mem_available {
164            v.reset();
165        }
166        if let Some(v) = &mut self.anon {
167            v.reset();
168        }
169    }
170}
171
172#[derive(Debug, Default, Clone, Copy)]
173pub struct Counters {
174    pub proc: ProcCounters,
175    pub global: GlobalCounters,
176}
177
178impl Counters {
179    pub fn update(&mut self, proc: &ProcSnapshot, global: &GlobalSnapshot) {
180        self.proc.update(proc);
181        self.global.update(global);
182    }
183
184    pub fn reset(&mut self) {
185        self.proc.reset();
186        self.global.reset();
187    }
188}