1use crate::dispatch::Dispatcher;
10use crate::oracle::{self, SoakStats, Violation};
11use crate::replica::ReplicaSet;
12use crate::target::S3Target;
13use crate::workload::{self, WorkloadConfig};
14use gonzalo_core::Store;
15use gonzalo_store_server::ServerStore;
16use std::sync::Arc;
17use std::time::Duration;
18
19pub struct SoakOutcome {
21 pub stats: SoakStats,
22 pub violations: Vec<Violation>,
23}
24
25impl SoakOutcome {
26 pub fn passed(&self) -> bool {
27 self.violations.is_empty()
28 }
29}
30
31pub fn dispatcher_over(base_urls: &[String]) -> Result<Arc<Dispatcher>, String> {
33 let mut replicas: Vec<Arc<dyn Store>> = Vec::with_capacity(base_urls.len());
34 for url in base_urls {
35 let store = ServerStore::http(url).map_err(|e| format!("connect {url}: {e}"))?;
36 replicas.push(Arc::new(store));
37 }
38 Ok(Arc::new(Dispatcher::new(replicas)))
39}
40
41pub async fn run_rounds(
48 target: &S3Target,
49 base_cfg: WorkloadConfig,
50 replicas: usize,
51 rounds: usize,
52) -> Result<Vec<SoakOutcome>, String> {
53 let mut set = ReplicaSet::spawn(replicas, target).await?;
54 let dispatcher = dispatcher_over(&set.base_urls())?;
55 let mut outcomes = Vec::with_capacity(rounds);
56
57 for r in 0..rounds {
58 let mut cfg = base_cfg.clone();
59 cfg.collection = format!("{}-r{r}", base_cfg.collection);
60
61 let workload_task = {
62 let d = dispatcher.clone();
63 tokio::spawn(async move { workload::run(d, cfg).await })
64 };
65
66 if replicas > 1 {
67 let victim = 1 + (r % (replicas - 1));
70 tokio::time::sleep(Duration::from_millis(200)).await;
71 set.kill(victim);
72 tokio::time::sleep(Duration::from_millis(450)).await;
73 set.respawn(victim).await?;
74 }
75
76 let stats = workload_task
77 .await
78 .map_err(|e| format!("workload task panicked: {e}"))?;
79 let violations = oracle::check(&stats);
80 outcomes.push(SoakOutcome { stats, violations });
81 }
82
83 Ok(outcomes)
84}