xocomil 0.3.0

A lightweight, zero-allocation HTTP/1.1 request parser and response writer
Documentation
//! Minimal-overhead profiling driver for `Request::parse`.
//!
//! Runs a tight loop over a fixture so `perf` samples land almost
//! exclusively in the parser, not in a benchmark harness. Criterion
//! adds ~15–30% of cycles in `Bencher::iter` machinery; this binary
//! has zero harness.
//!
//! # Build
//!
//! ```sh
//! cargo build --release --example perf_parse
//! ```
//!
//! # Profile (default fixture: htmx)
//!
//! ```sh
//! perf record -F 4000 --call-graph=dwarf \
//!     ./target/release/examples/perf_parse htmx
//! perf report --stdio --no-children -g none --percent-limit 0.5
//! ```
//!
//! # Available fixtures
//!
//! - `minimal`  — `GET / HTTP/1.1\r\nHost: localhost\r\n\r\n` (1 header)
//! - `htmx`     — typical 8-header HTMX request
//! - `browser`  — realistic Chrome request (~12 headers)
//! - `many`     — stress: 32 X-Header-N: value-N pairs
//!
//! Pass an iteration count as the second argument (default 100M):
//!
//! ```sh
//! ./target/release/examples/perf_parse htmx 50000000
//! ```

use std::hint::black_box;

use xocomil::request::Request;

const MINIMAL: &[u8] = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";

const HTMX: &[u8] = b"GET /about HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Accept: text/html\r\n\
HX-Request: true\r\n\
HX-Target: #content\r\n\
HX-Current-URL: http://localhost:8080/\r\n\
User-Agent: Mozilla/5.0\r\n\
Accept-Language: en-US,en;q=0.9\r\n\
Connection: keep-alive\r\n\
\r\n";

const BROWSER: &[u8] = b"GET /articles/digital-signatures-explained HTTP/1.1\r\n\
Host: authentidoc.com\r\n\
Connection: keep-alive\r\n\
Cache-Control: max-age=0\r\n\
Upgrade-Insecure-Requests: 1\r\n\
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\r\n\
Accept-Encoding: gzip, deflate, br\r\n\
Accept-Language: en-US,en;q=0.9,es;q=0.8\r\n\
Cookie: session=abc123; theme=dark\r\n\
If-None-Match: \"etag-value\"\r\n\
HX-Request: true\r\n\
HX-Target: #content\r\n\
HX-Current-URL: https://authentidoc.com/\r\n\
HX-Trigger: nav-link\r\n\
\r\n";

/// Builds the "many headers" fixture once at startup.
fn many_headers() -> Vec<u8> {
    let mut raw = b"GET /features HTTP/1.1\r\nHost: localhost\r\n".to_vec();
    for i in 0..30 {
        raw.extend_from_slice(format!("X-Header-{i}: value-{i}\r\n").as_bytes());
    }
    raw.extend_from_slice(b"\r\n");
    raw
}

fn main() {
    let mut args = std::env::args().skip(1);
    let fixture = args.next().unwrap_or_else(|| "htmx".to_string());
    let iters: u64 = args
        .next()
        .as_deref()
        .and_then(|s| s.parse().ok())
        .unwrap_or(100_000_000);

    let many = many_headers();
    let raw: &[u8] = match fixture.as_str() {
        "minimal" => MINIMAL,
        "htmx" => HTMX,
        "browser" => BROWSER,
        "many" => &many,
        other => {
            eprintln!("unknown fixture: {other}. expected one of: minimal htmx browser many");
            std::process::exit(2);
        }
    };

    eprintln!("fixture={fixture} bytes={} iters={iters}", raw.len());
    let start = std::time::Instant::now();

    // Tight loop. `black_box` on input prevents constant-folding the
    // parse; `black_box` on the output keeps the call from being
    // dead-code-eliminated. No allocations inside the loop body —
    // `Request::parse` borrows from the input slice.
    for _ in 0..iters {
        let r = Request::<32>::parse(black_box(raw)).unwrap();
        black_box(r);
    }

    let elapsed = start.elapsed();
    #[allow(clippy::cast_precision_loss)]
    let ns_per = elapsed.as_secs_f64() * 1e9 / iters as f64;
    eprintln!("elapsed={elapsed:?} ns/iter={ns_per:.2}");
}