use crate::daemon::DaemonManager; use crate::obnam::{Obnam, ObnamError}; use crate::result::{Measurement, OpMeasurements, Operation}; use crate::server::{ObnamServer, ObnamServerError}; use crate::specification::{Create, FileCount}; use crate::step::Step; use log::{debug, info}; use std::fs::File; use std::path::{Path, PathBuf}; use std::time::Instant; use walkdir::WalkDir; /// A running benchmark suite. /// /// This manages temporary data created for the benchmarks, and /// executes individual steps in the suite. pub struct Suite { manager: DaemonManager, benchmark: Option, } /// Possible errors from running a benchmark suite. #[derive(Debug, thiserror::Error)] pub enum SuiteError { /// Error creating a temporary directory. #[error(transparent)] TempDir(#[from] std::io::Error), /// File creation failed. #[error("Failed to create file {0}: {1}")] CreateFile(PathBuf, std::io::Error), /// Error from counting files. #[error("Error counting files in {0}: {1}")] FileCount(PathBuf, walkdir::Error), /// Error looking up file metadata. #[error("Error looking up file metadata: {0}: {1}")] FileMeta(PathBuf, walkdir::Error), /// Error managing an Obnam system. #[error(transparent)] Obnam(#[from] ObnamError), /// Error managing an Obnam server. #[error(transparent)] Server(#[from] ObnamServerError), } impl Suite { pub fn new() -> Result { Ok(Self { manager: DaemonManager::new(), benchmark: None, }) } /// Execute one step in the benchmark suite. /// /// Return a measurement of the step. pub fn execute(&mut self, step: &Step) -> Result { debug!("executing step {:?}", step); let time = Instant::now(); let mut om = match step { Step::Start(name) => { assert!(self.benchmark.is_none()); let mut benchmark = Benchmark::new(name, &self.manager)?; let om = benchmark.start()?; self.benchmark = Some(benchmark); om } Step::Stop(name) => { assert!(self.benchmark.is_some()); assert_eq!(name, self.benchmark.as_ref().unwrap().name()); let om = self.benchmark.as_mut().unwrap().stop()?; self.benchmark = None; om } Step::Create(x) => { assert!(self.benchmark.is_some()); self.benchmark.as_mut().unwrap().create(x)? } Step::Rename(x) => { assert!(self.benchmark.is_some()); self.benchmark.as_mut().unwrap().rename(x)? } Step::Delete(x) => { assert!(self.benchmark.is_some()); self.benchmark.as_mut().unwrap().delete(x)? } Step::Backup(x) => { assert!(self.benchmark.is_some()); self.benchmark.as_mut().unwrap().backup(*x)? } Step::Restore(x) => { assert!(self.benchmark.is_some()); self.benchmark.as_mut().unwrap().restore(*x)? } }; let t = std::time::Duration::from_millis(10); std::thread::sleep(t); let ms = time.elapsed().as_millis(); debug!("step duration was {} ms", ms); om.push(Measurement::DurationMs(ms)); Ok(om) } } struct Benchmark { name: String, // We store an Obnam in an Option so that we can destroy the // Obnam, and thereby delete any temporary files. We want to do // that intentionally, so that it can be measured. obnam: Option, server: Option, } impl Benchmark { fn new(name: &str, manager: &DaemonManager) -> Result { Ok(Self { name: name.to_string(), obnam: Some(Obnam::new()?), server: Some(ObnamServer::new(manager)?), }) } fn name(&self) -> &str { &self.name } fn obnam(&mut self) -> &mut Obnam { self.obnam.as_mut().unwrap() } fn start(&mut self) -> Result { info!("starting benchmark {}", self.name()); self.obnam().start_server()?; Ok(OpMeasurements::new(self.name(), Operation::Start)) } fn stop(&mut self) -> Result { info!("ending benchmark {}", self.name); self.obnam().stop_server()?; self.obnam.take().unwrap(); // This destroys the Obnam self.server.as_mut().unwrap().stop(); Ok(OpMeasurements::new(self.name(), Operation::Stop)) } fn create(&mut self, create: &Create) -> Result { info!("creating {} test data files", create.files); let root = self.obnam().root(); debug!("creating {} files in {}", create.files, root.display()); for i in 0..create.files { let filename = root.join(format!("{}", i)); debug!("creating {}", filename.display()); File::create(&filename).map_err(|err| SuiteError::CreateFile(filename, err))?; } Ok(OpMeasurements::new(self.name(), Operation::Create)) } fn rename(&mut self, count: &FileCount) -> Result { info!("renaming {} test data files", count.files); Ok(OpMeasurements::new(self.name(), Operation::Rename)) } fn delete(&mut self, count: &FileCount) -> Result { info!("deleting {} test data files", count.files); Ok(OpMeasurements::new(self.name(), Operation::Delete)) } fn backup(&mut self, n: usize) -> Result { info!("making backup {} in benchmark {}", n, self.name()); let mut om = OpMeasurements::new(self.name(), Operation::Backup); let stats = filestats(self.obnam().root())?; om.push(Measurement::TotalFiles(stats.count)); om.push(Measurement::TotalData(stats.size)); Ok(om) } fn restore(&mut self, n: usize) -> Result { info!("restoring backup {} in benchmark {}", n, self.name()); Ok(OpMeasurements::new(self.name(), Operation::Restore)) } } #[derive(Debug, Default)] struct FileStats { count: u64, size: u64, } fn filestats(dirname: &Path) -> Result { let mut stats = FileStats::default(); for e in WalkDir::new(dirname) { let e = e.map_err(|err| SuiteError::FileCount(dirname.to_path_buf(), err))?; stats.count += 1; stats.size += e .metadata() .map_err(|err| SuiteError::FileMeta(e.path().to_path_buf(), err))? .len(); } Ok(stats) }