/// A specification for a set of benchmarks for Obnam. /// /// One specification can contain any number of benchmarks. For each /// benchmark, any number of backups can be specified. For each /// benchmark, the specification contains instructions for how to /// create or change the data being backed up. /// /// The specification can be serialized into linear sequence of steps /// for execution. use crate::step::Step; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::fs::File; use std::path::{Path, PathBuf}; /// A benchmark specification. #[derive(Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Specification { benchmarks: Vec, } /// Possible errors from loading a specification from a file. #[derive(Debug, thiserror::Error)] pub enum SpecificationError { /// Two benchmarks have the same name. #[error("Duplicate benchmark name {0}")] DuplicateBenchmark( /// The name of the benchmark. String, ), /// I/O error opening the specification file. #[error("Couldn't open {0}: {1}")] Open( /// The name of the specification file. PathBuf, /// The I/O error. std::io::Error, ), /// YAML parsing problem in the specification file. #[error("Couldn't read YAML specification from {0}:\n {1}")] Yaml( /// The name of the specification file. PathBuf, /// The YAML error. serde_yaml::Error, ), } #[derive(Debug, Serialize, Deserialize)] struct Benchmark { benchmark: String, backups: Vec, } #[derive(Debug, Serialize, Deserialize)] struct Backup { pub changes: Vec, } #[derive(Debug, Serialize, Deserialize)] pub(crate) enum Change { #[serde(rename = "create")] Create(Create), #[serde(rename = "delete")] Delete(FileCount), #[serde(rename = "rename")] Rename(FileCount), } /// How many files to create, and how big they should be. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Create { /// File count. pub files: u64, /// Size, in bytes, of each file. pub file_size: u64, } /// How many files to operate on. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FileCount { /// File count. pub files: u64, } impl Specification { /// Load a benchmark specification from a named file. pub fn from_file(filename: &Path) -> Result { let f = File::open(filename) .map_err(|err| SpecificationError::Open(filename.to_path_buf(), err))?; let spec: Specification = serde_yaml::from_reader(f) .map_err(|err| SpecificationError::Yaml(filename.to_path_buf(), err))?; spec.check()?; Ok(spec) } fn check(&self) -> Result<(), SpecificationError> { let mut names = HashSet::new(); for name in self.benchmarks.iter().map(|b| b.benchmark.to_string()) { if names.contains(&name) { return Err(SpecificationError::DuplicateBenchmark(name)); } names.insert(name); } Ok(()) } /// Serialize the specification into a sequence of steps to execute it. pub fn steps(&self) -> Vec { let mut steps = vec![]; for b in self.benchmarks.iter() { let n = b.backups.len(); let after_base = n; let restore_base = 2 * n; steps.push(Step::Start(b.benchmark.to_string())); for (i, backup) in b.backups.iter().enumerate() { for change in backup.changes.iter() { steps.push(Step::from(change)); } steps.push(Step::ManifestLive(i)); steps.push(Step::Backup(i)); let after = after_base + i; steps.push(Step::ManifestLive(after)); steps.push(Step::CompareManifests(i, after)); } for (i, _) in b.backups.iter().enumerate() { steps.push(Step::Restore(i)); let restored = restore_base + i; steps.push(Step::ManifestRestored(restored)); steps.push(Step::CompareManifests(i, restored)); } steps.push(Step::Stop(b.benchmark.to_string())); } steps } }