meminfo/
entry.rs

1// -----------------------------------------------------------------------------------------------
2// -- Standard library imports --
3// -----------------------------------------------------------------------------------------------
4use std::num::ParseIntError;
5use std::ops::{Deref, DerefMut, Range};
6use std::{error, fmt, str};
7
8// -----------------------------------------------------------------------------------------------
9// -- Module error types --
10// -----------------------------------------------------------------------------------------------
11/// The error type for [`MemInfoEntry`] **size** parsing.
12#[derive(Debug)]
13pub struct ParseSizeError {
14    /// **Label** of the [`MemInfoEntry`] whose size could not be parsed.
15    pub(crate) label: String,
16    /// **Size** of the [`MemInfoEntry`] that could not be parsed.
17    pub(crate) size: String,
18    /// The error **source** (i.e. the reson why [`MemInfoEntry`] size could not be parsed).
19    pub(crate) source: ParseIntError,
20}
21
22impl ParseSizeError {
23    /// Constructs a new instance of the error from the `label` of the entry whose `size` could
24    /// not be parsed as `usize`, the string representation of the entry `size` that could not be
25    /// parsed and the `source` of the error (i.e. the error cause).
26    #[inline]
27    fn new(label: &str, size: &str, source: ParseIntError) -> Self {
28        Self {
29            label: label.to_owned(),
30            size: size.to_owned(),
31            source,
32        }
33    }
34}
35
36impl fmt::Display for ParseSizeError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        f.write_fmt(format_args!(
39            "failed to parse `{size}` as usize in `/proc/meminfo` entry `{label}`",
40            size = self.size,
41            label = self.label
42        ))
43    }
44}
45
46impl error::Error for ParseSizeError {
47    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
48        Some(&self.source)
49    }
50}
51
52// -----------------------------------------------------------------------------------------------
53// -- Module types --
54// -----------------------------------------------------------------------------------------------
55/// A parsed `/proc/meminfo` entry.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct MemInfoEntry<'m> {
58    /// Entry **label** (e.g. `MemAvailable`).
59    pub(crate) label: &'m str,
60    /// Entry **size** (e.g. `3911344`).
61    pub(crate) size: &'m str,
62    /// Entry **unit**, if present (e.g. `kB`).
63    ///
64    /// # Notes
65    ///
66    /// Unit might be missing for entries which do not represent memory sizes
67    /// (e.g. `HugePages_Total`, `HugePages_Free`, etc.).
68    pub(crate) unit: Option<&'m str>,
69}
70
71impl<'m> MemInfoEntry<'m> {
72    /// The delimiter between a `/proc/meminfo` entry label and its size.
73    pub(crate) const DELIMITER: u8 = b':';
74
75    /// Constructs a new instance from entry `label`, `size`, and `unit`, converting fields from
76    /// UTF-8 bytes to string slices.
77    ///
78    /// # Panics
79    ///
80    /// This functions panics if `bytes` do not represent valid UTF-8.
81    #[inline]
82    #[cfg(not(feature = "utf8-unchecked"))]
83    pub(crate) fn new(label: &'m [u8], size: &'m [u8], unit: Option<&'m [u8]>) -> Self {
84        /// Converts a slice of bytes to a string slice, validating its content to be UTF-8.
85        ///
86        /// # Panics
87        ///
88        /// This functions panics if `bytes` do not represent valid UTF-8.
89        #[inline(always)]
90        fn from_utf8(bytes: &[u8]) -> &str {
91            str::from_utf8(bytes).expect("`/proc/meminfo` to contain valid UTF-8")
92        }
93
94        Self {
95            label: from_utf8(label),
96            size: from_utf8(size),
97            unit: unit.map(from_utf8),
98        }
99    }
100
101    /// Constructs a new instance from entry `label`, `size` and `unit`, converting fields from
102    /// UTF-8 bytes to string slices.
103    ///
104    /// # Safety
105    ///
106    /// This function assumes `label`, `size` and `unit` (`/proc/meminfo` data) to be valid
107    /// UTF-8 bytes. If this is not the case (e.g. malformed `/proc/meminfo` data), calling this
108    /// function will result in undefined behaviour. In case of doubt, please use
109    /// [`MemInfoEntry::new`] instead, since performs UTF-8 validation and panics on malformed
110    /// data.
111    #[inline]
112    #[must_use]
113    #[cfg(feature = "utf8-unchecked")]
114    pub(crate) unsafe fn new_unchecked(
115        label: &'m [u8],
116        size: &'m [u8],
117        unit: Option<&'m [u8]>,
118    ) -> Self {
119        Self {
120            label: unsafe { str::from_utf8_unchecked(label) },
121            size: unsafe { str::from_utf8_unchecked(size) },
122            unit: unit.map(|unit| unsafe { str::from_utf8_unchecked(unit) }),
123        }
124    }
125
126    /// Returns the entry label (e.g. `MemAvailable`).
127    #[inline]
128    #[must_use]
129    pub fn label(&self) -> &str {
130        self.label
131    }
132
133    /// Returns the entry size (e.g. `3911344`).
134    ///
135    /// # Errors
136    ///
137    /// This method returns an error if the entry size could not be parsed as `usize`.
138    #[inline]
139    pub fn size(&self) -> Result<usize, ParseSizeError> {
140        self.size
141            .parse()
142            .map_err(|source| ParseSizeError::new(self.label, self.size, source))
143    }
144
145    /// Returns the entry unit, if present (e.g. `kB`).
146    #[inline]
147    #[must_use]
148    pub fn unit(&self) -> Option<&str> {
149        self.unit
150    }
151}
152
153/// A parsed `/proc/meminfo` entry with additional information about its position in the file
154/// stream.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct MemInfoEntryExtended<'m> {
157    /// The actual `/proc/meminfo` entry.
158    pub(crate) entry: MemInfoEntry<'m>,
159    /// The range of bytes this entry was parsed from in the `/proc/meminfo` file stream.
160    pub(crate) range: Range<usize>,
161}
162
163impl<'m> MemInfoEntryExtended<'m> {
164    /// Constructs a new instance from a plain `entry`, and the `start` and `end` byte positions
165    /// of the entry in the `/proc/meminfo` file stream.
166    #[inline]
167    pub(crate) fn new(entry: MemInfoEntry<'m>, start: usize, end: usize) -> Self {
168        Self {
169            entry,
170            range: start..end,
171        }
172    }
173
174    /// Returns the byte range of the entry in the `/proc/meminfo` file stream.
175    #[inline]
176    #[must_use]
177    pub fn byte_range(&self) -> &Range<usize> {
178        &self.range
179    }
180
181    /// Returns the start position of the entry in the `/proc/meminfo` file stream.
182    #[inline]
183    #[must_use]
184    pub fn start_pos(&self) -> usize {
185        self.range.start
186    }
187
188    /// Returns the end position of the entry in the `/proc/meminfo` file stream.
189    #[inline]
190    #[must_use]
191    pub fn end_pos(&self) -> usize {
192        self.range.end
193    }
194
195    /// Returns the required buffer capacity to read fit the entry raw bytes.
196    #[inline]
197    #[must_use]
198    pub fn required_capacity(&self) -> usize {
199        self.range.end - self.range.start
200    }
201}
202
203impl<'m> Deref for MemInfoEntryExtended<'m> {
204    type Target = MemInfoEntry<'m>;
205
206    fn deref(&self) -> &Self::Target {
207        &self.entry
208    }
209}
210
211impl<'m> DerefMut for MemInfoEntryExtended<'m> {
212    fn deref_mut(&mut self) -> &mut Self::Target {
213        &mut self.entry
214    }
215}