1use crate::builder::{ObnamBuilder, ObnamBuilderError};
2use crate::client::{ObnamClient, ObnamClientError};
3use crate::daemon::DaemonManager;
4use crate::junk::junk;
5use crate::result::{Measurement, OpMeasurements, Operation};
6use crate::server::{ObnamServer, ObnamServerError};
7use crate::specification::{Create, FileCount};
8use crate::step::Step;
9use crate::summain::{summain, SummainError};
10use log::{debug, error, info};
11use std::collections::HashMap;
12use std::fs::File;
13use std::path::{Path, PathBuf};
14use std::time::Instant;
15use tempfile::{tempdir, TempDir};
16use walkdir::WalkDir;
17
18pub struct Suite {
23 client: PathBuf,
24 server: PathBuf,
25 manager: DaemonManager,
26 benchmark: Option<Benchmark>,
27}
28
29#[derive(Debug, thiserror::Error)]
31pub enum SuiteError {
32 #[error(transparent)]
34 Build(ObnamBuilderError),
35
36 #[error(transparent)]
38 TempDir(#[from] std::io::Error),
39
40 #[error("Failed to create file {0}: {1}")]
42 CreateFile(PathBuf, std::io::Error),
43
44 #[error("Error counting files in {0}: {1}")]
46 FileCount(PathBuf, walkdir::Error),
47
48 #[error("Error looking up file metadata: {0}: {1}")]
50 FileMeta(PathBuf, walkdir::Error),
51
52 #[error("Error removing temporary directory: {0}: {1}")]
54 RemoveRestored(PathBuf, std::io::Error),
55
56 #[error(transparent)]
58 Client(#[from] ObnamClientError),
59
60 #[error(transparent)]
62 Server(#[from] ObnamServerError),
63
64 #[error("Suite already has manifest {0}: this is a bug")]
66 ManifestExists(usize),
67
68 #[error("Suite doesn't have a manifest {0}: this is a bug")]
70 ManifestMissing(usize),
71
72 #[error("Manifests {0} and {1} are not identical, as expected")]
74 ManifestsDiffer(usize, usize),
75
76 #[error(transparent)]
78 Summain(SummainError),
79}
80
81impl Suite {
82 pub fn new(builder: &ObnamBuilder) -> Result<Self, SuiteError> {
83 Ok(Self {
84 client: builder.client_binary().to_path_buf(),
85 server: builder.server_binary().to_path_buf(),
86 manager: DaemonManager::new(),
87 benchmark: None,
88 })
89 }
90
91 pub fn execute(&mut self, step: &Step) -> Result<OpMeasurements, SuiteError> {
95 let time = Instant::now();
96 eprintln!("step: {:?}", step);
97 let mut om = match step {
98 Step::Start(name) => {
99 assert!(self.benchmark.is_none());
100 let mut benchmark =
101 Benchmark::new(&self.client, &self.server, name, &self.manager)?;
102 let om = benchmark.start()?;
103 self.benchmark = Some(benchmark);
104 om
105 }
106 Step::Stop(name) => {
107 assert!(self.benchmark.is_some());
108 assert_eq!(name, self.benchmark.as_ref().unwrap().name());
109 let om = self.benchmark.as_mut().unwrap().stop()?;
110 self.benchmark = None;
111 om
112 }
113 Step::Create(x) => {
114 assert!(self.benchmark.is_some());
115 self.benchmark.as_mut().unwrap().create(x)?
116 }
117 Step::Rename(x) => {
118 assert!(self.benchmark.is_some());
119 self.benchmark.as_mut().unwrap().rename(x)?
120 }
121 Step::Delete(x) => {
122 assert!(self.benchmark.is_some());
123 self.benchmark.as_mut().unwrap().delete(x)?
124 }
125 Step::Backup(x) => {
126 assert!(self.benchmark.is_some());
127 self.benchmark.as_mut().unwrap().backup(*x)?
128 }
129 Step::Restore(x) => {
130 assert!(self.benchmark.is_some());
131 self.benchmark.as_mut().unwrap().restore(*x)?
132 }
133 Step::ManifestLive(id) => {
134 assert!(self.benchmark.is_some());
135 self.benchmark.as_mut().unwrap().manifest_live(*id)?
136 }
137 Step::ManifestRestored(id) => {
138 assert!(self.benchmark.is_some());
139 self.benchmark.as_mut().unwrap().manifest_restored(*id)?
140 }
141 Step::CompareManifests(first, second) => {
142 assert!(self.benchmark.is_some());
143 self.benchmark
144 .as_mut()
145 .unwrap()
146 .compare_manifests(*first, *second)?
147 }
148 };
149
150 let t = std::time::Duration::from_millis(10);
151 std::thread::sleep(t);
152
153 let ms = time.elapsed().as_millis();
154 debug!("step duration was {} ms", ms);
155 om.push(Measurement::DurationMs(ms));
156 Ok(om)
157 }
158}
159
160struct Benchmark {
161 name: String,
162 client: ObnamClient,
163 server: ObnamServer,
164 live: TempDir,
165 restored: TempDir,
166 gen_ids: HashMap<usize, String>,
167 manifests: HashMap<usize, String>,
168}
169
170impl Benchmark {
171 fn new(
172 client: &Path,
173 server: &Path,
174 name: &str,
175 manager: &DaemonManager,
176 ) -> Result<Self, SuiteError> {
177 let server = ObnamServer::new(server, manager)?;
178 let live = tempdir().map_err(SuiteError::TempDir)?;
179 let restored = tempdir().map_err(SuiteError::TempDir)?;
180 let client = ObnamClient::new(client, server.url(), live.path().to_path_buf())?;
181 Ok(Self {
182 name: name.to_string(),
183 client,
184 server,
185 live,
186 restored,
187 gen_ids: HashMap::new(),
188 manifests: HashMap::new(),
189 })
190 }
191
192 fn name(&self) -> &str {
193 &self.name
194 }
195
196 fn live(&self) -> PathBuf {
197 self.live.path().to_path_buf()
198 }
199
200 fn restored(&self) -> PathBuf {
201 self.restored.path().join("restored")
202 }
203
204 fn start(&mut self) -> Result<OpMeasurements, SuiteError> {
205 info!("starting benchmark {}", self.name());
206 self.client
207 .run(&["init", "--insecure-passphrase=hunter2"])?;
208 Ok(OpMeasurements::new(self.name(), Operation::Start))
209 }
210
211 fn stop(&mut self) -> Result<OpMeasurements, SuiteError> {
212 info!("ending benchmark {}", self.name);
213 self.server.stop();
214 Ok(OpMeasurements::new(self.name(), Operation::Stop))
215 }
216
217 fn create(&mut self, create: &Create) -> Result<OpMeasurements, SuiteError> {
218 let root = self.live();
219 info!(
220 "creating {} files of {} bytes each in {}",
221 create.files,
222 create.file_size,
223 root.display()
224 );
225
226 for i in 0..create.files {
227 let filename = root.join(format!("{}", i));
228 let mut f =
229 File::create(&filename).map_err(|err| SuiteError::CreateFile(filename, err))?;
230 junk(&mut f, create.file_size)?;
231 }
232
233 Ok(OpMeasurements::new(self.name(), Operation::Create))
234 }
235
236 fn rename(&mut self, count: &FileCount) -> Result<OpMeasurements, SuiteError> {
237 info!("renaming {} test data files", count.files);
238 Ok(OpMeasurements::new(self.name(), Operation::Rename))
239 }
240
241 fn delete(&mut self, count: &FileCount) -> Result<OpMeasurements, SuiteError> {
242 info!("deleting {} test data files", count.files);
243 Ok(OpMeasurements::new(self.name(), Operation::Delete))
244 }
245
246 fn backup(&mut self, n: usize) -> Result<OpMeasurements, SuiteError> {
247 info!("making backup {} in benchmark {}", n, self.name());
248 self.client.run(&["backup"])?;
249 let gen_id = self
250 .client
251 .run(&["resolve", "latest"])?
252 .strip_suffix('\n')
253 .or(Some(""))
254 .unwrap()
255 .to_string();
256 debug!("backed up generation {}", gen_id);
257 self.gen_ids.insert(n, gen_id);
258 let mut om = OpMeasurements::new(self.name(), Operation::Backup);
259 let stats = filestats(&self.live())?;
260 om.push(Measurement::TotalFiles(stats.count));
261 om.push(Measurement::TotalData(stats.size));
262 Ok(om)
263 }
264
265 fn restore(&mut self, n: usize) -> Result<OpMeasurements, SuiteError> {
266 info!("restoring backup {} in benchmark {}", n, self.name());
267 debug!("first removing all data from restore directory");
268 let restored = self.restored();
269 if restored.exists() {
270 std::fs::remove_dir_all(&restored)
271 .map_err(|err| SuiteError::RemoveRestored(restored, err))?;
272 }
273 let gen_id = self.gen_ids.get(&n).unwrap();
274 let path = self.restored().display().to_string();
275 self.client.run(&["restore", gen_id, &path])?;
276 Ok(OpMeasurements::new(self.name(), Operation::Restore))
277 }
278
279 fn manifest_live(&mut self, id: usize) -> Result<OpMeasurements, SuiteError> {
280 info!("make manifest {} of current test data", id);
281 if self.manifests.contains_key(&id) {
282 return Err(SuiteError::ManifestExists(id));
283 }
284 let m = summain(self.live.path()).map_err(SuiteError::Summain)?;
285 self.manifests.insert(id, m);
286 Ok(OpMeasurements::new(self.name(), Operation::ManifestLive))
287 }
288
289 fn manifest_restored(&mut self, id: usize) -> Result<OpMeasurements, SuiteError> {
290 info!("make manifest {} of latest restored data", id);
291 if self.manifests.contains_key(&id) {
292 return Err(SuiteError::ManifestExists(id));
293 }
294 debug!("self.restored()={}", self.restored().display());
295 let restored = format!(
296 "{}{}",
297 self.restored().display(),
298 self.live.path().display()
299 );
300 let restored = Path::new(&restored);
301 debug!("restored directory is {}", restored.display());
302 let m = summain(restored).map_err(SuiteError::Summain)?;
303 self.manifests.insert(id, m);
304 Ok(OpMeasurements::new(
305 self.name(),
306 Operation::ManifestRestored,
307 ))
308 }
309
310 fn compare_manifests(
311 &mut self,
312 first: usize,
313 second: usize,
314 ) -> Result<OpMeasurements, SuiteError> {
315 info!("compare manifests {} and {}", first, second);
316 let m1 = self.manifest(first)?;
317 let m2 = self.manifest(second)?;
318 if m1 != m2 {
319 error!("first manifest:\n{}", m1);
320 error!("second manifest:\n{}", m2);
321 return Err(SuiteError::ManifestsDiffer(first, second));
322 }
323 Ok(OpMeasurements::new(self.name(), Operation::CompareManiests))
324 }
325
326 fn manifest(&self, id: usize) -> Result<String, SuiteError> {
327 if let Some(m) = self.manifests.get(&id) {
328 Ok(m.clone())
329 } else {
330 Err(SuiteError::ManifestMissing(id))
331 }
332 }
333}
334
335#[derive(Debug, Default)]
336struct FileStats {
337 count: u64,
338 size: u64,
339}
340
341fn filestats(dirname: &Path) -> Result<FileStats, SuiteError> {
342 let mut stats = FileStats::default();
343 for e in WalkDir::new(dirname) {
344 let e = e.map_err(|err| SuiteError::FileCount(dirname.to_path_buf(), err))?;
345 stats.count += 1;
346 stats.size += e
347 .metadata()
348 .map_err(|err| SuiteError::FileMeta(e.path().to_path_buf(), err))?
349 .len();
350 }
351 Ok(stats)
352}