obnam_benchmark/
suite.rs

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
18/// A running benchmark suite.
19///
20/// This manages temporary data created for the benchmarks, and
21/// executes individual steps in the suite.
22pub struct Suite {
23    client: PathBuf,
24    server: PathBuf,
25    manager: DaemonManager,
26    benchmark: Option<Benchmark>,
27}
28
29/// Possible errors from running a benchmark suite.
30#[derive(Debug, thiserror::Error)]
31pub enum SuiteError {
32    /// Error building Obnam.
33    #[error(transparent)]
34    Build(ObnamBuilderError),
35
36    /// Error creating a temporary directory.
37    #[error(transparent)]
38    TempDir(#[from] std::io::Error),
39
40    /// File creation failed.
41    #[error("Failed to create file {0}: {1}")]
42    CreateFile(PathBuf, std::io::Error),
43
44    /// Error from counting files.
45    #[error("Error counting files in {0}: {1}")]
46    FileCount(PathBuf, walkdir::Error),
47
48    /// Error looking up file metadata.
49    #[error("Error looking up file metadata: {0}: {1}")]
50    FileMeta(PathBuf, walkdir::Error),
51
52    /// Error removing restored data.
53    #[error("Error removing temporary directory: {0}: {1}")]
54    RemoveRestored(PathBuf, std::io::Error),
55
56    /// Error using an Obnam client.
57    #[error(transparent)]
58    Client(#[from] ObnamClientError),
59
60    /// Error managing an Obnam server.
61    #[error(transparent)]
62    Server(#[from] ObnamServerError),
63
64    /// Suite already has a manifest with a given id.
65    #[error("Suite already has manifest {0}: this is a bug")]
66    ManifestExists(usize),
67
68    /// Suite doesn't have a manifest with a given id.
69    #[error("Suite doesn't have a manifest {0}: this is a bug")]
70    ManifestMissing(usize),
71
72    /// Manifests are not identical.
73    #[error("Manifests {0} and {1} are not identical, as expected")]
74    ManifestsDiffer(usize, usize),
75
76    /// Error running summain.
77    #[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    /// Execute one step in the benchmark suite.
92    ///
93    /// Return a measurement of the step.
94    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}