From 31ff9aa4d437c3c0a8a8ada1ca7957b44c3cee33 Mon Sep 17 00:00:00 2001 From: Alcibiades Athens Date: Thu, 12 Sep 2024 04:30:48 -0400 Subject: [PATCH] feat: use http2 in bench --- Cargo.toml | 2 +- benches/hello_world_tower_hyper_tls_tcp.rs | 96 ++++++++++++---------- src/http.rs | 9 +- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf99297..d2da35f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ http = "1.1.0" http-body = "1.0.1" http-body-util = "0.1.2" hyper = "1.4.1" -hyper-util = { version = "0.1.8", features = ["server", "tokio", "server-auto", "server-graceful", "service"] } +hyper-util = { version = "0.1.8", features = ["server", "tokio", "server-auto", "server-graceful", "service", "http2"] } pin-project = "1.1.5" pprof = { version = "0.13.0", features = ["flamegraph"], optional = true } ring = "0.17.8" diff --git a/benches/hello_world_tower_hyper_tls_tcp.rs b/benches/hello_world_tower_hyper_tls_tcp.rs index 244d10d..a6f3785 100644 --- a/benches/hello_world_tower_hyper_tls_tcp.rs +++ b/benches/hello_world_tower_hyper_tls_tcp.rs @@ -24,14 +24,15 @@ use std::sync::Arc; use bytes::Bytes; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use http::{Request, Response, StatusCode, Uri}; -use http_body_util::{BodyExt, Full}; +use http_body_util::{BodyExt, Empty, Full}; use hyper::body::Incoming; -use hyper_util::rt::TokioExecutor; +use hyper_rustls::HttpsConnectorBuilder; +use hyper_util::client::legacy::Client; +use hyper_util::rt::{TokioExecutor, TokioTimer}; use hyper_util::server::conn::auto::Builder as HttpConnectionBuilder; use hyper_util::service::TowerToHyperService; use rcgen::{CertificateParams, DistinguishedName, KeyPair}; -use reqwest::Client; -use rustls::crypto::aws_lc_rs::{default_provider, Ticketer}; +use rustls::crypto::aws_lc_rs::Ticketer; use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; use rustls::server::ServerSessionMemoryCache; use rustls::{ClientConfig, RootCertStore, ServerConfig}; @@ -210,6 +211,7 @@ fn create_optimized_runtime(thread_count: usize) -> io::Result { .build() } +#[inline] async fn echo(req: Request) -> Result>, hyper::Error> { match (req.method(), req.uri().path()) { (&hyper::Method::GET, "/") => Ok(Response::new(Full::new(Bytes::from("Hello, World!")))), @@ -273,20 +275,28 @@ async fn start_server( Ok((server_addr, shutdown_tx)) } +#[inline] async fn send_request( - client: &Client, + client: &Client< + hyper_rustls::HttpsConnector, + Empty, + >, url: Uri, ) -> Result<(Duration, usize), Box> { let start = Instant::now(); - let res = client.get(url.to_string()).send().await?; + let res = client.get(url).await?; assert_eq!(res.status(), StatusCode::OK); - let body = res.bytes().await?; + let body = res.into_body().collect().await?.to_bytes(); assert_eq!(&body[..], b"Hello, World!"); Ok((start.elapsed(), body.len())) } +#[inline] async fn concurrent_benchmark( - client: &Client, + client: &Client< + hyper_rustls::HttpsConnector, + Empty, + >, url: Uri, num_requests: usize, ) -> (Duration, Vec, usize) { @@ -315,46 +325,42 @@ async fn concurrent_benchmark( } fn bench_server(c: &mut Criterion) { - let bench_runtime = create_optimized_runtime(num_cpus::get() / 2).unwrap(); - - let (server_addr, shutdown_tx, client) = bench_runtime.block_on(async { - // Install the default provider for AWS LC RS crypto - default_provider().install_default().unwrap(); + let server_runtime = Arc::new(create_optimized_runtime(num_cpus::get() / 2).unwrap()); + let (server_addr, shutdown_tx, client) = server_runtime.block_on(async { + // Setup default rustls crypto provider + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Failed to install rustls crypto provider"); let tls_config = generate_shared_ecdsa_config(); let (server_addr, shutdown_tx) = start_server(tls_config.server_config.clone()) .await .expect("Failed to start server"); info!("Server started on {}", server_addr); - // Unfortunately hyper based http2 seems pretty busted here - // around not finding the tokio runtime timer - // https://github.com/rustls/hyper-rustls/issues/287 - // let https = HttpsConnectorBuilder::new() - // .with_tls_config(tls_config.client_config) - // .https_or_http() - // .enable_all_versions() - // .build(); - // - // let client: Client<_, Empty> = Client::builder(TokioExecutor::new()) - // .timer(TokioTimer::new()) - // .pool_timer(TokioTimer::new()) - // .build(https); - - let client = reqwest::Client::builder() - .use_rustls_tls() - // This breaks for the same reason that the hyper-tls/hyper client does - .http2_prior_knowledge() - // Increase connection pool size for better concurrency - .pool_max_idle_per_host(1250) - // Enable TCP keepalive - .tcp_keepalive(Some(Duration::from_secs(10))) - // Disable automatic redirect following to reduce overhead - .redirect(reqwest::redirect::Policy::none()) - // Use preconfigured TLS settings from the shared config - .use_preconfigured_tls(tls_config.client_config) - .build() - .expect("Failed to build reqwest client"); + let https = HttpsConnectorBuilder::new() + .with_tls_config(tls_config.client_config) + .https_or_http() + .enable_all_versions() + .build(); + + let client = Client::builder(TokioExecutor::new()) + // HTTP/2 settings + .http2_only(true) // Force HTTP/2 for consistent benchmarking and to match server config + .http2_initial_stream_window_size(2 * 1024 * 1024) // 2MB, matches server setting for better flow control + .http2_initial_connection_window_size(4 * 1024 * 1024) // 4MB, matches server setting for improved throughput + .http2_adaptive_window(true) // Enable dynamic flow control to optimize performance under varying conditions + .http2_max_frame_size(32 * 1024) // 32KB, matches server setting for larger data chunks + .http2_keep_alive_interval(Duration::from_secs(10)) // Maintain connection health, matching server's 10-second interval + .http2_keep_alive_timeout(Duration::from_secs(30)) // Allow time for keep-alive response, matching server's 30-second timeout + .http2_max_concurrent_reset_streams(2000) // Match server's max concurrent streams for better parallelism + .http2_max_send_buf_size(2 * 1024 * 1024) // 2MB, matches server setting for improved write performance + // Connection pooling settings + .pool_idle_timeout(Duration::from_secs(90)) // Keep connections alive longer for reuse in benchmarks + .pool_max_idle_per_host(2000) // Match max concurrent streams to fully utilize HTTP/2 multiplexing + .timer(TokioTimer::new()) + .pool_timer(TokioTimer::new()) + .build(https); (server_addr, shutdown_tx, client) }); @@ -375,7 +381,8 @@ fn bench_server(c: &mut Criterion) { group.bench_function("serial_latency", |b| { let client = client.clone(); let url = url.clone(); - b.to_async(&bench_runtime) + let client_runtime = create_optimized_runtime(num_cpus::get() / 2).unwrap(); + b.to_async(client_runtime) .iter(|| async { send_request(&client, url.clone()).await.unwrap() }); }); @@ -389,7 +396,8 @@ fn bench_server(c: &mut Criterion) { |b, &num_requests| { let client = client.clone(); let url = url.clone(); - b.to_async(&bench_runtime).iter(|| async { + let client_runtime = create_optimized_runtime(num_cpus::get() / 2).unwrap(); + b.to_async(client_runtime).iter(|| async { concurrent_benchmark(&client, url.clone(), num_requests).await }); }, @@ -398,7 +406,7 @@ fn bench_server(c: &mut Criterion) { group.finish(); - bench_runtime.block_on(async { + server_runtime.block_on(async { shutdown_tx.send(()).unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; }); diff --git a/src/http.rs b/src/http.rs index e7337ee..b497917 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,6 +1,6 @@ use crate::io::Transport; use std::future::pending; -use std::{future::Future, pin::pin, sync::Arc, time::Duration}; +use std::{future::Future, pin::pin, sync::Arc}; use tokio_rustls::TlsAcceptor; use bytes::Bytes; @@ -8,12 +8,14 @@ use http::{Request, Response}; use http_body::Body; use hyper::body::Incoming; use hyper::service::Service; +use hyper_util::rt::TokioTimer; use hyper_util::{ rt::TokioIo, server::conn::auto::{Builder as HttpConnectionBuilder, HttpServerConnExec}, }; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::time::sleep; +use tokio::time::Duration; use tokio_stream::Stream; use tokio_stream::StreamExt as _; use tracing::{debug, trace}; @@ -108,6 +110,11 @@ pub async fn serve_http_connection( .title_case_headers(false) // HTTP/2 settings .http2() + // Add the timer to the builder + // This will cause you all sorts of pain otherwise + // https://github.com/seanmonstar/reqwest/issues/2421 + // https://github.com/rustls/hyper-rustls/issues/287 + .timer(TokioTimer::new()) // Increase initial stream window size to 2MB for better throughput .initial_stream_window_size(Some(2 * 1024 * 1024)) // Increase initial connection window size to 4MB for improved performance