//! API Gateway Proxy Latency Test //! //! Quick validation test for proxy latency against <1ms target //! Measures REAL gRPC calls: API Gateway (50051) → Trading Service (50052) #[path = "common/mod.rs"] mod common; use anyhow::Result; use std::time::Instant; use tonic::{metadata::MetadataValue, Request}; use api::foxhunt::tli::{ trading_service_client::TradingServiceClient, OrderSide, OrderType, SubmitOrderRequest, }; /// Create authenticated request with JWT fn create_test_request() -> Request { let (token, _jti) = common::generate_test_token( "bench-user", vec!["trader".to_string()], vec!["trading.submit_order".to_string()], 3600, ) .unwrap(); 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::Uuid::new_v4().to_string(), }; let mut request = Request::new(order); let auth_value = MetadataValue::try_from(format!("Bearer {}", token)).unwrap(); request.metadata_mut().insert("authorization", auth_value); request } #[tokio::test] #[ignore = "Run manually: cargo test -p api proxy_latency --ignored -- --nocapture"] async fn test_proxy_cold_start_latency() -> Result<()> { println!("\n=== Test 1: Cold Start Latency ==="); let mut latencies = Vec::new(); // Measure 10 cold starts for i in 0..10 { let start = Instant::now(); let mut client = TradingServiceClient::connect("http://localhost:50051").await?; let request = create_test_request(); let _ = client.submit_order(request).await; let elapsed = start.elapsed(); latencies.push(elapsed); println!(" Cold start {}: {:?}", i + 1, elapsed); } latencies.sort(); let median = latencies[latencies.len() / 2]; let p99 = latencies[(latencies.len() as f64 * 0.99) as usize]; println!("\n 📊 Cold Start Statistics:"); println!(" Median: {:?}", median); println!(" P99: {:?}", p99); println!(" Target: <10ms (cold start allowance)"); assert!( p99.as_millis() < 10, "P99 cold start latency {} ms exceeds 10ms target", p99.as_millis() ); Ok(()) } #[tokio::test] #[ignore = "Run manually: cargo test -p api proxy_latency --ignored -- --nocapture"] async fn test_proxy_warm_cache_latency() -> Result<()> { println!("\n=== Test 2: Warm Cache Latency ==="); // Setup persistent connection let mut client = TradingServiceClient::connect("http://localhost:50051").await?; // Warmup: 100 requests println!(" Warming up with 100 requests..."); for _ in 0..100 { let request = create_test_request(); let _ = client.submit_order(request).await; } // Measure 1000 warm requests let mut latencies = Vec::new(); println!(" Measuring 1000 warm requests..."); for _ in 0..1000 { let start = Instant::now(); let request = create_test_request(); let _ = client.submit_order(request).await; latencies.push(start.elapsed()); } latencies.sort(); let p50 = latencies[latencies.len() / 2]; let p95 = latencies[(latencies.len() as f64 * 0.95) as usize]; let p99 = latencies[(latencies.len() as f64 * 0.99) as usize]; let min = latencies[0]; let max = latencies[latencies.len() - 1]; println!("\n 📊 Warm Cache Statistics:"); println!(" Min: {:>8} μs", min.as_micros()); println!(" P50: {:>8} μs", p50.as_micros()); println!(" P95: {:>8} μs", p95.as_micros()); println!(" P99: {:>8} μs", p99.as_micros()); println!(" Max: {:>8} μs", max.as_micros()); println!(" Target: < 1,000 μs (1ms)"); println!("\n Wave 132 Baseline: 21-488μs warm"); if p99.as_micros() < 1000 { println!(" ✅ PASS: P99 {} μs < 1ms target", p99.as_micros()); } else { println!(" ❌ FAIL: P99 {} μs >= 1ms target", p99.as_micros()); } assert!( p99.as_micros() < 1000, "P99 latency {} μs exceeds 1ms target", p99.as_micros() ); Ok(()) } #[tokio::test] #[ignore = "Run manually: cargo test -p api proxy_latency --ignored -- --nocapture"] async fn test_proxy_overhead_comparison() -> Result<()> { println!("\n=== Test 3: Proxy Overhead (Proxied vs Direct) ==="); // Setup both clients let mut proxy_client = TradingServiceClient::connect("http://localhost:50051").await?; let mut direct_client = TradingServiceClient::connect("http://localhost:50052").await?; // Warmup both println!(" Warming up proxy and direct clients..."); for _ in 0..100 { let r1 = create_test_request(); let r2 = create_test_request(); let _ = proxy_client.submit_order(r1).await; let _ = direct_client.submit_order(r2).await; } // Measure proxy latency let mut proxy_latencies = Vec::new(); for _ in 0..1000 { let start = Instant::now(); let request = create_test_request(); let _ = proxy_client.submit_order(request).await; proxy_latencies.push(start.elapsed()); } // Measure direct latency let mut direct_latencies = Vec::new(); for _ in 0..1000 { let start = Instant::now(); let request = create_test_request(); let _ = direct_client.submit_order(request).await; direct_latencies.push(start.elapsed()); } proxy_latencies.sort(); direct_latencies.sort(); let proxy_p50 = proxy_latencies[proxy_latencies.len() / 2]; let direct_p50 = direct_latencies[direct_latencies.len() / 2]; let proxy_p99 = proxy_latencies[(proxy_latencies.len() as f64 * 0.99) as usize]; let direct_p99 = direct_latencies[(direct_latencies.len() as f64 * 0.99) as usize]; let overhead_p50 = proxy_p50.saturating_sub(direct_p50); let overhead_p99 = proxy_p99.saturating_sub(direct_p99); let overhead_percent_p50 = if direct_p50.as_micros() > 0 { ((overhead_p50.as_micros() as f64 / direct_p50.as_micros() as f64) * 100.0) as u32 } else { 0 }; println!("\n 📊 Proxy vs Direct Comparison:"); println!(" Direct P50: {:>8} μs", direct_p50.as_micros()); println!(" Proxy P50: {:>8} μs", proxy_p50.as_micros()); println!( " Overhead P50: {:>8} μs ({}%)", overhead_p50.as_micros(), overhead_percent_p50 ); println!(); println!(" Direct P99: {:>8} μs", direct_p99.as_micros()); println!(" Proxy P99: {:>8} μs", proxy_p99.as_micros()); println!(" Overhead P99: {:>8} μs", overhead_p99.as_micros()); println!("\n Target: Proxy overhead < 100μs"); if overhead_p99.as_micros() < 100 { println!( " ✅ PASS: Overhead {} μs < 100μs", overhead_p99.as_micros() ); } else { println!( " ⚠️ WARNING: Overhead {} μs >= 100μs", overhead_p99.as_micros() ); } Ok(()) } #[tokio::test] #[ignore = "Run manually: cargo test -p api proxy_latency --ignored -- --nocapture"] async fn test_connection_pool_impact() -> Result<()> { println!("\n=== Test 4: Connection Pool Impact ==="); for concurrency in [1, 10, 50, 100] { let start = Instant::now(); let mut handles = vec![]; for _ in 0..concurrency { let handle = tokio::spawn(async move { let mut client = TradingServiceClient::connect("http://localhost:50051") .await .unwrap(); let request = create_test_request(); let _ = client.submit_order(request).await; }); handles.push(handle); } for handle in handles { let _ = handle.await; } let elapsed = start.elapsed(); let avg_per_request = elapsed / concurrency; println!( " Concurrency {:<3}: Total {:>6?}, Avg/req {:>6?}", concurrency, elapsed, avg_per_request ); } println!("\n ✅ Connection pool test complete"); Ok(()) }