1use std::os::unix::net::UnixDatagram;
9use std::path::Path;
10use std::sync::mpsc::sync_channel;
11use std::thread::{self, sleep};
12use std::time::{Duration, Instant};
13
14use antithesis_sdk::random::{random_choice, AntithesisRng};
15use rand::{rand_core::UnwrapErr, RngExt};
16
17use crate::payload::dogstatsd;
18
19#[derive(Clone, Copy, Debug)]
21pub enum Batch {
22 Clean,
24 Feral,
26 Mixed,
28}
29
30impl Batch {
31 #[must_use]
33 pub fn sample() -> Self {
34 match random_choice(&[Batch::Clean, Batch::Clean, Batch::Feral, Batch::Mixed]) {
35 Some(Batch::Feral) => Batch::Feral,
36 Some(Batch::Mixed) => Batch::Mixed,
37 _ => Batch::Clean,
38 }
39 }
40
41 fn vibe(self) -> dogstatsd::Vibe {
43 match self {
44 Batch::Clean => dogstatsd::Vibe::Clean,
45 Batch::Feral => dogstatsd::Vibe::Feral,
46 Batch::Mixed => dogstatsd::sample_vibe(),
47 }
48 }
49}
50
51enum Line {
53 Single { bytes: Vec<u8> },
55 Multi {
57 bytes: Vec<u8>,
59 count: usize,
61 },
62}
63
64impl Line {
65 fn bytes(&self) -> &[u8] {
67 match self {
68 Line::Single { bytes } | Line::Multi { bytes, .. } => bytes,
69 }
70 }
71}
72
73#[derive(Clone, Debug)]
75pub struct Stats {
76 pub received: usize,
78 pub sent: Vec<usize>,
80 pub max_packed: Vec<usize>,
83}
84
85#[must_use]
95pub fn run(batch: Batch, sockets: Vec<UnixDatagram>) -> Stats {
96 let count = {
97 let mut rng = UnwrapErr(AntithesisRng);
98 rng.random_range(0..=10_000u64)
99 };
100
101 let (tx, rx) = sync_channel::<Line>(2024);
102
103 let producer = thread::spawn(move || {
104 let mut rng = UnwrapErr(AntithesisRng);
105 for _ in 0..count {
106 let mut bytes = Vec::new();
107 let line = match dogstatsd::send(&mut rng, &mut bytes, batch.vibe()) {
108 None => Line::Single { bytes },
109 Some(count) => Line::Multi { bytes, count },
110 };
111 if tx.send(line).is_err() {
112 break;
113 }
114 }
115 });
116
117 let consumer = thread::spawn(move || {
118 let mut received = 0usize;
119 let mut sent = vec![0usize; sockets.len()];
120 let mut max_packed = vec![0usize; sockets.len()];
121 while let Ok(line) = rx.recv() {
122 received += 1;
123 for (i, socket) in sockets.iter().enumerate() {
124 if socket.send(line.bytes()).is_ok() {
125 sent[i] += 1;
126 if let Line::Multi { count, .. } = &line {
127 max_packed[i] = max_packed[i].max(*count);
128 }
129 }
130 }
131 }
132 Stats {
133 received,
134 sent,
135 max_packed,
136 }
137 });
138
139 producer.join().expect("producer thread panicked");
140 consumer.join().expect("consumer thread panicked")
141}
142
143#[must_use]
146pub fn connect_with_retry(path: &Path) -> Option<UnixDatagram> {
147 let deadline = Instant::now() + Duration::from_secs(30);
148 loop {
149 if let Ok(socket) = UnixDatagram::unbound() {
150 if socket.connect(path).is_ok() {
151 return Some(socket);
152 }
153 }
154 if Instant::now() >= deadline {
155 return None;
156 }
157 sleep(Duration::from_millis(250));
158 }
159}