use crate::rng::SimRng;
use crate::time::Duration;
pub trait LatencyModel {
fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FixedLatency;
impl LatencyModel for FixedLatency {
fn compute(&mut self, base_latency: Duration, _rng: &mut SimRng) -> Duration {
base_latency
}
}
#[derive(Debug, Clone, Copy)]
pub struct UniformJitter {
pub max_jitter: Duration,
}
impl UniformJitter {
pub fn new(max_jitter: Duration) -> Self {
UniformJitter { max_jitter }
}
}
impl LatencyModel for UniformJitter {
fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
rng.duration_with_jitter(base_latency, self.max_jitter)
}
}
#[derive(Debug, Clone, Copy)]
pub struct PercentageJitter {
pub fraction: f64,
}
impl PercentageJitter {
pub fn new(percent: f64) -> Self {
PercentageJitter {
fraction: percent / 100.0,
}
}
pub fn from_fraction(fraction: f64) -> Self {
PercentageJitter { fraction }
}
}
impl LatencyModel for PercentageJitter {
fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
let max_jitter_nanos = (base_latency.as_nanos() as f64 * self.fraction) as u64;
let max_jitter = Duration::from_nanos(max_jitter_nanos);
rng.duration_with_jitter(base_latency, max_jitter)
}
}
#[derive(Debug, Clone, Copy)]
pub struct OverheadPlusJitter {
pub overhead: Duration,
pub max_jitter: Duration,
}
impl OverheadPlusJitter {
pub fn new(overhead: Duration, max_jitter: Duration) -> Self {
OverheadPlusJitter {
overhead,
max_jitter,
}
}
}
impl LatencyModel for OverheadPlusJitter {
fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
let base_with_overhead = base_latency.saturating_add(self.overhead);
rng.duration_with_jitter(base_with_overhead, self.max_jitter)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SpikyLatency<L: LatencyModel> {
pub inner: L,
pub spike_probability: f64,
pub spike_max: Duration,
}
impl<L: LatencyModel> SpikyLatency<L> {
pub fn new(inner: L, spike_probability: f64, spike_max: Duration) -> Self {
SpikyLatency {
inner,
spike_probability,
spike_max,
}
}
}
impl<L: LatencyModel> LatencyModel for SpikyLatency<L> {
fn compute(&mut self, base_latency: Duration, rng: &mut SimRng) -> Duration {
let base = self.inner.compute(base_latency, rng);
if self.spike_probability > 0.0
&& self.spike_max.as_nanos() > 0
&& rng.bool(self.spike_probability)
{
let spike_nanos = rng.u64(self.spike_max.as_nanos() + 1);
base.saturating_add(Duration::from_nanos(spike_nanos))
} else {
base
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_latency() {
let mut model = FixedLatency;
let mut rng = SimRng::new(42);
let base = Duration::from_millis(100);
for _ in 0..100 {
assert_eq!(model.compute(base, &mut rng), base);
}
}
#[test]
fn uniform_jitter_bounds() {
let mut model = UniformJitter::new(Duration::from_millis(10));
let mut rng = SimRng::new(42);
let base = Duration::from_millis(100);
for _ in 0..1000 {
let latency = model.compute(base, &mut rng);
assert!(latency.as_millis() >= 90);
assert!(latency.as_millis() <= 110);
}
}
#[test]
fn uniform_jitter_clamps_negative() {
let mut model = UniformJitter::new(Duration::from_millis(100));
let mut rng = SimRng::new(42);
let base = Duration::from_millis(10);
for _ in 0..1000 {
let latency = model.compute(base, &mut rng);
let _ = latency.as_nanos();
}
}
#[test]
fn percentage_jitter() {
let mut model = PercentageJitter::new(10.0); let mut rng = SimRng::new(42);
let base = Duration::from_millis(100);
for _ in 0..1000 {
let latency = model.compute(base, &mut rng);
assert!(latency.as_millis() >= 90);
assert!(latency.as_millis() <= 110);
}
}
#[test]
fn overhead_plus_jitter() {
let mut model = OverheadPlusJitter::new(Duration::from_millis(5), Duration::from_millis(2));
let mut rng = SimRng::new(42);
let base = Duration::from_millis(100);
for _ in 0..1000 {
let latency = model.compute(base, &mut rng);
assert!(latency.as_millis() >= 103);
assert!(latency.as_millis() <= 107);
}
}
#[test]
fn deterministic_jitter() {
let mut model1 = UniformJitter::new(Duration::from_millis(10));
let mut model2 = UniformJitter::new(Duration::from_millis(10));
let mut rng1 = SimRng::new(42);
let mut rng2 = SimRng::new(42);
let base = Duration::from_millis(100);
for _ in 0..100 {
assert_eq!(
model1.compute(base, &mut rng1),
model2.compute(base, &mut rng2)
);
}
}
}