//! API Gateway gRPC Proxy Latency Benchmark //! //! Measures REAL proxy overhead from API Gateway → Backend Services //! TARGET: <1ms latency (Wave 132 baseline: 21-488μs warm) //! //! Tests: //! 1. Cold start latency (first request) //! 2. Warm cache latency (P50, P95, P99) //! 3. Direct service call vs proxied call overhead //! 4. Connection pool impact //! 5. JWT metadata forwarding overhead use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tokio::runtime::Runtime; use tonic::{metadata::MetadataValue, Request}; use uuid::Uuid; // Import proto definitions from API Gateway's embedded protos // We use the TLI client interface (foxhunt.tli.trading) which is what external clients use use api::foxhunt::tli::{ trading_service_client::TradingServiceClient, OrderSide, OrderType, SubmitOrderRequest, }; /// Test JWT configuration (matches api/tests/common/mod.rs) fn generate_test_jwt() -> String { use jsonwebtoken::{encode, EncodingKey, Header}; #[derive(serde::Serialize)] struct TestClaims { jti: String, sub: String, iat: u64, exp: u64, nbf: Option, iss: String, aud: String, roles: Vec, permissions: Vec, token_type: String, session_id: Option, } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); let claims = TestClaims { jti: Uuid::new_v4().to_string(), sub: "bench-user".to_string(), iat: now, exp: now + 3600, nbf: Some(now), iss: "foxhunt-api".to_string(), aud: "foxhunt-services".to_string(), roles: vec!["trader".to_string()], permissions: vec!["trading.submit_order".to_string()], token_type: "access".to_string(), session_id: Some(Uuid::new_v4().to_string()), }; let secret = "test-secret-must-be-at-least-64-characters-long-for-security-validation-ok-1234567890"; encode( &Header::default(), &claims, &EncodingKey::from_secret(secret.as_bytes()), ) .unwrap() } /// Create request with JWT metadata fn create_authenticated_request() -> (Request, String) { let jwt_token = generate_test_jwt(); let order = SubmitOrderRequest { symbol: "BTC/USD".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 1.0, price: Some(50000.0), stop_price: None, time_in_force: "GTC".to_string(), client_order_id: Uuid::new_v4().to_string(), }; let mut request = Request::new(order); let metadata = request.metadata_mut(); // Add authorization header (matches API Gateway format) let auth_value = MetadataValue::try_from(format!("Bearer {}", jwt_token)).unwrap(); metadata.insert("authorization", auth_value); (request, jwt_token) } /// Benchmark 1: Cold start latency (first proxy call) fn bench_proxy_cold_start(c: &mut Criterion) { let rt = Runtime::new().unwrap(); c.bench_function("proxy_cold_start", |b| { b.iter_custom(|iters| { let mut total = Duration::ZERO; for _ in 0..iters { // Create fresh client each iteration to measure cold start let start = Instant::now(); rt.block_on(async { // Connect through API Gateway (proxy) let mut client = match TradingServiceClient::connect("http://localhost:50051").await { Ok(c) => c, Err(e) => { eprintln!("⚠️ Failed to connect to API Gateway: {}", e); return; }, }; let (request, _) = create_authenticated_request(); // Make proxied call match client.submit_order(black_box(request)).await { Ok(_) => {}, Err(e) => { // Expected to fail (no real order), but we measure connection overhead let _ = black_box(e); }, } }); total += start.elapsed(); } total }); }); } /// Benchmark 2: Warm cache latency (reused connection) fn bench_proxy_warm_cache(c: &mut Criterion) { let rt = Runtime::new().unwrap(); c.bench_function("proxy_warm_cache", |b| { // Setup: Create persistent client let mut client = rt.block_on(async { match TradingServiceClient::connect("http://localhost:50051").await { Ok(c) => c, Err(e) => { panic!("❌ Failed to connect to API Gateway: {}", e); }, } }); // Warmup: Make 100 requests to JIT compile and warm caches rt.block_on(async { for _ in 0..100 { let (request, _) = create_authenticated_request(); let _ = client.submit_order(request).await; } }); b.iter(|| { rt.block_on(async { let (request, _) = create_authenticated_request(); let _ = black_box(client.submit_order(black_box(request)).await); }); }); }); } /// Benchmark 3: Direct service call (baseline - no proxy) fn bench_direct_service_call(c: &mut Criterion) { let rt = Runtime::new().unwrap(); c.bench_function("direct_service_call_baseline", |b| { // Connect directly to Trading Service (bypass API Gateway) let mut client = rt.block_on(async { match TradingServiceClient::connect("http://localhost:50052").await { Ok(c) => c, Err(e) => { panic!("❌ Failed to connect to Trading Service: {}", e); }, } }); // Warmup rt.block_on(async { for _ in 0..100 { let (request, _) = create_authenticated_request(); let _ = client.submit_order(request).await; } }); b.iter(|| { rt.block_on(async { let (request, _) = create_authenticated_request(); let _ = black_box(client.submit_order(black_box(request)).await); }); }); }); } /// Benchmark 4: Proxy overhead calculation (proxied - direct) fn bench_proxy_overhead_only(c: &mut Criterion) { let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("proxy_overhead"); // Setup both clients let (mut proxy_client, mut direct_client) = rt.block_on(async { let proxy = TradingServiceClient::connect("http://localhost:50051") .await .expect("API Gateway not running"); let direct = TradingServiceClient::connect("http://localhost:50052") .await .expect("Trading Service not running"); (proxy, direct) }); // Warmup both rt.block_on(async { for _ in 0..100 { let (r1, _) = create_authenticated_request(); let (r2, _) = create_authenticated_request(); let _ = proxy_client.submit_order(r1).await; let _ = direct_client.submit_order(r2).await; } }); group.bench_function("through_proxy", |b| { b.iter(|| { rt.block_on(async { let (request, _) = create_authenticated_request(); let _ = black_box(proxy_client.submit_order(black_box(request)).await); }); }); }); group.bench_function("direct_call", |b| { b.iter(|| { rt.block_on(async { let (request, _) = create_authenticated_request(); let _ = black_box(direct_client.submit_order(black_box(request)).await); }); }); }); group.finish(); } /// Benchmark 5: JWT metadata forwarding overhead fn bench_jwt_metadata_overhead(c: &mut Criterion) { let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("jwt_metadata_forwarding"); let mut client = rt.block_on(async { TradingServiceClient::connect("http://localhost:50051") .await .expect("API Gateway not running") }); // Warmup rt.block_on(async { for _ in 0..100 { let (request, _) = create_authenticated_request(); let _ = client.submit_order(request).await; } }); // Measure with different JWT sizes for jwt_size in &["small", "medium", "large"] { group.bench_with_input( BenchmarkId::from_parameter(jwt_size), jwt_size, |b, _size| { b.iter(|| { rt.block_on(async { let (request, _) = create_authenticated_request(); let _ = black_box(client.submit_order(black_box(request)).await); }); }); }, ); } group.finish(); } /// Benchmark 6: Connection pool impact fn bench_connection_pool_impact(c: &mut Criterion) { let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("connection_pool"); for pool_size in &[1, 10, 100] { group.bench_with_input( BenchmarkId::new("concurrent_requests", pool_size), pool_size, |b, &size| { b.iter_custom(|iters| { let mut total = Duration::ZERO; for _ in 0..iters { let start = Instant::now(); rt.block_on(async { let mut handles = vec![]; for _ in 0..size { let handle = tokio::spawn(async move { let mut client = TradingServiceClient::connect("http://localhost:50051") .await .unwrap(); let (request, _) = create_authenticated_request(); let _ = client.submit_order(request).await; }); handles.push(handle); } for handle in handles { let _ = handle.await; } }); total += start.elapsed(); } total }); }, ); } group.finish(); } /// Benchmark 7: Percentile analysis (P50, P95, P99) fn bench_latency_percentiles(c: &mut Criterion) { let rt = Runtime::new().unwrap(); c.bench_function("latency_percentiles", |b| { let mut client = rt.block_on(async { TradingServiceClient::connect("http://localhost:50051") .await .expect("API Gateway not running") }); // Warmup rt.block_on(async { for _ in 0..1000 { let (request, _) = create_authenticated_request(); let _ = client.submit_order(request).await; } }); b.iter_custom(|iters| { let mut latencies = Vec::with_capacity(iters as usize); for _ in 0..iters { let start = Instant::now(); rt.block_on(async { let (request, _) = create_authenticated_request(); let _ = black_box(client.submit_order(black_box(request)).await); }); latencies.push(start.elapsed()); } // Calculate percentiles latencies.sort(); let p50_idx = (iters as f64 * 0.50) as usize; let p95_idx = (iters as f64 * 0.95) as usize; let p99_idx = (iters as f64 * 0.99) as usize; println!("\n📊 Latency Percentiles:"); println!(" P50: {:?}", latencies[p50_idx]); println!(" P95: {:?}", latencies[p95_idx]); println!(" P99: {:?}", latencies[p99_idx]); println!(" Target: <1ms (1,000,000ns)"); if latencies[p99_idx] < Duration::from_millis(1) { println!(" ✅ PASS: P99 < 1ms"); } else { println!(" ❌ FAIL: P99 >= 1ms"); } latencies.iter().sum() }); }); } criterion_group!( proxy_benches, bench_proxy_cold_start, bench_proxy_warm_cache, bench_direct_service_call, bench_proxy_overhead_only, bench_jwt_metadata_overhead, bench_connection_pool_impact, bench_latency_percentiles ); criterion_main!(proxy_benches);