use std::collections::VecDeque;
use std::time::{Duration, Instant};
use crate::cloud::concurrency::IoSample;
#[derive(Clone, Copy, Debug)]
pub struct SignalStats {
pub bw_avg_bps: f64,
pub bw_hwm_bps: f64,
pub ttfb_min: Duration,
pub ttfb_avg: Duration,
}
impl SignalStats {
pub fn rtt_for_bdp(&self) -> Duration {
const QUEUING_RATIO_CAP: f64 = 10.0;
self.ttfb_avg.min(self.ttfb_min.mul_f64(QUEUING_RATIO_CAP))
}
pub fn bdp_bytes(&self) -> u64 {
(self.bw_hwm_bps * self.rtt_for_bdp().as_secs_f64()) as u64
}
}
#[derive(Debug)]
pub struct Model {
samples: VecDeque<IoSample>,
first_sample_time: Option<Instant>,
window: Duration,
bw_hwm_bps: Option<f64>,
bw_hwm_last_updated: Option<Instant>,
signal: Option<SignalStats>,
}
impl Model {
pub fn new(window: Duration) -> Self {
Self {
samples: VecDeque::with_capacity(1024),
first_sample_time: None,
window,
bw_hwm_bps: None,
bw_hwm_last_updated: None,
signal: None,
}
}
pub fn record(&mut self, sample: IoSample) {
if self.first_sample_time.is_none() {
self.first_sample_time = Some(sample.completion_time);
if polars_config::config().verbose() {
eprintln!(
"[InFlightConcurrency]: observed first RTT sample: {} ms, for {} bytes",
sample.ttfb.as_millis(),
sample.n_bytes
)
}
}
self.samples.push_back(sample);
}
pub fn signal(&self) -> Option<SignalStats> {
self.signal }
pub fn bw_hwm_bps(&self) -> Option<f64> {
self.bw_hwm_bps
}
pub fn update(&mut self, now: Instant) {
const N_SAMPLE_THRESHOLD: usize = 5;
self.evict_old(Instant::now());
if self.samples.len() < N_SAMPLE_THRESHOLD {
self.signal = None;
return;
}
let window_start = now - self.window;
let mut n_bytes: u64 = 0;
let mut n_samples: usize = 0;
let mut ttfb_sum = Duration::ZERO;
let mut ttfb_min: Option<Duration> = None;
for s in &self.samples {
if s.completion_time >= window_start && s.completion_time <= now {
n_bytes += s.n_bytes;
n_samples += 1;
ttfb_sum += s.ttfb;
ttfb_min = Some(ttfb_min.map_or(s.ttfb, |m| m.min(s.ttfb)));
}
}
let (Some(ttfb_min), true) = (ttfb_min, n_bytes > 0) else {
self.signal = None;
return;
};
let ttfb_avg = ttfb_sum.div_f64(n_samples as f64);
let bw_avg_bps = n_bytes as f64 / self.window.as_secs_f64();
if self.bw_hwm_bps.is_none_or(|hwm| bw_avg_bps > hwm) {
self.bw_hwm_bps = Some(bw_avg_bps);
self.bw_hwm_last_updated = Some(now);
}
let bw_hwm_bps = self.bw_hwm_bps.unwrap();
self.signal = Some(SignalStats {
bw_avg_bps,
bw_hwm_bps,
ttfb_min,
ttfb_avg,
});
}
pub fn sample_count(&self) -> usize {
self.samples.len()
}
fn evict_old(&mut self, now: Instant) {
while let Some(front) = self.samples.front() {
if now.duration_since(front.completion_time) > self.window {
self.samples.pop_front();
} else {
break;
}
}
}
}