1#![doc = include_str!("../README.md")]
2
3pub const MEMORY_STAT: &str = "/sys/fs/cgroup/memory.stat";
4pub const MEMORY_MAX: &str = "/sys/fs/cgroup/memory.max";
5
6use std::{
7 error::Error,
8 fmt::{self, Display, Formatter},
9 fs::{self, OpenOptions},
10 io::Read,
11 num::ParseIntError,
12};
13
14#[derive(Debug, Default)]
21pub struct MemoryStat {
22 pub anon: Option<u64>,
23 pub file: Option<u64>,
24 pub kernel: Option<u64>,
25 pub kernel_stack: Option<u64>,
26 pub pagetables: Option<u64>,
27 pub sec_pagetables: Option<u64>,
28 pub percpu: Option<u64>,
29 pub sock: Option<u64>,
30 pub vmalloc: Option<u64>,
31 pub shmem: Option<u64>,
32 pub zswap: Option<u64>,
33 pub zswapped: Option<u64>,
34 pub file_mapped: Option<u64>,
35 pub file_dirty: Option<u64>,
36 pub file_writeback: Option<u64>,
37 pub swapcached: Option<u64>,
38 pub anon_thp: Option<u64>,
39 pub file_thp: Option<u64>,
40 pub shmem_thp: Option<u64>,
41 pub inactive_anon: Option<u64>,
42 pub active_anon: Option<u64>,
43 pub inactive_file: Option<u64>,
44 pub active_file: Option<u64>,
45 pub unevictable: Option<u64>,
46 pub slab_reclaimable: Option<u64>,
47 pub slab_unreclaimable: Option<u64>,
48 pub slab: Option<u64>,
49 pub workingset_refault_anon: Option<u64>,
50 pub workingset_refault_file: Option<u64>,
51 pub workingset_activate_anon: Option<u64>,
52 pub workingset_activate_file: Option<u64>,
53 pub workingset_restore_anon: Option<u64>,
54 pub workingset_restore_file: Option<u64>,
55 pub workingset_nodereclaim: Option<u64>,
56 pub pgscan: Option<u64>,
57 pub pgsteal: Option<u64>,
58 pub pgscan_kswapd: Option<u64>,
59 pub pgscan_direct: Option<u64>,
60 pub pgscan_khugepaged: Option<u64>,
61 pub pgsteal_kswapd: Option<u64>,
62 pub pgsteal_direct: Option<u64>,
63 pub pgsteal_khugepaged: Option<u64>,
64 pub pgfault: Option<u64>,
65 pub pgmajfault: Option<u64>,
66 pub pgrefill: Option<u64>,
67 pub pgactivate: Option<u64>,
68 pub pgdeactivate: Option<u64>,
69 pub pglazyfree: Option<u64>,
70 pub pglazyfreed: Option<u64>,
71 pub zswpin: Option<u64>,
72 pub zswpout: Option<u64>,
73 pub thp_fault_alloc: Option<u64>,
74 pub thp_collapse_alloc: Option<u64>,
75}
76
77#[derive(Debug)]
78pub enum ReadParseError {
79 Io(std::io::Error),
80 Parse(ParseIntError),
81 Zero,
83}
84
85impl From<ParseIntError> for ReadParseError {
86 fn from(e: ParseIntError) -> Self {
87 ReadParseError::Parse(e)
88 }
89}
90
91impl From<std::io::Error> for ReadParseError {
92 fn from(e: std::io::Error) -> Self {
93 ReadParseError::Io(e)
94 }
95}
96
97impl Error for ReadParseError {}
98
99impl Display for ReadParseError {
100 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
101 match self {
102 ReadParseError::Io(io_error) => write!(f, "{}", io_error),
103 ReadParseError::Zero => write!(
104 f,
105 "The memory statistics could not be evaluted to a non-zero sum"
106 ),
107 ReadParseError::Parse(parse_error) => write!(f, "{}", parse_error),
108 }
109 }
110}
111
112pub fn memory_stat() -> Result<MemoryStat, ReadParseError> {
122 let memory_stat_string = fs::read_to_string(MEMORY_STAT).map_err(ReadParseError::Io)?;
123
124 println!("{}", memory_stat_string);
125
126 let mut ms = MemoryStat::default();
127
128 for line in memory_stat_string.lines() {
130 let parts: Vec<&str> = line.split_whitespace().collect();
131 if parts.len() == 2 {
132 let key = parts[0];
133
134 match key {
135 "anon" => ms.anon = parts[1].parse().ok(),
136 "file" => ms.file = parts[1].parse().ok(),
137 "kernel" => ms.kernel = parts[1].parse().ok(),
138 "kernel_stack" => ms.kernel_stack = parts[1].parse().ok(),
139 "pagetables" => ms.pagetables = parts[1].parse().ok(),
140 "sec_pagetables" => ms.sec_pagetables = parts[1].parse().ok(),
141 "percpu" => ms.percpu = parts[1].parse().ok(),
142 "sock" => ms.sock = parts[1].parse().ok(),
143 "vmalloc" => ms.vmalloc = parts[1].parse().ok(),
144 "shmem" => ms.shmem = parts[1].parse().ok(),
145 "zswap" => ms.zswap = parts[1].parse().ok(),
146 "zswapped" => ms.zswapped = parts[1].parse().ok(),
147 "file_mapped" => ms.file_mapped = parts[1].parse().ok(),
148 "file_dirty" => ms.file_dirty = parts[1].parse().ok(),
149 "file_writeback" => ms.file_writeback = parts[1].parse().ok(),
150 "swapcached" => ms.swapcached = parts[1].parse().ok(),
151 "anon_thp" => ms.anon_thp = parts[1].parse().ok(),
152 "file_thp" => ms.file_thp = parts[1].parse().ok(),
153 "shmem_thp" => ms.shmem_thp = parts[1].parse().ok(),
154 "inactive_anon" => ms.inactive_anon = parts[1].parse().ok(),
155 "active_anon" => ms.active_anon = parts[1].parse().ok(),
156 "inactive_file" => ms.inactive_file = parts[1].parse().ok(),
157 "active_file" => ms.active_file = parts[1].parse().ok(),
158 "unevictable" => ms.unevictable = parts[1].parse().ok(),
159 "slab_reclaimable" => ms.slab_reclaimable = parts[1].parse().ok(),
160 "slab_unreclaimable" => ms.slab_unreclaimable = parts[1].parse().ok(),
161 "slab" => ms.slab = parts[1].parse().ok(),
162 "workingset_refault_anon" => ms.workingset_refault_anon = parts[1].parse().ok(),
163 "workingset_refault_file" => ms.workingset_refault_file = parts[1].parse().ok(),
164 "workingset_activate_anon" => ms.workingset_activate_anon = parts[1].parse().ok(),
165 "workingset_activate_file" => ms.workingset_activate_file = parts[1].parse().ok(),
166 "workingset_restore_anon" => ms.workingset_restore_anon = parts[1].parse().ok(),
167 "workingset_restore_file" => ms.workingset_restore_file = parts[1].parse().ok(),
168 "workingset_nodereclaim" => ms.workingset_nodereclaim = parts[1].parse().ok(),
169 "pgscan" => ms.pgscan = parts[1].parse().ok(),
170 "pgsteal" => ms.pgsteal = parts[1].parse().ok(),
171 "pgscan_kswapd" => ms.pgscan_kswapd = parts[1].parse().ok(),
172 "pgscan_direct" => ms.pgscan_direct = parts[1].parse().ok(),
173 "pgscan_khugepaged" => ms.pgscan_khugepaged = parts[1].parse().ok(),
174 "pgsteal_kswapd" => ms.pgsteal_kswapd = parts[1].parse().ok(),
175 "pgsteal_direct" => ms.pgsteal_direct = parts[1].parse().ok(),
176 "pgsteal_khugepaged" => ms.pgsteal_khugepaged = parts[1].parse().ok(),
177 "pgfault" => ms.pgfault = parts[1].parse().ok(),
178 "pgmajfault" => ms.pgmajfault = parts[1].parse().ok(),
179 "pgrefill" => ms.pgrefill = parts[1].parse().ok(),
180 "pgactivate" => ms.pgactivate = parts[1].parse().ok(),
181 "pgdeactivate" => ms.pgdeactivate = parts[1].parse().ok(),
182 "pglazyfree" => ms.pglazyfree = parts[1].parse().ok(),
183 "pglazyfreed" => ms.pglazyfreed = parts[1].parse().ok(),
184 "zswpin" => ms.zswpin = parts[1].parse().ok(),
185 "zswpout" => ms.zswpout = parts[1].parse().ok(),
186 "thp_fault_alloc" => ms.thp_fault_alloc = parts[1].parse().ok(),
187 "thp_collapse_alloc" => ms.thp_collapse_alloc = parts[1].parse().ok(),
188 _ => {}
189 }
190 }
191 }
192 Ok(ms)
193}
194
195pub fn memory_net_used_calc(ms: &MemoryStat) -> Result<u64, ReadParseError> {
209 let total_net_used_memory = ms.anon.unwrap_or(0)
210 + ms.file.unwrap_or(0)
211 + ms.kernel.unwrap_or(0)
212 + ms.kernel_stack.unwrap_or(0)
213 + ms.pagetables.unwrap_or(0)
214 + ms.percpu.unwrap_or(0)
215 + ms.slab_unreclaimable.unwrap_or(0)
216 - ms.slab_reclaimable.unwrap_or(0);
217
218 if total_net_used_memory == 0 {
219 return Err(ReadParseError::Zero);
220 }
221
222 Ok(total_net_used_memory)
223}
224
225pub fn memory_net_used() -> Result<u64, ReadParseError> {
230 let ms = memory_stat()?;
231 memory_net_used_calc(&ms)
232}
233
234pub fn memory_max_parse(line: &str) -> Result<Option<u64>, ParseIntError> {
245 if line == "max" {
246 return Ok(None);
247 }
248
249 line.trim().parse::<u64>().map(Some)
250}
251
252fn memory_max_parse_unsafe(line: &str) -> Option<u64> {
262 if line == "max" {
263 return None;
264 }
265
266 unsafe { Some(line.trim().parse::<u64>().unwrap_unchecked()) }
267}
268
269pub fn memory_max() -> Result<Option<u64>, ReadParseError> {
285 let mut file = OpenOptions::new().read(true).open(MEMORY_MAX)?;
286 let mut buffer = [0; 4096];
287
288 let bytes_read = file.read(&mut buffer)?;
289 let content = std::str::from_utf8(&buffer[..bytes_read]).map_err(|_e| ReadParseError::Zero)?;
290 Ok(memory_max_parse(content)?)
291}
292
293pub fn memory_max_unsafe() -> Result<Option<u64>, ReadParseError> {
314 let mut file = OpenOptions::new()
315 .read(true)
316 .open(MEMORY_MAX)
317 .map_err(ReadParseError::Io)?;
318 let mut buffer = [0; 1024];
319 let bytes_read = file.read(&mut buffer).map_err(ReadParseError::Io)?;
320
321 let content = unsafe { std::str::from_utf8_unchecked(&buffer[..bytes_read]) };
323 Ok(memory_max_parse_unsafe(content))
324}
325
326pub fn memory_available() -> Result<Option<u64>, ReadParseError> {
345 let max = match memory_max()? {
346 Some(value) => value,
347 None => return Ok(None),
348 };
349 let net_used = memory_net_used()?;
350 Ok(Some(max - net_used))
351}