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