summaryrefslogtreecommitdiff
path: root/src/specification.rs
blob: 4eeb430744c7ca4e55a7dd52ab3be73f5d4c63fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/// 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<Benchmark>,
}

/// 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<Backup>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Backup {
    pub changes: Vec<Change>,
}

#[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<Self, SpecificationError> {
        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<Step> {
        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
    }
}