#![allow(missing_docs)]
use criterion::{Criterion, criterion_group, criterion_main};
use http_handle::response::Response;
use http_handle::{Server, ShutdownSignal};
use std::hint::black_box;
use std::io::{Cursor, Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
fn reserve_port() -> (String, TempDir) {
let probe = TcpListener::bind("127.0.0.1:0").expect("probe bind");
let addr = probe.local_addr().expect("addr").to_string();
drop(probe);
let root = TempDir::new().expect("tempdir");
(addr, root)
}
enum AcceptMode {
Basic,
ThreadPool(usize),
}
fn spawn_sync_server_with(body: &[u8], mode: AcceptMode) -> String {
let (addr, root) = reserve_port();
std::fs::write(root.path().join("test.html"), body)
.expect("write body");
std::fs::create_dir(root.path().join("404")).expect("404 dir");
std::fs::write(root.path().join("404/index.html"), b"404")
.expect("write 404");
let document_root =
root.path().to_str().expect("utf8 path").to_string();
let addr_for_server = addr.clone();
let _ = thread::spawn(move || {
let _root_keepalive = root;
let server = Server::new(&addr_for_server, &document_root);
match mode {
AcceptMode::Basic => {
let _ = server.start();
}
AcceptMode::ThreadPool(size) => {
let _ = server.start_with_thread_pool(size);
}
}
});
for _ in 0..100 {
if TcpStream::connect(&addr).is_ok() {
return addr;
}
thread::sleep(Duration::from_millis(5));
}
panic!("server never bound on {addr}");
}
fn spawn_sync_server(body: &[u8]) -> String {
spawn_sync_server_with(body, AcceptMode::Basic)
}
fn spawn_sync_server_with_rate_limit(
body: &[u8],
rate_per_minute: usize,
) -> String {
let (addr, root) = reserve_port();
std::fs::write(root.path().join("test.html"), body)
.expect("write body");
std::fs::create_dir(root.path().join("404")).expect("404 dir");
std::fs::write(root.path().join("404/index.html"), b"404")
.expect("write 404");
let document_root =
root.path().to_str().expect("utf8 path").to_string();
let addr_for_server = addr.clone();
let _ = thread::spawn(move || {
let _root_keepalive = root;
let server = Server::builder()
.address(&addr_for_server)
.document_root(&document_root)
.rate_limit_per_minute(rate_per_minute)
.build()
.expect("server build");
let _ = server.start();
});
for _ in 0..100 {
if TcpStream::connect(&addr).is_ok() {
return addr;
}
thread::sleep(Duration::from_millis(5));
}
panic!("server never bound on {addr}");
}
fn roundtrip(addr: &str) {
let mut stream = TcpStream::connect(addr).expect("connect");
stream
.write_all(b"GET /test.html HTTP/1.1\r\nHost: b\r\n\r\n")
.expect("write");
let mut sink = Vec::with_capacity(256);
let _ = stream.read_to_end(&mut sink).expect("read");
let _ = black_box(sink);
}
fn bench_sync_server_small_body(c: &mut Criterion) {
let addr =
spawn_sync_server(b"<html><body>Test Content</body></html>");
let _ = c.bench_function("sync_server_small_body_38B", |b| {
b.iter(|| roundtrip(&addr));
});
}
fn drive_concurrent(addr: &str, parallelism: usize) {
thread::scope(|s| {
let mut handles = Vec::with_capacity(parallelism);
for _ in 0..parallelism {
handles.push(s.spawn(|| roundtrip(addr)));
}
for h in handles {
let _ = h.join();
}
});
}
fn bench_sync_server_basic_concurrent_8(c: &mut Criterion) {
let addr =
spawn_sync_server(b"<html><body>Test Content</body></html>");
let _ = c.bench_function("sync_server_basic_concurrent_8", |b| {
b.iter(|| drive_concurrent(&addr, 8));
});
}
fn bench_sync_server_rate_limit_concurrent_8(c: &mut Criterion) {
let addr = spawn_sync_server_with_rate_limit(
b"<html><body>Test Content</body></html>",
usize::MAX / 2,
);
let _ =
c.bench_function("sync_server_rate_limit_concurrent_8", |b| {
b.iter(|| drive_concurrent(&addr, 8));
});
}
fn bench_sync_server_thread_pool_concurrent_8(c: &mut Criterion) {
let addr = spawn_sync_server_with(
b"<html><body>Test Content</body></html>",
AcceptMode::ThreadPool(8),
);
let _ =
c.bench_function("sync_server_thread_pool_concurrent_8", |b| {
b.iter(|| drive_concurrent(&addr, 8));
});
}
fn bench_sync_server_thread_pool_concurrent_32(c: &mut Criterion) {
let addr = spawn_sync_server_with(
b"<html><body>Test Content</body></html>",
AcceptMode::ThreadPool(16),
);
let _ = c.bench_function(
"sync_server_thread_pool_concurrent_32",
|b| {
b.iter(|| drive_concurrent(&addr, 32));
},
);
}
fn bench_sync_server_thread_pool_concurrent_64(c: &mut Criterion) {
let addr = spawn_sync_server_with(
b"<html><body>Test Content</body></html>",
AcceptMode::ThreadPool(16),
);
let _ = c.bench_function(
"sync_server_thread_pool_concurrent_64",
|b| {
b.iter(|| drive_concurrent(&addr, 64));
},
);
}
fn spawn_shutdown_aware_server(body: &[u8]) -> String {
let (probe_addr, root) = reserve_port();
std::fs::write(root.path().join("test.html"), body)
.expect("write body");
std::fs::create_dir(root.path().join("404")).expect("404 dir");
std::fs::write(root.path().join("404/index.html"), b"404")
.expect("write 404");
let document_root =
root.path().to_str().expect("utf8 path").to_string();
let shutdown =
Arc::new(ShutdownSignal::new(Duration::from_secs(2)));
let _shutdown_keep = shutdown.clone();
let (ready_tx, ready_rx) = mpsc::channel::<String>();
let _ = thread::spawn(move || {
let _root_keep = root;
let server = Server::builder()
.address(&probe_addr)
.document_root(&document_root)
.build()
.expect("server build");
let _ = server.start_with_shutdown_signal_and_ready(
shutdown,
move |addr| {
let _ = ready_tx.send(addr);
},
);
});
ready_rx
.recv_timeout(Duration::from_secs(2))
.expect("server never reported ready")
}
fn bench_sync_server_shutdown_aware_single(c: &mut Criterion) {
let addr = spawn_shutdown_aware_server(
b"<html><body>Test Content</body></html>",
);
let _ =
c.bench_function("sync_server_shutdown_aware_single", |b| {
b.iter(|| roundtrip(&addr));
});
}
fn bench_response_send_small(c: &mut Criterion) {
let mut response = Response::new(
200,
"OK",
b"<html><body>hello</body></html>".to_vec(),
);
response.add_header("Content-Type", "text/html");
response.add_header("ETag", "W/\"1f-68a0cf20\"");
response.add_header("Accept-Ranges", "bytes");
let _ =
c.bench_function("response_send_small_body_5_headers", |b| {
b.iter(|| {
let mut sink =
Cursor::new(Vec::<u8>::with_capacity(256));
response.send(&mut sink).expect("send");
let _ = black_box(sink.into_inner());
});
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(Duration::from_secs(2))
.measurement_time(Duration::from_secs(5))
.sample_size(60);
targets =
bench_sync_server_small_body,
bench_sync_server_basic_concurrent_8,
bench_sync_server_thread_pool_concurrent_8,
bench_sync_server_thread_pool_concurrent_32,
bench_sync_server_thread_pool_concurrent_64,
bench_sync_server_rate_limit_concurrent_8,
bench_sync_server_shutdown_aware_single,
bench_response_send_small
}
criterion_main!(benches);