1use rust_raknet::{RaknetSocket, Reliability};
3use std::{
4 error::Error,
5 sync::Arc,
6 time::{Duration, Instant},
7};
8type BenchError = Box<dyn Error + Send + Sync>;
9
10use tokio::{
11 io::{AsyncReadExt, AsyncWriteExt},
12 net::{TcpListener, TcpStream},
13 sync::{Barrier, Semaphore},
14 task::JoinSet,
15};
16
17fn payload(connection: usize, message: usize, size: usize) -> Vec<u8> {
18 let mut data = vec![0xfe; size];
19 data[1..9].copy_from_slice(&(connection as u64).to_le_bytes());
20 data[9..17].copy_from_slice(&(message as u64).to_le_bytes());
21 data
22}
23fn percentile(values: &[u128], percentile: usize) -> f64 {
24 values[(values.len() * percentile).div_ceil(100) - 1] as f64 / 1000.0
25}
26#[tokio::main(worker_threads = 4)]
27async fn main() -> Result<(), BenchError> {
28 let mut args: Vec<_> = std::env::args().collect();
29 if args.get(1).is_some_and(|arg| arg == "--tcp-server") {
30 if args.len() != 3 {
31 return Err("usage: concurrency_benchmark --tcp-server ADDRESS".into());
32 }
33 return tcp_server(args[2].parse()?).await;
34 }
35 let loaded_rtt = args.last().is_some_and(|arg| arg == "--loaded-rtt");
36 if loaded_rtt {
37 args.pop();
38 }
39 let tcp = args.get(1).is_some_and(|arg| arg == "--tcp");
40 if tcp {
41 args.remove(1);
42 }
43 let batch = args.get(1).is_some_and(|arg| arg == "--batch");
44 if batch {
45 args.remove(1);
46 }
47 if batch && tcp {
48 return Err("--batch is only supported for RakNet".into());
49 }
50 if args.len() != 5 {
51 return Err(
52 "usage: concurrency_benchmark [--tcp | --batch] ADDRESS CONNECTIONS MESSAGES PAYLOAD [--loaded-rtt]"
53 .into(),
54 );
55 }
56 let address = args[1].parse()?;
57 let count: usize = args[2].parse()?;
58 let messages: usize = args[3].parse()?;
59 let size: usize = args[4].parse()?;
60 if count == 0 || messages == 0 || size < 17 {
61 return Err("invalid counts".into());
62 }
63 if loaded_rtt {
64 tokio::time::timeout(
65 Duration::from_secs(180),
66 run::<true>(address, count, messages, size, tcp, batch),
67 )
68 .await??;
69 } else {
70 tokio::time::timeout(
71 Duration::from_secs(180),
72 run::<false>(address, count, messages, size, tcp, batch),
73 )
74 .await??;
75 }
76 Ok(())
77}
78
79async fn run<const LOADED: bool>(
80 address: std::net::SocketAddr,
81 count: usize,
82 messages: usize,
83 size: usize,
84 tcp: bool,
85 batch: bool,
86) -> Result<(), BenchError> {
87 println!(
88 "Sending API: {}",
89 if tcp {
90 "TCP complete-record writes"
91 } else if batch {
92 "send_bytes_batch (ready messages only)"
93 } else {
94 "send (no batch API)"
95 }
96 );
97 let setup = Instant::now();
98 let dialing = Arc::new(Semaphore::new(8));
99 let mut tasks = JoinSet::new();
100 for id in 0..count {
101 let dialing = dialing.clone();
102 tasks.spawn(async move {
103 let _permit = dialing.acquire().await.unwrap();
104 let socket = EchoConnection::connect(address, tcp).await?;
105 Ok::<_, BenchError>((id, socket))
106 });
107 }
108 let mut sockets = Vec::with_capacity(count);
109 while let Some(result) = tasks.join_next().await {
110 sockets.push(result??);
111 }
112 let setup_seconds = setup.elapsed().as_secs_f64();
113 let barrier = Arc::new(Barrier::new(count + 1));
114 let start_gate = Arc::new(Barrier::new(count + 1));
115 let mut exchanges = JoinSet::new();
116 for (id, mut socket) in sockets {
117 let barrier = barrier.clone();
118 let start_gate = start_gate.clone();
119 exchanges.spawn(async move {
120 let mut rtts = Vec::with_capacity(if LOADED { messages.max(20) } else { 20 });
121 let mut stamps = if LOADED {
122 vec![Instant::now(); 16]
123 } else {
124 Vec::new()
125 };
126 let mut expected = payload(id, 0, size);
127 let mut outgoing = expected.clone();
128 let mut actual = Vec::with_capacity(size);
129 for message in 0..20 {
130 expected[9..17].copy_from_slice(&(message as u64).to_le_bytes());
131 let start = Instant::now();
132 socket.send(&expected).await?;
133 socket.recv(size, &mut actual).await?;
134 if actual != expected {
135 return Err::<_, BenchError>("echo payload or order mismatch".into());
136 }
137 rtts.push(start.elapsed().as_nanos());
138 }
139 if LOADED {
140 rtts.clear();
141 }
142 barrier.wait().await;
143 start_gate.wait().await;
144 if batch {
147 let EchoConnection::RakNet(ref connection) = socket else {
148 unreachable!()
149 };
150 let mut sent = 0;
151 let mut received = 0;
152 let mut incoming = Vec::with_capacity(16);
153 let mut pending = Vec::with_capacity(16);
154 for message in 0..messages.min(16) {
155 pending.push(rust_raknet::Bytes::from(payload(id, message + 20, size)));
156 if LOADED {
157 stamps[message % 16] = Instant::now();
158 }
159 sent += 1;
160 }
161 connection
162 .send_bytes_batch(&pending, Reliability::ReliableOrdered)
163 .await?;
164 while received < messages {
165 connection.recv_bytes_batch(&mut incoming, 16).await?;
166 for actual in &incoming {
167 expected[9..17].copy_from_slice(&((received + 20) as u64).to_le_bytes());
168 if actual.as_ref() != expected {
169 return Err::<_, BenchError>("echo payload or order mismatch".into());
170 }
171 if LOADED {
172 rtts.push(stamps[received % 16].elapsed().as_nanos());
173 }
174 received += 1;
175 }
176 pending.clear();
177 for _ in 0..incoming.len().min(messages - sent) {
178 pending.push(rust_raknet::Bytes::from(payload(id, sent + 20, size)));
179 if LOADED {
180 stamps[sent % 16] = Instant::now();
181 }
182 sent += 1;
183 }
184 if !pending.is_empty() {
185 connection
186 .send_bytes_batch(&pending, Reliability::ReliableOrdered)
187 .await?;
188 }
189 }
190 } else {
191 let mut sent = 0;
192 while sent < messages.min(16) {
193 outgoing[9..17].copy_from_slice(&((sent + 20) as u64).to_le_bytes());
194 if LOADED {
195 stamps[sent % 16] = Instant::now();
196 }
197 socket.send(&outgoing).await?;
198 sent += 1;
199 }
200 for received in 0..messages {
201 expected[9..17].copy_from_slice(&((received + 20) as u64).to_le_bytes());
202 socket.recv(size, &mut actual).await?;
203 if actual != expected {
204 return Err::<_, BenchError>("echo payload or order mismatch".into());
205 }
206 if LOADED {
207 rtts.push(stamps[received % 16].elapsed().as_nanos());
208 }
209 if sent < messages {
210 outgoing[9..17].copy_from_slice(&((sent + 20) as u64).to_le_bytes());
211 if LOADED {
212 stamps[sent % 16] = Instant::now();
213 }
214 socket.send(&outgoing).await?;
215 sent += 1;
216 }
217 }
218 }
219 Ok::<_, BenchError>((socket, rtts))
221 });
222 }
223 barrier.wait().await;
224 let started = Instant::now();
225 start_gate.wait().await;
226 let mut retained = Vec::with_capacity(count);
227 let mut rtts = Vec::with_capacity(count * if LOADED { messages } else { 20 });
228 while let Some(result) = exchanges.join_next().await {
229 let (socket, values) = result??;
230 retained.push(socket);
231 rtts.extend(values);
232 }
233 let elapsed = started.elapsed().as_secs_f64();
234 println!("RTT samples: {}", rtts.len());
235 rtts.sort_unstable();
236 println!(
237 "Connections: {count}\nMessages per connection: {messages}\nPayload size: {size} bytes\nSetup: {setup_seconds:.6} s\nElapsed: {elapsed:.6} s"
238 );
239 println!(
240 "Echo payload throughput (per direction): {:.2} MiB/s",
241 count as f64 * messages as f64 * size as f64 / 1048576.0 / elapsed
242 );
243 println!(
244 "RTT p50: {:.1} us\nRTT p95: {:.1} us\nRTT p99: {:.1} us",
245 percentile(&rtts, 50),
246 percentile(&rtts, 95),
247 percentile(&rtts, 99)
248 );
249 println!(
250 "RTT measurement: {}",
251 if LOADED {
252 "per-message throughout measured burst"
253 } else {
254 "sequential warmup before burst"
255 }
256 );
257 println!("Verified ordered echoes: {}", count * (messages + 20));
258 drop(retained);
259 Ok(())
260}
261
262enum EchoConnection {
265 RakNet(RaknetSocket),
266 Tcp { stream: TcpStream, record: Vec<u8> },
267}
268
269impl EchoConnection {
270 async fn connect(address: std::net::SocketAddr, tcp: bool) -> Result<Self, BenchError> {
271 if tcp {
272 let stream = TcpStream::connect(address).await?;
273 stream.set_nodelay(true)?;
274 Ok(Self::Tcp {
275 stream,
276 record: Vec::new(),
277 })
278 } else {
279 Ok(Self::RakNet(
280 RaknetSocket::connect_with_version(&address, 11).await?,
281 ))
282 }
283 }
284
285 async fn send(&mut self, payload: &[u8]) -> Result<(), BenchError> {
286 match self {
287 Self::RakNet(socket) => socket.send(payload, Reliability::ReliableOrdered).await?,
288 Self::Tcp { stream, record, .. } => {
289 let length = u32::try_from(payload.len())?;
290 record.clear();
291 record.extend_from_slice(&length.to_le_bytes());
292 record.extend_from_slice(payload);
293 stream.write_all(record).await?;
294 }
295 }
296 Ok(())
297 }
298
299 async fn recv(&mut self, size: usize, incoming: &mut Vec<u8>) -> Result<(), BenchError> {
300 match self {
301 Self::RakNet(socket) => *incoming = socket.recv().await?,
302 Self::Tcp { stream, .. } => {
303 let length = stream.read_u32_le().await? as usize;
304 if length != size {
305 return Err("unexpected TCP record length".into());
306 }
307 incoming.resize(length, 0);
308 stream.read_exact(incoming).await?;
309 }
310 }
311 Ok(())
312 }
313}
314
315async fn echo_tcp(mut stream: TcpStream) -> Result<(), BenchError> {
316 stream.set_nodelay(true)?;
317 let mut record = Vec::new();
318 loop {
319 let mut header = [0; 4];
320 match stream.read_exact(&mut header).await {
321 Ok(_) => {}
322 Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()),
323 Err(error) => return Err(error.into()),
324 }
325 let length = u32::from_le_bytes(header) as usize;
326 if length > 65536 {
327 return Err("TCP record exceeds benchmark limit".into());
328 }
329 record.resize(length + 4, 0);
330 record[..4].copy_from_slice(&header);
331 stream.read_exact(&mut record[4..]).await?;
332 stream.write_all(&record).await?;
333 }
334}
335
336#[cfg(target_os = "linux")]
337async fn tcp_server(address: std::net::SocketAddr) -> Result<(), BenchError> {
338 use socket2::{Domain, Protocol, Socket, Type};
339 let mut listeners = Vec::with_capacity(4);
340 for _ in 0..4 {
341 let socket = Socket::new(
342 Domain::for_address(address),
343 Type::STREAM,
344 Some(Protocol::TCP),
345 )?;
346 socket.set_reuse_address(true)?;
347 socket.set_reuse_port(true)?;
348 socket.set_nonblocking(true)?;
349 socket.bind(&address.into())?;
350 socket.listen(4096)?;
351 listeners.push(TcpListener::from_std(socket.into())?);
352 }
353 println!("TCP echo listening on {address} with four receive sockets");
354 let mut tasks = JoinSet::new();
355 for listener in listeners {
356 tasks.spawn(async move {
357 loop {
358 let (stream, _) = listener.accept().await?;
359 tokio::spawn(async move {
360 if let Err(error) = echo_tcp(stream).await {
361 eprintln!("TCP echo failed: {error}");
362 }
363 });
364 }
365 #[allow(unreachable_code)]
366 Ok::<(), BenchError>(())
367 });
368 }
369 while let Some(result) = tasks.join_next().await {
370 result??;
371 }
372 Ok(())
373}
374
375#[cfg(not(target_os = "linux"))]
376async fn tcp_server(_address: std::net::SocketAddr) -> Result<(), BenchError> {
377 Err("The four-socket TCP benchmark server requires Linux".into())
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[tokio::test]
385 async fn tcp_records_preserve_payload_order_and_reuse_buffers() -> Result<(), BenchError> {
386 let listener = TcpListener::bind("127.0.0.1:0").await?;
387 let address = listener.local_addr()?;
388 let server = tokio::spawn(async move {
389 let (stream, _) = listener.accept().await?;
390 echo_tcp(stream).await
391 });
392 let mut connection = EchoConnection::connect(address, true).await?;
393 let mut received = Vec::new();
394 for size in [17, 64, 800, 4096, 64] {
395 for message in 0..32 {
396 let expected = payload(3, message, size);
397 connection.send(&expected).await?;
398 connection.recv(size, &mut received).await?;
399 assert_eq!(received, expected);
400 }
401 }
402 let capacity = received.capacity();
403 connection.send(&payload(3, 40, 64)).await?;
404 connection.recv(64, &mut received).await?;
405 assert_eq!(received.capacity(), capacity);
406 drop(connection);
407 server.await??;
408 Ok(())
409 }
410}