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}