use crate::network::config::{LatencyDistribution, random_latency_for_seed};
use crate::sim::rng::{config_random_bool, sim_random_range};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct StorageConfiguration {
pub iops: u64,
pub bandwidth: u64,
pub read_latency: LatencyDistribution,
pub write_latency: LatencyDistribution,
pub sync_latency: LatencyDistribution,
pub read_fault_probability: f64,
pub write_fault_probability: f64,
pub crash_fault_probability: f64,
pub misdirect_write_probability: f64,
pub misdirect_read_probability: f64,
pub phantom_write_probability: f64,
pub sync_failure_probability: f64,
pub disk_stall_probability: f64,
pub disk_stall_duration: Duration,
pub disk_throttle_probability: f64,
pub disk_throttle_duration: Duration,
pub disk_throttle_iops_multiplier: f64,
pub disk_throttle_bandwidth_multiplier: f64,
}
impl Default for StorageConfiguration {
fn default() -> Self {
Self {
iops: 25_000,
bandwidth: 150_000_000, read_latency: LatencyDistribution::Uniform {
start: Duration::from_micros(50),
end: Duration::from_micros(200),
},
write_latency: LatencyDistribution::Uniform {
start: Duration::from_micros(100),
end: Duration::from_micros(500),
},
sync_latency: LatencyDistribution::Uniform {
start: Duration::from_millis(1),
end: Duration::from_millis(5),
},
read_fault_probability: 0.0,
write_fault_probability: 0.0,
crash_fault_probability: 0.0,
misdirect_write_probability: 0.0,
misdirect_read_probability: 0.0,
phantom_write_probability: 0.0,
sync_failure_probability: 0.0,
disk_stall_probability: 0.0,
disk_stall_duration: Duration::ZERO,
disk_throttle_probability: 0.0,
disk_throttle_duration: Duration::ZERO,
disk_throttle_iops_multiplier: 1.0,
disk_throttle_bandwidth_multiplier: 1.0,
}
}
}
impl StorageConfiguration {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn random_for_seed() -> Self {
Self {
iops: sim_random_range(10_000..100_000),
bandwidth: sim_random_range(50_000_000..500_000_000),
read_latency: random_latency_for_seed(
Duration::from_micros(sim_random_range(20..100))
..Duration::from_micros(sim_random_range(100..500)),
),
write_latency: random_latency_for_seed(
Duration::from_micros(sim_random_range(50..200))
..Duration::from_micros(sim_random_range(200..1000)),
),
sync_latency: random_latency_for_seed(
Duration::from_micros(sim_random_range(500..2000))
..Duration::from_micros(sim_random_range(2000..10000)),
),
read_fault_probability: f64::from(sim_random_range(0..100)) / 100_000.0,
write_fault_probability: f64::from(sim_random_range(0..100)) / 100_000.0,
crash_fault_probability: f64::from(sim_random_range(0..50)) / 100_000.0,
misdirect_write_probability: f64::from(sim_random_range(0..10)) / 100_000.0,
misdirect_read_probability: f64::from(sim_random_range(0..10)) / 100_000.0,
phantom_write_probability: f64::from(sim_random_range(0..20)) / 100_000.0,
sync_failure_probability: f64::from(sim_random_range(0..50)) / 100_000.0,
disk_stall_probability: f64::from(sim_random_range(0..100)) / 100_000.0,
disk_stall_duration: Duration::from_millis(sim_random_range(50..500)),
disk_throttle_probability: f64::from(sim_random_range(0..100)) / 100_000.0,
disk_throttle_duration: Duration::from_millis(sim_random_range(500..5000)),
disk_throttle_iops_multiplier: f64::from(sim_random_range(2..20)),
disk_throttle_bandwidth_multiplier: f64::from(sim_random_range(2..20)),
}
}
#[must_use]
pub fn swarm_for_seed() -> Self {
let mut config = Self::random_for_seed();
config.apply_swarm_mask();
config
}
fn apply_swarm_mask(&mut self) {
if !config_random_bool(0.5) {
self.read_fault_probability = 0.0;
}
if !config_random_bool(0.5) {
self.write_fault_probability = 0.0;
}
if !config_random_bool(0.5) {
self.crash_fault_probability = 0.0;
}
if !config_random_bool(0.5) {
self.misdirect_read_probability = 0.0;
}
if !config_random_bool(0.5) {
self.misdirect_write_probability = 0.0;
}
if !config_random_bool(0.5) {
self.phantom_write_probability = 0.0;
}
if !config_random_bool(0.5) {
self.sync_failure_probability = 0.0;
}
if !config_random_bool(0.5) {
self.disk_stall_probability = 0.0;
}
if !config_random_bool(0.5) {
self.disk_throttle_probability = 0.0;
}
}
pub fn apply_buggify_knobs(&mut self) {
self.iops = crate::buggify_knob!(self.iops, 100..5_000);
self.bandwidth = crate::buggify_knob!(self.bandwidth, 1_000_000..20_000_000);
self.sync_failure_probability =
crate::buggify_knob!(self.sync_failure_probability, 0.05..0.2);
self.disk_stall_probability = crate::buggify_knob!(self.disk_stall_probability, 0.1..0.5);
self.disk_stall_duration = crate::buggify_knob!(
self.disk_stall_duration,
Duration::from_millis(100)..Duration::from_millis(500)
);
self.disk_throttle_probability =
crate::buggify_knob!(self.disk_throttle_probability, 0.1..0.5);
self.disk_throttle_duration = crate::buggify_knob!(
self.disk_throttle_duration,
Duration::from_secs(1)..Duration::from_secs(5)
);
}
#[must_use]
pub fn fast_local() -> Self {
let one_us = Duration::from_micros(1);
let uniform = LatencyDistribution::Uniform {
start: one_us,
end: one_us,
};
Self {
iops: 1_000_000, bandwidth: 1_000_000_000, read_latency: uniform.clone(),
write_latency: uniform.clone(),
sync_latency: uniform,
read_fault_probability: 0.0,
write_fault_probability: 0.0,
crash_fault_probability: 0.0,
misdirect_write_probability: 0.0,
misdirect_read_probability: 0.0,
phantom_write_probability: 0.0,
sync_failure_probability: 0.0,
disk_stall_probability: 0.0,
disk_stall_duration: Duration::ZERO,
disk_throttle_probability: 0.0,
disk_throttle_duration: Duration::ZERO,
disk_throttle_iops_multiplier: 1.0,
disk_throttle_bandwidth_multiplier: 1.0,
}
}
}
#[cfg(test)]
mod swarm_tests {
use super::StorageConfiguration;
use crate::sim::rng::{reset_sim_rng, set_config_seed, set_sim_seed};
fn enabled_families(config: &StorageConfiguration) -> [bool; 9] {
[
config.read_fault_probability > 0.0,
config.write_fault_probability > 0.0,
config.crash_fault_probability > 0.0,
config.misdirect_read_probability > 0.0,
config.misdirect_write_probability > 0.0,
config.phantom_write_probability > 0.0,
config.sync_failure_probability > 0.0,
config.disk_stall_probability > 0.0,
config.disk_throttle_probability > 0.0,
]
}
fn swarm_for(seed: u64) -> StorageConfiguration {
reset_sim_rng();
set_sim_seed(seed);
set_config_seed(seed);
StorageConfiguration::swarm_for_seed()
}
#[test]
fn swarm_subset_is_deterministic_per_seed() {
for seed in [0_u64, 1, 42, 12_345] {
let first = enabled_families(&swarm_for(seed));
let second = enabled_families(&swarm_for(seed));
assert_eq!(
first, second,
"swarm subset must be reproducible for seed {seed}"
);
}
}
#[test]
fn swarm_reaches_all_off_and_mixed_subsets() {
let mut saw_all_off = false;
let mut saw_mixed = false;
for seed in 0..1000_u64 {
let families = enabled_families(&swarm_for(seed));
let on = families.iter().filter(|&&e| e).count();
if on == 0 {
saw_all_off = true;
}
if on > 0 && on < families.len() {
saw_mixed = true;
}
if saw_all_off && saw_mixed {
break;
}
}
assert!(
saw_all_off,
"no seed in 0..1000 produced the all-off subset"
);
assert!(saw_mixed, "no seed in 0..1000 produced a mixed subset");
}
#[test]
fn swarm_all_off_seed_has_zero_fault_probabilities() {
let seed = (0..1000_u64)
.find(|&s| enabled_families(&swarm_for(s)).iter().all(|&e| !e))
.expect("expected an all-off seed within 0..1000");
let config = swarm_for(seed);
assert_zero(config.read_fault_probability);
assert_zero(config.write_fault_probability);
assert_zero(config.crash_fault_probability);
assert_zero(config.misdirect_read_probability);
assert_zero(config.misdirect_write_probability);
assert_zero(config.phantom_write_probability);
assert_zero(config.sync_failure_probability);
assert_zero(config.disk_stall_probability);
assert_zero(config.disk_throttle_probability);
}
fn assert_zero(value: f64) {
assert_eq!(
value.to_bits(),
0.0_f64.to_bits(),
"expected 0.0, got {value}"
);
}
}
#[cfg(test)]
mod buggify_knob_tests {
use super::StorageConfiguration;
use crate::chaos::{buggify_init, buggify_reset};
use crate::sim::rng::{reset_sim_rng, set_config_seed, set_sim_seed};
fn buggified_knobs(seed: u64) -> [u64; 5] {
reset_sim_rng();
set_sim_seed(seed);
set_config_seed(seed);
buggify_init(0.8, 0.8);
let mut config = StorageConfiguration::swarm_for_seed();
config.apply_buggify_knobs();
buggify_reset();
[
config.iops,
config.bandwidth,
config.sync_failure_probability.to_bits(),
config.disk_stall_probability.to_bits(),
u64::try_from(config.disk_throttle_duration.as_nanos()).unwrap_or(u64::MAX),
]
}
#[test]
fn buggify_knobs_deterministic_per_seed() {
for seed in [0_u64, 1, 42, 12_345] {
assert_eq!(
buggified_knobs(seed),
buggified_knobs(seed),
"buggify knob spikes must be reproducible for seed {seed}"
);
}
}
#[test]
fn buggify_knobs_vary_across_seeds() {
let distinct: std::collections::HashSet<_> = (0..50_u64).map(buggified_knobs).collect();
assert!(
distinct.len() > 1,
"buggify knob spikes should vary across seeds"
);
}
}