use crate::sim::rng::{sim_random_range, sim_random_range_or_default};
use std::ops::Range;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LatencyDistribution {
#[default]
Uniform,
Bimodal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PartitionStrategy {
#[default]
Random,
UniformSize,
IsolateSingle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ConnectFailureMode {
#[default]
Disabled,
AlwaysFail,
Probabilistic,
}
impl ConnectFailureMode {
pub fn random_for_seed() -> Self {
match sim_random_range(0..3) {
0 => Self::Disabled,
1 => Self::AlwaysFail,
_ => Self::Probabilistic,
}
}
}
#[derive(Debug, Clone)]
pub struct ChaosConfiguration {
pub clog_probability: f64,
pub clog_duration: Range<Duration>,
pub partition_probability: f64,
pub partition_duration: Range<Duration>,
pub bit_flip_probability: f64,
pub bit_flip_min_bits: u32,
pub bit_flip_max_bits: u32,
pub bit_flip_cooldown: Duration,
pub partial_write_max_bytes: usize,
pub random_close_probability: f64,
pub random_close_cooldown: Duration,
pub random_close_explicit_ratio: f64,
pub clock_drift_enabled: bool,
pub clock_drift_max: Duration,
pub buggified_delay_enabled: bool,
pub buggified_delay_max: Duration,
pub buggified_delay_probability: f64,
pub connect_failure_mode: ConnectFailureMode,
pub connect_failure_probability: f64,
pub latency_distribution: LatencyDistribution,
pub slow_latency_probability: f64,
pub slow_latency_multiplier: f64,
pub handshake_delay_enabled: bool,
pub handshake_delay_max: Duration,
pub partition_strategy: PartitionStrategy,
}
impl Default for ChaosConfiguration {
fn default() -> Self {
Self {
clog_probability: 0.0,
clog_duration: Duration::from_millis(100)..Duration::from_millis(300),
partition_probability: 0.0,
partition_duration: Duration::from_millis(200)..Duration::from_secs(2),
bit_flip_probability: 0.0001, bit_flip_min_bits: 1,
bit_flip_max_bits: 32,
bit_flip_cooldown: Duration::ZERO, partial_write_max_bytes: 1000, random_close_probability: 0.00001, random_close_cooldown: Duration::from_secs(5), random_close_explicit_ratio: 0.3, clock_drift_enabled: true, clock_drift_max: Duration::from_millis(100), buggified_delay_enabled: true, buggified_delay_max: Duration::from_millis(100), buggified_delay_probability: 0.25, connect_failure_mode: ConnectFailureMode::Probabilistic, connect_failure_probability: 0.5, latency_distribution: LatencyDistribution::default(),
slow_latency_probability: 0.001, slow_latency_multiplier: 10.0, handshake_delay_enabled: true,
handshake_delay_max: Duration::from_millis(10), partition_strategy: PartitionStrategy::default(),
}
}
}
impl ChaosConfiguration {
pub fn disabled() -> Self {
Self {
clog_probability: 0.0,
clog_duration: Duration::ZERO..Duration::ZERO,
partition_probability: 0.0,
partition_duration: Duration::ZERO..Duration::ZERO,
bit_flip_probability: 0.0,
bit_flip_min_bits: 1,
bit_flip_max_bits: 32,
bit_flip_cooldown: Duration::ZERO,
partial_write_max_bytes: 1000,
random_close_probability: 0.0,
random_close_cooldown: Duration::ZERO,
random_close_explicit_ratio: 0.3,
clock_drift_enabled: false,
clock_drift_max: Duration::from_millis(100),
buggified_delay_enabled: false,
buggified_delay_max: Duration::from_millis(100),
buggified_delay_probability: 0.25,
connect_failure_mode: ConnectFailureMode::Disabled,
connect_failure_probability: 0.5,
latency_distribution: LatencyDistribution::Uniform, slow_latency_probability: 0.0, slow_latency_multiplier: 1.0, handshake_delay_enabled: false, handshake_delay_max: Duration::ZERO,
partition_strategy: PartitionStrategy::Random, }
}
pub fn random_for_seed() -> Self {
Self {
clog_probability: sim_random_range(0..20) as f64 / 100.0, clog_duration: Duration::from_micros(sim_random_range(50000..300000))
..Duration::from_micros(sim_random_range(100000..500000)),
partition_probability: sim_random_range(0..15) as f64 / 100.0, partition_duration: Duration::from_millis(sim_random_range(100..1000))
..Duration::from_millis(sim_random_range(500..3000)),
bit_flip_probability: sim_random_range(1..20) as f64 / 100000.0,
bit_flip_min_bits: 1,
bit_flip_max_bits: 32,
bit_flip_cooldown: Duration::from_millis(sim_random_range(0..100)),
partial_write_max_bytes: sim_random_range(100..2000), random_close_probability: sim_random_range(1..100) as f64 / 1000000.0,
random_close_cooldown: Duration::from_millis(sim_random_range(1000..10000)),
random_close_explicit_ratio: sim_random_range(20..40) as f64 / 100.0, clock_drift_enabled: true,
clock_drift_max: Duration::from_millis(sim_random_range(50..150)), buggified_delay_enabled: true,
buggified_delay_max: Duration::from_millis(sim_random_range(50..150)), buggified_delay_probability: sim_random_range(20..30) as f64 / 100.0, connect_failure_mode: ConnectFailureMode::random_for_seed(),
connect_failure_probability: sim_random_range(40..60) as f64 / 100.0, latency_distribution: if sim_random_range(0..2) == 0 {
LatencyDistribution::Uniform
} else {
LatencyDistribution::Bimodal
},
slow_latency_probability: sim_random_range(1..5) as f64 / 1000.0, slow_latency_multiplier: sim_random_range(5..20) as f64, handshake_delay_enabled: true,
handshake_delay_max: Duration::from_millis(sim_random_range(5..20)), partition_strategy: match sim_random_range(0..3) {
0 => PartitionStrategy::Random,
1 => PartitionStrategy::UniformSize,
_ => PartitionStrategy::IsolateSingle,
},
}
}
}
#[derive(Debug, Clone)]
pub struct NetworkConfiguration {
pub bind_latency: Range<Duration>,
pub accept_latency: Range<Duration>,
pub connect_latency: Range<Duration>,
pub read_latency: Range<Duration>,
pub write_latency: Range<Duration>,
pub chaos: ChaosConfiguration,
}
impl Default for NetworkConfiguration {
fn default() -> Self {
Self {
bind_latency: Duration::from_micros(50)..Duration::from_micros(150),
accept_latency: Duration::from_millis(1)..Duration::from_millis(6),
connect_latency: Duration::from_millis(1)..Duration::from_millis(11),
read_latency: Duration::from_micros(10)..Duration::from_micros(60),
write_latency: Duration::from_micros(100)..Duration::from_micros(600),
chaos: ChaosConfiguration::default(),
}
}
}
pub fn sample_duration(range: &Range<Duration>) -> Duration {
let start_nanos = range.start.as_nanos() as u64;
let end_nanos = range.end.as_nanos() as u64;
let random_nanos = sim_random_range_or_default(start_nanos..end_nanos);
Duration::from_nanos(random_nanos)
}
pub fn sample_duration_bimodal(range: &Range<Duration>, chaos: &ChaosConfiguration) -> Duration {
use crate::sim::rng::sim_random;
let base_duration = sample_duration(range);
match chaos.latency_distribution {
LatencyDistribution::Uniform => base_duration,
LatencyDistribution::Bimodal => {
if sim_random::<f64>() < chaos.slow_latency_probability {
let slow_nanos =
(base_duration.as_nanos() as f64 * chaos.slow_latency_multiplier) as u64;
tracing::trace!(
"Bimodal slow latency: {:?} -> {:?}",
base_duration,
Duration::from_nanos(slow_nanos)
);
Duration::from_nanos(slow_nanos)
} else {
base_duration
}
}
}
}
pub fn sample_handshake_delay(chaos: &ChaosConfiguration) -> Duration {
use crate::sim::rng::sim_random;
if !chaos.handshake_delay_enabled || chaos.handshake_delay_max == Duration::ZERO {
return Duration::ZERO;
}
let random_factor = sim_random::<f64>();
Duration::from_nanos((chaos.handshake_delay_max.as_nanos() as f64 * random_factor) as u64)
}
impl NetworkConfiguration {
pub fn new() -> Self {
Self::default()
}
pub fn random_for_seed() -> Self {
Self {
bind_latency: Duration::from_micros(sim_random_range(10..200))
..Duration::from_micros(sim_random_range(50..300)),
accept_latency: Duration::from_micros(sim_random_range(1000..10000))
..Duration::from_micros(sim_random_range(5000..15000)),
connect_latency: Duration::from_micros(sim_random_range(1000..50000))
..Duration::from_micros(sim_random_range(10000..100000)),
read_latency: Duration::from_micros(sim_random_range(5..100))
..Duration::from_micros(sim_random_range(50..200)),
write_latency: Duration::from_micros(sim_random_range(50..1000))
..Duration::from_micros(sim_random_range(200..2000)),
chaos: ChaosConfiguration::random_for_seed(),
}
}
pub fn fast_local() -> Self {
let one_us = Duration::from_micros(1);
let ten_us = Duration::from_micros(10);
Self {
bind_latency: one_us..one_us,
accept_latency: ten_us..ten_us,
connect_latency: ten_us..ten_us,
read_latency: one_us..one_us,
write_latency: one_us..one_us,
chaos: ChaosConfiguration::disabled(),
}
}
}