Skip to content

Commit

Permalink
refactor: General refactor and documentation update
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoradocchia committed Nov 29, 2023
1 parent 5e06394 commit f09b64f
Show file tree
Hide file tree
Showing 6 changed files with 946 additions and 404 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 4 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
[package]
name = "linux-meminfo"
name = "meminfo"
version = "0.1.0"
edition = "2021"
authors = ["Marco Radocchia <marco.radocchia@outlook.com"]
rust-version = "1.73.0"
description = "A zero-allocations /proc/meminfo parsing library for Rust."
readme = "README.md"
repository = "https://github.com/marcoradocchia/linux-meminfo"
license = "GPL-3.0-only"
keywords = ["linux", "meminfo", "procfs", "memory", "parser"]
categories = ["parsing"]

[lib]
name = "meminfo"
path = "src/lib.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[features]
utf8-unchecked = []
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Linux MemInfo

This library provides easy and low level access to `meminfo`, the _pseudofile_
placed by the Linux kernel inside the `proc` _pseudo-filesystem_ (for more
information, see the `proc` manpage, online version at
<https://man7.org/linux/man-pages/man5/proc.5.html>).

The public API is built around the `MemInfo` type, a struct responsible for
retrieving memory-related information about the system. Calling its constructor
opens the `/proc/meminfo` pseudofile and reads its data into an internal buffer.
Having [`MemInfo`] to own both the open file and a buffer of its data allows
separation of concerns between _reading_ from the pseudofile, _managing_ and
_parsing_ the buffered data.

## Examples

The following example shows the most basic usage of the [`MemInfo`] API. First
we construct a new instance, which translates to `/proc/meminfo` being opened
and read into the internal buffer; then we call the [`MemInfo::parse`], which
returns a **lazy** iterator over parsed entries, in this case represented by
the [`MemInfoEntry`] type. The iterator being lazy meaning it parses a new
entry on each call to the `next` method. In other words: you pay only for the
entries you parse.

```rust
use std::error;

use meminfo::MemInfo;

fn main() -> Result<(), Box<dyn error::Error>> {
let mut meminfo = MemInfo::new()?;
let mut entries = meminfo.parse();

let mem_total = entries.next().unwrap();
assert_eq!("MemTotal", mem_total.label());
assert_eq!(Some("kB"), mem_total.unit());

println!("System's total usable RAM: {}kB", mem_total.size()?);

Ok(())
}
```

## License

This library is licensed under the terms of the [GPLv3](LICENSE) license.

## Contributions

Any contribution is welcome and encouraged.
193 changes: 193 additions & 0 deletions src/entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// -----------------------------------------------------------------------------------------------
// -- Standard library imports --
// -----------------------------------------------------------------------------------------------
use std::num::ParseIntError;
use std::ops::{Deref, DerefMut, Range};
use std::{error, fmt, str};

// -----------------------------------------------------------------------------------------------
// -- Module error types --
// -----------------------------------------------------------------------------------------------
/// The error type for [`MemInfoEntry`] **size** parsing.
#[derive(Debug)]
pub struct ParseSizeError {
/// **Label** of the [`MemInfoEntry`] whose size could not be parsed.
pub(crate) label: String,
/// **Size** of the [`MemInfoEntry`] that could not be parsed.
pub(crate) size: String,
/// The error **source** (i.e. the reson why [`MemInfoEntry`] size could not be parsed).
pub(crate) source: ParseIntError,
}

impl ParseSizeError {
/// Constructs a new instance of the error from the `label` of the entry whose `size` could
/// not be parsed as `usize`, the string representation of the entry `size` that could not be
/// parsed and the `source` of the error (i.e. the error cause).
#[inline]
fn new(label: &str, size: &str, source: ParseIntError) -> Self {
Self {
label: label.to_owned(),
size: size.to_owned(),
source,
}
}
}

impl fmt::Display for ParseSizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"failed to parse `{size}` as usize in `/proc/meminfo` entry `{label}`",
size = self.size,
label = self.label
))
}
}

impl error::Error for ParseSizeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(&self.source)
}
}

// -----------------------------------------------------------------------------------------------
// -- Module types --
// -----------------------------------------------------------------------------------------------
/// A parsed `/proc/meminfo` entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemInfoEntry<'m> {
/// Entry **label** (e.g. `MemAvailable`).
pub(crate) label: &'m str,
/// Entry **size** (e.g. `3911344`).
pub(crate) size: &'m str,
/// Entry **unit**, if present (e.g. `kB`).
///
/// # Notes
///
/// Unit might be missing for entries which do not represent memory sizes
/// (e.g. `HugePages_Total`, `HugePages_Free`, etc.).
pub(crate) unit: Option<&'m str>,
}

impl<'m> MemInfoEntry<'m> {
/// The delimiter between a `/proc/meminfo` entry label and its size.
pub(crate) const DELIMITER: u8 = b':';

/// Constructs a new instance from entry `label`, `size`, and `unit`, converting fields from
/// UTF-8 bytes to string slices.
///
/// # Panics
///
/// This functions panics if `bytes` do not represent valid UTF-8.
#[inline]
#[cfg(not(feature = "utf8-unchecked"))]
pub(crate) fn new(label: &'m [u8], size: &'m [u8], unit: Option<&'m [u8]>) -> Self {
/// Converts a slice of bytes to a string slice, validating its content to be UTF-8.
///
/// # Panics
///
/// This functions panics if `bytes` do not represent valid UTF-8.
#[inline(always)]
fn from_utf8(bytes: &[u8]) -> &str {
str::from_utf8(bytes).expect("`/proc/meminfo` to contain valid UTF-8")
}

Self {
label: from_utf8(label),
size: from_utf8(size),
unit: unit.map(from_utf8),
}
}

/// Constructs a new instance from entry `label`, `size` and `unit`, converting fields from
/// UTF-8 bytes to string slices.
///
/// # Safety
///
/// This function assumes `label`, `size` and `unit` (`/proc/meminfo` data) to be valid
/// UTF-8 bytes. If this is not the case (e.g. malformed `/proc/meminfo` data), calling this
/// function will result in undefined behaviour. In case of doubt, please use
/// [`MemInfoEntry::new`] instead, since performs UTF-8 validation and panics on malformed
/// data.
#[inline]
#[must_use]
#[cfg(feature = "utf8-unchecked")]
pub(crate) unsafe fn new_unchecked(
label: &'m [u8],
size: &'m [u8],
unit: Option<&'m [u8]>,
) -> Self {
Self {
label: unsafe { str::from_utf8_unchecked(label) },
size: unsafe { str::from_utf8_unchecked(size) },
unit: unit.map(|unit| unsafe { str::from_utf8_unchecked(unit) }),
}
}

/// Returns the entry label (e.g. `MemAvailable`).
#[inline]
#[must_use]
pub fn label(&self) -> &str {
self.label
}

/// Returns the entry size (e.g. `3911344`).
///
/// # Errors
///
/// This method returns an error if the entry size could not be parsed as `usize`.
#[inline]
pub fn size(&self) -> Result<usize, ParseSizeError> {
self.size
.parse()
.map_err(|source| ParseSizeError::new(self.label, self.size, source))
}

/// Returns the entry unit, if present (e.g. `kB`).
#[inline]
#[must_use]
pub fn unit(&self) -> Option<&str> {
self.unit
}
}

/// A parsed `/proc/meminfo` entry with additional information about its position in the file
/// stream.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemInfoEntryExtended<'m> {
/// The actual `/proc/meminfo` entry.
pub(crate) entry: MemInfoEntry<'m>,
/// The range of bytes this entry was parsed from in the `/proc/meminfo` file stream.
pub(crate) range: Range<usize>,
}

impl<'m> MemInfoEntryExtended<'m> {
/// Constructs a new instance from a plain `entry`, and the `start` and `end` byte positions
/// of the entry in the `/proc/meminfo` file stream.
#[inline]
pub(crate) fn new(entry: MemInfoEntry<'m>, start: usize, end: usize) -> Self {
Self {
entry,
range: start..end,
}
}

#[inline]
#[must_use]
pub fn byte_range(&self) -> &Range<usize> {
&self.range
}
}

impl<'m> Deref for MemInfoEntryExtended<'m> {
type Target = MemInfoEntry<'m>;

fn deref(&self) -> &Self::Target {
&self.entry
}
}

impl<'m> DerefMut for MemInfoEntryExtended<'m> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.entry
}
}
Loading

0 comments on commit f09b64f

Please sign in to comment.