obnam_benchmark/
specification.rs

1/// A specification for a set of benchmarks for Obnam.
2///
3/// One specification can contain any number of benchmarks. For each
4/// benchmark, any number of backups can be specified. For each
5/// benchmark, the specification contains instructions for how to
6/// create or change the data being backed up.
7///
8/// The specification can be serialized into linear sequence of steps
9/// for execution.
10use crate::step::Step;
11use serde::{Deserialize, Serialize};
12use std::collections::HashSet;
13use std::fs::File;
14use std::path::{Path, PathBuf};
15
16/// A benchmark specification.
17#[derive(Debug, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct Specification {
20    benchmarks: Vec<Benchmark>,
21}
22
23/// Possible errors from loading a specification from a file.
24#[derive(Debug, thiserror::Error)]
25pub enum SpecificationError {
26    /// Two benchmarks have the same name.
27    #[error("Duplicate benchmark name {0}")]
28    DuplicateBenchmark(
29        /// The name of the benchmark.
30        String,
31    ),
32
33    /// I/O error opening the specification file.
34    #[error("Couldn't open {0}: {1}")]
35    Open(
36        /// The name of the specification file.
37        PathBuf,
38        /// The I/O error.
39        std::io::Error,
40    ),
41
42    /// YAML parsing problem in the specification file.
43    #[error("Couldn't read YAML specification from {0}:\n  {1}")]
44    Yaml(
45        /// The name of the specification file.
46        PathBuf,
47        /// The YAML error.
48        serde_yaml::Error,
49    ),
50}
51
52#[derive(Debug, Serialize, Deserialize)]
53struct Benchmark {
54    benchmark: String,
55    backups: Vec<Backup>,
56}
57
58#[derive(Debug, Serialize, Deserialize)]
59struct Backup {
60    pub changes: Vec<Change>,
61}
62
63#[derive(Debug, Serialize, Deserialize)]
64pub(crate) enum Change {
65    #[serde(rename = "create")]
66    Create(Create),
67
68    #[serde(rename = "delete")]
69    Delete(FileCount),
70
71    #[serde(rename = "rename")]
72    Rename(FileCount),
73}
74
75/// How many files to create, and how big they should be.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct Create {
78    /// File count.
79    pub files: u64,
80    /// Size, in bytes, of each file.
81    pub file_size: u64,
82}
83
84/// How many files to operate on.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct FileCount {
87    /// File count.
88    pub files: u64,
89}
90
91impl Specification {
92    /// Load a benchmark specification from a named file.
93    pub fn from_file(filename: &Path) -> Result<Self, SpecificationError> {
94        let f = File::open(filename)
95            .map_err(|err| SpecificationError::Open(filename.to_path_buf(), err))?;
96        let spec: Specification = serde_yaml::from_reader(f)
97            .map_err(|err| SpecificationError::Yaml(filename.to_path_buf(), err))?;
98        spec.check()?;
99        Ok(spec)
100    }
101
102    fn check(&self) -> Result<(), SpecificationError> {
103        let mut names = HashSet::new();
104        for name in self.benchmarks.iter().map(|b| b.benchmark.to_string()) {
105            if names.contains(&name) {
106                return Err(SpecificationError::DuplicateBenchmark(name));
107            }
108            names.insert(name);
109        }
110        Ok(())
111    }
112
113    /// Serialize the specification into a sequence of steps to execute it.
114    pub fn steps(&self) -> Vec<Step> {
115        let mut steps = vec![];
116        for b in self.benchmarks.iter() {
117            let n = b.backups.len();
118            let after_base = n;
119            let restore_base = 2 * n;
120
121            steps.push(Step::Start(b.benchmark.to_string()));
122            for (i, backup) in b.backups.iter().enumerate() {
123                for change in backup.changes.iter() {
124                    steps.push(Step::from(change));
125                }
126                steps.push(Step::ManifestLive(i));
127                steps.push(Step::Backup(i));
128                let after = after_base + i;
129                steps.push(Step::ManifestLive(after));
130                steps.push(Step::CompareManifests(i, after));
131            }
132            for (i, _) in b.backups.iter().enumerate() {
133                steps.push(Step::Restore(i));
134                let restored = restore_base + i;
135                steps.push(Step::ManifestRestored(restored));
136                steps.push(Step::CompareManifests(i, restored));
137            }
138            steps.push(Step::Stop(b.benchmark.to_string()));
139        }
140        steps
141    }
142}