diff options
Diffstat (limited to 'src/report.rs')
-rw-r--r-- | src/report.rs | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/src/report.rs b/src/report.rs new file mode 100644 index 0000000..0538760 --- /dev/null +++ b/src/report.rs @@ -0,0 +1,164 @@ +use crate::result::{Measurement, Operation, SuiteMeasurements}; +use std::collections::HashSet; +use std::io::Write; +use std::iter::FromIterator; + +#[derive(Debug, Default)] +pub struct Reporter { + results: Vec<SuiteMeasurements>, +} + +impl Reporter { + pub fn push(&mut self, result: SuiteMeasurements) { + self.results.push(result); + } + + pub fn write(&self, f: &mut dyn Write) -> Result<(), std::io::Error> { + // Summary tables of all benchmarks. + writeln!(f, "# Summaries of all benchmark runs")?; + let hosts = self.hosts(); + for bench in self.benchmarks() { + writeln!(f)?; + writeln!(f, "## Benchmark {}", bench)?; + for op in [Operation::Backup, Operation::Restore] { + for host in hosts.iter() { + writeln!(f)?; + writeln!(f, "Table: {:?} on host {}, times in ms", op, host)?; + writeln!(f)?; + let mut want_headings = true; + for r in self.results_for(host) { + // Column data + let cols = self.durations(r, &bench, op); + + if want_headings { + want_headings = false; + self.headings(f, &cols)?; + } + self.rows(f, r, &cols)?; + } + } + } + } + + writeln!(f)?; + writeln!(f, "# Individual benchmark runs")?; + for host in self.hosts() { + writeln!(f)?; + writeln!(f, "## Host `{}`", host)?; + for r in self.results_for(&host) { + writeln!(f)?; + writeln!(f, "### Benchmark run {}", r.timestamp())?; + writeln!(f)?; + writeln!(f, "* CPUs: {}", r.cpus())?; + writeln!(f, "* RAM: {} MiB", r.ram() / 1024 / 1024)?; + for bench in r.benchmark_names() { + writeln!(f)?; + writeln!(f, "#### Benchmark `{}`", bench)?; + writeln!(f)?; + for opm in r.ops().filter(|o| o.name() == bench) { + let op = opm.op(); + match op { + Operation::Backup => { + writeln!(f, "* Backup: {} ms", opm.millis())?; + } + Operation::Restore => { + writeln!(f, "* Restore: {} ms", opm.millis())?; + } + _ => continue, + } + for m in opm.iter() { + match m { + Measurement::DurationMs(_) => (), + Measurement::TotalFiles(n) => { + writeln!(f, " * files: {}", n)?; + } + &Measurement::TotalData(n) => { + writeln!(f, " * data: {} bytes", n)?; + } + } + } + } + + for op in [Operation::Backup, Operation::Restore] { + writeln!(f)?; + writeln!(f, "Table: {:?}", op)?; + writeln!(f)?; + let cols = self.durations(r, &bench, op); + self.headings(f, &cols)?; + self.rows(f, r, &cols)?; + } + } + } + } + + Ok(()) + } + + fn durations(&self, r: &SuiteMeasurements, bench: &str, op: Operation) -> Vec<u128> { + r.ops() + .filter(|r| r.name() == bench) + .filter(|r| r.op() == op) + .map(|r| r.millis()) + .collect() + } + + fn headings(&self, f: &mut dyn Write, durations: &[u128]) -> Result<(), std::io::Error> { + // Column headings. + let mut headings: Vec<String> = durations + .iter() + .enumerate() + .map(|(i, _)| format!("step {}", i)) + .collect(); + headings.insert(0, "Version".to_string()); + for h in headings.iter() { + write!(f, "| {}", h)?; + } + writeln!(f, "|")?; + + // Heading separator. + for _ in headings.iter() { + write!(f, "|----")?; + } + writeln!(f, "|")?; + Ok(()) + } + + fn rows( + &self, + f: &mut dyn Write, + r: &SuiteMeasurements, + durations: &[u128], + ) -> Result<(), std::io::Error> { + write!(f, "| {}", r.obnam_version())?; + for ms in durations.iter() { + write!(f, "| {}", ms)?; + } + writeln!(f, "|")?; + Ok(()) + } + + fn benchmarks(&self) -> Vec<String> { + let mut names = HashSet::new(); + for r in self.results.iter() { + for opm in r.ops() { + names.insert(opm.name()); + } + } + let mut names: Vec<String> = names.iter().map(|x| x.to_string()).collect(); + names.sort(); + names + } + + fn hosts(&self) -> Vec<String> { + let names: HashSet<&str> = HashSet::from_iter(self.results.iter().map(|r| r.hostname())); + let mut names: Vec<String> = names.iter().map(|x| x.to_string()).collect(); + names.sort(); + names + } + + fn results_for<'a>(&'a self, hostname: &'a str) -> impl Iterator<Item = &'a SuiteMeasurements> { + self.results + .iter() + .filter(move |r| r.hostname() == hostname) + } +} |