resource_tracker/collector/
memory.rs1use crate::metrics::MemoryMetrics;
2use procfs::Meminfo;
3use procfs::prelude::*;
4
5type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
6
7pub struct MemoryCollector;
11
12impl MemoryCollector {
13 pub fn new() -> Self {
14 Self
15 }
16
17 pub fn collect(&self) -> Result<MemoryMetrics> {
18 let info = Meminfo::current()?;
19
20 let to_mib = |bytes: u64| bytes / 1_048_576;
24
25 let total_mib = to_mib(info.mem_total);
26 let free_mib = to_mib(info.mem_free);
27 let available_mib = to_mib(info.mem_available.unwrap_or(info.mem_free));
28 let buffers_mib = to_mib(info.buffers);
29 let cached_mib = to_mib(info.cached) + to_mib(info.s_reclaimable.unwrap_or(0));
30 let used_mib = total_mib
32 .saturating_sub(free_mib)
33 .saturating_sub(buffers_mib)
34 .saturating_sub(cached_mib);
35 let used_pct = if total_mib > 0 {
36 used_mib as f64 / total_mib as f64 * 100.0
37 } else {
38 0.0
39 };
40
41 let swap_total_mib = to_mib(info.swap_total);
42 let swap_used_mib = swap_total_mib.saturating_sub(to_mib(info.swap_free));
43 let swap_used_pct = if swap_total_mib > 0 {
44 swap_used_mib as f64 / swap_total_mib as f64 * 100.0
45 } else {
46 0.0
47 };
48
49 Ok(MemoryMetrics {
50 total_mib,
51 free_mib,
52 available_mib,
53 used_mib,
54 used_pct,
55 buffers_mib,
56 cached_mib,
57 swap_total_mib,
58 swap_used_mib,
59 swap_used_pct,
60 active_mib: to_mib(info.active),
61 inactive_mib: to_mib(info.inactive),
62 })
63 }
64}
65
66#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
76 fn test_memory_collect_ok_and_total_positive() {
77 let m = MemoryCollector::new()
78 .collect()
79 .expect("collect() must succeed on Linux");
80 assert!(
81 m.total_mib > 0,
82 "total_mib must be > 0, got {}",
83 m.total_mib
84 );
85 }
86
87 #[test]
89 fn test_memory_used_pct_in_range() {
90 let m = MemoryCollector::new().collect().expect("collect() failed");
91 assert!(
92 m.used_pct >= 0.0 && m.used_pct <= 100.0,
93 "used_pct out of range: {}",
94 m.used_pct
95 );
96 }
97
98 #[test]
100 fn test_memory_free_and_available_le_total() {
101 let m = MemoryCollector::new().collect().expect("collect() failed");
102 assert!(
103 m.free_mib <= m.total_mib,
104 "free_mib {} > total_mib {}",
105 m.free_mib,
106 m.total_mib
107 );
108 assert!(
109 m.available_mib <= m.total_mib,
110 "available_mib {} > total_mib {}",
111 m.available_mib,
112 m.total_mib
113 );
114 }
115
116 #[test]
118 fn test_memory_swap_fields_consistent() {
119 let m = MemoryCollector::new().collect().expect("collect() failed");
120 assert!(
121 m.swap_used_mib <= m.swap_total_mib,
122 "swap_used_mib {} > swap_total_mib {}",
123 m.swap_used_mib,
124 m.swap_total_mib
125 );
126 if m.swap_total_mib == 0 {
127 assert_eq!(
128 m.swap_used_mib, 0,
129 "swap_used_mib must be 0 when swap_total_mib is 0"
130 );
131 assert_eq!(
132 m.swap_used_pct, 0.0,
133 "swap_used_pct must be 0.0 when swap_total_mib is 0"
134 );
135 }
136 }
137
138 #[test]
140 fn test_memory_collect_is_repeatable() {
141 let c = MemoryCollector::new();
142 let _ = c.collect().expect("first collect() failed");
143 let _ = c.collect().expect("second collect() failed");
144 }
145}