obnam-benchmark 0.1.0

a backup program
Documentation
use chrono::prelude::*;
use git_testament::{git_testament, render_testament};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs::File;
use std::iter::FromIterator;
use std::path::{Path, PathBuf};

git_testament!(TESTAMENT);

#[derive(Debug, Deserialize, Serialize)]
pub struct SuiteMeasurements {
    measurements: Vec<OpMeasurements>,
    obnam_version: String,
    obnam_commit: Option<String>,
    obnam_benchmark_version: String,
    benchmark_started: String,
    hostname: String,
    host_cpus: usize,
    host_ram: u64,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OpMeasurements {
    benchmark: String,
    op: Operation,
    measurements: Vec<Measurement>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Measurement {
    TotalFiles(u64),
    TotalData(u64),
    DurationMs(u128),
}

#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub enum Operation {
    Start,
    Stop,
    Create,
    Rename,
    Delete,
    Backup,
    Restore,
    ManifestLive,
    ManifestRestored,
    CompareManiests,
}

#[derive(Debug, thiserror::Error)]
pub enum SuiteMeasurementsError {
    #[error("failed to get CPU info: {0}")]
    CpuInfo(procfs::ProcError),

    #[error("failed to get RAM info: {0}")]
    MemInfo(procfs::ProcError),

    #[error("failed to get hostname: {0}")]
    Hostname(nix::Error),

    #[error("failed to open result file {0} for reading: {1}")]
    Open(PathBuf, std::io::Error),

    #[error("failed to read result file {0}: {1}")]
    Read(PathBuf, serde_json::Error),
}

impl SuiteMeasurements {
    pub fn new(
        obnam_version: String,
        obnam_commit: Option<String>,
    ) -> Result<Self, SuiteMeasurementsError> {
        let cpu = procfs::CpuInfo::new().map_err(SuiteMeasurementsError::CpuInfo)?;
        let mem = procfs::Meminfo::new().map_err(SuiteMeasurementsError::MemInfo)?;
        let mut buf = [0u8; 1024];
        let hostname =
            nix::unistd::gethostname(&mut buf).map_err(SuiteMeasurementsError::Hostname)?;
        let hostname = hostname.to_string_lossy();
        Ok(Self {
            measurements: vec![],
            obnam_version,
            obnam_commit,
            obnam_benchmark_version: render_testament!(TESTAMENT),
            benchmark_started: Utc::now().format("%Y-%m-%dT%H%M%S").to_string(),
            hostname: hostname.to_string(),
            host_ram: mem.mem_total,
            host_cpus: cpu.num_cores(),
        })
    }

    pub fn from_file(filename: &Path) -> Result<Self, SuiteMeasurementsError> {
        let data = File::open(filename)
            .map_err(|err| SuiteMeasurementsError::Open(filename.to_path_buf(), err))?;
        let m: Self = serde_json::from_reader(&data)
            .map_err(|err| SuiteMeasurementsError::Read(filename.to_path_buf(), err))?;
        Ok(m)
    }

    pub fn hostname(&self) -> &str {
        &self.hostname
    }

    pub fn timestamp(&self) -> &str {
        &self.benchmark_started
    }

    pub fn cpus(&self) -> usize {
        self.host_cpus
    }

    pub fn ram(&self) -> u64 {
        self.host_ram
    }

    pub fn obnam_version(&self) -> &str {
        &self.obnam_version
    }

    pub fn obnam_commit(&self) -> Option<&str> {
        self.obnam_commit.as_deref()
    }

    pub fn push(&mut self, m: OpMeasurements) {
        self.measurements.push(m);
    }

    pub fn benchmark_names(&self) -> Vec<String> {
        let names: HashSet<&str> = HashSet::from_iter(self.measurements.iter().map(|m| m.name()));
        let mut names: Vec<String> = names.iter().map(|x| x.to_string()).collect();
        names.sort();
        names
    }

    pub fn ops(&self) -> impl Iterator<Item = &OpMeasurements> {
        self.measurements.iter()
    }
}

impl OpMeasurements {
    pub fn new(benchmark: &str, op: Operation) -> Self {
        let benchmark = benchmark.to_string();
        Self {
            benchmark,
            op,
            measurements: vec![],
        }
    }

    pub fn name(&self) -> &str {
        &self.benchmark
    }

    pub fn push(&mut self, m: Measurement) {
        self.measurements.push(m);
    }

    pub fn op(&self) -> Operation {
        self.op
    }

    pub fn iter(&self) -> impl Iterator<Item = &Measurement> {
        self.measurements.iter()
    }

    pub fn millis(&self) -> u128 {
        for m in self.iter() {
            if let Measurement::DurationMs(ms) = m {
                return *ms;
            }
        }
        0
    }
}