use std::time::Duration;
use malachitebft_peer::PeerId;
use super::{Score, ScoringStrategy, SyncResult};
#[derive(Copy, Clone, Debug)]
pub struct ExponentialMovingAverage {
pub alpha_success: f64,
pub alpha_timeout: f64,
pub alpha_failure: f64,
pub slow_threshold: Duration,
}
impl Default for ExponentialMovingAverage {
fn default() -> Self {
Self::new(
0.2, 0.1, 0.15, Duration::from_secs(1), )
}
}
impl ExponentialMovingAverage {
pub fn new(
alpha_success: f64,
alpha_timeout: f64,
alpha_failure: f64,
slow_threshold: Duration,
) -> Self {
assert!(
(0.0..=1.0).contains(&alpha_success),
"alpha_success must be between 0.0 and 1.0"
);
assert!(
(0.0..=1.0).contains(&alpha_timeout),
"alpha_timeout must be between 0.0 and 1.0"
);
assert!(
(0.0..=1.0).contains(&alpha_failure),
"alpha_failure must be between 0.0 and 1.0"
);
assert!(
slow_threshold.as_secs_f64() > 0.0,
"slow_threshold must be greater than zero"
);
Self {
alpha_success,
alpha_timeout,
alpha_failure,
slow_threshold,
}
}
}
impl ScoringStrategy for ExponentialMovingAverage {
fn initial_score(&self, _peer_id: PeerId) -> Score {
0.5 }
fn update_score(&mut self, previous_score: Score, result: SyncResult) -> Score {
match result {
SyncResult::Success(response_time) => {
let response_time_secs = response_time.as_secs_f64();
let threshold_secs = self.slow_threshold.as_secs_f64();
let quality = if response_time_secs <= threshold_secs {
1.0
} else {
(-(response_time_secs - threshold_secs) / threshold_secs).exp()
};
let new_score =
self.alpha_success * quality + (1.0 - self.alpha_success) * previous_score;
#[cfg(test)]
{
println!("Response time: {response_time_secs:.3}s, Quality: {quality:.3}");
println!(" => Updating score: prev={previous_score:.3}, new={new_score:.3}");
}
new_score
}
SyncResult::Timeout => {
(1.0 - self.alpha_timeout) * previous_score
}
SyncResult::Failure => {
(1.0 - self.alpha_failure) * previous_score
}
}
}
}