use bytes::Bytes;
use criterion::{Criterion, criterion_group, criterion_main};
use hitbox::predicate::{Predicate, PredicateExt};
use hitbox_http::predicates::{
NeutralRequestPredicate,
request::{
HeaderPredicate,
header::Operation as HeaderOperation,
method::MethodPredicate,
path::PathPredicate,
query::{Operation as QueryOperation, QueryPredicate},
},
};
use hitbox_http::{BufferedBody, CacheableHttpRequest};
use http::{Method, Request};
use http_body_util::Empty;
use std::hint::black_box;
fn create_simple_request() -> Request<BufferedBody<Empty<Bytes>>> {
Request::builder()
.method(Method::GET)
.uri("http://example.com/api/users/123")
.header("content-type", "application/json")
.header("authorization", "Bearer token123")
.body(BufferedBody::Passthrough(Empty::new()))
.unwrap()
}
fn create_complex_request() -> Request<BufferedBody<Empty<Bytes>>> {
Request::builder()
.method(Method::POST)
.uri("http://example.com/api/v2/users/456/posts?filter=active&sort=date&limit=10")
.header("content-type", "application/json")
.header("authorization", "Bearer token456")
.header("x-api-key", "secret123")
.header("x-tenant-id", "tenant-a")
.header("accept", "application/json")
.header("user-agent", "Mozilla/5.0")
.body(BufferedBody::Passthrough(Empty::new()))
.unwrap()
}
fn bench_single_predicates(c: &mut Criterion) {
let mut group = c.benchmark_group("single_predicates");
let rt = tokio::runtime::Runtime::new().unwrap();
group.bench_function("method_match", |b| {
let predicate = NeutralRequestPredicate::new().method(Method::GET);
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("path_match", |b| {
let predicate = NeutralRequestPredicate::new().path("/api/users/{user_id}".to_string());
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("header_single_match", |b| {
let predicate = NeutralRequestPredicate::new().header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("header_exist", |b| {
let predicate = NeutralRequestPredicate::new()
.header(HeaderOperation::Exist("authorization".parse().unwrap()));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("query_match", |b| {
let predicate = NeutralRequestPredicate::new().query(QueryOperation::Eq(
"filter".to_string(),
"active".to_string(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_complex_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.finish();
}
fn bench_small_chains(c: &mut Criterion) {
let mut group = c.benchmark_group("small_chains");
let rt = tokio::runtime::Runtime::new().unwrap();
group.bench_function("method_and_path", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::GET)
.path("/api/users/{user_id}".to_string());
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("method_path_header", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::GET)
.path("/api/users/{user_id}".to_string())
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("method_path_header_query", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::POST)
.path("/api/v2/users/{user_id}/posts".to_string())
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
))
.query(QueryOperation::Eq(
"filter".to_string(),
"active".to_string(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_complex_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("or_two_methods", |b| {
let left = NeutralRequestPredicate::new().method(Method::GET);
let right = NeutralRequestPredicate::new().method(Method::POST);
let predicate = left.or(right);
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("not_method", |b| {
let predicate = NeutralRequestPredicate::new().method(Method::POST).not();
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.finish();
}
fn bench_medium_chains(c: &mut Criterion) {
let mut group = c.benchmark_group("medium_chains");
let rt = tokio::runtime::Runtime::new().unwrap();
group.bench_function("method_and_four_headers", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::POST)
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"authorization".parse().unwrap(),
"Bearer token456".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"x-api-key".parse().unwrap(),
"secret123".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"x-tenant-id".parse().unwrap(),
"tenant-a".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_complex_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("method_path_four_headers", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::POST)
.path("/api/v2/users/{user_id}/posts".to_string())
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"authorization".parse().unwrap(),
"Bearer token456".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"x-api-key".parse().unwrap(),
"secret123".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"x-tenant-id".parse().unwrap(),
"tenant-a".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_complex_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("full_chain_7_predicates", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::POST)
.path("/api/v2/users/{user_id}/posts".to_string())
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"authorization".parse().unwrap(),
"Bearer token456".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"x-api-key".parse().unwrap(),
"secret123".parse().unwrap(),
))
.query(QueryOperation::Eq(
"filter".to_string(),
"active".to_string(),
))
.query(QueryOperation::Eq("sort".to_string(), "date".to_string()));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_complex_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.finish();
}
fn bench_early_exit(c: &mut Criterion) {
let mut group = c.benchmark_group("early_exit");
let rt = tokio::runtime::Runtime::new().unwrap();
group.bench_function("fail_first_predicate", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::DELETE) .path("/api/users/{user_id}".to_string())
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("fail_third_predicate", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::GET)
.path("/api/users/{user_id}".to_string())
.header(HeaderOperation::Eq(
"x-nonexistent".parse().unwrap(),
"value".parse().unwrap(),
)); b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("all_predicates_pass", |b| {
let predicate = NeutralRequestPredicate::new()
.method(Method::GET)
.path("/api/users/{user_id}".to_string())
.header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
))
.header(HeaderOperation::Eq(
"authorization".parse().unwrap(),
"Bearer token123".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.finish();
}
fn bench_predicate_type_comparison(c: &mut Criterion) {
let mut group = c.benchmark_group("predicate_types");
let rt = tokio::runtime::Runtime::new().unwrap();
group.bench_function("type_method", |b| {
let predicate = NeutralRequestPredicate::new().method(Method::GET);
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("type_path", |b| {
let predicate = NeutralRequestPredicate::new().path("/api/users/{user_id}".to_string());
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("type_header_eq", |b| {
let predicate = NeutralRequestPredicate::new().header(HeaderOperation::Eq(
"content-type".parse().unwrap(),
"application/json".parse().unwrap(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("type_header_exist", |b| {
let predicate = NeutralRequestPredicate::new()
.header(HeaderOperation::Exist("authorization".parse().unwrap()));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_simple_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.bench_function("type_query", |b| {
let predicate = NeutralRequestPredicate::new().query(QueryOperation::Eq(
"filter".to_string(),
"active".to_string(),
));
b.to_async(&rt).iter(|| async {
let req = CacheableHttpRequest::from_request(create_complex_request());
black_box(predicate.check(black_box(req)).await)
});
});
group.finish();
}
criterion_group!(
benches,
bench_single_predicates,
bench_small_chains,
bench_medium_chains,
bench_early_exit,
bench_predicate_type_comparison
);
criterion_main!(benches);