hyperfoot 0.2.1

Benchmark the resource footprint of commands
use crate::measure::{Accounting, RunStats};

#[derive(Debug, Clone)]
pub struct BenchResult {
    pub command: String,
    pub runs: Vec<RunStats>,
}

impl BenchResult {
    pub fn mean_wall_secs(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.wall_time.as_secs_f64()))
    }

    pub fn stddev_wall_secs(&self) -> f64 {
        stddev(self.runs.iter().map(|r| r.wall_time.as_secs_f64()))
    }

    pub fn mean_cpu_secs(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.cpu_time.as_secs_f64()))
    }

    pub fn mean_cpu_percent(&self) -> f64 {
        mean(self.runs.iter().map(|r| {
            let wall = r.wall_time.as_secs_f64();
            if wall > 0.0 {
                100.0 * r.cpu_time.as_secs_f64() / wall
            } else {
                0.0
            }
        }))
    }

    pub fn mean_peak_memory_bytes(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.peak_memory_bytes as f64))
    }

    pub fn mean_disk_read_bytes(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.disk_read_bytes as f64))
    }

    pub fn mean_disk_write_bytes(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.disk_write_bytes as f64))
    }

    pub fn mean_max_processes(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.max_processes as f64))
    }

    pub fn mean_max_threads(&self) -> f64 {
        mean(self.runs.iter().map(|r| r.max_threads as f64))
    }

    /// Whether every run in this result used exact cgroup accounting, or at
    /// least one fell back to sampling.
    pub fn accounting(&self) -> Accounting {
        if self.runs.iter().all(|r| r.accounting == Accounting::Cgroup) {
            Accounting::Cgroup
        } else {
            Accounting::Sampled
        }
    }
}

fn mean(values: impl Iterator<Item = f64> + Clone) -> f64 {
    let (sum, count) = values.fold((0.0, 0usize), |(s, c), v| (s + v, c + 1));
    if count == 0 { 0.0 } else { sum / count as f64 }
}

fn stddev(values: impl Iterator<Item = f64> + Clone) -> f64 {
    let m = mean(values.clone());
    let (sum_sq, count) = values.fold((0.0, 0usize), |(s, c), v| (s + (v - m).powi(2), c + 1));
    if count < 2 {
        0.0
    } else {
        (sum_sq / (count - 1) as f64).sqrt()
    }
}