431 lines
14 KiB
Rust
431 lines
14 KiB
Rust
//! QUIC Multi-Stream Benchmarks
|
|
//!
|
|
//! Comprehensive performance benchmarks for quic-multistream crate covering:
|
|
//! - Stream throughput (target: >100 MB/s)
|
|
//! - Connection establishment latency (target: <10ms)
|
|
//! - Multiplexing performance (target: >1000 concurrent streams)
|
|
//! - 0-RTT connection time
|
|
//! - Backpressure handling
|
|
//! - Error recovery time
|
|
|
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
|
use std::sync::Arc;
|
|
use tokio::runtime::Runtime;
|
|
|
|
// Mock QUIC components for benchmarking (since we need a server for real tests)
|
|
mod mock {
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
pub struct MockConnection {
|
|
bytes_sent: Arc<AtomicU64>,
|
|
bytes_received: Arc<AtomicU64>,
|
|
rtt_us: u64,
|
|
}
|
|
|
|
impl MockConnection {
|
|
pub fn new(rtt_us: u64) -> Self {
|
|
Self {
|
|
bytes_sent: Arc::new(AtomicU64::new(0)),
|
|
bytes_received: Arc::new(AtomicU64::new(0)),
|
|
rtt_us,
|
|
}
|
|
}
|
|
|
|
pub async fn open_bi_stream(&self) -> MockStream {
|
|
MockStream::new(
|
|
self.bytes_sent.clone(),
|
|
self.bytes_received.clone(),
|
|
self.rtt_us,
|
|
)
|
|
}
|
|
|
|
pub fn stats(&self) -> ConnectionStats {
|
|
ConnectionStats {
|
|
bytes_sent: self.bytes_sent.load(Ordering::Relaxed),
|
|
bytes_received: self.bytes_received.load(Ordering::Relaxed),
|
|
rtt_ms: (self.rtt_us as f64) / 1000.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct MockStream {
|
|
bytes_sent: Arc<AtomicU64>,
|
|
bytes_received: Arc<AtomicU64>,
|
|
rtt_us: u64,
|
|
}
|
|
|
|
impl MockStream {
|
|
fn new(
|
|
bytes_sent: Arc<AtomicU64>,
|
|
bytes_received: Arc<AtomicU64>,
|
|
rtt_us: u64,
|
|
) -> Self {
|
|
Self {
|
|
bytes_sent,
|
|
bytes_received,
|
|
rtt_us,
|
|
}
|
|
}
|
|
|
|
pub async fn send(&mut self, data: &[u8]) -> Result<usize, String> {
|
|
// Simulate network delay
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(self.rtt_us / 2)).await;
|
|
self.bytes_sent.fetch_add(data.len() as u64, Ordering::Relaxed);
|
|
Ok(data.len())
|
|
}
|
|
|
|
pub async fn recv(&mut self, buf: &mut [u8]) -> Result<usize, String> {
|
|
// Simulate network delay
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(self.rtt_us / 2)).await;
|
|
let len = buf.len().min(8192); // Simulate typical packet size
|
|
self.bytes_received
|
|
.fetch_add(len as u64, Ordering::Relaxed);
|
|
Ok(len)
|
|
}
|
|
|
|
pub async fn finish(&mut self) -> Result<(), String> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub struct ConnectionStats {
|
|
pub bytes_sent: u64,
|
|
pub bytes_received: u64,
|
|
pub rtt_ms: f64,
|
|
}
|
|
}
|
|
|
|
/// Benchmark stream throughput with various payload sizes
|
|
fn benchmark_stream_throughput(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("stream_throughput");
|
|
|
|
// Test various payload sizes: 1KB, 10KB, 100KB, 1MB
|
|
for size in [1024, 10 * 1024, 100 * 1024, 1024 * 1024].iter() {
|
|
group.throughput(Throughput::Bytes(*size as u64));
|
|
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100); // 100μs RTT
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; size];
|
|
|
|
// Send data
|
|
black_box(stream.send(&data).await.unwrap());
|
|
stream.finish().await.unwrap();
|
|
});
|
|
});
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark connection establishment latency
|
|
fn benchmark_connection_latency(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("connection_latency");
|
|
|
|
// Test different RTT scenarios
|
|
for rtt_us in [100, 500, 1000, 5000].iter() {
|
|
group.bench_with_input(
|
|
BenchmarkId::new("establish", format!("{}us", rtt_us)),
|
|
rtt_us,
|
|
|b, &rtt_us| {
|
|
b.to_async(&rt).iter(|| async {
|
|
// Simulate connection establishment
|
|
let start = std::time::Instant::now();
|
|
let _conn = mock::MockConnection::new(rtt_us);
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(rtt_us * 3)).await;
|
|
black_box(start.elapsed());
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark multiplexing performance with concurrent streams
|
|
fn benchmark_multiplexing(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("multiplexing");
|
|
|
|
// Test concurrent stream handling: 10, 100, 500, 1000 streams
|
|
for num_streams in [10, 100, 500, 1000].iter() {
|
|
group.bench_with_input(
|
|
BenchmarkId::from_parameter(num_streams),
|
|
num_streams,
|
|
|b, &num_streams| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = Arc::new(mock::MockConnection::new(100));
|
|
let mut handles = Vec::new();
|
|
|
|
// Open and use multiple concurrent streams
|
|
for _ in 0..num_streams {
|
|
let conn = conn.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; 1024];
|
|
stream.send(&data).await.unwrap();
|
|
stream.finish().await.unwrap();
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all streams to complete
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark 0-RTT connection time
|
|
fn benchmark_zero_rtt(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("zero_rtt");
|
|
|
|
group.bench_function("0rtt_connection", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
// Simulate 0-RTT connection (no handshake delay)
|
|
let start = std::time::Instant::now();
|
|
let _conn = mock::MockConnection::new(0);
|
|
black_box(start.elapsed());
|
|
});
|
|
});
|
|
|
|
group.bench_function("1rtt_connection", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
// Simulate 1-RTT connection (standard handshake)
|
|
let start = std::time::Instant::now();
|
|
let _conn = mock::MockConnection::new(100);
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(100)).await;
|
|
black_box(start.elapsed());
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark backpressure handling
|
|
fn benchmark_backpressure(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("backpressure");
|
|
|
|
// Test backpressure with different buffer sizes
|
|
for buffer_size in [16, 64, 256, 1024].iter() {
|
|
group.bench_with_input(
|
|
BenchmarkId::new("buffer_kb", buffer_size),
|
|
buffer_size,
|
|
|b, &buffer_size| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100);
|
|
let mut stream = conn.open_bi_stream().await;
|
|
|
|
// Send data with simulated backpressure
|
|
for _ in 0..10 {
|
|
let data = vec![0u8; buffer_size * 1024];
|
|
stream.send(&data).await.unwrap();
|
|
}
|
|
stream.finish().await.unwrap();
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark error recovery time
|
|
fn benchmark_error_recovery(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("error_recovery");
|
|
|
|
group.bench_function("stream_reset", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100);
|
|
let mut stream = conn.open_bi_stream().await;
|
|
|
|
// Simulate stream reset
|
|
let data = vec![0u8; 1024];
|
|
stream.send(&data).await.unwrap();
|
|
|
|
// Reset and create new stream
|
|
let mut new_stream = conn.open_bi_stream().await;
|
|
black_box(new_stream.send(&data).await.unwrap());
|
|
});
|
|
});
|
|
|
|
group.bench_function("connection_migration", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
// Simulate connection migration
|
|
let _conn1 = mock::MockConnection::new(100);
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(50)).await;
|
|
|
|
// Create new connection (simulating migration)
|
|
let _conn2 = mock::MockConnection::new(100);
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(50)).await;
|
|
black_box(());
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark bidirectional vs unidirectional streams
|
|
fn benchmark_stream_types(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("stream_types");
|
|
|
|
group.bench_function("bidirectional", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100);
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; 4096];
|
|
|
|
stream.send(&data).await.unwrap();
|
|
let mut buf = vec![0u8; 4096];
|
|
stream.recv(&mut buf).await.unwrap();
|
|
stream.finish().await.unwrap();
|
|
});
|
|
});
|
|
|
|
group.bench_function("unidirectional", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100);
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; 4096];
|
|
|
|
stream.send(&data).await.unwrap();
|
|
stream.finish().await.unwrap();
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark stream priority handling
|
|
fn benchmark_stream_priority(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("stream_priority");
|
|
|
|
// Simulate different priority streams
|
|
group.bench_function("mixed_priority", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = Arc::new(mock::MockConnection::new(100));
|
|
let mut handles = Vec::new();
|
|
|
|
// Create streams with different "priorities" (simulated by varying delays)
|
|
for priority in 0..4 {
|
|
let conn = conn.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; 1024];
|
|
|
|
// Higher priority = less delay
|
|
tokio::time::sleep(tokio::time::Duration::from_micros(priority * 10)).await;
|
|
stream.send(&data).await.unwrap();
|
|
stream.finish().await.unwrap();
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark connection statistics collection
|
|
fn benchmark_stats_collection(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("stats_collection");
|
|
|
|
group.bench_function("get_stats", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100);
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; 1024];
|
|
|
|
stream.send(&data).await.unwrap();
|
|
|
|
// Get connection stats
|
|
let stats = conn.stats();
|
|
black_box(stats);
|
|
});
|
|
});
|
|
|
|
group.bench_function("high_frequency_stats", |b| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let conn = mock::MockConnection::new(100);
|
|
|
|
// Simulate frequent stats polling
|
|
for _ in 0..100 {
|
|
let stats = conn.stats();
|
|
black_box(stats);
|
|
}
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark concurrent connections
|
|
fn benchmark_concurrent_connections(c: &mut Criterion) {
|
|
let rt = Runtime::new().unwrap();
|
|
let mut group = c.benchmark_group("concurrent_connections");
|
|
|
|
for num_conns in [1, 10, 50, 100].iter() {
|
|
group.bench_with_input(
|
|
BenchmarkId::from_parameter(num_conns),
|
|
num_conns,
|
|
|b, &num_conns| {
|
|
b.to_async(&rt).iter(|| async {
|
|
let mut handles = Vec::new();
|
|
|
|
// Create multiple concurrent connections
|
|
for _ in 0..num_conns {
|
|
let handle = tokio::spawn(async move {
|
|
let conn = mock::MockConnection::new(100);
|
|
let mut stream = conn.open_bi_stream().await;
|
|
let data = vec![0u8; 1024];
|
|
stream.send(&data).await.unwrap();
|
|
stream.finish().await.unwrap();
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
benchmark_stream_throughput,
|
|
benchmark_connection_latency,
|
|
benchmark_multiplexing,
|
|
benchmark_zero_rtt,
|
|
benchmark_backpressure,
|
|
benchmark_error_recovery,
|
|
benchmark_stream_types,
|
|
benchmark_stream_priority,
|
|
benchmark_stats_collection,
|
|
benchmark_concurrent_connections,
|
|
);
|
|
|
|
criterion_main!(benches);
|