fact-rs 0.1.0

System facts, shipped to your door!
Documentation
use crate::filesystem::slurp;

use crate::components::Collector;
use anyhow::{Context, Result};
use serde::Serialize;
use serde_json::to_value;
use std::collections::HashMap;
use std::path::Path;

#[derive(Serialize, Debug)]
pub struct MemoryType {
    pub total_bytes: u64,
}

#[derive(Serialize, Debug)]
#[serde(transparent)]
pub struct MemoryFacts(pub HashMap<String, MemoryType>);

pub struct MemoryComponent;

impl MemoryComponent {
    pub fn new() -> Self {
        Self
    }
}
impl Collector for MemoryComponent {
    fn name(&self) -> &'static str {
        "memory"
    }

    fn collect(&self) -> Result<serde_json::Value> {
        let contents = slurp(Path::new("/proc/meminfo")).context("failed to read file")?;
        let meminfo = parse_meminfo(&contents);
        let facts = build_memory_facts(meminfo);
        let j = to_value(facts).context("serializing to json value")?;
        Ok(j)
    }
}

fn parse_meminfo(contents: &str) -> HashMap<String, u64> {
    contents
        .lines()
        .filter_map(|line| {
            let (label, rest) = line.split_once(':')?;
            let mut parts = rest.split_whitespace();
            let value = parts.next()?.parse::<u64>().ok()?;
            let multiplier = match parts.next() {
                Some("kB") => 1024,
                Some("mB") => 1_000_000,
                Some("MB") => 1_000_000,
                Some("B") | None => 1,
                _ => 1,
            };
            Some((label.to_string(), value * multiplier))
        })
        .collect()
}

fn build_memory_facts(meminfo: HashMap<String, u64>) -> MemoryFacts {
    let mut sections: HashMap<String, MemoryType> = HashMap::new();

    for (key, total_bytes) in meminfo.iter() {
        match key.as_str() {
            "MemTotal" => sections.insert(
                "real".to_string(),
                MemoryType {
                    total_bytes: *total_bytes,
                },
            ),
            "SwapTotal" => sections.insert(
                "swap".to_string(),
                MemoryType {
                    total_bytes: *total_bytes,
                },
            ),
            _ => continue,
        };
    }

    MemoryFacts(sections)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn test_parse_meminfo() {
        let meminfo_content = "MemTotal:       16384 kB\nSwapTotal:      8192 kB\n";
        let meminfo = parse_meminfo(meminfo_content);
        assert_eq!(meminfo.get("MemTotal"), Some(&16777216));
        assert_eq!(meminfo.get("SwapTotal"), Some(&8388608));
    }

    #[test]
    fn test_build_memory_facts() {
        let mut meminfo = HashMap::new();
        meminfo.insert("MemTotal".to_string(), 16777216);
        meminfo.insert("SwapTotal".to_string(), 8388608);
        let memory_facts = build_memory_facts(meminfo);
        assert_eq!(memory_facts.0.get("real").unwrap().total_bytes, 16777216);
        assert_eq!(memory_facts.0.get("swap").unwrap().total_bytes, 8388608);
    }
}