ferrix_lib/
ram.rs

1/* ram.rs
2 *
3 * Copyright 2025 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Get information about RAM
22//!
23//! Reads information from `/proc/meminfo` file
24//!
25//! ## Example
26//! ```
27//! use ferrix_lib::ram::RAM;
28//! use ferrix_lib::traits::ToJson;
29//!
30//! let ram = RAM::new().unwrap();
31//! let json = ram.to_json().unwrap();
32//! dbg!(json);
33//! ```
34
35use anyhow::{Result, anyhow};
36use serde::Serialize;
37use std::fs::read_to_string;
38
39use crate::utils::Size;
40use crate::traits::ToJson;
41
42/// A structure containing data from the `/proc/meminfo` file
43#[derive(Debug, Serialize, Default, Clone)]
44pub struct RAM {
45    /// Total usable physical RAM (excludes reserved/firmware memory)
46    pub total: Size,
47
48    /// Completely unused physical RAM. Often **misleadingly low**
49    /// due to caching
50    pub free: Size,
51
52    /// **Best estimate** of memory available for new apps (accounts
53    /// for caches/reclaimable memory)
54    pub available: Size,
55
56    /// Temporary storage for raw disk blocks (e.g. filesystem
57    /// metadata)
58    pub buffers: Size,
59
60    /// Page cache for files read from disk (reclaimed when apps
61    /// need memory)
62    pub cached: Size,
63
64    /// Memory swapped out but later accessed, now present in both
65    /// RAM and swap
66    pub swap_cached: Size,
67
68    /// Recently used memory (harder to reclaim)
69    pub active: Size,
70
71    /// Less recently used memory (eased to reclaim)
72    pub inactive: Size,
73
74    /// Active anonymous pages (e.g. heap/stack, not file-backed)
75    pub active_anon: Size,
76
77    /// Inactive anonymous pages
78    pub inactive_anon: Size,
79
80    /// Active file-backed pages (cached files)
81    pub active_file: Size,
82
83    /// Inactive file-backed pages
84    pub inactive_file: Size,
85
86    /// Memory that cannot be pages out (e.g. locked with `mlock()`)
87    pub unevictable: Size,
88
89    /// Memory that is locked (cannot be swapped out)
90    pub mlocked: Size,
91
92    /// Total amount of swap space available
93    pub swap_total: Size,
94
95    /// Amount of swap space that is currently unused
96    pub swap_free: Size,
97
98    pub zswap: Size,
99    pub zswapped: Size,
100
101    /// Data waiting to be written to disk
102    pub dirty: Size,
103
104    /// Data actively being written to disk
105    pub writeback: Size,
106
107    /// Non file-backed pages mapped into user-space page tables
108    pub anon_pages: Size,
109
110    /// Files (like libraries) that have been mapped into memory
111    /// (also includes `tmpfs`/`shmem`)
112    pub mapped: Size,
113
114    /// Total memory used by shared memory (`shmem`) and `tmpfs`
115    pub shmem: Size,
116
117    /// Kernel allocations that the kernel will attempt to reclaim
118    /// under memory pressure (includes `SReclaimable` and other
119    /// reclaimable slabs)
120    pub kreclaimable: Size,
121
122    /// In-kernel data structures cache (includes `SReclaimable` and
123    /// `SUnreclaim`)
124    pub slab: Size,
125
126    /// Part of `Slab` that might be reclaimed, such as caches for
127    /// directory inodes, etc.
128    pub sreclaimable: Size,
129
130    /// Part of `Slab` that cannot be reclaimed
131    pub sunreclaim: Size,
132
133    /// Memory used by kernel stacks
134    pub kernel_stack: Size,
135
136    /// Memory used by page tables (to map virtual to physical
137    /// addresses)
138    pub page_tables: Size,
139
140    pub sec_page_tables: Size,
141
142    /// Memory that has been sent to the NFS server but not yet
143    /// committed to stable storage
144    pub nfs_unstable: Size,
145
146    /// Memory used for block device bounce buffers (rarely used on
147    /// modern systems)
148    pub bounce: Size,
149
150    /// Memory used by FUSE for temporary writeback buffers
151    pub writeback_tmp: Size,
152
153    /// Based on the overcommit ratio, this is the total amount of
154    /// memory currently available to be allocated
155    pub commit_limit: Size,
156
157    /// he amount of memory currently allocated by the system. The
158    /// kernel may overcommit this
159    pub commited_as: Size,
160
161    /// Total size of `vmalloc` memory area
162    pub vmalloc_total: Size,
163
164    /// Amount of `vmalloc` area which is used
165    pub vmalloc_used: Size,
166
167    /// Largest contiguous block of free `vmalloc` space
168    pub vmalloc_chunk: Size,
169
170    /// Memory used for per-cpu allocations (each CPU has its own
171    /// block)
172    pub percpu: Size,
173
174    /// Memory that the kernel identified as corrupted (when
175    /// `CONFIG_MEMORY_FAILURE` is enabled)
176    pub hardware_corrupted: Size,
177
178    /// Non-file backed huge pages mapped into user-space page tables
179    /// (transparent hugepages)
180    pub anon_huge_pages: Size,
181
182    /// Huge pages used by shared memory (`shmem`) and `tmpfs`.
183    pub shmem_huge_pages: Size,
184
185    /// `shmem`/`tmpfs` memory that is mapped into user space with
186    /// huge pages
187    pub shmem_pmd_mapped: Size,
188
189    /// Total CMA (Contiguous Memory Allocator) area
190    pub cma_total: Option<Size>,
191
192    /// Free memory in the CMA area
193    pub cma_free: Option<Size>,
194
195    pub file_huge_pages: Size,
196    pub file_pmd_mapped: Size,
197    pub unaccepted: Size,
198    pub huge_pages_total: u32,
199    pub huge_pages_free: u32,
200    pub huge_pages_rsvd: u32,
201    pub huge_pages_surp: u32,
202    pub huge_page_size: Size,
203    pub huge_tlb: Size,
204    pub direct_map_4k: Size,
205    pub direct_map_2m: Size,
206    pub direct_map_1g: Size,
207}
208
209impl RAM {
210    pub fn new() -> Result<Self> {
211        let chunks = read_to_string("/proc/meminfo")?;
212        let chunks = chunks
213            .lines()
214            .map(|item| {
215                let mut items = item.split(':').map(|item| item.trim());
216                (items.next(), items.next())
217            })
218            .collect::<Vec<_>>();
219        let mut ram = RAM::default();
220        for chunk in chunks {
221            match chunk {
222                (Some(key), Some(val)) => match key {
223                    "MemTotal" => ram.total = Size::try_from(val)?,
224                    "MemFree" => ram.free = Size::try_from(val)?,
225                    "MemAvailable" => ram.available = Size::try_from(val)?,
226                    "Buffers" => ram.buffers = Size::try_from(val)?,
227                    "Cached" => ram.cached = Size::try_from(val)?,
228                    "SwapCached" => ram.swap_cached = Size::try_from(val)?,
229                    "Active" => ram.active = Size::try_from(val)?,
230                    "Inactive" => ram.inactive = Size::try_from(val)?,
231                    "Active(anon)" => ram.active_anon = Size::try_from(val)?,
232                    "Inactive(anon)" => ram.inactive_anon = Size::try_from(val)?,
233                    "Active(file)" => ram.active_file = Size::try_from(val)?,
234                    "Inactive(file)" => ram.inactive_file = Size::try_from(val)?,
235                    "Unevictable" => ram.unevictable = Size::try_from(val)?,
236                    "Mlocked" => ram.mlocked = Size::try_from(val)?,
237                    "SwapTotal" => ram.swap_total = Size::try_from(val)?,
238                    "SwapFree" => ram.swap_free = Size::try_from(val)?,
239                    "Zswap" => ram.zswap = Size::try_from(val)?,
240                    "Zswapped" => ram.zswapped = Size::try_from(val)?,
241                    "Dirty" => ram.dirty = Size::try_from(val)?,
242                    "Writeback" => ram.writeback = Size::try_from(val)?,
243                    "AnonPages" => ram.anon_pages = Size::try_from(val)?,
244                    "Mapped" => ram.mapped = Size::try_from(val)?,
245                    "Shmem" => ram.shmem = Size::try_from(val)?,
246                    "KReclaimable" => ram.kreclaimable = Size::try_from(val)?,
247                    "Slab" => ram.slab = Size::try_from(val)?,
248                    "SReclaimable" => ram.sreclaimable = Size::try_from(val)?,
249                    "SUnreclaim" => ram.sunreclaim = Size::try_from(val)?,
250                    "KernelStack" => ram.kernel_stack = Size::try_from(val)?,
251                    "PageTables" => ram.page_tables = Size::try_from(val)?,
252                    "SecPageTables" => ram.sec_page_tables = Size::try_from(val)?,
253                    "NFS_Unstable" => ram.nfs_unstable = Size::try_from(val)?,
254                    "Bounce" => ram.bounce = Size::try_from(val)?,
255                    "WritebackTmp" => ram.writeback_tmp = Size::try_from(val)?,
256                    "CommitLimit" => ram.commit_limit = Size::try_from(val)?,
257                    "Committed_AS" => ram.commited_as = Size::try_from(val)?,
258                    "VmallocTotal" => ram.vmalloc_total = Size::try_from(val)?,
259                    "VmallocUsed" => ram.vmalloc_used = Size::try_from(val)?,
260                    "VmallocChunk" => ram.vmalloc_chunk = Size::try_from(val)?,
261                    "Percpu" => ram.percpu = Size::try_from(val)?,
262                    "HardwareCorrupted" => ram.hardware_corrupted = Size::try_from(val)?,
263                    "AnonHugePages" => ram.anon_huge_pages = Size::try_from(val)?,
264                    "ShmemHugePages" => ram.shmem_huge_pages = Size::try_from(val)?,
265                    "ShmemPmdMapped" => ram.shmem_pmd_mapped = Size::try_from(val)?,
266                    "CmaTotal" => ram.cma_total = Size::try_from(val).ok(),
267                    "CmaFree" => ram.cma_free = Size::try_from(val).ok(),
268                    "FileHugePages" => ram.file_huge_pages = Size::try_from(val)?,
269                    "FilePmdMapped" => ram.file_pmd_mapped = Size::try_from(val)?,
270                    "Unaccepted" => ram.unaccepted = Size::try_from(val)?,
271                    "HugePages_Total" => ram.huge_pages_total = val.parse()?,
272                    "HugePages_Free" => ram.huge_pages_free = val.parse()?,
273                    "HugePages_Rsvd" => ram.huge_pages_rsvd = val.parse()?,
274                    "HugePages_Surp" => ram.huge_pages_surp = val.parse()?,
275                    "Hugepagesize" => ram.huge_page_size = Size::try_from(val)?,
276                    "Hugetlb" => ram.huge_tlb = Size::try_from(val)?,
277                    "DirectMap4k" => ram.direct_map_4k = Size::try_from(val)?,
278                    "DirectMap2M" => ram.direct_map_2m = Size::try_from(val)?,
279                    "DirectMap1G" => ram.direct_map_1g = Size::try_from(val)?,
280                    _ => continue,
281                },
282                _ => {}
283            }
284        }
285        Ok(ram)
286    }
287}
288
289impl ToJson for RAM {}
290
291/// Information about swap files or partitions
292#[derive(Debug, Serialize)]
293pub struct Swaps {
294    pub swaps: Vec<Swap>,
295}
296
297impl Swaps {
298    pub fn new() -> Result<Self> {
299        let mut swaps = vec![];
300
301        let data = read_to_string("/proc/swaps")?;
302        let items = data.lines().skip(1);
303
304        for swap in items {
305            swaps.push(Swap::try_from(swap)?)
306        }
307
308        Ok(Self { swaps })
309    }
310}
311
312impl ToJson for Swaps {}
313
314#[derive(Debug, Serialize)]
315pub struct Swap {
316    /// Path to the file or partition
317    pub filename: String,
318
319    /// Type
320    pub swap_type: String,
321
322    /// Swap size
323    pub size: Size,
324
325    /// Used space
326    pub used: Size,
327
328    /// Priority of this swap file/partition
329    pub priority: i8,
330}
331
332impl TryFrom<&str> for Swap {
333    type Error = anyhow::Error;
334
335    fn try_from(value: &str) -> Result<Self> {
336        let data = value.split_whitespace().collect::<Vec<_>>();
337        if data.len() != 5 {
338            return Err(anyhow!("Format of the \"{value}\" string is incorrect!"));
339        }
340
341        Ok(Self {
342            filename: data[0].to_string(),
343            swap_type: data[1].to_string(),
344            size: Size::B(data[2].parse()?),
345            used: Size::B(data[3].parse()?),
346            priority: data[4].parse()?,
347        })
348    }
349}
350impl ToJson for Swap {}