use crate::entry::{MemInfoEntry, MemInfoEntryExtended};
trait ByteClassify {
fn is_whitespace(&self) -> bool;
fn is_not_delimiter(&self) -> bool;
}
impl ByteClassify for u8 {
fn is_whitespace(&self) -> bool {
*self == b' '
}
fn is_not_delimiter(&self) -> bool {
*self != MemInfoEntry::DELIMITER
}
}
#[derive(Debug)]
pub(crate) struct MemInfoParser<'m> {
bytes: &'m [u8],
cursor: usize,
}
impl<'m> MemInfoParser<'m> {
#[inline]
pub(crate) fn new(bytes: &'m [u8]) -> Self {
Self { bytes, cursor: 0 }
}
#[inline]
fn offset<F>(&self, predicate: F) -> usize
where
F: Fn(&u8) -> bool,
{
self.bytes[self.cursor..]
.iter()
.take_while(|byte| predicate(byte))
.count()
}
#[inline]
fn skip_whitespace(&mut self) {
self.cursor += self.offset(u8::is_whitespace);
}
#[inline]
fn is_not_eof(&mut self) -> bool {
self.cursor += self.offset(u8::is_ascii_whitespace);
self.cursor < self.bytes.len()
}
#[inline]
fn byte_slice(&self, offset: usize) -> &'m [u8] {
&self.bytes[self.cursor..self.cursor + offset]
}
#[inline]
fn get_label(&mut self) -> &'m [u8] {
self.skip_whitespace();
let offset = self.offset(u8::is_not_delimiter);
let label = self.byte_slice(offset);
self.cursor += offset + 1;
label
}
#[inline]
fn get_size(&mut self) -> &'m [u8] {
self.skip_whitespace();
let offset = self.offset(u8::is_ascii_digit);
let size = self.byte_slice(offset);
self.cursor += offset;
size
}
#[inline]
fn get_unit(&mut self) -> Option<&'m [u8]> {
self.skip_whitespace();
let offset = self.offset(u8::is_ascii_alphabetic);
(offset > 0).then(|| {
let unit = self.byte_slice(offset);
self.cursor += offset;
unit
})
}
}
impl<'m> From<&'m str> for MemInfoParser<'m> {
#[inline]
fn from(value: &'m str) -> Self {
MemInfoParser::new(value.as_bytes())
}
}
impl<'m> Iterator for MemInfoParser<'m> {
type Item = MemInfoEntry<'m>;
fn next(&mut self) -> Option<Self::Item> {
self.is_not_eof().then(|| {
let label = self.get_label();
let size = self.get_size();
let unit = self.get_unit();
#[cfg(not(feature = "utf8-unchecked"))]
return MemInfoEntry::new(label, size, unit);
#[cfg(feature = "utf8-unchecked")]
return unsafe { MemInfoEntry::new_unchecked(label, size, unit) };
})
}
}
#[derive(Debug)]
pub(crate) struct MemInfoParserExtended<'m> {
parser: MemInfoParser<'m>,
}
impl<'m> MemInfoParserExtended<'m> {
#[inline]
pub(crate) fn new(bytes: &'m [u8]) -> Self {
Self {
parser: MemInfoParser::new(bytes),
}
}
}
impl<'m> From<&'m str> for MemInfoParserExtended<'m> {
#[inline]
fn from(string: &'m str) -> Self {
MemInfoParserExtended::new(string.as_bytes())
}
}
impl<'m> Iterator for MemInfoParserExtended<'m> {
type Item = MemInfoEntryExtended<'m>;
fn next(&mut self) -> Option<Self::Item> {
self.parser.is_not_eof().then(|| {
let start = self.parser.cursor;
let label = self.parser.get_label();
let size = self.parser.get_size();
let unit = self.parser.get_unit();
let end = self.parser.cursor;
#[cfg(not(feature = "utf8-unchecked"))]
let entry = MemInfoEntry::new(label, size, unit);
#[cfg(feature = "utf8-unchecked")]
let entry = unsafe { MemInfoEntry::new_unchecked(label, size, unit) };
MemInfoEntryExtended::new(entry, start, end)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const MEMINFO: &'static str = r#"MemTotal: 16333776 kB
MemFree: 11779556 kB
MemAvailable: 12900200 kB
Buffers: 149028 kB
SwapTotal: 16777212 kB
SwapFree: 16777212 kB
HugePages_Surp: 0
"#;
#[test]
fn parse_entries() {
let mut parser = MemInfoParser::from(MEMINFO);
assert_eq!(
Some(MemInfoEntry {
label: "MemTotal",
size: "16333776",
unit: Some("kB"),
}),
parser.next(),
);
assert_eq!(
Some(MemInfoEntry {
label: "MemFree",
size: "11779556",
unit: Some("kB"),
}),
parser.next(),
);
assert_eq!(
Some(MemInfoEntry {
label: "MemAvailable",
size: "12900200",
unit: Some("kB"),
}),
parser.next(),
);
assert_eq!(
Some(MemInfoEntry {
label: "Buffers",
size: "149028",
unit: Some("kB"),
}),
parser.next(),
);
assert_eq!(
Some(MemInfoEntry {
label: "SwapTotal",
size: "16777212",
unit: Some("kB"),
}),
parser.next(),
);
assert_eq!(
Some(MemInfoEntry {
label: "SwapFree",
size: "16777212",
unit: Some("kB"),
}),
parser.next(),
);
assert_eq!(
Some(MemInfoEntry {
label: "HugePages_Surp",
size: "0",
unit: None,
}),
parser.next(),
);
assert_eq!(None, parser.next());
}
#[test]
fn parse_entries_extended() {
let entries = MEMINFO.lines().collect::<Vec<&str>>();
let mut parser = MemInfoParserExtended::from(MEMINFO);
let start = 0;
let end = entries[0].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "MemTotal",
size: "16333776",
unit: Some("kB"),
},
range: start..end,
}),
parser.next(),
);
let start = end + 1;
let end = start + entries[1].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "MemFree",
size: "11779556",
unit: Some("kB"),
},
range: start..end,
}),
parser.next(),
);
let start = end + 1;
let end = start + entries[2].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "MemAvailable",
size: "12900200",
unit: Some("kB"),
},
range: start..end,
}),
parser.next(),
);
let start = end + 1;
let end = start + entries[3].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "Buffers",
size: "149028",
unit: Some("kB"),
},
range: start..end,
}),
parser.next(),
);
let start = end + 1;
let end = start + entries[4].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "SwapTotal",
size: "16777212",
unit: Some("kB"),
},
range: start..end,
}),
parser.next(),
);
let start = end + 1;
let end = start + entries[5].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "SwapFree",
size: "16777212",
unit: Some("kB"),
},
range: start..end,
}),
parser.next(),
);
let start = end + 1;
let end = start + entries[6].len();
assert_eq!(
Some(MemInfoEntryExtended {
entry: MemInfoEntry {
label: "HugePages_Surp",
size: "0",
unit: None,
},
range: start..end,
}),
parser.next(),
);
assert_eq!(None, parser.next());
}
}