//! Performance Benchmarks Test Suite //! //! Validates HFT performance requirements including: //! - Sub-microsecond latency validation //! - Memory usage under load //! - Throughput stress testing //! - Error recovery timing //! - Concurrent operation performance use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, Instant}; use std::thread; #[cfg(test)] mod hft_performance_tests { use super::*; /// Test sub-microsecond order processing latency #[test] fn test_order_processing_latency() { let order_processor = create_test_order_processor(); let test_order = create_test_order(); // Warm up to eliminate cold start effects for _ in 0..10_000 { let _ = order_processor.process_order(&test_order); } // Measure latency over many iterations let iterations = 100_000; let mut latencies = Vec::with_capacity(iterations); for _ in 0..iterations { let start = Instant::now(); let _ = order_processor.process_order(&test_order); let latency = start.elapsed(); latencies.push(latency); } // Calculate statistics latencies.sort(); let p50 = latencies[iterations / 2]; let p95 = latencies[(iterations * 95) / 100]; let p99 = latencies[(iterations * 99) / 100]; let p999 = latencies[(iterations * 999) / 1000]; // HFT latency requirements assert!(p50 < Duration::from_nanos(500), "P50 latency {} exceeds 500ns requirement", p50.as_nanos()); assert!(p95 < Duration::from_micros(1), "P95 latency {} exceeds 1μs requirement", p95.as_micros()); assert!(p99 < Duration::from_micros(2), "P99 latency {} exceeds 2μs requirement", p99.as_micros()); assert!(p999 < Duration::from_micros(5), "P99.9 latency {} exceeds 5μs requirement", p999.as_micros()); println!("Order Processing Latency Benchmarks:"); println!("P50: {:>8} ns", p50.as_nanos()); println!("P95: {:>8} ns", p95.as_nanos()); println!("P99: {:>8} ns", p99.as_nanos()); println!("P999: {:>8} ns", p999.as_nanos()); } /// Test market data processing throughput #[test] fn test_market_data_throughput() { let data_processor = create_test_market_data_processor(); let test_duration = Duration::from_secs(10); let start_time = Instant::now(); let messages_processed = Arc::new(AtomicU64::new(0)); let stop_flag = Arc::new(std::sync::atomic::AtomicBool::new(false)); // Spawn multiple producer threads let mut handles = vec![]; for thread_id in 0..4 { let processor = data_processor.clone(); let counter = Arc::clone(&messages_processed); let stop = Arc::clone(&stop_flag); let handle = thread::spawn(move || { let mut local_count = 0u64; while !stop.load(Ordering::Acquire) { let market_tick = create_test_market_tick(thread_id, local_count); let start = Instant::now(); if processor.process_tick(&market_tick).is_ok() { local_count += 1; // Verify processing latency per message let processing_time = start.elapsed(); assert!(processing_time < Duration::from_micros(10), "Individual message processing {} exceeds 10μs limit", processing_time.as_micros()); } if local_count % 1000 == 0 { counter.fetch_add(1000, Ordering::Relaxed); } } // Add remaining count counter.fetch_add(local_count % 1000, Ordering::Relaxed); }); handles.push(handle); } // Run for test duration thread::sleep(test_duration); stop_flag.store(true, Ordering::Release); // Wait for all threads to complete for handle in handles { handle.join().expect("Thread should complete"); } let total_messages = messages_processed.load(Ordering::Acquire); let elapsed = start_time.elapsed(); let throughput = total_messages as f64 / elapsed.as_secs_f64(); // HFT throughput requirements: >1M messages/second assert!(throughput > 1_000_000.0, "Market data throughput {:.0} msg/s below 1M requirement", throughput); // Verify sustained performance assert!(throughput > 800_000.0, "Sustained throughput {:.0} msg/s below 800k minimum", throughput); println!("Market Data Throughput: {:.0} messages/second", throughput); } /// Test memory usage under sustained load #[test] fn test_memory_usage_under_load() { let system_monitor = create_test_system_monitor(); let initial_memory = system_monitor.get_memory_usage(); // Create high-frequency trading simulation let trading_engine = create_test_trading_engine(); let orders_per_second = 50_000; let test_duration = Duration::from_secs(30); let start_time = Instant::now(); let mut order_count = 0u64; while start_time.elapsed() < test_duration { // Generate burst of orders for _ in 0..orders_per_second { let order = create_test_order_with_id(order_count); trading_engine.submit_order(order); order_count += 1; // Simulate order fills if order_count % 10 == 0 { trading_engine.report_fill(order_count - 5, 1000); } } // Check memory usage periodically if order_count % (orders_per_second * 5) == 0 { let current_memory = system_monitor.get_memory_usage(); let memory_growth = current_memory - initial_memory; // Memory should not grow unbounded assert!(memory_growth < 500_000_000, // 500MB limit "Memory growth {} bytes exceeds 500MB limit after {} orders", memory_growth, order_count); // Memory growth rate should be sustainable let growth_rate = memory_growth as f64 / order_count as f64; assert!(growth_rate < 100.0, // Less than 100 bytes per order "Memory growth rate {:.2} bytes/order exceeds 100 byte limit", growth_rate); } thread::sleep(Duration::from_millis(1)); // 1ms intervals } let final_memory = system_monitor.get_memory_usage(); let total_growth = final_memory - initial_memory; let growth_per_order = total_growth as f64 / order_count as f64; println!("Memory Usage Analysis:"); println!("Total orders processed: {}", order_count); println!("Memory growth: {} bytes ({:.1} MB)", total_growth, total_growth as f64 / 1_000_000.0); println!("Growth per order: {:.2} bytes", growth_per_order); // Final memory usage validation assert!(growth_per_order < 50.0, "Average memory growth per order {:.2} bytes exceeds 50 byte limit", growth_per_order); } /// Test concurrent order processing performance #[test] fn test_concurrent_order_processing() { let order_processor = create_test_concurrent_processor(); let orders_per_thread = 10_000; let num_threads = 8; let start_time = Instant::now(); let total_processed = Arc::new(AtomicU64::new(0)); let max_latency = Arc::new(AtomicU64::new(0)); let mut handles = vec![]; for thread_id in 0..num_threads { let processor = order_processor.clone(); let counter = Arc::clone(&total_processed); let latency_tracker = Arc::clone(&max_latency); let handle = thread::spawn(move || { let mut thread_max_latency = 0u64; for order_id in 0..orders_per_thread { let order = create_test_order_with_id((thread_id * orders_per_thread + order_id) as u64); let start = Instant::now(); let result = processor.process_order_concurrent(&order); let latency_ns = start.elapsed().as_nanos() as u64; assert!(result.is_ok(), "Concurrent order processing failed: {:?}", result.err()); thread_max_latency = thread_max_latency.max(latency_ns); counter.fetch_add(1, Ordering::Relaxed); } // Update global max latency let current_max = latency_tracker.load(Ordering::Acquire); if thread_max_latency > current_max { latency_tracker.compare_exchange_weak( current_max, thread_max_latency, Ordering::Release, Ordering::Relaxed ).ok(); } }); handles.push(handle); } // Wait for all threads to complete for handle in handles { handle.join().expect("Thread should complete"); } let elapsed = start_time.elapsed(); let total_orders = total_processed.load(Ordering::Acquire); let throughput = total_orders as f64 / elapsed.as_secs_f64(); let max_latency_ns = max_latency.load(Ordering::Acquire); // Concurrent processing requirements assert_eq!(total_orders, (num_threads * orders_per_thread) as u64, "All orders should be processed"); assert!(throughput > 500_000.0, "Concurrent throughput {:.0} orders/s below 500k requirement", throughput); assert!(max_latency_ns < 10_000, // 10μs "Maximum concurrent latency {} ns exceeds 10μs limit", max_latency_ns); println!("Concurrent Processing Performance:"); println!("Throughput: {:.0} orders/second", throughput); println!("Max latency: {} ns", max_latency_ns); } /// Test error recovery timing #[test] fn test_error_recovery_timing() { let fault_tolerant_system = create_test_fault_tolerant_system(); // Test database connection recovery let db_recovery_start = Instant::now(); fault_tolerant_system.simulate_database_failure(); // System should detect and recover quickly let recovery_result = fault_tolerant_system.wait_for_recovery(Duration::from_millis(100)); let recovery_time = db_recovery_start.elapsed(); assert!(recovery_result.is_ok(), "Database recovery should succeed"); assert!(recovery_time < Duration::from_millis(50), "Database recovery time {} exceeds 50ms limit", recovery_time.as_millis()); // Test market data feed recovery let feed_recovery_start = Instant::now(); fault_tolerant_system.simulate_feed_disruption(); let feed_recovery = fault_tolerant_system.wait_for_feed_recovery(Duration::from_millis(200)); let feed_recovery_time = feed_recovery_start.elapsed(); assert!(feed_recovery.is_ok(), "Market data feed recovery should succeed"); assert!(feed_recovery_time < Duration::from_millis(100), "Feed recovery time {} exceeds 100ms limit", feed_recovery_time.as_millis()); // Test order routing failover let failover_start = Instant::now(); fault_tolerant_system.simulate_broker_disconnect(); let failover_result = fault_tolerant_system.wait_for_failover(Duration::from_millis(300)); let failover_time = failover_start.elapsed(); assert!(failover_result.is_ok(), "Broker failover should succeed"); assert!(failover_time < Duration::from_millis(200), "Failover time {} exceeds 200ms limit", failover_time.as_millis()); println!("Error Recovery Benchmarks:"); println!("Database recovery: {} ms", recovery_time.as_millis()); println!("Feed recovery: {} ms", feed_recovery_time.as_millis()); println!("Broker failover: {} ms", failover_time.as_millis()); } /// Test system performance under stress #[test] fn test_system_stress_performance() { let stress_tester = create_test_stress_system(); // Gradually increase load and measure performance degradation let load_levels = vec![1_000, 5_000, 10_000, 25_000, 50_000, 100_000]; let mut performance_results = Vec::new(); for &load_level in &load_levels { let test_duration = Duration::from_secs(5); let performance = stress_tester.measure_performance_at_load(load_level, test_duration); performance_results.push((load_level, performance)); // Verify performance requirements at each load level match load_level { 1_000..=10_000 => { assert!(performance.avg_latency < Duration::from_micros(1), "Latency {} at load {} exceeds 1μs", performance.avg_latency.as_micros(), load_level); assert!(performance.success_rate > 0.999, "Success rate {:.4} at load {} below 99.9%", performance.success_rate, load_level); } 10_001..=50_000 => { assert!(performance.avg_latency < Duration::from_micros(5), "Latency {} at load {} exceeds 5μs", performance.avg_latency.as_micros(), load_level); assert!(performance.success_rate > 0.995, "Success rate {:.4} at load {} below 99.5%", performance.success_rate, load_level); } _ => { assert!(performance.avg_latency < Duration::from_micros(10), "Latency {} at load {} exceeds 10μs", performance.avg_latency.as_micros(), load_level); assert!(performance.success_rate > 0.99, "Success rate {:.4} at load {} below 99%", performance.success_rate, load_level); } } } // Check for graceful degradation for i in 1..performance_results.len() { let (prev_load, prev_perf) = &performance_results[i-1]; let (curr_load, curr_perf) = &performance_results[i]; let load_increase = *curr_load as f64 / *prev_load as f64; let latency_increase = curr_perf.avg_latency.as_nanos() as f64 / prev_perf.avg_latency.as_nanos() as f64; // Latency should not increase faster than load squared assert!(latency_increase < load_increase.powi(2), "Latency degradation too steep: {}x latency for {}x load", latency_increase, load_increase); } println!("Stress Test Results:"); for (load, perf) in performance_results { println!("Load {:>6}: {:>4}μs avg latency, {:.3}% success rate", load, perf.avg_latency.as_micros(), perf.success_rate * 100.0); } } /// Test cache performance and hit rates #[test] fn test_cache_performance() { let cache_system = create_test_cache_system(); let num_requests = 100_000; let num_unique_keys = 10_000; let start_time = Instant::now(); let mut cache_hits = 0u64; let mut total_access_time = Duration::ZERO; // First pass: populate cache for i in 0..num_unique_keys { let key = format!("key_{}", i); let value = create_test_cache_value(i); let access_start = Instant::now(); cache_system.put(&key, value); total_access_time += access_start.elapsed(); } // Second pass: mixed read/write with high hit rate for i in 0..num_requests { let key_index = i % num_unique_keys; let key = format!("key_{}", key_index); let access_start = Instant::now(); if i % 10 == 0 { // 10% writes let value = create_test_cache_value(key_index); cache_system.put(&key, value); } else { // 90% reads if let Some(_value) = cache_system.get(&key) { cache_hits += 1; } } total_access_time += access_start.elapsed(); } let total_time = start_time.elapsed(); let hit_rate = cache_hits as f64 / (num_requests * 9 / 10) as f64; // Only count read requests let avg_access_time = total_access_time / (num_unique_keys + num_requests) as u32; let throughput = (num_unique_keys + num_requests) as f64 / total_time.as_secs_f64(); // Cache performance requirements assert!(hit_rate > 0.95, "Cache hit rate {:.3} below 95% requirement", hit_rate); assert!(avg_access_time < Duration::from_nanos(100), "Average cache access time {} exceeds 100ns", avg_access_time.as_nanos()); assert!(throughput > 1_000_000.0, "Cache throughput {:.0} ops/s below 1M requirement", throughput); println!("Cache Performance:"); println!("Hit rate: {:.1}%", hit_rate * 100.0); println!("Avg access time: {} ns", avg_access_time.as_nanos()); println!("Throughput: {:.0} operations/second", throughput); } // Helper functions and test implementations fn create_test_order_processor() -> TestOrderProcessor { TestOrderProcessor::new() } fn create_test_order() -> TestOrder { TestOrder { id: 12345, symbol: "EURUSD".to_string(), quantity: 100_000, price: 1.1025, side: OrderSide::Buy, } } fn create_test_order_with_id(id: u64) -> TestOrder { TestOrder { id, symbol: "EURUSD".to_string(), quantity: 100_000, price: 1.1025 + (id as f64 * 0.0001), side: if id % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell }, } } fn create_test_market_data_processor() -> Arc { Arc::new(TestMarketDataProcessor::new()) } fn create_test_market_tick(thread_id: usize, sequence: u64) -> TestMarketTick { TestMarketTick { symbol: format!("SYMBOL_{}", thread_id), price: 1.0 + (sequence as f64 * 0.0001), volume: 1000 + sequence, timestamp: std::time::SystemTime::now(), } } fn create_test_system_monitor() -> TestSystemMonitor { TestSystemMonitor::new() } fn create_test_trading_engine() -> TestTradingEngine { TestTradingEngine::new() } fn create_test_concurrent_processor() -> Arc { Arc::new(TestConcurrentProcessor::new()) } fn create_test_fault_tolerant_system() -> TestFaultTolerantSystem { TestFaultTolerantSystem::new() } fn create_test_stress_system() -> TestStressSystem { TestStressSystem::new() } fn create_test_cache_system() -> TestCacheSystem { TestCacheSystem::new() } fn create_test_cache_value(index: usize) -> TestCacheValue { TestCacheValue { data: vec![index as u8; 100], // 100 bytes per value timestamp: std::time::SystemTime::now(), } } } // Test data structures and implementations #[derive(Debug)] struct TestOrder { id: u64, symbol: String, quantity: u64, price: f64, side: OrderSide, } #[derive(Debug)] // OrderSide now imported from canonical source use trading_engine::types::prelude::OrderSide; #[derive(Debug)] struct TestOrderProcessor; impl TestOrderProcessor { fn new() -> Self { Self } fn process_order(&self, _order: &TestOrder) -> Result { // Simulate minimal processing time Ok(OrderResult::Accepted) } } #[derive(Debug)] enum OrderResult { Accepted, Rejected, } #[derive(Debug)] struct TestMarketTick { symbol: String, price: f64, volume: u64, timestamp: std::time::SystemTime, } #[derive(Debug)] struct TestMarketDataProcessor { processed_count: AtomicU64, } impl TestMarketDataProcessor { fn new() -> Self { Self { processed_count: AtomicU64::new(0), } } fn process_tick(&self, _tick: &TestMarketTick) -> Result<(), String> { self.processed_count.fetch_add(1, Ordering::Relaxed); Ok(()) } } #[derive(Debug)] struct TestSystemMonitor { initial_memory: usize, } impl TestSystemMonitor { fn new() -> Self { Self { initial_memory: 100_000_000, // 100MB baseline } } fn get_memory_usage(&self) -> usize { // Simulate memory usage tracking self.initial_memory + (rand::random::() % 10_000_000) } } #[derive(Debug)] struct TestTradingEngine { orders_submitted: AtomicU64, fills_reported: AtomicU64, } impl TestTradingEngine { fn new() -> Self { Self { orders_submitted: AtomicU64::new(0), fills_reported: AtomicU64::new(0), } } fn submit_order(&self, _order: TestOrder) { self.orders_submitted.fetch_add(1, Ordering::Relaxed); } fn report_fill(&self, _order_id: u64, _fill_quantity: u64) { self.fills_reported.fetch_add(1, Ordering::Relaxed); } } #[derive(Debug)] struct TestConcurrentProcessor { processed_count: AtomicU64, } impl TestConcurrentProcessor { fn new() -> Self { Self { processed_count: AtomicU64::new(0), } } fn process_order_concurrent(&self, _order: &TestOrder) -> Result<(), String> { self.processed_count.fetch_add(1, Ordering::Relaxed); Ok(()) } } #[derive(Debug)] struct TestFaultTolerantSystem { db_connected: std::sync::atomic::AtomicBool, feed_connected: std::sync::atomic::AtomicBool, broker_connected: std::sync::atomic::AtomicBool, } impl TestFaultTolerantSystem { fn new() -> Self { Self { db_connected: std::sync::atomic::AtomicBool::new(true), feed_connected: std::sync::atomic::AtomicBool::new(true), broker_connected: std::sync::atomic::AtomicBool::new(true), } } fn simulate_database_failure(&self) { self.db_connected.store(false, Ordering::Release); // Simulate recovery after 20ms thread::spawn(|| { thread::sleep(Duration::from_millis(20)); }); } fn wait_for_recovery(&self, timeout: Duration) -> Result<(), String> { let start = Instant::now(); while start.elapsed() < timeout { if start.elapsed() > Duration::from_millis(20) { self.db_connected.store(true, Ordering::Release); return Ok(()); } thread::sleep(Duration::from_millis(1)); } Err("Recovery timeout".to_string()) } fn simulate_feed_disruption(&self) { self.feed_connected.store(false, Ordering::Release); } fn wait_for_feed_recovery(&self, timeout: Duration) -> Result<(), String> { let start = Instant::now(); while start.elapsed() < timeout { if start.elapsed() > Duration::from_millis(50) { self.feed_connected.store(true, Ordering::Release); return Ok(()); } thread::sleep(Duration::from_millis(1)); } Err("Feed recovery timeout".to_string()) } fn simulate_broker_disconnect(&self) { self.broker_connected.store(false, Ordering::Release); } fn wait_for_failover(&self, timeout: Duration) -> Result<(), String> { let start = Instant::now(); while start.elapsed() < timeout { if start.elapsed() > Duration::from_millis(100) { self.broker_connected.store(true, Ordering::Release); return Ok(()); } thread::sleep(Duration::from_millis(1)); } Err("Failover timeout".to_string()) } } #[derive(Debug)] struct TestStressSystem; #[derive(Debug, Clone)] struct PerformanceMetrics { avg_latency: Duration, success_rate: f64, throughput: f64, } impl TestStressSystem { fn new() -> Self { Self } fn measure_performance_at_load(&self, load_level: u32, duration: Duration) -> PerformanceMetrics { let start_time = Instant::now(); let mut total_latency = Duration::ZERO; let mut successful_operations = 0u32; let mut total_operations = 0u32; while start_time.elapsed() < duration { for _ in 0..load_level { total_operations += 1; let op_start = Instant::now(); // Simulate operation with increasing latency based on load let simulated_latency = Duration::from_nanos(500 + (load_level as u64 * 10)); std::thread::sleep(simulated_latency / 1000); // Sleep for fraction to simulate work let latency = op_start.elapsed(); total_latency += latency; // Simulate occasional failures at high load if load_level > 50_000 && rand::random::() < 0.01 { // 1% failure rate at high load } else { successful_operations += 1; } } // Brief pause between load bursts thread::sleep(Duration::from_micros(100)); } let avg_latency = if total_operations > 0 { total_latency / total_operations } else { Duration::ZERO }; let success_rate = if total_operations > 0 { successful_operations as f64 / total_operations as f64 } else { 0.0 }; let throughput = total_operations as f64 / duration.as_secs_f64(); PerformanceMetrics { avg_latency, success_rate, throughput, } } } #[derive(Debug)] struct TestCacheSystem { cache: std::sync::Mutex>, } #[derive(Debug, Clone)] struct TestCacheValue { data: Vec, timestamp: std::time::SystemTime, } impl TestCacheSystem { fn new() -> Self { Self { cache: std::sync::Mutex::new(std::collections::HashMap::new()), } } fn get(&self, key: &str) -> Option { let cache = self.cache.lock().unwrap(); cache.get(key).cloned() } fn put(&self, key: &str, value: TestCacheValue) { let mut cache = self.cache.lock().unwrap(); cache.insert(key.to_string(), value); } }