summaryrefslogtreecommitdiff
path: root/src/report.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/report.rs')
-rw-r--r--src/report.rs164
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)
+ }
+}