arcature 2026.2.1

Arcature application framework: a high-level Application facade over the certified Arcature subsystems, with the low-level Axum/Tower escape hatch preserved.
Documentation
//! Engine pipeline overhead benchmark: raw Axum vs `App` vs `Application`.
//!
//! Measures the full request/response path (TCP connect, HTTP/1.1 parse,
//! route, respond) for a simple GET route across four configurations:
//!
//! 1. **Raw Axum** — `axum::Router` served via `axum::serve` (the baseline).
//! 2. **`App`** — the low-level kernel (`App::new().route(...).serve`).
//! 3. **`Application` minimal** — the high-level engine with no subsystem
//!    features (no proxy, no observe, no pages). Measures the pre-routing
//!    `ProxyLayer` pass-through cost (always applied for a uniform service
//!    type) plus the pipeline assembly overhead.
//! 4. **`Application` + proxy + observe + pages** — the full pre-routing
//!    stack (`RequestIdLayer` + `ProxyLayer`) plus the observe route-layers
//!    (`MatchedRoute` adapter + `HttpLayer`) and the pages 404 fallback.
//!
//! No benchmarking framework is added (engine spec / Phase 1 §19). Timing uses
//! `std::time::Instant`; the benchmark is end-to-end over real TCP.
//!
//! # Run
//!
//! ```sh
//! cargo run --release --example benchmark_engine --features "observe,pages"
//! ```
//!
//! The `macros` feature is required for `Application::run`-style helpers, but
//! this benchmark uses `serve_with_shutdown` directly (the runtime-independent
//! seam), so only `observe` and `pages` are needed for the full-stack case.

use arcature::App;
use arcature::axum::routing::get;
use arcature::prelude::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

/// Sequential requests per implementation.
const REQUESTS: usize = 5_000;

async fn one_request(addr: std::net::SocketAddr) -> std::io::Result<()> {
    let mut stream = TcpStream::connect(addr).await?;
    stream
        .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
        .await?;
    let mut buf = Vec::with_capacity(128);
    stream.read_to_end(&mut buf).await?;
    if !buf.starts_with(b"HTTP/1.1 200 OK") {
        return Err(std::io::Error::other("unexpected response"));
    }
    Ok(())
}

async fn time_requests(addr: std::net::SocketAddr, n: usize) -> std::time::Duration {
    // Warmup so the accept loop is ready.
    let _ = one_request(addr).await;
    let start = std::time::Instant::now();
    for _ in 0..n {
        one_request(addr)
            .await
            .expect("request succeeded in warmup; benchmark request should too");
    }
    start.elapsed()
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    println!("Arcature Engine Phase — pipeline overhead benchmark");
    println!("==================================================");
    println!("Requests per implementation: {REQUESTS} (sequential, real TCP)");
    println!(
        "Build: {} (run with --release for meaningful numbers)",
        build_mode()
    );
    println!();

    // 1. Raw Axum.
    let raw_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let raw_addr = raw_listener.local_addr().unwrap();
    let (raw_tx, raw_rx) = tokio::sync::oneshot::channel::<()>();
    let raw_server = tokio::spawn(async move {
        let router = axum::Router::new().route("/", get(|| async { "hello" }));
        axum::serve(raw_listener, router)
            .with_graceful_shutdown(async {
                let _ = raw_rx.await;
            })
            .await
    });

    // 2. App (low-level kernel).
    let app_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let app_addr = app_listener.local_addr().unwrap();
    let (app_tx, app_rx) = tokio::sync::oneshot::channel::<()>();
    let app_server = tokio::spawn(async move {
        let app = App::new().route("/", get(|| async { "hello" }));
        app.serve_with_shutdown(app_listener, async {
            let _ = app_rx.await;
        })
        .await
    });

    // 3. Application minimal (no features — ProxyLayer pass-through).
    let engine_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let engine_addr = engine_listener.local_addr().unwrap();
    let (engine_tx, engine_rx) = tokio::sync::oneshot::channel::<()>();
    let engine_server = tokio::spawn(async move {
        let app = Application::new()
            .routes(Routes::new().route("/", get(|| async { "hello" })))
            .build();
        app.serve_with_shutdown(engine_listener, async {
            let _ = engine_rx.await;
        })
        .await
        .expect("engine serve");
    });

    let raw_time = time_requests(raw_addr, REQUESTS).await;
    let app_time = time_requests(app_addr, REQUESTS).await;
    let engine_time = time_requests(engine_addr, REQUESTS).await;

    // Shut down the first three.
    let _ = raw_tx.send(());
    let _ = app_tx.send(());
    let _ = engine_tx.send(());
    let _ = raw_server.await;
    let _ = app_server.await;
    let _ = engine_server.await;

    // 4. Application + proxy + observe + pages (full pre-routing stack).
    #[cfg(all(feature = "observe", feature = "pages"))]
    let full_us = {
        let full_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let full_addr = full_listener.local_addr().unwrap();
        let (full_tx, full_rx) = tokio::sync::oneshot::channel::<()>();
        let full_server = tokio::spawn(async move {
            let app = Application::new()
                .routes(Routes::new().route("/", get(|| async { "hello" })))
                .proxy(|_req| ProxyAction::continue_default())
                .pages(arcature::pages::Pages::new())
                .build();
            app.serve_with_shutdown(full_listener, async {
                let _ = full_rx.await;
            })
            .await
            .expect("full engine serve");
        });
        let full_time = time_requests(full_addr, REQUESTS).await;
        let _ = full_tx.send(());
        let _ = full_server.await;
        full_time.as_secs_f64() * 1_000_000.0 / REQUESTS as f64
    };

    #[cfg(not(all(feature = "observe", feature = "pages")))]
    println!("(skipping full-stack case: build with --features \"observe,pages\" to include)");

    // Report.
    let raw_us = raw_time.as_secs_f64() * 1_000_000.0 / REQUESTS as f64;
    let app_us = app_time.as_secs_f64() * 1_000_000.0 / REQUESTS as f64;
    let engine_us = engine_time.as_secs_f64() * 1_000_000.0 / REQUESTS as f64;

    println!("--- Results (μs per request, sequential, end-to-end TCP) ---");
    println!("  raw Axum:          {raw_us:>10.2} μs/req  (total {raw_time:?})");
    println!("  App (kernel):      {app_us:>10.2} μs/req  (total {app_time:?})");
    println!(
        "  Application (min): {engine_us:>10.2} μs/req  (total {engine_time:?})  — ProxyLayer pass-through"
    );

    #[cfg(all(feature = "observe", feature = "pages"))]
    println!("  Application (full): {full_us:>10.2} μs/req  — proxy + observe + pages");

    println!();
    println!("--- Overhead vs raw Axum ---");
    let app_overhead = (app_us - raw_us) / raw_us * 100.0;
    let engine_overhead = (engine_us - raw_us) / raw_us * 100.0;
    println!("  App (kernel):      {app_overhead:+.1}%");
    println!("  Application (min):  {engine_overhead:+.1}%");
    #[cfg(all(feature = "observe", feature = "pages"))]
    {
        let full_overhead = (full_us - raw_us) / raw_us * 100.0;
        println!("  Application (full): {full_overhead:+.1}%");
    }

    println!();
    println!("--- Interpretation ---");
    println!("  - App (kernel) forwards to axum::serve with the same Router, so");
    println!("    per-request overhead is expected to be ~0 (within timing noise).");
    println!("  - Application (min) always applies ProxyLayer (a zero-overhead");
    println!("    pass-through when no proxy fn is installed) — the cost is one");
    println!("    `Option::is_none` check per request plus a `Box::pin` allocation");
    println!("    shared with axum::serve's per-connection setup.");
    println!("  - Application (full) adds RequestIdLayer (pre-routing), the");
    println!("    MatchedRoute adapter + HttpLayer (route-layers), and the pages");
    println!("    404 fallback. Each adds a small per-request cost; the benchmark");
    println!("    shows the cumulative engine pipeline overhead.");
    println!("  - Sequential requests over loopback TCP; the *difference* is the");
    println!("    relevant signal (TCP/HTTP overhead is common to all).");
}

fn build_mode() -> &'static str {
    if cfg!(debug_assertions) {
        "debug"
    } else {
        "release"
    }
}