| use axum::{ |
| extract::State, |
| routing::{get, post}, |
| Extension, Json, Router, Server, |
| }; |
| use hyper::server::conn::AddrIncoming; |
| use serde::{Deserialize, Serialize}; |
| use std::{ |
| io::BufRead, |
| process::{Command, Stdio}, |
| }; |
| |
| fn main() { |
| if on_ci() { |
| install_rewrk(); |
| } else { |
| ensure_rewrk_is_installed(); |
| } |
| |
| benchmark("minimal").run(Router::new); |
| |
| benchmark("basic") |
| .path("/a/b/c") |
| .run(|| Router::new().route("/a/b/c", get(|| async { "Hello, World!" }))); |
| |
| benchmark("basic-merge").path("/a/b/c").run(|| { |
| let inner = Router::new().route("/a/b/c", get(|| async { "Hello, World!" })); |
| Router::new().merge(inner) |
| }); |
| |
| benchmark("basic-nest").path("/a/b/c").run(|| { |
| let c = Router::new().route("/c", get(|| async { "Hello, World!" })); |
| let b = Router::new().nest("/b", c); |
| Router::new().nest("/a", b) |
| }); |
| |
| benchmark("routing").path("/foo/bar/baz").run(|| { |
| let mut app = Router::new(); |
| for a in 0..10 { |
| for b in 0..10 { |
| for c in 0..10 { |
| app = app.route(&format!("/foo-{a}/bar-{b}/baz-{c}"), get(|| async {})); |
| } |
| } |
| } |
| app.route("/foo/bar/baz", get(|| async {})) |
| }); |
| |
| benchmark("receive-json") |
| .method("post") |
| .headers(&[("content-type", "application/json")]) |
| .body(r#"{"n": 123, "s": "hi there", "b": false}"#) |
| .run(|| Router::new().route("/", post(|_: Json<Payload>| async {}))); |
| |
| benchmark("send-json").run(|| { |
| Router::new().route( |
| "/", |
| get(|| async { |
| Json(Payload { |
| n: 123, |
| s: "hi there".to_owned(), |
| b: false, |
| }) |
| }), |
| ) |
| }); |
| |
| let state = AppState { |
| _string: "aaaaaaaaaaaaaaaaaa".to_owned(), |
| _vec: Vec::from([ |
| "aaaaaaaaaaaaaaaaaa".to_owned(), |
| "bbbbbbbbbbbbbbbbbb".to_owned(), |
| "cccccccccccccccccc".to_owned(), |
| ]), |
| }; |
| |
| benchmark("extension").run(|| { |
| Router::new() |
| .route("/", get(|_: Extension<AppState>| async {})) |
| .layer(Extension(state.clone())) |
| }); |
| |
| benchmark("state").run(|| { |
| Router::new() |
| .route("/", get(|_: State<AppState>| async {})) |
| .with_state(state.clone()) |
| }); |
| } |
| |
| #[derive(Clone)] |
| struct AppState { |
| _string: String, |
| _vec: Vec<String>, |
| } |
| |
| #[derive(Deserialize, Serialize)] |
| struct Payload { |
| n: u32, |
| s: String, |
| b: bool, |
| } |
| |
| fn benchmark(name: &'static str) -> BenchmarkBuilder { |
| BenchmarkBuilder { |
| name, |
| path: None, |
| method: None, |
| headers: None, |
| body: None, |
| } |
| } |
| |
| struct BenchmarkBuilder { |
| name: &'static str, |
| path: Option<&'static str>, |
| method: Option<&'static str>, |
| headers: Option<&'static [(&'static str, &'static str)]>, |
| body: Option<&'static str>, |
| } |
| |
| macro_rules! config_method { |
| ($name:ident, $ty:ty) => { |
| fn $name(mut self, $name: $ty) -> Self { |
| self.$name = Some($name); |
| self |
| } |
| }; |
| } |
| |
| impl BenchmarkBuilder { |
| config_method!(path, &'static str); |
| config_method!(method, &'static str); |
| config_method!(headers, &'static [(&'static str, &'static str)]); |
| config_method!(body, &'static str); |
| |
| fn run<F>(self, f: F) |
| where |
| F: FnOnce() -> Router<()>, |
| { |
| // support only running some benchmarks with |
| // ``` |
| // cargo bench -- routing send-json |
| // ``` |
| let args = std::env::args().collect::<Vec<_>>(); |
| if args.len() != 1 { |
| let names = &args[1..args.len() - 1]; |
| if !names.is_empty() && !names.contains(&self.name.to_owned()) { |
| return; |
| } |
| } |
| |
| let app = f(); |
| |
| let rt = tokio::runtime::Builder::new_multi_thread() |
| .enable_all() |
| .build() |
| .unwrap(); |
| |
| let listener = rt |
| .block_on(tokio::net::TcpListener::bind("0.0.0.0:0")) |
| .unwrap(); |
| let addr = listener.local_addr().unwrap(); |
| |
| std::thread::spawn(move || { |
| rt.block_on(async move { |
| let incoming = AddrIncoming::from_listener(listener).unwrap(); |
| Server::builder(incoming) |
| .serve(app.into_make_service()) |
| .await |
| .unwrap(); |
| }); |
| }); |
| |
| let mut cmd = Command::new("rewrk"); |
| cmd.stdout(Stdio::piped()); |
| |
| cmd.arg("--host"); |
| cmd.arg(format!("http://{}{}", addr, self.path.unwrap_or(""))); |
| |
| cmd.args(["--connections", "10"]); |
| cmd.args(["--threads", "10"]); |
| |
| if on_ci() { |
| // don't slow down CI by running the benchmarks for too long |
| // but do run them for a bit |
| cmd.args(["--duration", "1s"]); |
| } else { |
| cmd.args(["--duration", "10s"]); |
| } |
| |
| if let Some(method) = self.method { |
| cmd.args(["--method", method]); |
| } |
| |
| for (key, value) in self.headers.into_iter().flatten() { |
| cmd.arg("--header"); |
| cmd.arg(format!("{key}: {value}")); |
| } |
| |
| if let Some(body) = self.body { |
| cmd.args(["--body", body]); |
| } |
| |
| eprintln!("Running {:?} benchmark", self.name); |
| |
| // indent output from `rewrk` so its easier to read when running multiple benchmarks |
| let mut child = cmd.spawn().unwrap(); |
| let stdout = child.stdout.take().unwrap(); |
| let stdout = std::io::BufReader::new(stdout); |
| for line in stdout.lines() { |
| let line = line.unwrap(); |
| println!(" {line}"); |
| } |
| |
| let status = child.wait().unwrap(); |
| |
| if !status.success() { |
| eprintln!("`rewrk` command failed"); |
| std::process::exit(status.code().unwrap()); |
| } |
| } |
| } |
| |
| fn install_rewrk() { |
| println!("installing rewrk"); |
| let mut cmd = Command::new("cargo"); |
| cmd.args([ |
| "install", |
| "rewrk", |
| "--git", |
| "https://github.com/ChillFish8/rewrk.git", |
| ]); |
| let status = cmd |
| .status() |
| .unwrap_or_else(|_| panic!("failed to install rewrk")); |
| if !status.success() { |
| panic!("failed to install rewrk"); |
| } |
| } |
| |
| fn ensure_rewrk_is_installed() { |
| let mut cmd = Command::new("rewrk"); |
| cmd.arg("--help"); |
| cmd.stdout(Stdio::null()); |
| cmd.stderr(Stdio::null()); |
| cmd.status().unwrap_or_else(|_| { |
| panic!("rewrk is not installed. See https://github.com/lnx-search/rewrk") |
| }); |
| } |
| |
| fn on_ci() -> bool { |
| std::env::var("GITHUB_ACTIONS").is_ok() |
| } |