use std::time::Duration;
#[derive(Clone, Debug)]
pub struct Gradient2Config {
pub initial_concurrency: f64,
pub min_concurrency: f64,
pub max_concurrency: f64,
pub tolerance: f64,
pub gain: f64,
pub smoothing: f64,
pub failure_factor: f64,
}
impl Default for Gradient2Config {
fn default() -> Self {
Self {
initial_concurrency: 1.0,
min_concurrency: 0.25,
max_concurrency: 1024.0,
tolerance: 1.5,
gain: 0.1,
smoothing: 0.2,
failure_factor: 0.7,
}
}
}
#[derive(Clone, Debug)]
pub struct Gradient2 {
config: Gradient2Config,
concurrency: f64,
last_gradient: f64,
updates: u64,
}
impl Gradient2 {
pub fn new(config: Gradient2Config) -> Self {
assert!(
config.min_concurrency.is_finite()
&& config.min_concurrency > 0.0
&& config.max_concurrency.is_finite()
&& config.max_concurrency >= config.min_concurrency,
"Gradient2 concurrency bounds must be finite and ordered"
);
assert!(
config.initial_concurrency >= config.min_concurrency
&& config.initial_concurrency <= config.max_concurrency,
"initial concurrency must be within the Gradient2 bounds"
);
assert!(
config.tolerance.is_finite()
&& config.tolerance >= 1.0
&& config.gain.is_finite()
&& config.gain > 0.0,
"Gradient2 tolerance must be finite and >= 1 and gain must be positive"
);
assert!(
config.smoothing.is_finite()
&& (0.0..=1.0).contains(&config.smoothing)
&& config.smoothing > 0.0,
"Gradient2 smoothing must be finite and in (0, 1]"
);
assert!(
(0.0..=1.0).contains(&config.failure_factor) && config.failure_factor > 0.0,
"failure factor must be in (0, 1]"
);
Self {
concurrency: config.initial_concurrency,
config,
last_gradient: 1.0,
updates: 0,
}
}
pub fn on_rtt(&mut self, current_rtt: Duration, long_rtt: Duration, inflight: usize) -> bool {
self.on_rtt_with_pacing(current_rtt, long_rtt, inflight, true)
}
pub fn on_rtt_with_pacing(
&mut self,
current_rtt: Duration,
long_rtt: Duration,
inflight: usize,
was_paced: bool,
) -> bool {
self.on_rtt_with_reference(current_rtt, long_rtt, inflight, was_paced)
}
pub fn on_rtt_with_baseline(
&mut self,
current_rtt: Duration,
baseline_rtt: Duration,
inflight: usize,
was_paced: bool,
) -> bool {
self.on_rtt_with_reference(current_rtt, baseline_rtt, inflight, was_paced)
}
fn on_rtt_with_reference(
&mut self,
current_rtt: Duration,
reference_rtt: Duration,
inflight: usize,
was_paced: bool,
) -> bool {
let current_rtt = current_rtt.as_secs_f64().max(f64::MIN_POSITIVE);
let reference_rtt = reference_rtt.as_secs_f64().max(f64::MIN_POSITIVE);
let application_limited = !was_paced || (inflight as f64) < self.concurrency / 2.0;
if application_limited {
return false;
}
let gradient = (self.config.tolerance * reference_rtt / current_rtt).clamp(0.5, 1.0);
let estimate = self.concurrency * gradient + self.config.gain;
self.concurrency = (self.concurrency * (1.0 - self.config.smoothing)
+ estimate * self.config.smoothing)
.clamp(self.config.min_concurrency, self.config.max_concurrency);
self.last_gradient = gradient;
self.updates += 1;
true
}
pub fn on_failure(&mut self) {
self.concurrency = (self.concurrency * self.config.failure_factor)
.clamp(self.config.min_concurrency, self.config.max_concurrency);
self.last_gradient = 0.0;
self.updates += 1;
}
pub fn concurrency(&self) -> f64 {
self.concurrency
}
pub fn last_gradient(&self) -> f64 {
self.last_gradient
}
pub fn updates(&self) -> u64 {
self.updates
}
}