use bytes::Bytes;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use http::Method;
use http_body_util::Full;
use oxihttp_core::OxiHttpError;
use std::hint::black_box;
use std::time::Duration;
async fn dummy_handler(
_req: oxihttp_server::router::Request,
) -> Result<hyper::Response<Full<Bytes>>, OxiHttpError> {
Ok(hyper::Response::new(Full::new(Bytes::from_static(b""))))
}
fn build_router(n: usize) -> oxihttp_server::Router {
let mut router = oxihttp_server::Router::new();
for i in 0..n {
let path = format!("/route_{i}");
router = router.get(Box::leak(path.into_boxed_str()), dummy_handler);
}
router
}
async fn spawn_test_server(
router: oxihttp_server::Router,
) -> (std::net::SocketAddr, tokio::sync::oneshot::Sender<()>) {
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let (addr, _handle) = oxihttp_server::Server::bind("127.0.0.1:0")
.with_graceful_shutdown(async move {
let _ = rx.await;
})
.serve_with_addr(router)
.await
.expect("server bind");
tokio::time::sleep(Duration::from_millis(10)).await;
(addr, tx)
}
fn bench_router_dispatch(c: &mut Criterion) {
let mut group = c.benchmark_group("router_dispatch");
for &n in &[10usize, 100, 1000] {
let router = build_router(n);
let method = Method::GET;
let best_target = "/route_0".to_string();
group.bench_with_input(BenchmarkId::new("best_case", n), &n, |b, _| {
b.iter(|| {
black_box(router.resolve(black_box(&method), black_box(best_target.as_str())))
});
});
let worst_target = format!("/route_{}", n - 1);
group.bench_with_input(BenchmarkId::new("worst_case", n), &n, |b, _| {
b.iter(|| {
black_box(router.resolve(black_box(&method), black_box(worst_target.as_str())))
});
});
let miss_target = "/nonexistent".to_string();
group.bench_with_input(BenchmarkId::new("miss", n), &n, |b, _| {
b.iter(|| {
black_box(router.resolve(black_box(&method), black_box(miss_target.as_str())))
});
});
}
group.finish();
}
fn bench_middleware_overhead(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().expect("tokio rt");
let mut group = c.benchmark_group("middleware_overhead");
group.measurement_time(Duration::from_secs(10));
{
let router = oxihttp_server::Router::new().get("/bench", |_req| async {
oxihttp_server::response::text_response("ok")
});
let (addr, _shutdown_tx) = rt.block_on(spawn_test_server(router));
let client = oxihttp_client::ClientBuilder::new()
.build()
.expect("client");
let url = format!("http://{addr}/bench");
group.bench_function("no_middleware", |b| {
b.to_async(&rt).iter(|| async {
let resp = client
.get(black_box(url.as_str()))
.expect("GET builder")
.send()
.await
.expect("send");
black_box(resp.status());
});
});
}
{
let router = oxihttp_server::Router::new().get("/bench", |_req| async {
oxihttp_server::response::text_response("ok")
});
let (addr, _shutdown_tx) = rt.block_on(async {
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let (addr, _handle) = oxihttp_server::Server::bind("127.0.0.1:0")
.with_cors(oxihttp_server::CorsConfig::permissive())
.with_graceful_shutdown(async move {
let _ = rx.await;
})
.serve_with_addr(router)
.await
.expect("server bind");
tokio::time::sleep(Duration::from_millis(10)).await;
(addr, tx)
});
let client = oxihttp_client::ClientBuilder::new()
.build()
.expect("client");
let url = format!("http://{addr}/bench");
group.bench_function("cors_only", |b| {
b.to_async(&rt).iter(|| async {
let resp = client
.get(black_box(url.as_str()))
.expect("GET builder")
.send()
.await
.expect("send");
black_box(resp.status());
});
});
}
{
let router = oxihttp_server::Router::new().get("/bench", |_req| async {
oxihttp_server::response::text_response("ok")
});
let (addr, _shutdown_tx) = rt.block_on(async {
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let (addr, _handle) = oxihttp_server::Server::bind("127.0.0.1:0")
.with_cors(oxihttp_server::CorsConfig::permissive())
.with_body_limit(1024 * 1024) .with_rate_limiter(oxihttp_server::RateLimiter::new(10_000, 10_000.0))
.with_graceful_shutdown(async move {
let _ = rx.await;
})
.serve_with_addr(router)
.await
.expect("server bind");
tokio::time::sleep(Duration::from_millis(10)).await;
(addr, tx)
});
let client = oxihttp_client::ClientBuilder::new()
.build()
.expect("client");
let url = format!("http://{addr}/bench");
group.bench_function("cors_body_limit_rate", |b| {
b.to_async(&rt).iter(|| async {
let resp = client
.get(black_box(url.as_str()))
.expect("GET builder")
.send()
.await
.expect("send");
black_box(resp.status());
});
});
}
#[cfg(feature = "tower")]
{
use oxihttp_server::RequestIdLayer;
let router = oxihttp_server::Router::new().get("/bench", |_req| async {
oxihttp_server::response::text_response("ok")
});
let (addr, _shutdown_tx) = rt.block_on(async {
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let (addr, _handle) = oxihttp_server::Server::bind("127.0.0.1:0")
.with_layer(RequestIdLayer)
.with_layer(RequestIdLayer)
.with_layer(RequestIdLayer)
.with_layer(RequestIdLayer)
.with_layer(RequestIdLayer)
.with_graceful_shutdown(async move {
let _ = rx.await;
})
.serve_with_addr(router)
.await
.expect("server bind");
tokio::time::sleep(Duration::from_millis(10)).await;
(addr, tx)
});
let client = oxihttp_client::ClientBuilder::new()
.build()
.expect("client");
let url = format!("http://{addr}/bench");
group.bench_function("tower_5_layers", |b| {
b.to_async(&rt).iter(|| async {
let resp = client
.get(black_box(url.as_str()))
.expect("GET builder")
.send()
.await
.expect("send");
black_box(resp.status());
});
});
}
group.finish();
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(Duration::from_secs(1))
.measurement_time(Duration::from_secs(5));
targets = bench_router_dispatch, bench_middleware_overhead
}
criterion_main!(benches);