obnam_benchmark/
result.rs1use 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}