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
}
}