1 use std::time::Duration;
2 
3 use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput, BatchSize};
4 
5 const REQ_SHORT: &[u8] = b"\
6 GET / HTTP/1.0\r\n\
7 Host: example.com\r\n\
8 Cookie: session=60; user_id=1\r\n\r\n";
9 
10 const REQ: &[u8] = b"\
11 GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
12 Host: www.kittyhell.com\r\n\
13 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\
14 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
15 Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
16 Accept-Encoding: gzip,deflate\r\n\
17 Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
18 Keep-Alive: 115\r\n\
19 Connection: keep-alive\r\n\
20 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";
21 
req(c: &mut Criterion)22 fn req(c: &mut Criterion) {
23     c.benchmark_group("req")
24         .throughput(Throughput::Bytes(REQ.len() as u64))
25         .bench_function("req", |b| b.iter_batched_ref(|| {
26             [httparse::Header {
27                 name: "",
28                 value: &[],
29             }; 16]
30         },|headers| {
31             let mut req = httparse::Request::new(headers);
32             assert_eq!(
33                 black_box(req.parse(REQ).unwrap()),
34                 httparse::Status::Complete(REQ.len())
35             );
36         }, BatchSize::SmallInput));
37 }
38 
req_short(c: &mut Criterion)39 fn req_short(c: &mut Criterion) {
40     c.benchmark_group("req_short")
41         .throughput(Throughput::Bytes(REQ_SHORT.len() as u64))
42         .bench_function("req_short", |b| b.iter_batched_ref(|| {
43             [httparse::Header {
44                 name: "",
45                 value: &[],
46             }; 16]
47         },|headers| {
48             let mut req = httparse::Request::new(headers);
49             assert_eq!(
50                 req.parse(black_box(REQ_SHORT)).unwrap(),
51                 httparse::Status::Complete(REQ_SHORT.len())
52             );
53         }, BatchSize::SmallInput));
54 }
55 
56 const RESP_SHORT: &[u8] = b"\
57 HTTP/1.0 200 OK\r\n\
58 Date: Wed, 21 Oct 2015 07:28:00 GMT\r\n\
59 Set-Cookie: session=60; user_id=1\r\n\r\n";
60 
61 // These particular headers don't all make semantic sense for a response, but they're syntactically valid.
62 const RESP: &[u8] = b"\
63 HTTP/1.1 200 OK\r\n\
64 Date: Wed, 21 Oct 2015 07:28:00 GMT\r\n\
65 Host: www.kittyhell.com\r\n\
66 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\
67 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
68 Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
69 Accept-Encoding: gzip,deflate\r\n\
70 Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
71 Keep-Alive: 115\r\n\
72 Connection: keep-alive\r\n\
73 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";
74 
resp(c: &mut Criterion)75 fn resp(c: &mut Criterion) {
76     c.benchmark_group("resp")
77         .throughput(Throughput::Bytes(RESP.len() as u64))
78         .bench_function("resp", |b| b.iter_batched_ref(|| {
79             [httparse::Header {
80                 name: "",
81                 value: &[],
82             }; 16]
83         }, |headers| {
84             let mut resp = httparse::Response::new(headers);
85             assert_eq!(
86                 resp.parse(black_box(RESP)).unwrap(),
87                 httparse::Status::Complete(RESP.len())
88             );
89         }, BatchSize::SmallInput));
90 }
91 
resp_short(c: &mut Criterion)92 fn resp_short(c: &mut Criterion) {
93     c.benchmark_group("resp_short")
94         .throughput(Throughput::Bytes(RESP_SHORT.len() as u64))
95         .bench_function("resp_short", |b| b.iter_batched_ref(|| {
96             [httparse::Header {
97                 name: "",
98                 value: &[],
99             }; 16]
100         },
101         |headers| {
102             let mut resp = httparse::Response::new(headers);
103             assert_eq!(
104                 resp.parse(black_box(RESP_SHORT)).unwrap(),
105                 httparse::Status::Complete(RESP_SHORT.len())
106             );
107         }, BatchSize::SmallInput));
108 }
109 
uri(c: &mut Criterion)110 fn uri(c: &mut Criterion) {
111     fn _uri(c: &mut Criterion, name: &str, input: &'static [u8]) {
112         c.benchmark_group("uri")
113         .throughput(Throughput::Bytes(input.len() as u64))
114         .bench_function(name, |b| b.iter(|| {
115             let mut b = httparse::_benchable::Bytes::new(black_box(input));
116             httparse::_benchable::parse_uri(&mut b).unwrap()
117         }));
118     }
119 
120     const S: &[u8] = b" ";
121     const CHUNK64: &[u8] = b"/wp-content/uploads/2022/08/31/hello-kitty-darth-vader-pink.webp";
122     let chunk_4k = CHUNK64.repeat(64);
123 
124     // 1b to 4096b
125     for p in 0..=12 {
126         let n = 1 << p;
127         _uri(c, &format!("uri_{:04}b", n), [chunk_4k[..n].to_vec(), S.into()].concat().leak());
128     }
129 }
130 
header(c: &mut Criterion)131 fn header(c: &mut Criterion) {
132     fn _header(c: &mut Criterion, name: &str, input: &'static [u8]) {
133         c.benchmark_group("header")
134         .throughput(Throughput::Bytes(input.len() as u64))
135         .bench_function(name, |b| b.iter_batched_ref(|| [httparse::EMPTY_HEADER; 128],|headers| {
136             let status = httparse::parse_headers(black_box(input), headers).unwrap();
137             black_box(status.unwrap()).0
138         }, BatchSize::SmallInput));
139     }
140 
141     const RN: &[u8] = b"\r\n";
142     const RNRN: &[u8] = b"\r\n\r\n";
143     const TINY_RN: &[u8] = b"a: b\r\n"; // minimal header line
144     const XFOOBAR: &[u8] = b"X-Foobar";
145     let xfoobar_4k = XFOOBAR.repeat(4096/XFOOBAR.len());
146 
147     // header names 1b to 4096b
148     for p in 0..=12 {
149         let n = 1 << p;
150         let payload = [&xfoobar_4k[..n], b": b", RNRN].concat().leak();
151         _header(c, &format!("name_{:04}b", n), payload);
152     }
153 
154     // header values 1b to 4096b
155     for p in 0..=12 {
156         let n = 1 << p;
157         let payload = [b"a: ", &xfoobar_4k[..n], RNRN].concat().leak();
158         _header(c, &format!("value_{:04}b", n), payload);
159     }
160 
161     // 1 to 128
162     for p in 0..=7 {
163         let n = 1 << p;
164         _header(c, &format!("count_{:03}", n), [TINY_RN.repeat(n), RN.into()].concat().leak());
165     }
166 }
167 
version(c: &mut Criterion)168 fn version(c: &mut Criterion) {
169     fn _version(c: &mut Criterion, name: &str, input: &'static [u8]) {
170         c.benchmark_group("version")
171         .throughput(Throughput::Bytes(input.len() as u64))
172         .bench_function(name, |b| b.iter(|| {
173             let mut b = httparse::_benchable::Bytes::new(black_box(input));
174             httparse::_benchable::parse_version(&mut b).unwrap()
175         }));
176     }
177 
178     _version(c, "http10", b"HTTP/1.0\r\n");
179     _version(c, "http11", b"HTTP/1.1\r\n");
180     _version(c, "partial", b"HTTP/1.");
181 }
182 
method(c: &mut Criterion)183 fn method(c: &mut Criterion) {
184     fn _method(c: &mut Criterion, name: &str, input: &'static [u8]) {
185         c.benchmark_group("method")
186         .throughput(Throughput::Bytes(input.len() as u64))
187         .bench_function(name, |b| b.iter(|| {
188             let mut b = httparse::_benchable::Bytes::new(black_box(input));
189             httparse::_benchable::parse_method(&mut b).unwrap()
190         }));
191     }
192 
193     // Common methods should be fast-pathed
194     const COMMON_METHODS: &[&str] = &["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"];
195     for method in COMMON_METHODS {
196         _method(c, &method.to_lowercase(), format!("{} / HTTP/1.1\r\n", method).into_bytes().leak());
197     }
198     // Custom methods should be infrequent and thus not worth optimizing
199     _method(c, "custom", b"CUSTOM / HTTP/1.1\r\n");
200 }
201 
202 const WARMUP: Duration = Duration::from_millis(100);
203 const MTIME: Duration = Duration::from_millis(100);
204 const SAMPLES: usize = 200;
205 criterion_group!{
206     name = benches;
207     config = Criterion::default().sample_size(SAMPLES).warm_up_time(WARMUP).measurement_time(MTIME);
208     targets = req, req_short, resp, resp_short, uri, header, version, method
209 }
210 criterion_main!(benches);
211