use core::time::Duration;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(autotune_persistence, derive(serde::Serialize, serde::Deserialize))]
pub struct ResourceBound {
pub amount: usize,
pub peak_per_s: f64,
}
impl ResourceBound {
pub fn time_at_peak(&self) -> Option<Duration> {
if self.peak_per_s.is_normal() {
Some(Duration::from_secs_f64(
self.amount as f64 / self.peak_per_s,
))
} else {
None
}
}
}
pub fn binding_resource(bounds: &[ResourceBound]) -> Option<&ResourceBound> {
bounds
.iter()
.filter(|bound| bound.time_at_peak().is_some())
.max_by_key(|bound| bound.time_at_peak())
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AchievedThroughput {
pub achieved_per_s: f64,
pub fraction_of_peak: f64,
}
pub fn score_resources(duration: Duration, bounds: &[ResourceBound]) -> Vec<AchievedThroughput> {
bounds
.iter()
.map(|bound| {
let achieved_per_s = if duration.is_zero() {
f64::NAN
} else {
bound.amount as f64 / duration.as_secs_f64()
};
AchievedThroughput {
achieved_per_s,
fraction_of_peak: achieved_per_s / bound.peak_per_s,
}
})
.collect()
}
pub fn binding_achieved(scores: &[AchievedThroughput]) -> Option<&AchievedThroughput> {
scores
.iter()
.filter(|score| score.fraction_of_peak.is_finite())
.max_by(|a, b| a.fraction_of_peak.total_cmp(&b.fraction_of_peak))
}
#[cfg(test)]
mod tests {
use super::*;
fn bound(amount: usize, peak_per_s: f64) -> ResourceBound {
ResourceBound { amount, peak_per_s }
}
#[test]
fn time_at_peak_is_amount_over_peak() {
assert_eq!(bound(8, 4.0).time_at_peak(), Some(Duration::from_secs(2)));
}
#[test]
fn time_at_peak_is_none_for_a_non_normal_peak() {
assert_eq!(bound(8, 0.0).time_at_peak(), None);
assert_eq!(bound(8, f64::NAN).time_at_peak(), None);
assert_eq!(bound(8, f64::INFINITY).time_at_peak(), None);
}
#[test]
fn binding_resource_is_the_one_needing_the_most_time_at_peak() {
let slower = bound(8, 4.0);
let faster = bound(8, 8.0);
assert_eq!(binding_resource(&[slower, faster]), Some(&slower));
}
#[test]
fn binding_resource_skips_non_normal_peaks_and_is_none_if_all_are() {
let unusable = bound(8, 0.0);
let usable = bound(8, 4.0);
assert_eq!(binding_resource(&[unusable, usable]), Some(&usable));
assert_eq!(binding_resource(&[unusable]), None);
assert_eq!(binding_resource(&[]), None);
}
#[test]
fn score_resources_reports_achieved_rate_and_fraction_of_peak() {
let bounds = [bound(100, 200.0), bound(400, 800.0)];
let scores = score_resources(Duration::from_secs(1), &bounds);
assert_eq!(scores[0].achieved_per_s, 100.0);
assert_eq!(scores[0].fraction_of_peak, 0.5);
assert_eq!(scores[1].achieved_per_s, 400.0);
assert_eq!(scores[1].fraction_of_peak, 0.5);
}
#[test]
fn a_zero_duration_reports_nan_instead_of_dividing_by_zero() {
let scores = score_resources(Duration::ZERO, &[bound(100, 200.0)]);
assert!(scores[0].achieved_per_s.is_nan());
assert!(scores[0].fraction_of_peak.is_nan());
}
#[test]
fn resources_with_different_peaks_score_independently_and_pick_the_slower_one() {
let duration = Duration::from_secs(1);
let read = bound(900_000, 1_000_000.0); let write = bound(100_000, 200_000.0);
assert_eq!(binding_resource(&[read, write]), Some(&read));
let scores = score_resources(duration, &[read, write]);
assert_eq!(scores[0].achieved_per_s, 900_000.0);
assert_eq!(scores[0].fraction_of_peak, 0.9);
assert_eq!(scores[1].achieved_per_s, 100_000.0);
assert_eq!(scores[1].fraction_of_peak, 0.5);
let binding = binding_achieved(&scores).unwrap();
assert_eq!(binding.fraction_of_peak, 0.9);
}
#[test]
fn binding_achieved_skips_non_finite_entries_and_is_none_if_all_are() {
let finite = AchievedThroughput {
achieved_per_s: 10.0,
fraction_of_peak: 0.4,
};
let non_finite = AchievedThroughput {
achieved_per_s: f64::NAN,
fraction_of_peak: f64::NAN,
};
assert_eq!(
binding_achieved(&[non_finite, finite])
.unwrap()
.fraction_of_peak,
0.4
);
assert!(binding_achieved(&[non_finite]).is_none());
assert!(binding_achieved(&[]).is_none());
}
}