oxedyne/fe2o3/fe2o3_sys/src/mem.rs
3.1 KiB, 1 run
created by r1870400018:10586, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Memory accounting from `/proc/meminfo`. |
| 2 | |
| 3 | use crate::parse::{ |
| 4 | parse_num, |
| 5 | read_to_string, |
| 6 | }; |
| 7 | |
| 8 | use oxedyne_fe2o3_core::prelude::*; |
| 9 | |
| 10 | /// Snapshot of the key memory figures reported by the kernel. |
| 11 | /// All values are in bytes; the `/proc/meminfo` kB values are |
| 12 | /// multiplied on ingest so downstream consumers never have to |
| 13 | /// worry about the unit. |
| 14 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] |
| 15 | pub struct MemInfo { |
| 16 | /// Total usable RAM. |
| 17 | pub total: u64, |
| 18 | /// Free RAM (no use by kernel or user). |
| 19 | pub free: u64, |
| 20 | /// Kernel's best guess at actually free RAM including |
| 21 | /// reclaimable page cache and buffers. |
| 22 | pub available: u64, |
| 23 | /// Block-device buffers. |
| 24 | pub buffers: u64, |
| 25 | /// Page cache. |
| 26 | pub cached: u64, |
| 27 | /// Total swap space configured. |
| 28 | pub swap_total: u64, |
| 29 | /// Swap currently in use. |
| 30 | pub swap_free: u64, |
| 31 | } |
| 32 | |
| 33 | impl MemInfo { |
| 34 | /// Read `/proc/meminfo` and parse the key fields. |
| 35 | pub fn sample() -> Outcome<Self> { |
| 36 | let content = res!(read_to_string("/proc/meminfo")); |
| 37 | Self::from_meminfo(&content) |
| 38 | } |
| 39 | |
| 40 | /// Parse a `/proc/meminfo` body. Each line has the form |
| 41 | /// `Name: <value> kB`; unknown names are ignored. |
| 42 | pub fn from_meminfo(content: &str) -> Outcome<Self> { |
| 43 | let mut out = Self::default(); |
| 44 | for line in content.lines() { |
| 45 | let (name, rest) = match line.split_once(':') { |
| 46 | Some(t) => t, |
| 47 | None => continue, |
| 48 | }; |
| 49 | let mut iter = rest.split_whitespace(); |
| 50 | let value = match iter.next() { |
| 51 | Some(v) => v, |
| 52 | None => continue, |
| 53 | }; |
| 54 | let kb = res!(parse_num::<u64>(value, name.trim())); |
| 55 | let bytes = kb.saturating_mul(1024); |
| 56 | match name.trim() { |
| 57 | "MemTotal" => out.total = bytes, |
| 58 | "MemFree" => out.free = bytes, |
| 59 | "MemAvailable" => out.available = bytes, |
| 60 | "Buffers" => out.buffers = bytes, |
| 61 | "Cached" => out.cached = bytes, |
| 62 | "SwapTotal" => out.swap_total = bytes, |
| 63 | "SwapFree" => out.swap_free = bytes, |
| 64 | _ => (), |
| 65 | } |
| 66 | } |
| 67 | if out.total == 0 { |
| 68 | return Err(err!( |
| 69 | "/proc/meminfo did not contain a MemTotal field."; |
| 70 | Input, Missing, Decode)); |
| 71 | } |
| 72 | Ok(out) |
| 73 | } |
| 74 | |
| 75 | /// Bytes actually in use by processes and the kernel, |
| 76 | /// approximated as `total - available`. Prefer this over |
| 77 | /// `total - free` which double-counts page cache as "used". |
| 78 | pub fn used(&self) -> u64 { |
| 79 | self.total.saturating_sub(self.available) |
| 80 | } |
| 81 | |
| 82 | /// Used memory as a fraction (0..=1) of total. |
| 83 | pub fn used_fraction(&self) -> f64 { |
| 84 | if self.total == 0 { |
| 85 | 0.0 |
| 86 | } else { |
| 87 | self.used() as f64 / self.total as f64 |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | /// Swap currently in use, in bytes. |
| 92 | pub fn swap_used(&self) -> u64 { |
| 93 | self.swap_total.saturating_sub(self.swap_free) |
| 94 | } |
| 95 | } |