obnam_benchmark/
result.rs

1use chrono::prelude::*;
2use git_testament::{git_testament, render_testament};
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5use std::fs::File;
6use std::iter::FromIterator;
7use std::path::{Path, PathBuf};
8
9git_testament!(TESTAMENT);
10
11#[derive(Debug, Deserialize, Serialize)]
12pub struct SuiteMeasurements {
13    measurements: Vec<OpMeasurements>,
14    obnam_version: String,
15    obnam_commit: Option<String>,
16    obnam_benchmark_version: String,
17    benchmark_started: String,
18    hostname: String,
19    host_cpus: usize,
20    host_ram: u64,
21}
22
23#[derive(Debug, Clone, Deserialize, Serialize)]
24pub struct OpMeasurements {
25    benchmark: String,
26    op: Operation,
27    measurements: Vec<Measurement>,
28}
29
30#[derive(Debug, Clone, Deserialize, Serialize)]
31pub enum Measurement {
32    TotalFiles(u64),
33    TotalData(u64),
34    DurationMs(u128),
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
38pub enum Operation {
39    Start,
40    Stop,
41    Create,
42    Rename,
43    Delete,
44    Backup,
45    Restore,
46    ManifestLive,
47    ManifestRestored,
48    CompareManiests,
49}
50
51#[derive(Debug, thiserror::Error)]
52pub enum SuiteMeasurementsError {
53    #[error("failed to get CPU info: {0}")]
54    CpuInfo(procfs::ProcError),
55
56    #[error("failed to get RAM info: {0}")]
57    MemInfo(procfs::ProcError),
58
59    #[error("failed to get hostname: {0}")]
60    Hostname(nix::Error),
61
62    #[error("failed to open result file {0} for reading: {1}")]
63    Open(PathBuf, std::io::Error),
64
65    #[error("failed to read result file {0}: {1}")]
66    Read(PathBuf, serde_json::Error),
67}
68
69impl SuiteMeasurements {
70    pub fn new(
71        obnam_version: String,
72        obnam_commit: Option<String>,
73    ) -> Result<Self, SuiteMeasurementsError> {
74        let cpu = procfs::CpuInfo::new().map_err(SuiteMeasurementsError::CpuInfo)?;
75        let mem = procfs::Meminfo::new().map_err(SuiteMeasurementsError::MemInfo)?;
76        let mut buf = [0u8; 1024];
77        let hostname =
78            nix::unistd::gethostname(&mut buf).map_err(SuiteMeasurementsError::Hostname)?;
79        let hostname = hostname.to_string_lossy();
80        Ok(Self {
81            measurements: vec![],
82            obnam_version,
83            obnam_commit,
84            obnam_benchmark_version: render_testament!(TESTAMENT),
85            benchmark_started: Utc::now().format("%Y-%m-%dT%H%M%S").to_string(),
86            hostname: hostname.to_string(),
87            host_ram: mem.mem_total,
88            host_cpus: cpu.num_cores(),
89        })
90    }
91
92    pub fn from_file(filename: &Path) -> Result<Self, SuiteMeasurementsError> {
93        let data = File::open(filename)
94            .map_err(|err| SuiteMeasurementsError::Open(filename.to_path_buf(), err))?;
95        let m: Self = serde_json::from_reader(&data)
96            .map_err(|err| SuiteMeasurementsError::Read(filename.to_path_buf(), err))?;
97        Ok(m)
98    }
99
100    pub fn hostname(&self) -> &str {
101        &self.hostname
102    }
103
104    pub fn timestamp(&self) -> &str {
105        &self.benchmark_started
106    }
107
108    pub fn cpus(&self) -> usize {
109        self.host_cpus
110    }
111
112    pub fn ram(&self) -> u64 {
113        self.host_ram
114    }
115
116    pub fn obnam_version(&self) -> &str {
117        &self.obnam_version
118    }
119
120    pub fn obnam_commit(&self) -> Option<&str> {
121        self.obnam_commit.as_deref()
122    }
123
124    pub fn push(&mut self, m: OpMeasurements) {
125        self.measurements.push(m);
126    }
127
128    pub fn benchmark_names(&self) -> Vec<String> {
129        let names: HashSet<&str> = HashSet::from_iter(self.measurements.iter().map(|m| m.name()));
130        let mut names: Vec<String> = names.iter().map(|x| x.to_string()).collect();
131        names.sort();
132        names
133    }
134
135    pub fn ops(&self) -> impl Iterator<Item = &OpMeasurements> {
136        self.measurements.iter()
137    }
138}
139
140impl OpMeasurements {
141    pub fn new(benchmark: &str, op: Operation) -> Self {
142        let benchmark = benchmark.to_string();
143        Self {
144            benchmark,
145            op,
146            measurements: vec![],
147        }
148    }
149
150    pub fn name(&self) -> &str {
151        &self.benchmark
152    }
153
154    pub fn push(&mut self, m: Measurement) {
155        self.measurements.push(m);
156    }
157
158    pub fn op(&self) -> Operation {
159        self.op
160    }
161
162    pub fn iter(&self) -> impl Iterator<Item = &Measurement> {
163        self.measurements.iter()
164    }
165
166    pub fn millis(&self) -> u128 {
167        for m in self.iter() {
168            if let Measurement::DurationMs(ms) = m {
169                return *ms;
170            }
171        }
172        0
173    }
174}