1use crate::model::MetricState;
9use crate::units::{Percent, Rate};
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
15pub enum MemorySemantics {
16 LinuxMemAvailable,
20 MacosVmStatistics,
24 SysinfoBaseline,
27}
28
29impl MemorySemantics {
30 #[must_use]
32 pub const fn description(self) -> &'static str {
33 match self {
34 Self::LinuxMemAvailable => {
35 "used = total - MemAvailable; page cache and buffers are not counted as \
36 application use"
37 }
38 Self::MacosVmStatistics => {
39 "available = free + inactive + purgeable; wired and compressed pages are \
40 reported separately and are not reclaimable like Linux page cache"
41 }
42 Self::SysinfoBaseline => {
43 "cross-platform baseline; coarser than the native definition and not \
44 byte-for-byte comparable with it"
45 }
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58pub struct MemoryDetail {
59 pub cached: MetricState<u64>,
61 pub buffers: MetricState<u64>,
63 pub shared: MetricState<u64>,
65 pub active: MetricState<u64>,
67 pub inactive: MetricState<u64>,
69 pub wired: MetricState<u64>,
71 pub compressed: MetricState<u64>,
73 pub dirty: MetricState<u64>,
75}
76
77impl MemoryDetail {
78 pub const WARMING_UP: Self = Self {
80 cached: MetricState::WarmingUp,
81 buffers: MetricState::WarmingUp,
82 shared: MetricState::WarmingUp,
83 active: MetricState::WarmingUp,
84 inactive: MetricState::WarmingUp,
85 wired: MetricState::WarmingUp,
86 compressed: MetricState::WarmingUp,
87 dirty: MetricState::WarmingUp,
88 };
89}
90
91#[derive(Clone, Copy, Debug, PartialEq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize))]
98pub struct SwapSnapshot {
99 pub total_bytes: u64,
102 pub used: MetricState<u64>,
104 pub usage: MetricState<Percent>,
106 pub in_rate: MetricState<Rate>,
108 pub out_rate: MetricState<Rate>,
110}
111
112impl SwapSnapshot {
113 #[must_use]
115 pub const fn is_enabled(&self) -> bool {
116 self.total_bytes > 0
117 }
118
119 #[must_use]
121 pub const fn disabled() -> Self {
122 Self {
123 total_bytes: 0,
124 used: MetricState::Available(0),
125 usage: MetricState::Unsupported,
126 in_rate: MetricState::Unsupported,
127 out_rate: MetricState::Unsupported,
128 }
129 }
130}
131
132#[derive(Clone, Copy, Debug, PartialEq)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize))]
135pub struct MemorySnapshot {
136 pub total_bytes: u64,
138 pub available: MetricState<u64>,
140 pub used: MetricState<u64>,
142 pub free: MetricState<u64>,
144 pub usage: MetricState<Percent>,
146 pub detail: MemoryDetail,
148 pub swap: SwapSnapshot,
150 pub semantics: MemorySemantics,
152 pub cgroup_limit_bytes: MetricState<u64>,
158}
159
160impl MemorySnapshot {
161 #[must_use]
163 pub const fn warming_up(total_bytes: u64, semantics: MemorySemantics) -> Self {
164 Self {
165 total_bytes,
166 available: MetricState::WarmingUp,
167 used: MetricState::WarmingUp,
168 free: MetricState::WarmingUp,
169 usage: MetricState::WarmingUp,
170 detail: MemoryDetail::WARMING_UP,
171 swap: SwapSnapshot {
172 total_bytes: 0,
173 used: MetricState::WarmingUp,
174 usage: MetricState::WarmingUp,
175 in_rate: MetricState::WarmingUp,
176 out_rate: MetricState::WarmingUp,
177 },
178 semantics,
179 cgroup_limit_bytes: MetricState::WarmingUp,
180 }
181 }
182
183 #[must_use]
189 pub fn effective_limit_bytes(&self) -> u64 {
190 match self.cgroup_limit_bytes.fresh() {
191 Some(&limit) if limit > 0 && limit < self.total_bytes => limit,
192 _ => self.total_bytes,
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn each_platform_semantics_explains_itself() {
203 for semantics in [
204 MemorySemantics::LinuxMemAvailable,
205 MemorySemantics::MacosVmStatistics,
206 MemorySemantics::SysinfoBaseline,
207 ] {
208 assert!(!semantics.description().is_empty());
209 }
210 assert!(
212 MemorySemantics::LinuxMemAvailable
213 .description()
214 .contains("page cache")
215 );
216 }
217
218 #[test]
219 fn disabled_swap_is_a_fact_not_an_unavailable_metric() {
220 let swap = SwapSnapshot::disabled();
221 assert!(!swap.is_enabled());
222 assert_eq!(
223 swap.used.fresh(),
224 Some(&0),
225 "0 of 0 bytes used is a real measurement"
226 );
227 assert!(swap.usage.fresh().is_none());
229 }
230
231 #[test]
232 fn a_cgroup_limit_below_the_host_total_becomes_the_effective_ceiling() {
233 let mut memory =
234 MemorySnapshot::warming_up(32 * 1024 * 1024 * 1024, MemorySemantics::LinuxMemAvailable);
235 assert_eq!(memory.effective_limit_bytes(), 32 * 1024 * 1024 * 1024);
236
237 memory.cgroup_limit_bytes = MetricState::Available(2 * 1024 * 1024 * 1024);
238 assert_eq!(memory.effective_limit_bytes(), 2 * 1024 * 1024 * 1024);
239 }
240
241 #[test]
242 fn an_unlimited_cgroup_does_not_shrink_the_ceiling() {
243 let mut memory =
244 MemorySnapshot::warming_up(32 * 1024 * 1024 * 1024, MemorySemantics::LinuxMemAvailable);
245 memory.cgroup_limit_bytes = MetricState::Available(u64::MAX);
247 assert_eq!(memory.effective_limit_bytes(), 32 * 1024 * 1024 * 1024);
248 memory.cgroup_limit_bytes = MetricState::Available(0);
249 assert_eq!(memory.effective_limit_bytes(), 32 * 1024 * 1024 * 1024);
250 }
251
252 #[test]
253 fn warming_up_preserves_the_requested_semantics() {
254 let memory = MemorySnapshot::warming_up(1024, MemorySemantics::MacosVmStatistics);
255 assert_eq!(memory.semantics, MemorySemantics::MacosVmStatistics);
256 assert_eq!(memory.total_bytes, 1024);
257 assert!(memory.available.is_warming_up());
258 }
259}