From f26ac8b6cf10abae3ff7801e3d1e36989ffea777 Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Fri, 14 Aug 2026 07:17:27 +0200 Subject: [PATCH 1/2] Add axum framework --- frameworks/axum/Cargo.toml | 16 +++++ frameworks/axum/Dockerfile | 11 +++ frameworks/axum/README.md | 26 +++++++ frameworks/axum/meta.json | 19 ++++++ frameworks/axum/src/main.rs | 133 ++++++++++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 frameworks/axum/Cargo.toml create mode 100644 frameworks/axum/Dockerfile create mode 100644 frameworks/axum/README.md create mode 100644 frameworks/axum/meta.json create mode 100644 frameworks/axum/src/main.rs diff --git a/frameworks/axum/Cargo.toml b/frameworks/axum/Cargo.toml new file mode 100644 index 000000000..618c0262e --- /dev/null +++ b/frameworks/axum/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "httparena-axum" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.8" +tokio = { version = "1", features = ["full"] } +tower-http = { version = "0.6", features = ["compression-gzip"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.release] +opt-level = 3 +codegen-units = 1 +lto = "thin" diff --git a/frameworks/axum/Dockerfile b/frameworks/axum/Dockerfile new file mode 100644 index 000000000..76a6dd9a6 --- /dev/null +++ b/frameworks/axum/Dockerfile @@ -0,0 +1,11 @@ +FROM rust:1.94 AS build +WORKDIR /app +COPY Cargo.toml . +RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src/ target/release/httparena-axum* target/release/deps/httparena_axum* +COPY src ./src +RUN cargo build --release + +FROM debian:bookworm-slim +COPY --from=build /app/target/release/httparena-axum /server +EXPOSE 8080 +CMD ["/server"] diff --git a/frameworks/axum/README.md b/frameworks/axum/README.md new file mode 100644 index 000000000..8f9898034 --- /dev/null +++ b/frameworks/axum/README.md @@ -0,0 +1,26 @@ +# axum + +Axum 0.8 on hyper with the multi-threaded Tokio runtime, default configuration. + +## Stack + +- **Language:** Rust 1.94 +- **Framework:** Axum 0.8 +- **Build:** Multi-stage, `debian:bookworm-slim` runtime + +## Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/pipeline` | GET | Returns `ok` (plain text) | +| `/baseline11` | GET | Sums query parameter values | +| `/baseline11` | POST | Sums query parameters + request body | +| `/json/{count}?m=N` | GET | First `count` dataset items with `total = price * quantity * m` | +| `/upload` | POST | Reads the body and returns the byte count | + +## Notes + +- Routing and extraction through the Axum `Path`, `Query` and body extractors +- JSON through the `Json` response, serialized by serde per request +- Compression through the tower-http `CompressionLayer` +- The dataset is leaked once at startup so responses borrow it instead of cloning diff --git a/frameworks/axum/meta.json b/frameworks/axum/meta.json new file mode 100644 index 000000000..7b954bf6b --- /dev/null +++ b/frameworks/axum/meta.json @@ -0,0 +1,19 @@ +{ + "display_name": "axum", + "language": "Rust", + "type": "flagship", + "mode": "standard", + "engine": "hyper", + "description": "Axum 0.8 on hyper with the multi-threaded Tokio runtime, default configuration. Routing and path/query extractors through the Axum API, serde_json via the Json response, gzip through the tower-http compression layer.", + "repo": "https://github.com/tokio-rs/axum", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn", + "json", + "json-comp", + "upload" + ], + "maintainers": [] +} diff --git a/frameworks/axum/src/main.rs b/frameworks/axum/src/main.rs new file mode 100644 index 000000000..1deb1215f --- /dev/null +++ b/frameworks/axum/src/main.rs @@ -0,0 +1,133 @@ +use std::collections::HashMap; + +use axum::body::Bytes; +use axum::extract::{DefaultBodyLimit, Path, Query, State}; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use serde::{Deserialize, Serialize}; +use tower_http::compression::CompressionLayer; + +const MAX_BODY: usize = 25 * 1024 * 1024; + +#[derive(Deserialize)] +struct Rating { + score: i64, + count: i64, +} + +#[derive(Deserialize)] +struct DatasetItem { + id: i64, + name: String, + category: String, + price: i64, + quantity: i64, + active: bool, + tags: Vec, + rating: Rating, +} + +#[derive(Serialize)] +struct ProcessedRating { + score: i64, + count: i64, +} + +#[derive(Serialize)] +struct ProcessedItem<'a> { + id: i64, + name: &'a str, + category: &'a str, + price: i64, + quantity: i64, + active: bool, + tags: &'a [String], + rating: ProcessedRating, + total: i64, +} + +#[derive(Serialize)] +struct ProcessResponse<'a> { + items: Vec>, + count: usize, +} + +#[derive(Deserialize)] +struct JsonParams { + m: Option, +} + +fn load_dataset() -> Vec { + let path = std::env::var("DATASET_PATH").unwrap_or_else(|_| "/data/dataset.json".to_string()); + match std::fs::read_to_string(path) { + Ok(data) => serde_json::from_str(&data).unwrap_or_default(), + Err(_) => Vec::new(), + } +} + +async fn pipeline() -> &'static str { + "ok" +} + +async fn baseline11(Query(params): Query>, body: String) -> String { + let mut sum: i64 = params + .values() + .filter_map(|value| value.parse::().ok()) + .sum(); + if let Ok(n) = body.trim().parse::() { + sum += n; + } + sum.to_string() +} + +async fn json_items( + State(dataset): State<&'static [DatasetItem]>, + Path(count): Path, + Query(params): Query, +) -> Json> { + let count = count.min(dataset.len()); + let m = params.m.unwrap_or(1); + + let items = dataset[..count] + .iter() + .map(|item| ProcessedItem { + id: item.id, + name: &item.name, + category: &item.category, + price: item.price, + quantity: item.quantity, + active: item.active, + tags: &item.tags, + rating: ProcessedRating { + score: item.rating.score, + count: item.rating.count, + }, + total: item.price * item.quantity * m, + }) + .collect(); + + Json(ProcessResponse { items, count }) +} + +async fn upload(body: Bytes) -> String { + body.len().to_string() +} + +#[tokio::main] +async fn main() { + // Leaked once at startup so handlers can borrow the items instead of + // cloning every string into the response. + let dataset: &'static [DatasetItem] = Box::leak(load_dataset().into_boxed_slice()); + + let app = Router::new() + .route("/pipeline", get(pipeline)) + .route("/baseline11", get(baseline11).post(baseline11)) + .route("/json/{count}", get(json_items)) + .route("/upload", post(upload)) + .layer(CompressionLayer::new()) + .layer(DefaultBodyLimit::max(MAX_BODY)) + .with_state(dataset); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} From 36a6db6c4748e054d579b652f1b55bfe423d0dac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Aug 2026 06:17:56 +0000 Subject: [PATCH 2/2] Benchmark results: axum [skip ci] --- site/data/current.json | 2 +- site/data/frameworks.json | 8 + site/data/results/axum.json | 243 ++++++++++++++++++++ site/static/logs/baseline/4096/axum.log | 0 site/static/logs/baseline/512/axum.log | 0 site/static/logs/json-comp/16384/axum.log | 0 site/static/logs/json-comp/4096/axum.log | 0 site/static/logs/json-comp/512/axum.log | 0 site/static/logs/json/4096/axum.log | 0 site/static/logs/limited-conn/4096/axum.log | 0 site/static/logs/limited-conn/512/axum.log | 0 site/static/logs/pipelined/4096/axum.log | 0 site/static/logs/pipelined/512/axum.log | 0 site/static/logs/upload/256/axum.log | 0 site/static/logs/upload/32/axum.log | 0 15 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 site/data/results/axum.json create mode 100644 site/static/logs/baseline/4096/axum.log create mode 100644 site/static/logs/baseline/512/axum.log create mode 100644 site/static/logs/json-comp/16384/axum.log create mode 100644 site/static/logs/json-comp/4096/axum.log create mode 100644 site/static/logs/json-comp/512/axum.log create mode 100644 site/static/logs/json/4096/axum.log create mode 100644 site/static/logs/limited-conn/4096/axum.log create mode 100644 site/static/logs/limited-conn/512/axum.log create mode 100644 site/static/logs/pipelined/4096/axum.log create mode 100644 site/static/logs/pipelined/512/axum.log create mode 100644 site/static/logs/upload/256/axum.log create mode 100644 site/static/logs/upload/32/axum.log diff --git a/site/data/current.json b/site/data/current.json index f8d0404a9..46e72dfff 100644 --- a/site/data/current.json +++ b/site/data/current.json @@ -8,7 +8,7 @@ "kernel": "6.17.0-22-generic", "docker": "29.3.0", "docker_runtime": "runc", - "governor": "performance", + "governor": "powersave", "tcp": { "lo_mtu": "1500", "congestion": "cubic", diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 7f879a980..f98b3f441 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -123,6 +123,14 @@ "engine": "kestrel", "mode": "standard" }, + "axum": { + "dir": "axum", + "description": "Axum 0.8 on hyper with the multi-threaded Tokio runtime, default configuration. Routing and path/query extractors through the Axum API, serde_json via the Json response, gzip through the tower-http compression layer.", + "repo": "https://github.com/tokio-rs/axum", + "type": "flagship", + "engine": "hyper", + "mode": "standard" + }, "bananabread": { "dir": "bananabread", "description": "Seagreen \u2014 a TypeScript port of GenHTTP \u2014 on Bun's raw-TCP engine, with kotlinx-style decorators and Bun's built-in SQL.", diff --git a/site/data/results/axum.json b/site/data/results/axum.json new file mode 100644 index 000000000..24ddee1cc --- /dev/null +++ b/site/data/results/axum.json @@ -0,0 +1,243 @@ +{ + "framework": "axum", + "results": { + "baseline-4096": { + "framework": "axum", + "language": "Rust", + "rps": 755895, + "avg_latency": "1.79ms", + "p99_latency": "11.30ms", + "cpu": "3178.0%", + "memory": "124MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "85.04MB/s", + "input_bw": "58.39MB/s", + "reconnects": 0, + "status_2xx": 3779477, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-512": { + "framework": "axum", + "language": "Rust", + "rps": 432272, + "avg_latency": "1.11ms", + "p99_latency": "4.79ms", + "cpu": "2174.5%", + "memory": "61MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "48.63MB/s", + "input_bw": "33.39MB/s", + "reconnects": 0, + "status_2xx": 2161360, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-4096": { + "framework": "axum", + "language": "Rust", + "rps": 216299, + "avg_latency": "1.02ms", + "p99_latency": "15.00ms", + "cpu": "1525.8%", + "memory": "43MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "753.13MB/s", + "input_bw": "10.31MB/s", + "reconnects": 43261, + "status_2xx": 1081496, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-16384": { + "framework": "axum", + "language": "Rust", + "rps": 100837, + "avg_latency": "2.74ms", + "p99_latency": "32.30ms", + "cpu": "2706.1%", + "memory": "90MiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "135.19MB/s", + "input_bw": "7.50MB/s", + "reconnects": 20144, + "status_2xx": 504187, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-4096": { + "framework": "axum", + "language": "Rust", + "rps": 107463, + "avg_latency": "2.56ms", + "p99_latency": "28.60ms", + "cpu": "4391.3%", + "memory": "60MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "144.14MB/s", + "input_bw": "7.99MB/s", + "reconnects": 21539, + "status_2xx": 537317, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-512": { + "framework": "axum", + "language": "Rust", + "rps": 113007, + "avg_latency": "1.88ms", + "p99_latency": "26.90ms", + "cpu": "4129.0%", + "memory": "66MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "151.54MB/s", + "input_bw": "8.41MB/s", + "reconnects": 22619, + "status_2xx": 565039, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "limited-conn-4096": { + "framework": "axum", + "language": "Rust", + "rps": 315827, + "avg_latency": "854us", + "p99_latency": "3.83ms", + "cpu": "1313.4%", + "memory": "53MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "35.53MB/s", + "input_bw": "24.40MB/s", + "reconnects": 157927, + "status_2xx": 1579137, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "limited-conn-512": { + "framework": "axum", + "language": "Rust", + "rps": 312369, + "avg_latency": "676us", + "p99_latency": "3.80ms", + "cpu": "1332.7%", + "memory": "49MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "35.17MB/s", + "input_bw": "24.13MB/s", + "reconnects": 156197, + "status_2xx": 1561847, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-4096": { + "framework": "axum", + "language": "Rust", + "rps": 6500142, + "avg_latency": "5.01ms", + "p99_latency": "16.70ms", + "cpu": "6549.7%", + "memory": "106MiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "731.31MB/s", + "reconnects": 0, + "status_2xx": 32500714, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-512": { + "framework": "axum", + "language": "Rust", + "rps": 5822816, + "avg_latency": "1.30ms", + "p99_latency": "3.30ms", + "cpu": "6158.7%", + "memory": "45MiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "655.05MB/s", + "reconnects": 0, + "status_2xx": 29114080, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "upload-256": { + "framework": "axum", + "language": "Rust", + "rps": 1487, + "avg_latency": "166.16ms", + "p99_latency": "937.40ms", + "cpu": "4974.6%", + "memory": "4.9GiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "178.94KB/s", + "input_bw": "11.79GB/s", + "reconnects": 1468, + "status_2xx": 7438, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "upload-32": { + "framework": "axum", + "language": "Rust", + "rps": 1403, + "avg_latency": "22.77ms", + "p99_latency": "78.20ms", + "cpu": "2948.3%", + "memory": "1.5GiB", + "connections": 32, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "168.84KB/s", + "input_bw": "11.13GB/s", + "reconnects": 1402, + "status_2xx": 7019, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + } + } +} diff --git a/site/static/logs/baseline/4096/axum.log b/site/static/logs/baseline/4096/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline/512/axum.log b/site/static/logs/baseline/512/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/16384/axum.log b/site/static/logs/json-comp/16384/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/4096/axum.log b/site/static/logs/json-comp/4096/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/512/axum.log b/site/static/logs/json-comp/512/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json/4096/axum.log b/site/static/logs/json/4096/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/4096/axum.log b/site/static/logs/limited-conn/4096/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/512/axum.log b/site/static/logs/limited-conn/512/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/4096/axum.log b/site/static/logs/pipelined/4096/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/512/axum.log b/site/static/logs/pipelined/512/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/upload/256/axum.log b/site/static/logs/upload/256/axum.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/upload/32/axum.log b/site/static/logs/upload/32/axum.log new file mode 100644 index 000000000..e69de29bb