use std::{hint::black_box, time::Duration};
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use ntex_httparse::{Header, HeaderParsed, Request, Response, SlicePos};
const REQ_SHORT: &[u8] = b"\
GET / HTTP/1.0\r\n\
Host: example.com\r\n\
Cookie: session=60; user_id=1\r\n\r\n";
const REQ: &[u8] = b"\
GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
Host: www.kittyhell.com\r\n\
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
Accept-Encoding: gzip,deflate\r\n\
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
Keep-Alive: 115\r\n\
Connection: keep-alive\r\n\
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n";
#[inline(always)]
fn parse_headers(header: &mut Header, input: &[u8], mut consumed: usize) -> usize {
loop {
match black_box(
header
.parse(black_box(&input[consumed..]))
.unwrap()
.unwrap(),
) {
HeaderParsed::Header(l) => consumed += l,
HeaderParsed::Eof(l) => return consumed + l,
}
}
}
fn empty_header() -> Header {
Header {
name: SlicePos { start: 0, end: 0 },
value: SlicePos { start: 0, end: 0 },
}
}
fn req(c: &mut Criterion) {
c.benchmark_group("req")
.throughput(Throughput::Bytes(REQ.len() as u64))
.bench_function("req", |b| {
let mut msg = Request::default();
let mut header = empty_header();
b.iter(|| {
let consumed = black_box(msg.parse(black_box(REQ)).unwrap().unwrap());
let consumed = parse_headers(&mut header, REQ, consumed);
assert_eq!(consumed, REQ.len());
})
});
}
fn req_short(c: &mut Criterion) {
c.benchmark_group("req_short")
.throughput(Throughput::Bytes(REQ_SHORT.len() as u64))
.bench_function("req_short", |b| {
let mut msg = Request::default();
let mut header = empty_header();
b.iter(|| {
let consumed = black_box(msg.parse(black_box(REQ_SHORT)).unwrap().unwrap());
let consumed = parse_headers(&mut header, REQ_SHORT, consumed);
assert_eq!(consumed, REQ_SHORT.len());
})
});
}
const RESP_SHORT: &[u8] = b"\
HTTP/1.0 200 OK\r\n\
Date: Wed, 21 Oct 2015 07:28:00 GMT\r\n\
Set-Cookie: session=60; user_id=1\r\n\r\n";
const RESP: &[u8] = b"\
HTTP/1.1 200 OK\r\n\
Date: Wed, 21 Oct 2015 07:28:00 GMT\r\n\
Host: www.kittyhell.com\r\n\
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
Accept-Encoding: gzip,deflate\r\n\
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
Keep-Alive: 115\r\n\
Connection: keep-alive\r\n\
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n";
fn resp(c: &mut Criterion) {
c.benchmark_group("resp")
.throughput(Throughput::Bytes(RESP.len() as u64))
.bench_function("resp", |b| {
let mut msg = Response::default();
let mut header = empty_header();
b.iter(|| {
let consumed = black_box(msg.parse(black_box(RESP)).unwrap().unwrap());
let consumed = parse_headers(&mut header, RESP, consumed);
assert_eq!(consumed, RESP.len());
})
});
}
fn resp_short(c: &mut Criterion) {
c.benchmark_group("resp_short")
.throughput(Throughput::Bytes(RESP_SHORT.len() as u64))
.bench_function("resp_short", |b| {
let mut msg = Response::default();
let mut header = empty_header();
b.iter(|| {
let consumed = black_box(msg.parse(black_box(RESP_SHORT)).unwrap().unwrap());
let consumed = parse_headers(&mut header, RESP_SHORT, consumed);
assert_eq!(consumed, RESP_SHORT.len());
})
});
}
fn uri(c: &mut Criterion) {
fn _uri(c: &mut Criterion, name: &str, input: &'static [u8]) {
c.benchmark_group("uri")
.throughput(Throughput::Bytes(input.len() as u64))
.bench_function(name, |b| {
b.iter(|| black_box(ntex_httparse::parse_uri(input).unwrap()))
});
}
const S: &[u8] = b" ";
const CHUNK64: &[u8] = b"/wp-content/uploads/2022/08/31/hello-kitty-darth-vader-pink.webp";
let chunk_4k = CHUNK64.repeat(64);
for p in 0..=12 {
let n = 1 << p;
_uri(
c,
&format!("uri_{:04}b", n),
[chunk_4k[..n].to_vec(), S.into()].concat().leak(),
);
}
}
fn header(c: &mut Criterion) {
fn _header(c: &mut Criterion, name: &str, input: &'static [u8]) {
c.benchmark_group("header")
.throughput(Throughput::Bytes(input.len() as u64))
.bench_function(name, |b| {
let mut header = empty_header();
b.iter(|| parse_headers(&mut header, input, 0))
});
}
const RN: &[u8] = b"\r\n";
const RNRN: &[u8] = b"\r\n\r\n";
const TINY_RN: &[u8] = b"a: b\r\n"; const XFOOBAR: &[u8] = b"X-Foobar";
let xfoobar_4k = XFOOBAR.repeat(4096 / XFOOBAR.len());
for p in 0..=12 {
let n = 1 << p;
let payload = [&xfoobar_4k[..n], b": b", RNRN].concat().leak();
_header(c, &format!("name_{:04}b", n), payload);
}
for p in 0..=12 {
let n = 1 << p;
let payload = [b"a: ", &xfoobar_4k[..n], RNRN].concat().leak();
_header(c, &format!("value_{:04}b", n), payload);
}
for p in 0..=7 {
let n = 1 << p;
_header(
c,
&format!("count_{:03}", n),
[TINY_RN.repeat(n), RN.into()].concat().leak(),
);
}
}
fn reason(c: &mut Criterion) {
fn _reason(c: &mut Criterion, name: &str, input: &'static [u8]) {
c.benchmark_group("reason")
.throughput(Throughput::Bytes(input.len() as u64))
.bench_function(name, |b| {
let mut resp = Response::default();
b.iter(|| black_box(resp.parse(black_box(input)).unwrap().unwrap()))
});
}
const REASON: &[u8] = b"Non-Authoritative Information from the upstream proxy server ";
let reason_4k = REASON.repeat(4096 / REASON.len() + 1);
_reason(c, "ok", b"HTTP/1.1 200 OK\r\n");
for p in 0..=10 {
let n = 1 << p;
let payload = [b"HTTP/1.1 200 ", &reason_4k[..n], b"\r\n"].concat().leak();
_reason(c, &format!("reason_{:04}b", n), payload);
}
}
fn version(c: &mut Criterion) {
fn _version(c: &mut Criterion, name: &str, input: &'static [u8]) {
c.benchmark_group("version")
.throughput(Throughput::Bytes(input.len() as u64))
.bench_function(name, |b| {
b.iter(|| black_box(ntex_httparse::parse_version(input).unwrap()))
});
}
_version(c, "http10", b"HTTP/1.0\r\n");
_version(c, "http11", b"HTTP/1.1\r\n");
_version(c, "partial", b"HTTP/1.");
}
fn method(c: &mut Criterion) {
fn _method(c: &mut Criterion, name: &str, input: &[u8]) {
c.benchmark_group("method")
.throughput(Throughput::Bytes(input.len() as u64))
.bench_function(name, |b| {
b.iter(|| black_box(ntex_httparse::parse_method(input).unwrap()))
});
}
const COMMON_METHODS: &[&str] = &[
"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH",
];
for method in COMMON_METHODS {
_method(
c,
&method.to_lowercase(),
format!("{} / HTTP/1.1\r\n", method).as_bytes(),
);
}
_method(c, "custom", b"CUSTOM / HTTP/1.1\r\n");
_method(c, "w3!rd", b"w3!rd / HTTP/1.1\r\n");
}
fn many_requests(c: &mut Criterion) {
use rand::{SeedableRng, rngs::StdRng, seq::SliceRandom};
let mut requests = [
("GET", 500),
("POST", 300),
("OPTIONS", 100),
("HEAD", 50),
("w3!r`d", 20),
]
.iter()
.flat_map(|&(method, count)| std::iter::repeat_n(method, count))
.map(|method| format!("{method} / HTTP/1.1\r\n\r\n"))
.collect::<Vec<_>>();
SliceRandom::shuffle(&mut *requests, &mut StdRng::seed_from_u64(0));
let total_bytes: usize = requests.iter().map(String::len).sum();
c.benchmark_group("many_requests")
.throughput(Throughput::Bytes(total_bytes as u64))
.measurement_time(Duration::from_secs(1))
.sample_size(1000)
.bench_function("_", |b| {
b.iter(|| {
requests.iter().for_each(|req| {
black_box(ntex_httparse::parse_method(req.as_bytes()).unwrap());
});
})
});
}
const WARMUP: Duration = Duration::from_millis(100);
const MTIME: Duration = Duration::from_millis(100);
const SAMPLES: usize = 200;
criterion_group! {
name = benches;
config = Criterion::default().sample_size(SAMPLES).warm_up_time(WARMUP).measurement_time(MTIME);
targets = req, req_short, resp, resp_short, uri, header, reason, version, method, many_requests
}
criterion_main!(benches);