meminfo/lib.rs
1//! # Linux MemInfo
2//!
3//! This library provides easy and low level access to `meminfo`, the _pseudofile_ placed
4//! by the Linux kernel inside the `proc` _pseudo-filesystem_ (for more information, see the `proc`
5//! [manpage](https://man7.org/linux/man-pages/man5/proc.5.html)).
6//!
7//! The public API is built around the [`MemInfo`] type, a struct responsible for retrieving
8//! memory-related information about the system. Calling its [constructor](`MemInfo::new`)
9//! opens the `/proc/meminfo` pseudofile and reads its data into an internal buffer.
10//! Having [`MemInfo`] to own both the open file and a buffer of its data allows separation of
11//! concerns between _reading_ from the pseudofile, _managing_ and _parsing_ the buffered data.
12//!
13//! The parser implementation responsible for parsing `/proc/meminfo` entries works exclusively on
14//! string slices, just owning a reference to the pseudofile buffered bytes. This allows for
15//! efficient, **zero-allocation** parsing.
16//!
17//! ## Examples
18//!
19//! The following example shows the most basic usage of the [`MemInfo`] API. First we construct
20//! a new instance, which translates to `/proc/meminfo` being opened and read into the internal
21//! buffer; then we call the [`MemInfo::parse`], which returns a **lazy** iterator over parsed
22//! entries, in this case represented by the [`MemInfoEntry`] type. The iterator being lazy
23//! meaning it parses a new entry on each call to the `next` method. In other words: _you only pay
24//! for the entries you parse_.
25//!
26//! ```rust
27//! use std::error;
28//!
29//! use meminfo::MemInfo;
30//!
31//! fn main() -> Result<(), Box<dyn error::Error>> {
32//! let mut meminfo = MemInfo::new()?;
33//! let mut entries = meminfo.parse();
34//!
35//! let mem_total = entries.next().unwrap();
36//! assert_eq!("MemTotal", mem_total.label());
37//! assert_eq!(Some("kB"), mem_total.unit());
38//!
39//! println!("System's total usable RAM: {}kB", mem_total.size()?);
40//!
41//! Ok(())
42//! }
43//! ```
44//!
45//! Users may have distinct use cases that call for regular retrieval of a particular set of
46//! entries within the `/proc/meminfo` pseudofile. The [`MemInfo::parse_extended`] efficiently
47//! addresses this requirement, extending parsed entries with additional information pertaining to
48//! the byte range they occupy in the file stream. This functionality allows users to selectively
49//! read and parse specific entries as needed. Also, this way, the internal buffer can be shrank to
50//! the capacity required to read in such entries, reducing the runtime memory footprint of the
51//! program.
52//!
53//! ```no_run
54//! use std::io::SeekFrom;
55//! use std::time::Duration;
56//! use std::{error, thread};
57//!
58//! use meminfo::{MemInfo, MemInfoError};
59//!
60//! #[derive(Debug)]
61//! struct MemAvailable {
62//! size: usize,
63//! start_pos: usize,
64//! }
65//!
66//! impl MemAvailable {
67//! fn new(meminfo: &mut MemInfo) -> Result<Self, MemInfoError> {
68//! let mut entries = meminfo.parse_extended().skip(2);
69//!
70//! let mem_available = entries.next().unwrap();
71//! assert_eq!("MemAvailable", mem_available.label());
72//! assert_eq!(Some("kB"), mem_available.unit());
73//!
74//! let size = mem_available.size().unwrap();
75//! let start_pos = mem_available.start_pos();
76//! let capacity = mem_available.required_capacity();
77//!
78//! drop(entries);
79//! meminfo.clear();
80//! meminfo.shrink_to(capacity);
81//!
82//! Ok(MemAvailable { size, start_pos })
83//! }
84//!
85//! fn fetch(&mut self, meminfo: &mut MemInfo) -> Result<(), MemInfoError> {
86//! let seek_pos = SeekFrom::Start(self.start_pos as u64);
87//! meminfo.seek(seek_pos)?;
88//!
89//! meminfo.clear();
90//! meminfo.read()?;
91//!
92//! let entry = meminfo.parse().next().unwrap();
93//! self.size = entry.size().unwrap();
94//!
95//! Ok(())
96//! }
97//! }
98//!
99//! fn main() -> Result<(), Box<dyn error::Error>> {
100//! let mut meminfo = MemInfo::new()?;
101//! let mut mem_available = MemAvailable::new(&mut meminfo)?;
102//!
103//! loop {
104//! println!("System's available RAM: {}kB", mem_available.size);
105//! thread::sleep(Duration::from_secs(2));
106//! mem_available.fetch(&mut meminfo)?;
107//! }
108//! }
109//! ```
110//!
111//! ## Features
112//!
113//! By default, the [`MemInfoEntry`] and [`MemInfoEntryExtended`] constructors perform UTF-8
114//! validation on `/proc/meminfo` parsed data. Malformed data would cause a panic in this case.
115//! However, enabling the `utf8-unchecked` feature removes such validation, potentially increasing
116//! parsing performance. To achieve this, the new constructors make use of unsafe code, thus the
117//! user should be aware that malformed `/proc/meminfo` data would cause undefined behaviour.
118
119// -----------------------------------------------------------------------------------------------
120// -- Crate modules --
121// -----------------------------------------------------------------------------------------------
122mod entry;
123mod parser;
124
125// -----------------------------------------------------------------------------------------------
126// -- Standard library imports --
127// -----------------------------------------------------------------------------------------------
128use std::fs::File;
129use std::io::{Read, Seek, SeekFrom};
130use std::{error, fmt, io, result};
131
132// -----------------------------------------------------------------------------------------------
133// -- Crate imports --
134// -----------------------------------------------------------------------------------------------
135use crate::parser::{MemInfoParser, MemInfoParserExtended};
136
137// -----------------------------------------------------------------------------------------------
138// -- Crate public imports (or re-exports) --
139// -----------------------------------------------------------------------------------------------
140pub use crate::entry::{MemInfoEntry, MemInfoEntryExtended, ParseSizeError};
141
142// -----------------------------------------------------------------------------------------------
143// -- Module type aliases --
144// -----------------------------------------------------------------------------------------------
145/// Return type of fallible [`MemInfo`] functions, or methods.
146pub type Result<T> = result::Result<T, MemInfoError>;
147
148// -----------------------------------------------------------------------------------------------
149// -- Module error types --
150// -----------------------------------------------------------------------------------------------
151/// A list of error categories related to manipulation of the `/proc/meminfo` pseudofile.
152#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
153pub enum MemInfoErrorKind {
154 /// Opening `/proc/meminfo` failed.
155 Open,
156 /// Reading `/proc/meminfo` failed.
157 Read,
158 /// Rewinding `/proc/meminfo` failed.
159 Rewind,
160 /// Seeking `/proc/meminfo` failed.
161 Seek,
162}
163
164impl fmt::Display for MemInfoErrorKind {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match *self {
167 Self::Open => f.write_str("open"),
168 Self::Read => f.write_str("read"),
169 Self::Rewind => f.write_str("rewind"),
170 Self::Seek => f.write_str("seek"),
171 }
172 }
173}
174
175/// The error type for the [`MemInfo`] struct type.
176#[derive(Debug)]
177pub struct MemInfoError {
178 /// The error category, (e.g. read, open, etc.).
179 kind: MemInfoErrorKind,
180 /// The error source (i.e. the reson why the error occurred).
181 source: io::Error,
182}
183
184impl MemInfoError {
185 /// Constructs a new instance of the error from a `kind` and a `source`.
186 #[inline]
187 const fn new(kind: MemInfoErrorKind, source: io::Error) -> Self {
188 Self { kind, source }
189 }
190
191 /// Constructs a new instance of the error from a `source`, with kind
192 /// [`MemInfoErrorKind::Open`].
193 #[inline]
194 const fn open(source: io::Error) -> Self {
195 Self::new(MemInfoErrorKind::Open, source)
196 }
197
198 /// Constructs a new instance of the error from a `source`, with kind
199 /// [`MemInfoErrorKind::Read`].
200 #[inline]
201 const fn read(source: io::Error) -> Self {
202 Self::new(MemInfoErrorKind::Read, source)
203 }
204
205 /// Constructs a new instance of the error from a `source`, with kind
206 /// [`MemInfoErrorKind::Rewind`].
207 #[inline]
208 const fn rewind(source: io::Error) -> Self {
209 Self::new(MemInfoErrorKind::Rewind, source)
210 }
211
212 /// Constructs a new instance of the error from a `source`, with kind
213 /// [`MemInfoErrorKind::Seek`].
214 #[inline]
215 const fn seek(source: io::Error) -> Self {
216 Self::new(MemInfoErrorKind::Seek, source)
217 }
218
219 /// Returns the error kind.
220 #[inline]
221 #[must_use]
222 pub fn kind(&self) -> &MemInfoErrorKind {
223 &self.kind
224 }
225}
226
227impl fmt::Display for MemInfoError {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 f.write_fmt(format_args!("failed to {} `/proc/meminfo`", self.kind))
230 }
231}
232
233impl error::Error for MemInfoError {
234 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
235 Some(&self.source)
236 }
237}
238
239// -----------------------------------------------------------------------------------------------
240// -- Module types --
241// -----------------------------------------------------------------------------------------------
242/// An object providing buffered access to the `/proc/meminfo` pseudofile.
243///
244/// This struct is responsible for retrieving memory-related information about the system. Its
245/// [constructor](`MemInfo::new`) attempts to:
246/// - **open** the `/proc/meminfo` pseudofile;
247/// - **read** its data into the internal buffer;
248/// - **rewind** to the beginning of the file stream, in order to prepare for the next read call.
249pub struct MemInfo {
250 /// The buffer holind data from `/proc/meminfo`.
251 buf: Vec<u8>,
252 /// The `/proc/meminfo` pseudofile.
253 file: File,
254}
255
256impl MemInfo {
257 /// Opens the `/proc/meminfo` pseudofile in read-only mode.
258 ///
259 /// # Errors
260 ///
261 /// This method returns an error if `/proc/meminfo` fails to open.
262 #[inline]
263 fn open() -> Result<File> {
264 File::open("/proc/meminfo").map_err(MemInfoError::open)
265 }
266
267 /// Constructs a new instance, opening the `/proc/meminfo` pseudofile, reading all of its
268 /// data into the internal buffer and rewinding to the beginning of the stream.
269 ///
270 /// # Errors
271 ///
272 /// This function returns an error if `/proc/meminfo` could not be opened, read into the
273 /// internal buffer, or rewinded.
274 #[inline]
275 pub fn new() -> Result<Self> {
276 let mut meminfo = Self {
277 file: Self::open()?,
278 buf: Vec::new(),
279 };
280
281 meminfo.read_to_end()?;
282 meminfo.rewind()?;
283
284 Ok(meminfo)
285 }
286
287 /// Constructs a new intance, opening the `/proc/meminfo` pseudofile, reading its data
288 /// into the internal buffer up to buffer `capacity` and rewinding to the beginning of the
289 /// stream.
290 ///
291 /// # Errors
292 ///
293 /// This function returns an error if `/proc/meminfo` could not be opened, read into the
294 /// internal buffer, or rewinded.
295 #[inline]
296 pub fn with_capacity(capacity: usize) -> Result<Self> {
297 let mut meminfo = Self {
298 file: Self::open()?,
299 buf: Vec::with_capacity(capacity),
300 };
301
302 meminfo.read()?;
303 meminfo.rewind()?;
304
305 Ok(meminfo)
306 }
307
308 /// Clears the internal buffer, removing its content without affecting its allocated capacity.
309 #[inline]
310 pub fn clear(&mut self) {
311 self.buf.clear();
312 }
313
314 /// Shrinks the capacity of the internal buffer as much as possible close to the buffer length.
315 ///
316 /// # Notes
317 ///
318 /// This method does **NOT** clear the internal buffer before attempting to resize its
319 /// capacity.
320 ///
321 /// If the current buffer capacity matches the buffer length, calling this method will result
322 /// in a **no-op**.
323 #[inline]
324 pub fn shrink_to_fit(&mut self) {
325 self.buf.shrink_to_fit();
326 }
327
328 /// Shrinks the capacity of the internal buffer with a lower `capacity` bound.
329 ///
330 /// # Notes
331 ///
332 /// This method does **NOT** clear the internal buffer before attempting to resize its
333 /// capacity, meaning that specifying a `size` smaller the buffer _length_ is equivalent to
334 /// calling [`MemInfo::shrink_to_fit`].
335 ///
336 /// Also, if the current buffer _capacity_ is smaller than the specified `size`, this method
337 /// will result in a **no-op**.
338 #[inline]
339 pub fn shrink_to(&mut self, capacity: usize) {
340 self.buf.shrink_to(capacity);
341 }
342
343 /// Rewinds `/proc/meminfo` to the beginning of the stream.
344 ///
345 /// # Notes
346 ///
347 /// Calling this method is equivalent to calling `self.seek(SeekFrom::Start(0))`.
348 ///
349 /// # Errors
350 ///
351 /// Returns an error if `/proc/meminfo` could not be rewinded.
352 #[inline]
353 pub fn rewind(&mut self) -> Result<()> {
354 self.file.rewind().map_err(MemInfoError::rewind)
355 }
356
357 /// Seeks `/proc/meminfo` to an offset and returns the new position from the start of the
358 /// stream.
359 ///
360 /// # Errors
361 ///
362 /// Returns an error if `/proc/meminfo` could not be sought.
363 #[inline]
364 pub fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
365 self.file.seek(pos).map_err(MemInfoError::seek)
366 }
367
368 /// Reads an exact number of `bytes` from `/proc/meminfo` into the internal buffer.
369 ///
370 /// # Notes
371 ///
372 /// The buffered data is **NOT** cleared before the new data is read into the buffer.
373 ///
374 /// # Errors
375 ///
376 /// This method returns an error if `/proc/meminfo` could not be read into the internal buffer.
377 #[inline]
378 pub fn read_exact(&mut self, bytes: u64) -> Result<usize> {
379 self.file
380 .by_ref()
381 .take(bytes)
382 .read_to_end(&mut self.buf)
383 .map_err(MemInfoError::read)
384 }
385
386 /// Reads the exact number of bytes from `/proc/meminfo` required to fill the internal buffer
387 /// and returns the number of bytes read.
388 ///
389 /// # Notes
390 ///
391 /// The buffered data is **NOT** cleared before the new data is read into the buffer.
392 ///
393 /// # Errors
394 ///
395 /// This method returns an error if `/proc/meminfo` could not be read into the internal buffer.
396 #[inline]
397 pub fn read(&mut self) -> Result<usize> {
398 let buf_capacity = self.buf.capacity();
399 self.read_exact(buf_capacity as u64)
400 }
401
402 /// Reads bytes from `/proc/meminfo` until `EOF` into the internal buffer and returns the total
403 /// number of bytes read.
404 ///
405 /// # Notes
406 ///
407 /// - The buffered data is **NOT** cleared before the new data is read into the buffer.
408 /// - If the internal buffer is not large enough, this method will allocate for data to fit.
409 ///
410 /// # Errors
411 ///
412 /// This method returns an error if `/proc/meminfo` could not be read into the internal buffer.
413 #[inline]
414 pub fn read_to_end(&mut self) -> Result<usize> {
415 self.file
416 .read_to_end(&mut self.buf)
417 .map_err(MemInfoError::read)
418 }
419
420 /// Returns a **lazy** iterator over parsed `/proc/meminfo` entries.
421 ///
422 /// # Notes
423 ///
424 /// For richer `/proc/meminfo` entry information see [`MemInfo::parse_extended`], which
425 /// is an extension of this methods, since it also collects each entry's start and end
426 /// positions in the file stream (useful for [`MemInfo::seek`] calls).
427 #[inline]
428 pub fn parse(&self) -> impl Iterator<Item = MemInfoEntry> {
429 MemInfoParser::new(&self.buf)
430 }
431
432 /// Returns an iterator over parsed `/proc/meminfo` entries.
433 /// Compared to [`MemInfo::parse`], in this case the elements being iterated over are extended
434 /// with information about the `start` and `end` bytes of the file they were parsed from.
435 ///
436 /// # Notes
437 ///
438 /// For simpler and slimmer `/proc/meminfo` entry information see [`MemInfo::parse`].
439 #[inline]
440 pub fn parse_extended(&self) -> impl Iterator<Item = MemInfoEntryExtended> {
441 MemInfoParserExtended::new(&self.buf)
442 }
443}
444
445impl fmt::Debug for MemInfo {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 f.debug_struct("MemInfo")
448 .field("buf", &BufDebug::from(&self.buf))
449 .field("file", &self.file)
450 .finish()
451 }
452}
453
454/// Helper struct implementing [`Debug`], used in the [`MemInfo`]'s [`Debug`] trait implemenation.
455///
456/// # Notes
457///
458/// It is used to replace the default `Vec<u8>` [`Debug`] implementation, which displays the
459/// actual bytes, with information about the vector's length and capacity.
460struct BufDebug {
461 /// The [`MemInfo`] interal buffer length.
462 length: usize,
463 /// The [`MemInfo`] interal buffer capacity.
464 capacity: usize,
465}
466
467impl From<&Vec<u8>> for BufDebug {
468 #[inline]
469 fn from(buf: &Vec<u8>) -> Self {
470 BufDebug {
471 length: buf.len(),
472 capacity: buf.capacity(),
473 }
474 }
475}
476
477impl fmt::Debug for BufDebug {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 f.write_fmt(format_args!(
480 "{{\n\tlength: {},\n\tcapacity: {}\n}}",
481 &self.length, &self.capacity
482 ))
483 }
484}