tower/tower/src/hedge/rotating_histogram.rs

74 lines
2.3 KiB
Rust

use hdrhistogram::Histogram;
use std::time::Duration;
use tokio::time::Instant;
use tracing::trace;
/// This represents a "rotating" histogram which stores two histogram, one which
/// should be read and one which should be written to. Every period, the read
/// histogram is discarded and replaced by the write histogram. The idea here
/// is that the read histogram should always contain a full period (the previous
/// period) of write operations.
#[derive(Debug)]
pub struct RotatingHistogram {
read: Histogram<u64>,
write: Histogram<u64>,
last_rotation: Instant,
period: Duration,
}
impl RotatingHistogram {
pub fn new(period: Duration) -> RotatingHistogram {
RotatingHistogram {
read: Histogram::<u64>::new_with_bounds(1, 10_000, 3)
.expect("Invalid histogram params"),
write: Histogram::<u64>::new_with_bounds(1, 10_000, 3)
.expect("Invalid histogram params"),
last_rotation: Instant::now(),
period,
}
}
pub fn read(&mut self) -> &mut Histogram<u64> {
self.maybe_rotate();
&mut self.read
}
pub fn write(&mut self) -> &mut Histogram<u64> {
self.maybe_rotate();
&mut self.write
}
fn maybe_rotate(&mut self) {
let delta = Instant::now() - self.last_rotation;
// TODO: replace with delta.duration_div when it becomes stable.
let rotations = (nanos(delta) / nanos(self.period)) as u32;
if rotations >= 2 {
trace!("Time since last rotation is {:?}. clearing!", delta);
self.clear();
} else if rotations == 1 {
trace!("Time since last rotation is {:?}. rotating!", delta);
self.rotate();
}
self.last_rotation += self.period * rotations;
}
fn rotate(&mut self) {
std::mem::swap(&mut self.read, &mut self.write);
trace!("Rotated {:?} points into read", self.read.len());
self.write.clear();
}
fn clear(&mut self) {
self.read.clear();
self.write.clear();
}
}
const NANOS_PER_SEC: u64 = 1_000_000_000;
fn nanos(duration: Duration) -> u64 {
duration
.as_secs()
.saturating_mul(NANOS_PER_SEC)
.saturating_add(u64::from(duration.subsec_nanos()))
}