use crate::client::{ObnamClient, ObnamClientError}; use crate::daemon::DaemonManager; use crate::junk::junk; 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 tempfile::{tempdir, TempDir}; 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 using an Obnam client. #[error(transparent)] Client(#[from] ObnamClientError), /// 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 { let time = Instant::now(); eprintln!("step: {:?}", step); 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, client: ObnamClient, server: ObnamServer, live: TempDir, } impl Benchmark { fn new(name: &str, manager: &DaemonManager) -> Result { let server = ObnamServer::new(manager)?; let live = tempdir().map_err(SuiteError::TempDir)?; let client = ObnamClient::new(server.url(), live.path().to_path_buf())?; Ok(Self { name: name.to_string(), client, server, live, }) } fn name(&self) -> &str { &self.name } fn live(&self) -> PathBuf { self.live.path().to_path_buf() } fn start(&mut self) -> Result { info!("starting benchmark {}", self.name()); self.client .run(&["init", "--insecure-passphrase=hunter2"])?; Ok(OpMeasurements::new(self.name(), Operation::Start)) } fn stop(&mut self) -> Result { info!("ending benchmark {}", self.name); self.server.stop(); Ok(OpMeasurements::new(self.name(), Operation::Stop)) } fn create(&mut self, create: &Create) -> Result { let root = self.live(); info!( "creating {} files of {} bytes each in {}", create.files, create.file_size, root.display() ); for i in 0..create.files { let filename = root.join(format!("{}", i)); let mut f = File::create(&filename).map_err(|err| SuiteError::CreateFile(filename, err))?; junk(&mut f, create.file_size)?; } 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()); self.client.run(&["backup"])?; let mut om = OpMeasurements::new(self.name(), Operation::Backup); let stats = filestats(&self.live())?; 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) }