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 tcp = args.get(1).is_some_and(|arg| arg == "--tcp");
36 if tcp {
37 args.remove(1);
38 }
39 if args.len() != 5 {
40 return Err(
41 "usage: concurrency_benchmark [--tcp] ADDRESS CONNECTIONS MESSAGES PAYLOAD".into(),
42 );
43 }
44 let address = args[1].parse()?;
45 let count: usize = args[2].parse()?;
46 let messages: usize = args[3].parse()?;
47 let size: usize = args[4].parse()?;
48 if count == 0 || messages == 0 || size < 17 {
49 return Err("invalid counts".into());
50 }
51 tokio::time::timeout(
52 Duration::from_secs(180),
53 run(address, count, messages, size, tcp),
54 )
55 .await??;
56 Ok(())
57}
58
59async fn run(
60 address: std::net::SocketAddr,
61 count: usize,
62 messages: usize,
63 size: usize,
64 tcp: bool,
65) -> Result<(), BenchError> {
66 let setup = Instant::now();
67 let dialing = Arc::new(Semaphore::new(8));
68 let mut tasks = JoinSet::new();
69 for id in 0..count {
70 let dialing = dialing.clone();
71 tasks.spawn(async move {
72 let _permit = dialing.acquire().await.unwrap();
73 let socket = EchoConnection::connect(address, tcp).await?;
74 Ok::<_, BenchError>((id, socket))
75 });
76 }
77 let mut sockets = Vec::with_capacity(count);
78 while let Some(result) = tasks.join_next().await {
79 sockets.push(result??);
80 }
81 let setup_seconds = setup.elapsed().as_secs_f64();
82 let barrier = Arc::new(Barrier::new(count + 1));
83 let start_gate = Arc::new(Barrier::new(count + 1));
84 let mut exchanges = JoinSet::new();
85 for (id, mut socket) in sockets {
86 let barrier = barrier.clone();
87 let start_gate = start_gate.clone();
88 exchanges.spawn(async move {
89 let mut rtts = Vec::with_capacity(20);
90 let mut expected = payload(id, 0, size);
91 let mut outgoing = expected.clone();
92 let mut actual = Vec::with_capacity(size);
93 for message in 0..20 {
94 expected[9..17].copy_from_slice(&(message as u64).to_le_bytes());
95 let start = Instant::now();
96 socket.send(&expected).await?;
97 socket.recv(size, &mut actual).await?;
98 if actual != expected {
99 return Err::<_, BenchError>("echo payload or order mismatch".into());
100 }
101 rtts.push(start.elapsed().as_nanos());
102 }
103 barrier.wait().await;
104 start_gate.wait().await;
105 let mut sent = 0;
108 while sent < messages.min(16) {
109 outgoing[9..17].copy_from_slice(&((sent + 20) as u64).to_le_bytes());
110 socket.send(&outgoing).await?;
111 sent += 1;
112 }
113 for received in 0..messages {
114 expected[9..17].copy_from_slice(&((received + 20) as u64).to_le_bytes());
115 socket.recv(size, &mut actual).await?;
116 if actual != expected {
117 return Err::<_, BenchError>("echo payload or order mismatch".into());
118 }
119 if sent < messages {
120 outgoing[9..17].copy_from_slice(&((sent + 20) as u64).to_le_bytes());
121 socket.send(&outgoing).await?;
122 sent += 1;
123 }
124 }
125 Ok::<_, BenchError>((socket, rtts))
127 });
128 }
129 barrier.wait().await;
130 let started = Instant::now();
131 start_gate.wait().await;
132 let mut retained = Vec::with_capacity(count);
133 let mut rtts = Vec::with_capacity(count * 20);
134 while let Some(result) = exchanges.join_next().await {
135 let (socket, values) = result??;
136 retained.push(socket);
137 rtts.extend(values);
138 }
139 let elapsed = started.elapsed().as_secs_f64();
140 rtts.sort_unstable();
141 println!(
142 "Connections: {count}\nMessages per connection: {messages}\nPayload size: {size} bytes\nSetup: {setup_seconds:.6} s\nElapsed: {elapsed:.6} s"
143 );
144 println!(
145 "Echo payload throughput (per direction): {:.2} MiB/s",
146 count as f64 * messages as f64 * size as f64 / 1048576.0 / elapsed
147 );
148 println!(
149 "RTT p50: {:.1} us\nRTT p95: {:.1} us\nRTT p99: {:.1} us",
150 percentile(&rtts, 50),
151 percentile(&rtts, 95),
152 percentile(&rtts, 99)
153 );
154 println!("Verified ordered echoes: {}", count * (messages + 20));
155 drop(retained);
156 Ok(())
157}
158
159enum EchoConnection {
162 RakNet(RaknetSocket),
163 Tcp { stream: TcpStream, record: Vec<u8> },
164}
165
166impl EchoConnection {
167 async fn connect(address: std::net::SocketAddr, tcp: bool) -> Result<Self, BenchError> {
168 if tcp {
169 let stream = TcpStream::connect(address).await?;
170 stream.set_nodelay(true)?;
171 Ok(Self::Tcp {
172 stream,
173 record: Vec::new(),
174 })
175 } else {
176 Ok(Self::RakNet(
177 RaknetSocket::connect_with_version(&address, 11).await?,
178 ))
179 }
180 }
181
182 async fn send(&mut self, payload: &[u8]) -> Result<(), BenchError> {
183 match self {
184 Self::RakNet(socket) => socket.send(payload, Reliability::ReliableOrdered).await?,
185 Self::Tcp { stream, record, .. } => {
186 let length = u32::try_from(payload.len())?;
187 record.clear();
188 record.extend_from_slice(&length.to_le_bytes());
189 record.extend_from_slice(payload);
190 stream.write_all(record).await?;
191 }
192 }
193 Ok(())
194 }
195
196 async fn recv(&mut self, size: usize, incoming: &mut Vec<u8>) -> Result<(), BenchError> {
197 match self {
198 Self::RakNet(socket) => *incoming = socket.recv().await?,
199 Self::Tcp { stream, .. } => {
200 let length = stream.read_u32_le().await? as usize;
201 if length != size {
202 return Err("unexpected TCP record length".into());
203 }
204 incoming.resize(length, 0);
205 stream.read_exact(incoming).await?;
206 }
207 }
208 Ok(())
209 }
210}
211
212async fn echo_tcp(mut stream: TcpStream) -> Result<(), BenchError> {
213 stream.set_nodelay(true)?;
214 let mut record = Vec::new();
215 loop {
216 let mut header = [0; 4];
217 match stream.read_exact(&mut header).await {
218 Ok(_) => {}
219 Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()),
220 Err(error) => return Err(error.into()),
221 }
222 let length = u32::from_le_bytes(header) as usize;
223 if length > 65536 {
224 return Err("TCP record exceeds benchmark limit".into());
225 }
226 record.resize(length + 4, 0);
227 record[..4].copy_from_slice(&header);
228 stream.read_exact(&mut record[4..]).await?;
229 stream.write_all(&record).await?;
230 }
231}
232
233#[cfg(target_os = "linux")]
234async fn tcp_server(address: std::net::SocketAddr) -> Result<(), BenchError> {
235 use socket2::{Domain, Protocol, Socket, Type};
236 let mut listeners = Vec::with_capacity(4);
237 for _ in 0..4 {
238 let socket = Socket::new(
239 Domain::for_address(address),
240 Type::STREAM,
241 Some(Protocol::TCP),
242 )?;
243 socket.set_reuse_address(true)?;
244 socket.set_reuse_port(true)?;
245 socket.set_nonblocking(true)?;
246 socket.bind(&address.into())?;
247 socket.listen(4096)?;
248 listeners.push(TcpListener::from_std(socket.into())?);
249 }
250 println!("TCP echo listening on {address} with four receive sockets");
251 let mut tasks = JoinSet::new();
252 for listener in listeners {
253 tasks.spawn(async move {
254 loop {
255 let (stream, _) = listener.accept().await?;
256 tokio::spawn(async move {
257 if let Err(error) = echo_tcp(stream).await {
258 eprintln!("TCP echo failed: {error}");
259 }
260 });
261 }
262 #[allow(unreachable_code)]
263 Ok::<(), BenchError>(())
264 });
265 }
266 while let Some(result) = tasks.join_next().await {
267 result??;
268 }
269 Ok(())
270}
271
272#[cfg(not(target_os = "linux"))]
273async fn tcp_server(_address: std::net::SocketAddr) -> Result<(), BenchError> {
274 Err("The four-socket TCP benchmark server requires Linux".into())
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[tokio::test]
282 async fn tcp_records_preserve_payload_order_and_reuse_buffers() -> Result<(), BenchError> {
283 let listener = TcpListener::bind("127.0.0.1:0").await?;
284 let address = listener.local_addr()?;
285 let server = tokio::spawn(async move {
286 let (stream, _) = listener.accept().await?;
287 echo_tcp(stream).await
288 });
289 let mut connection = EchoConnection::connect(address, true).await?;
290 let mut received = Vec::new();
291 for size in [17, 64, 800, 4096, 64] {
292 for message in 0..32 {
293 let expected = payload(3, message, size);
294 connection.send(&expected).await?;
295 connection.recv(size, &mut received).await?;
296 assert_eq!(received, expected);
297 }
298 }
299 let capacity = received.capacity();
300 connection.send(&payload(3, 40, 64)).await?;
301 connection.recv(64, &mut received).await?;
302 assert_eq!(received.capacity(), capacity);
303 drop(connection);
304 server.await??;
305 Ok(())
306 }
307}