A minimal load testing tool for HTTP/3 servers built in Rust.
- HTTP/3 support using
quichefor direct QUIC implementation - Concurrent request execution using Tokio async runtime
- Comprehensive metrics collection: success/failure counts, RPS, and latency percentiles
- Self-signed certificate support for local testing
- Simple console output with detailed statistics
- CLI interface with configurable parameters
- Rich latency metrics (avg, p50, p90, p95, p99, min, max)
- Error tracking and classification (send errors, recv errors, QUIC errors, stream resets)
- HTTP status code validation and breakdown
- Verbose mode for debugging
- Concurrent worker system with request distribution
- Duration-based and request-count-based load testing
- Self-signed certificate support
The load testing tool includes:
-
HTTP/3 Client
- Uses
quichelibrary for direct QUIC/HTTP3 implementation - Configurable QUIC parameters
- Supports both secure and insecure (self-signed) connections
- Async UDP socket handling with proper timeout management
- Uses
-
Concurrent Worker System
- Spawns multiple async tasks using
tokio::spawn - Distributes total requests across workers using quotient + remainder logic
- Each worker is assigned base requests, with first N workers getting one extra
- Stops when either all requests are completed OR duration expires (whichever comes first)
- Tracks worker panics and cancellations
- Spawns multiple async tasks using
-
Metrics Collection
- Per-request latency measurement
- Aggregation of status codes with per-code counts
- Error categorization: network send/recv, QUIC protocol, stream resets
- Percentile latency calculation with linear interpolation
-
Console Reporting
- Total time elapsed and requests per second
- Completion reason (all requests done or duration limit reached)
- HTTP status code breakdown
- Latency percentiles (min, max, avg, p50, p90, p95, p99)
- Error summary with counts by error type
- Worker failure warnings
# Build
cargo build --release
# Run with default settings (443, localhost, 1000 requests, 1 worker)
./target/release/vex --target example.com
# Run 5000 requests with 100 concurrent workers
cargo run --release -- --target example.com --workers 100 --requests 5000
# Run for 60 seconds with 200 workers
cargo run --release -- --target example.com --workers 200 --duration 60 --insecure
# Test specific endpoint
cargo run --release -- --target service.local --port 8443 --workers 50 --requests 1000 --path "/api/health" --insecure
# Full example with all options
cargo run --release -- --target example.com --port 443 --workers 100 --requests 10000 --duration 120 --path "/api/v1/test"
# Verbose output (prints response headers)
cargo run --release -- --target example.com --workers 10 --requests 100 --verbose--target TARGET- Target host or IP address (required)--port PORT- Target port (default: 443)--workers N- Number of concurrent workers (default: 1, minimum: 1)--requests N- Total number of requests to send (default: 1000)--duration SECS- Maximum duration in seconds (default: 30)--path PATH- Request path (default: /)--insecure- Disable TLS certificate verification--verbose- Print response headers for each request--success-status PATTERN- Success criteria (default: 2xx)
For N total requests across M workers:
- Each worker gets at least
N / Mrequests - The first
N % Mworkers get one additional request - This ensures all N requests are distributed exactly, with no gaps or duplicates
Example: 10 requests across 3 workers
- Worker 0: 4 requests (2 + 1 extra)
- Worker 1: 4 requests (2 + 1 extra)
- Worker 2: 2 requests (2)
The load test continues until one of these conditions is met:
- All requested requests have been sent
- The duration limit has expired
- A worker panics or is cancelled
The completion reason is reported in the output. If the duration expires before all requests are sent, some workers may stop early.
- 0: Success (all tests completed without worker failures and requests matched expected count)
- 1: Worker failure or panic detected
- 1: Request count mismatch when not hitting duration limit
Measured on 2026-04-24 against google.com (HTTP/3, redirects counted as success):
cargo run --release -- --target google.com --insecure \
--success-status 2xx,3xx -n 1000 --workers 10 -c 100
Total time: 1.10s
Total requests: 1000
Successful: 1000
Failed: 0
Requests/sec: 907.55
Completion reason: All 1000 requests completed
HTTP Status code breakdown:
302: 3xx Redirect (1000)
Latency metrics (ms):
Min: 283.63
Max: 1052.91
Avg: 690.64
p50: 685.00
p90: 854.11
p95: 864.72
p99: 930.92
Google closes the QUIC connection after every response, so each request pays a full TLS+QUIC handshake (~300-1000ms). The 907 req/s figure reflects 10 workers × 100 concurrent streams saturating the reconnect pipeline in parallel.
- Tokio - Async runtime for concurrent request handling
- Quiche - Rust implementation of QUIC and HTTP/3 protocols
- Clap - Command line argument parser
- Rand - Random number generation for connection IDs
Written in Rust for performance and fine-grained control over concurrency. Uses the Tokio async runtime for efficient concurrent request handling with proper timeout and error management.