//! Comprehensive Performance and Stress Tests //! //! This test suite provides performance benchmarking and stress testing //! for critical components of the Foxhunt HFT system. #![allow(unused_crate_dependencies)] use std::collections::HashMap; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::time::Instant; use tokio::sync::RwLock; // Import common types use chrono::Utc; use common::{OrderId, OrderSide, OrderStatus, OrderType, TimeInForce}; use rust_decimal::Decimal; // Import trading engine modules with correct paths use trading_engine::lockfree::{HftMessage, LockFreeRingBuffer}; use trading_engine::simd::{AlignedPrices, AlignedVolumes, SimdPriceOps}; use trading_engine::timing::{calibrate_tsc, HardwareTimestamp}; use trading_engine::trading::order_manager::OrderManager; use trading_engine::trading_operations::TradingOrder; #[cfg(test)] mod performance_and_stress_tests { use super::*; // ======================================================================== // High-Frequency Trading Performance Tests // ======================================================================== #[tokio::test] async fn test_order_processing_latency_target() { let order_manager = OrderManager::new(); let iterations = 1_000; // Try to calibrate TSC, but don't fail if it doesn't work let _ = calibrate_tsc(); let mut latencies_ns = Vec::with_capacity(iterations); // Warm up for _ in 0..100 { let order = create_test_order(); order_manager.add_order(order).await; } // Benchmark order processing latency for i in 0..iterations { let order = create_test_order_with_id(i); let start_time = HardwareTimestamp::now(); order_manager.add_order(order).await; let end_time = HardwareTimestamp::now(); let latency_ns = end_time.latency_ns(&start_time); latencies_ns.push(latency_ns); } // Calculate percentiles latencies_ns.sort_unstable(); let p50 = latencies_ns[latencies_ns.len() / 2]; let p95 = latencies_ns[(latencies_ns.len() as f64 * 0.95) as usize]; let p99 = latencies_ns[(latencies_ns.len() as f64 * 0.99) as usize]; println!("Order Processing Latency Results:"); println!(" P50: {}ns ({:.2}μs)", p50, p50 as f64 / 1000.0); println!(" P95: {}ns ({:.2}μs)", p95, p95 as f64 / 1000.0); println!(" P99: {}ns ({:.2}μs)", p99, p99 as f64 / 1000.0); // Verify sub-millisecond performance (relaxed for test environment) assert!(p95 < 10_000_000, "P95 latency {}ns exceeds 10ms", p95); } #[tokio::test] async fn test_simd_price_calculations_performance() { if !std::arch::is_x86_feature_detected!("avx2") { println!("Skipping SIMD test - AVX2 not available"); return; } const ARRAY_SIZE: usize = 10_000; const ITERATIONS: usize = 100; // Generate test price data let prices: Vec = (0..ARRAY_SIZE).map(|i| 100.0 + (i as f64 * 0.01)).collect(); let volumes: Vec = (0..ARRAY_SIZE) .map(|i| 1000.0 + (i as f64 * 10.0)) .collect(); let aligned_prices = AlignedPrices::from_slice(&prices); let aligned_volumes = AlignedVolumes::from_slice(&volumes); // SIMD performance test let simd_start = Instant::now(); let mut simd_results = Vec::new(); // SAFETY: AVX2 support verified above unsafe { let simd_ops = SimdPriceOps::new(); for _ in 0..ITERATIONS { let vwap = simd_ops.calculate_vwap_aligned(&aligned_prices, &aligned_volumes); simd_results.push(vwap); } } let simd_time = simd_start.elapsed(); // Scalar performance test (for comparison) let scalar_start = Instant::now(); let mut scalar_results = Vec::new(); for _ in 0..ITERATIONS { let total_pv: f64 = prices.iter().zip(volumes.iter()).map(|(p, v)| p * v).sum(); let total_volume: f64 = volumes.iter().sum(); let vwap = if total_volume > 0.0 { total_pv / total_volume } else { 0.0 }; scalar_results.push(vwap); } let scalar_time = scalar_start.elapsed(); // Verify results are equivalent assert_eq!(simd_results.len(), scalar_results.len()); for (simd, scalar) in simd_results.into_iter().zip(scalar_results.into_iter()) { assert!( (simd - scalar).abs() < 1e-10, "SIMD and scalar results differ: {} vs {}", simd, scalar ); } let simd_throughput = (ITERATIONS * ARRAY_SIZE) as f64 / simd_time.as_secs_f64(); let scalar_throughput = (ITERATIONS * ARRAY_SIZE) as f64 / scalar_time.as_secs_f64(); let speedup = scalar_time.as_secs_f64() / simd_time.as_secs_f64(); println!("SIMD Performance Comparison:"); println!(" SIMD time: {:?}", simd_time); println!(" Scalar time: {:?}", scalar_time); println!(" SIMD throughput: {:.0} ops/sec", simd_throughput); println!(" Scalar throughput: {:.0} ops/sec", scalar_throughput); println!(" Speedup ratio: {:.2}x", speedup); // SIMD should provide some speedup assert!(simd_time <= scalar_time, "SIMD not faster than scalar"); } #[tokio::test] async fn test_lock_free_structures_performance() { const NUM_MESSAGES: usize = 100_000; let ring_buffer = Arc::new( LockFreeRingBuffer::::new(1_000_000).expect("Failed to create ring buffer"), ); let start_time = Instant::now(); let processed_count = Arc::new(AtomicU64::new(0)); // Spawn producer task let buffer_clone = Arc::clone(&ring_buffer); let producer_handle = tokio::spawn(async move { let mut local_count = 0; for msg_id in 0..NUM_MESSAGES { let message = HftMessage::new(1, [msg_id as u64, 0, 0, 0, 0, 0, 0, 0]); while buffer_clone.try_push(message).is_err() { tokio::task::yield_now().await; // Back pressure } local_count += 1; } local_count }); // Spawn consumer task let buffer_clone = Arc::clone(&ring_buffer); let counter_clone = Arc::clone(&processed_count); let consumer_handle = tokio::spawn(async move { let mut local_count = 0; loop { if let Some(_event) = buffer_clone.try_pop() { local_count += 1; counter_clone.fetch_add(1, Ordering::Relaxed); } else { tokio::task::yield_now().await; } // Check if we've processed all messages if counter_clone.load(Ordering::Relaxed) >= NUM_MESSAGES as u64 { break; } } local_count }); // Wait for completion let total_produced = producer_handle.await.expect("Producer task failed"); let total_consumed = consumer_handle.await.expect("Consumer task failed"); let total_time = start_time.elapsed(); let throughput = NUM_MESSAGES as f64 / total_time.as_secs_f64(); println!("Lock-Free Ring Buffer Performance:"); println!(" Total messages: {}", NUM_MESSAGES); println!( " Produced: {}, Consumed: {}", total_produced, total_consumed ); println!(" Processing time: {:?}", total_time); println!(" Throughput: {:.0} messages/sec", throughput); assert_eq!(total_produced, NUM_MESSAGES); assert_eq!(total_consumed, NUM_MESSAGES); assert!( throughput > 100_000.0, "Lock-free throughput {:.0} below 100K messages/s", throughput ); } // ======================================================================== // Stress Testing - System Under Load // ======================================================================== #[tokio::test] async fn test_concurrent_order_processing_stress() { let order_manager = Arc::new(RwLock::new(OrderManager::new())); let concurrent_traders = 10; let orders_per_trader = 100; let total_orders = concurrent_traders * orders_per_trader; let start_time = Instant::now(); let success_counter = Arc::new(AtomicU64::new(0)); // Launch concurrent trading sessions let mut trader_handles = Vec::new(); for trader_id in 0..concurrent_traders { let manager_clone = Arc::clone(&order_manager); let success_clone = Arc::clone(&success_counter); let handle = tokio::spawn(async move { let mut local_success = 0; for order_id in 0..orders_per_trader { let order = TradingOrder { id: OrderId::from(format!("TRADER{:03}_{:06}", trader_id, order_id)), symbol: format!("SYMBOL{:02}", order_id % 10), side: if order_id % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell }, order_type: OrderType::Limit, quantity: Decimal::from((order_id + 1) * 1000), price: Decimal::new(10000 + order_id as i64, 4), // e.g. 1.0001 time_in_force: TimeInForce::GoodTillCancel, account_id: None, metadata: HashMap::new(), created_at: Utc::now(), submitted_at: None, executed_at: None, status: OrderStatus::New, fill_quantity: Decimal::ZERO, average_fill_price: Some(Decimal::ZERO), }; let mgr = manager_clone.write().await; mgr.add_order(order).await; local_success += 1; success_clone.fetch_add(1, Ordering::Relaxed); // Simulate realistic trading pace if order_id % 10 == 0 { tokio::task::yield_now().await; } } local_success }); trader_handles.push(handle); } // Wait for all traders to complete let mut total_success = 0; for handle in trader_handles { let success = handle.await.expect("Trader task failed"); total_success += success; } let total_time = start_time.elapsed(); let throughput = total_success as f64 / total_time.as_secs_f64(); println!("Concurrent Order Processing Stress Test:"); println!(" Concurrent traders: {}", concurrent_traders); println!(" Orders per trader: {}", orders_per_trader); println!(" Total orders: {}", total_orders); println!(" Successful orders: {}", total_success); println!(" Total time: {:?}", total_time); println!(" Throughput: {:.0} orders/sec", throughput); // All orders should succeed assert_eq!(total_success, total_orders); } #[tokio::test] async fn test_memory_pressure_handling() { // Test system behavior under memory pressure let large_allocation_size = 100_000; // 100K elements let num_allocations = 10; let mut allocations = Vec::new(); println!("Starting memory pressure test..."); // Gradually increase memory pressure for i in 0..num_allocations { let allocation: Vec = (0..large_allocation_size) .map(|j| (i * large_allocation_size + j) as f64) .collect(); allocations.push(allocation); // Test system responsiveness under memory pressure if i % 5 == 0 { // Verify system can still process orders let order_manager = OrderManager::new(); let test_order = create_test_order(); let start_time = Instant::now(); order_manager.add_order(test_order).await; let latency = start_time.elapsed(); assert!( latency.as_millis() < 1000, "Order latency {}ms too high under memory pressure", latency.as_millis() ); } } // Clean up allocations.clear(); // Force some async yields for _ in 0..10 { tokio::task::yield_now().await; } println!("Memory pressure test completed successfully"); } } // ============================================================================ // Test Utilities and Helper Functions // ============================================================================ fn create_test_order() -> TradingOrder { TradingOrder { id: OrderId::from(uuid::Uuid::new_v4().to_string()), symbol: "EURUSD".to_string(), side: OrderSide::Buy, order_type: OrderType::Limit, quantity: Decimal::from(10000), price: Decimal::new(12345, 4), // 1.2345 time_in_force: TimeInForce::GoodTillCancel, account_id: None, metadata: HashMap::new(), created_at: Utc::now(), submitted_at: None, executed_at: None, status: OrderStatus::New, fill_quantity: Decimal::ZERO, average_fill_price: Some(Decimal::ZERO), } } fn create_test_order_with_id(id: usize) -> TradingOrder { TradingOrder { id: OrderId::from(format!("TEST_ORDER_{:06}", id)), symbol: "EURUSD".to_string(), side: if id % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell }, order_type: OrderType::Limit, quantity: Decimal::from((id + 1) * 1000), price: Decimal::new(12345 + id as i64, 4), // 1.2345 + small increment time_in_force: TimeInForce::GoodTillCancel, account_id: None, metadata: HashMap::new(), created_at: Utc::now(), submitted_at: None, executed_at: None, status: OrderStatus::New, fill_quantity: Decimal::ZERO, average_fill_price: Some(Decimal::ZERO), } }