energy-bench 0.2.5

Methods for benchmarking the energy consumption of programs.
use std::{
    ops::Index,
    path::PathBuf,
    thread,
    time::Duration,
};

use file_backed_value::FileBackedValue;
use gethostname::gethostname;
use indexmap::IndexMap;

use crate::EnergyAccumulator;

pub struct IdlePower(IndexMap<String, f32>);

impl IdlePower {
    pub fn init(path: Option<&PathBuf>, idle_seconds: usize, probes: &mut Box<dyn EnergyAccumulator>) -> Self {
        let filename = gethostname()
            .to_ascii_lowercase()
            .to_str()
            .map_or(format!("idle-{idle_seconds}s.json"), |user| {
                format!("idle-{idle_seconds}s-{user}.json")
            });

        let mut file = if let Some(path) = path {
            FileBackedValue::new_at(&filename, &path)
        } else {
            FileBackedValue::new(&filename)
        };

        log::info!("Idle file (will be) stored at {:?}", file.path());
        file.set_dirty_time(Duration::from_hours(18));
        let idle = file
            .get_or_insert_with(|| measure_idle(probes, idle_seconds));

        let idle = match idle {
            Ok(idle) => idle.clone(),
            Err(e) => {
                log::error!("Error reading idle file: {:?}", e);
                log::warn!("Recalculating idle due to file error");
                measure_idle(probes, idle_seconds)
            },
        };

        Self(idle)
    }
}

impl Index<&str> for IdlePower {
    type Output = f32;

    fn index(&self, key: &str) -> &Self::Output {
        &self.0[key]
    }
}

fn measure_idle(probes: &mut Box<dyn EnergyAccumulator>, idle_seconds: usize) -> IndexMap<String, f32> {
    if idle_seconds > 0 {
        let before = idle_seconds.min(30);
        log::trace!("Waiting for system to stabilise for {}s", before);
        thread::sleep(Duration::from_secs(before as u64));
    }

    log::info!("Measuring idle for {}s", idle_seconds);

    probes.reset();
    thread::sleep(Duration::from_secs(1));
    let mut min_power = probes.elapsed();
    min_power.shift_remove("runtime");

    for _ in 1..idle_seconds {
        probes.reset();
        thread::sleep(Duration::from_secs(1));
        for (k, v) in probes.elapsed() {
            min_power[&k] = min_power[&k].min(v);
        }
    }

    log::info!("Idle power draw: {:#?}", min_power);
    min_power
}