1use anyhow::{Result, anyhow};
36use serde::Serialize;
37use std::fs::read_to_string;
38use std::path::Path;
39
40use crate::traits::ToJson;
41use crate::utils::Size;
42
43#[derive(Debug, Serialize, Default, Clone)]
45pub struct RAM {
46 pub total: Size,
48
49 pub free: Size,
52
53 pub available: Size,
56
57 pub buffers: Size,
60
61 pub cached: Size,
64
65 pub swap_cached: Size,
68
69 pub active: Size,
71
72 pub inactive: Size,
74
75 pub active_anon: Size,
77
78 pub inactive_anon: Size,
80
81 pub active_file: Size,
83
84 pub inactive_file: Size,
86
87 pub unevictable: Size,
89
90 pub mlocked: Size,
92
93 pub swap_total: Size,
95
96 pub swap_free: Size,
98
99 pub zswap: Size,
100 pub zswapped: Size,
101
102 pub dirty: Size,
104
105 pub writeback: Size,
107
108 pub anon_pages: Size,
110
111 pub mapped: Size,
114
115 pub shmem: Size,
117
118 pub kreclaimable: Size,
122
123 pub slab: Size,
126
127 pub sreclaimable: Size,
130
131 pub sunreclaim: Size,
133
134 pub kernel_stack: Size,
136
137 pub page_tables: Size,
140
141 pub sec_page_tables: Size,
142
143 pub nfs_unstable: Size,
146
147 pub bounce: Size,
150
151 pub writeback_tmp: Size,
153
154 pub commit_limit: Size,
157
158 pub commited_as: Size,
161
162 pub vmalloc_total: Size,
164
165 pub vmalloc_used: Size,
167
168 pub vmalloc_chunk: Size,
170
171 pub percpu: Size,
174
175 pub hardware_corrupted: Size,
178
179 pub anon_huge_pages: Size,
182
183 pub shmem_huge_pages: Size,
185
186 pub shmem_pmd_mapped: Size,
189
190 pub cma_total: Option<Size>,
192
193 pub cma_free: Option<Size>,
195
196 pub file_huge_pages: Size,
197 pub file_pmd_mapped: Size,
198 pub unaccepted: Size,
199 pub huge_pages_total: u32,
200 pub huge_pages_free: u32,
201 pub huge_pages_rsvd: u32,
202 pub huge_pages_surp: u32,
203 pub huge_page_size: Size,
204 pub huge_tlb: Size,
205 pub direct_map_4k: Size,
206 pub direct_map_2m: Size,
207 pub direct_map_1g: Size,
208}
209
210impl RAM {
211 pub fn new() -> Result<Self> {
212 let chunks = read_to_string("/proc/meminfo")?;
213 let chunks = chunks
214 .lines()
215 .map(|item| {
216 let mut items = item.split(':').map(|item| item.trim());
217 (items.next(), items.next())
218 })
219 .collect::<Vec<_>>();
220 let mut ram = RAM::default();
221 for chunk in chunks {
222 match chunk {
223 (Some(key), Some(val)) => match key {
224 "MemTotal" => ram.total = Size::try_from(val)?,
225 "MemFree" => ram.free = Size::try_from(val)?,
226 "MemAvailable" => ram.available = Size::try_from(val)?,
227 "Buffers" => ram.buffers = Size::try_from(val)?,
228 "Cached" => ram.cached = Size::try_from(val)?,
229 "SwapCached" => ram.swap_cached = Size::try_from(val)?,
230 "Active" => ram.active = Size::try_from(val)?,
231 "Inactive" => ram.inactive = Size::try_from(val)?,
232 "Active(anon)" => ram.active_anon = Size::try_from(val)?,
233 "Inactive(anon)" => ram.inactive_anon = Size::try_from(val)?,
234 "Active(file)" => ram.active_file = Size::try_from(val)?,
235 "Inactive(file)" => ram.inactive_file = Size::try_from(val)?,
236 "Unevictable" => ram.unevictable = Size::try_from(val)?,
237 "Mlocked" => ram.mlocked = Size::try_from(val)?,
238 "SwapTotal" => ram.swap_total = Size::try_from(val)?,
239 "SwapFree" => ram.swap_free = Size::try_from(val)?,
240 "Zswap" => ram.zswap = Size::try_from(val)?,
241 "Zswapped" => ram.zswapped = Size::try_from(val)?,
242 "Dirty" => ram.dirty = Size::try_from(val)?,
243 "Writeback" => ram.writeback = Size::try_from(val)?,
244 "AnonPages" => ram.anon_pages = Size::try_from(val)?,
245 "Mapped" => ram.mapped = Size::try_from(val)?,
246 "Shmem" => ram.shmem = Size::try_from(val)?,
247 "KReclaimable" => ram.kreclaimable = Size::try_from(val)?,
248 "Slab" => ram.slab = Size::try_from(val)?,
249 "SReclaimable" => ram.sreclaimable = Size::try_from(val)?,
250 "SUnreclaim" => ram.sunreclaim = Size::try_from(val)?,
251 "KernelStack" => ram.kernel_stack = Size::try_from(val)?,
252 "PageTables" => ram.page_tables = Size::try_from(val)?,
253 "SecPageTables" => ram.sec_page_tables = Size::try_from(val)?,
254 "NFS_Unstable" => ram.nfs_unstable = Size::try_from(val)?,
255 "Bounce" => ram.bounce = Size::try_from(val)?,
256 "WritebackTmp" => ram.writeback_tmp = Size::try_from(val)?,
257 "CommitLimit" => ram.commit_limit = Size::try_from(val)?,
258 "Committed_AS" => ram.commited_as = Size::try_from(val)?,
259 "VmallocTotal" => ram.vmalloc_total = Size::try_from(val)?,
260 "VmallocUsed" => ram.vmalloc_used = Size::try_from(val)?,
261 "VmallocChunk" => ram.vmalloc_chunk = Size::try_from(val)?,
262 "Percpu" => ram.percpu = Size::try_from(val)?,
263 "HardwareCorrupted" => ram.hardware_corrupted = Size::try_from(val)?,
264 "AnonHugePages" => ram.anon_huge_pages = Size::try_from(val)?,
265 "ShmemHugePages" => ram.shmem_huge_pages = Size::try_from(val)?,
266 "ShmemPmdMapped" => ram.shmem_pmd_mapped = Size::try_from(val)?,
267 "CmaTotal" => ram.cma_total = Size::try_from(val).ok(),
268 "CmaFree" => ram.cma_free = Size::try_from(val).ok(),
269 "FileHugePages" => ram.file_huge_pages = Size::try_from(val)?,
270 "FilePmdMapped" => ram.file_pmd_mapped = Size::try_from(val)?,
271 "Unaccepted" => ram.unaccepted = Size::try_from(val)?,
272 "HugePages_Total" => ram.huge_pages_total = val.parse()?,
273 "HugePages_Free" => ram.huge_pages_free = val.parse()?,
274 "HugePages_Rsvd" => ram.huge_pages_rsvd = val.parse()?,
275 "HugePages_Surp" => ram.huge_pages_surp = val.parse()?,
276 "Hugepagesize" => ram.huge_page_size = Size::try_from(val)?,
277 "Hugetlb" => ram.huge_tlb = Size::try_from(val)?,
278 "DirectMap4k" => ram.direct_map_4k = Size::try_from(val)?,
279 "DirectMap2M" => ram.direct_map_2m = Size::try_from(val)?,
280 "DirectMap1G" => ram.direct_map_1g = Size::try_from(val)?,
281 _ => continue,
282 },
283 _ => {}
284 }
285 }
286 Ok(ram)
287 }
288
289 pub fn used_ram(&self, base: u8) -> Size {
290 if base != 2 && base != 10 {
291 panic!("Unknown base: {base} (supported values: 2 or 10)");
292 }
293
294 let total = if base == 2 {
295 self.total.get_bytes2().unwrap_or(0) as f32 / 1024. / 1024. / 1024.
296 } else
297 {
299 self.total.get_bytes10().unwrap_or(0) as f32 / 1000. / 1000. / 1000.
300 };
301
302 let avail = if base == 2 {
303 self.available.get_bytes2().unwrap_or(0) as f32 / 1024. / 1024. / 1024.
304 } else
305 {
307 self.available.get_bytes10().unwrap_or(0) as f32 / 1000. / 1000. / 1000.
308 };
309 let used = total - avail;
310
311 Size::GB(used)
312 }
313
314 pub fn usage_percentage(&self) -> Option<f64> {
315 let total_ram = self.total.get_bytes2()?;
316 let avail_ram = self.available.get_bytes2()?;
317 let used_ram = total_ram - avail_ram;
318
319 Some(used_ram as f64 / total_ram as f64 * 100.)
320 }
321}
322
323impl ToJson for RAM {}
324
325#[derive(Debug, Serialize, Clone)]
327pub struct Swaps {
328 pub swaps: Vec<Swap>,
329}
330
331impl Swaps {
332 pub fn new() -> Result<Self> {
333 let mut swaps = vec![];
334
335 let data = read_to_string("/proc/swaps")?;
336 let items = data.lines().skip(1);
337
338 for swap in items {
339 swaps.push(Swap::try_from(swap)?)
340 }
341
342 Ok(Self { swaps })
343 }
344}
345
346impl ToJson for Swaps {}
347
348#[derive(Debug, Serialize, Clone)]
349pub struct Swap {
350 pub filename: String,
352
353 pub swap_type: String,
355
356 pub size: Size,
358
359 pub used: Size,
361
362 pub priority: i8,
364}
365
366impl Swap {
367 pub fn usage_percentage(&self) -> Option<f64> {
368 let total_swap = self.size.get_bytes2()?;
369 let used_swap = self.used.get_bytes2()?;
370
371 Some(used_swap as f64 / total_swap as f64 * 100.)
372 }
373}
374
375impl TryFrom<&str> for Swap {
376 type Error = anyhow::Error;
377
378 fn try_from(value: &str) -> Result<Self> {
379 let data = value.split_whitespace().collect::<Vec<_>>();
380 if data.len() != 5 {
381 return Err(anyhow!("Format of the \"{value}\" string is incorrect!"));
382 }
383
384 Ok(Self {
385 filename: data[0].to_string(),
386 swap_type: data[1].to_string(),
387 size: Size::KB(data[2].parse()?),
388 used: Size::KB(data[3].parse()?),
389 priority: data[4].parse()?,
390 })
391 }
392}
393impl ToJson for Swap {}
394
395#[derive(Debug, Clone, Serialize)]
396pub struct Zswap {
397 pub accept_threshold_percent: Option<u8>,
398 pub compressor: Option<String>,
399 pub enabled: Option<bool>,
400 pub max_pool_percent: Option<u8>,
401 pub non_same_filled_pages_enabled: Option<bool>,
402 pub same_filled_pages_enabled: Option<bool>,
403 pub zpool: Option<String>,
404 pub pool_total_size: Option<Size>,
405 pub stored_pages_size: Option<Size>,
406 pub compression_ratio: Option<f32>,
407}
408
409fn get_page_size() -> u64 {
410 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 }
411}
412
413impl Zswap {
414 pub fn new() -> Self {
415 let rdbg = |f: &str| read_to_string(Path::new("/sys/kernel/debug/zswap/").join(f)).ok();
416 let fcnf =
417 |f: &str| read_to_string(Path::new("/sys/module/zswap/parameters/").join(f)).ok();
418 let get_bool = |content: &str| {
419 if content.trim() == "Y" || content != "0" {
420 true
421 } else {
422 false
423 }
424 };
425
426 let accept_threshold_percent =
427 fcnf("accept_threshold_percent").and_then(|atp| atp.trim().parse::<u8>().ok());
428 let compressor = fcnf("compressor");
429 let enabled = fcnf("enabled").and_then(|e| Some(get_bool(e.trim())));
430 let max_pool_percent =
431 fcnf("max_pool_percent").and_then(|mpp| mpp.trim().parse::<u8>().ok());
432 let non_same_filled_pages_enabled =
433 fcnf("non_same_filled_pages_enabled").and_then(|nsfpe| Some(get_bool(nsfpe.trim())));
434 let same_filled_pages_enabled =
435 fcnf("same_filled_pages_enabled").and_then(|sfpe| Some(get_bool(sfpe.trim())));
436 let zpool = fcnf("zpool");
437
438 let pool_total_size =
439 rdbg("pool_total_size").and_then(|pts| pts.trim().parse::<u64>().ok()); let stored_page_size =
441 rdbg("stored_page_size").and_then(|sps| sps.trim().parse::<u64>().ok()); let compression_ratio = match (pool_total_size, stored_page_size) {
443 (Some(pts), Some(sps)) => Some(sps as f32 / pts as f32),
444 _ => None,
445 };
446
447 Self {
448 accept_threshold_percent,
449 compressor,
450 enabled,
451 max_pool_percent,
452 non_same_filled_pages_enabled,
453 same_filled_pages_enabled,
454 zpool,
455 pool_total_size: pool_total_size.and_then(|pts| Some(Size::B(pts))),
456 stored_pages_size: stored_page_size
457 .and_then(|sps| Some(Size::B(sps * get_page_size()))),
458 compression_ratio,
459 }
460 }
461}