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, obnam_version: String, obnam_commit: Option, 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, } #[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, ) -> Result { 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 { 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 { let names: HashSet<&str> = HashSet::from_iter(self.measurements.iter().map(|m| m.name())); let mut names: Vec = names.iter().map(|x| x.to_string()).collect(); names.sort(); names } pub fn ops(&self) -> impl Iterator { 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 { self.measurements.iter() } pub fn millis(&self) -> u128 { for m in self.iter() { if let Measurement::DurationMs(ms) = m { return *ms; } } 0 } }