use crate::{Error, Result};
use std::time::Instant;
#[derive(Debug)]
pub(super) struct WrappingCounter {
modulus: f64,
last: f64,
}
impl WrappingCounter {
pub(super) fn new(modulus: f64, initial: f64) -> Self {
Self {
modulus,
last: initial,
}
}
pub(super) fn delta(&mut self, current: f64) -> f64 {
let previous = std::mem::replace(&mut self.last, current);
if current >= previous {
current - previous
} else {
current - previous + self.modulus
}
}
}
#[derive(Debug, Default)]
pub(super) struct Interval {
last_read_at: Option<Instant>,
}
impl Interval {
pub(super) fn tick(&mut self) -> Option<f64> {
let now = Instant::now();
let previous = self.last_read_at.replace(now)?;
let elapsed = now.duration_since(previous).as_secs_f64();
(elapsed > 0.0).then_some(elapsed)
}
}
pub(super) fn repeat_failure(sensor: &'static str, error: &Error) -> Error {
match error {
Error::PermissionDenied {
sensor: original,
detail,
} => Error::permission(original, detail.clone()),
other => Error::sensor(sensor, other.to_string()),
}
}
pub(super) fn warn_unavailable<T>(sensor: &'static str, opened: &Result<T>) {
if let Err(e) = opened {
log::warn!(
"{sensor} could not be opened, so CPU power will be reported as unavailable: {e}"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_rising_counter_reports_the_plain_difference() {
let mut counter = WrappingCounter::new(100.0, 10.0);
assert_eq!(counter.delta(30.0), 20.0);
assert_eq!(counter.delta(45.0), 15.0);
}
#[test]
fn a_wrapped_counter_spans_the_wrap_point() {
let mut counter = WrappingCounter::new(100.0, 90.0);
assert_eq!(counter.delta(5.0), 15.0);
}
#[test]
fn an_unchanged_counter_reports_no_energy() {
let mut counter = WrappingCounter::new(100.0, 42.0);
assert_eq!(counter.delta(42.0), 0.0);
}
#[test]
fn the_first_tick_has_nothing_to_measure_against() {
let mut interval = Interval::default();
assert_eq!(interval.tick(), None);
assert!(interval.tick().is_some_and(|elapsed| elapsed > 0.0));
}
#[test]
fn a_permission_failure_keeps_its_variant() {
let stored = Error::permission("Intel RAPL", "not readable");
assert!(matches!(
repeat_failure("Intel RAPL", &stored),
Error::PermissionDenied { .. }
));
}
#[test]
fn any_other_failure_repeats_its_reason() {
let stored = Error::sensor("Intel RAPL", "no powercap domains");
let repeated = repeat_failure("Intel RAPL", &stored);
assert!(
repeated.to_string().contains("no powercap domains"),
"unexpected error: {repeated}"
);
}
}