//! Performance and load integration tests for TLI system //! //! This module tests system performance under various load conditions including //! concurrent operations, high-frequency trading scenarios, and stress testing. use futures::future::join_all; use std::sync::{ atomic::{AtomicUsize, Ordering}, Arc, }; use std::time::{Duration, Instant}; use tokio::sync::Semaphore; use tokio::time::{sleep, timeout}; use uuid::Uuid; use crate::integration::{TestConfig, TestUtilities}; use crate::mocks::grpc_server::{MockBacktestingServer, MockTradingServer}; use fxt::client::{BacktestingClient, TliClientBuilder, TradingClient}; use fxt::prelude::*; /// Performance test configuration #[derive(Debug, Clone)] pub struct PerformanceTestConfig { pub base_test_config: TestConfig, pub max_concurrent_requests: usize, pub test_duration: Duration, pub warmup_duration: Duration, pub target_latency_p99: Duration, pub target_throughput_rps: f64, pub memory_limit_mb: usize, } impl Default for PerformanceTestConfig { fn default() -> Self { Self { base_test_config: TestConfig::default(), max_concurrent_requests: 100, test_duration: Duration::from_secs(30), warmup_duration: Duration::from_secs(5), target_latency_p99: Duration::from_millis(100), target_throughput_rps: 1000.0, memory_limit_mb: 512, } } } /// Performance metrics collector #[derive(Debug, Clone)] pub struct PerformanceMetrics { pub total_requests: usize, pub successful_requests: usize, pub failed_requests: usize, pub latencies: Vec, pub start_time: Instant, pub end_time: Instant, pub peak_memory_mb: f64, pub cpu_usage_percent: f64, } impl PerformanceMetrics { pub fn new() -> Self { Self { total_requests: 0, successful_requests: 0, failed_requests: 0, latencies: Vec::new(), start_time: Instant::now(), end_time: Instant::now(), peak_memory_mb: 0.0, cpu_usage_percent: 0.0, } } pub fn add_request_result(&mut self, latency: Duration, success: bool) { self.total_requests += 1; self.latencies.push(latency); if success { self.successful_requests += 1; } else { self.failed_requests += 1; } } pub fn finalize(&mut self) { self.end_time = Instant::now(); self.latencies.sort(); } pub fn duration(&self) -> Duration { self.end_time.duration_since(self.start_time) } pub fn throughput_rps(&self) -> f64 { self.total_requests as f64 / self.duration().as_secs_f64() } pub fn success_rate(&self) -> f64 { if self.total_requests == 0 { 0.0 } else { self.successful_requests as f64 / self.total_requests as f64 } } pub fn percentile_latency(&self, percentile: f64) -> Option { if self.latencies.is_empty() { return None; } let index = ((percentile / 100.0) * (self.latencies.len() - 1) as f64) as usize; Some(self.latencies[index]) } pub fn average_latency(&self) -> Option { if self.latencies.is_empty() { return None; } let total_nanos: u64 = self.latencies.iter().map(|d| d.as_nanos() as u64).sum(); let avg_nanos = total_nanos / self.latencies.len() as u64; Some(Duration::from_nanos(avg_nanos)) } } /// Performance test environment pub struct PerformanceTestEnvironment { config: PerformanceTestConfig, trading_client: Option, backtesting_client: Option, mock_servers: Vec>, metrics: Arc>, } impl PerformanceTestEnvironment { pub fn new(config: PerformanceTestConfig) -> Self { Self { config, trading_client: None, backtesting_client: None, mock_servers: Vec::new(), metrics: Arc::new(tokio::sync::Mutex::new(PerformanceMetrics::new())), } } /// Setup high-performance test environment pub async fn setup(&mut self) -> FxtResult<()> { tracing::info!("Setting up performance test environment"); // Start high-performance mock servers let mut trading_server = MockTradingServer::new_high_performance(self.config.base_test_config.mock_server_port)?; let trading_port = trading_server.start()?; self.mock_servers.push(Box::new(trading_server)); let mut backtesting_server = MockBacktestingServer::new_high_performance( self.config.base_test_config.mock_server_port + 100, )?; let backtesting_port = backtesting_server.start()?; self.mock_servers.push(Box::new(backtesting_server)); // Wait for services to be ready sleep(Duration::from_millis(500)).await; // Create optimized TLI client suite let client_suite = TliClientBuilder::new() .with_service_endpoint( "trading_service".to_string(), format!("http://localhost:{}", trading_port), ) .with_service_endpoint( "backtesting_service".to_string(), format!("http://localhost:{}", backtesting_port), ) .with_trading_config(create_high_performance_trading_config()) .with_backtesting_config(create_high_performance_backtesting_config()) .build() .await?; self.trading_client = client_suite.trading_client; self.backtesting_client = client_suite.backtesting_client; tracing::info!("Performance test environment setup complete"); Ok(()) } /// Cleanup test environment pub async fn teardown(&mut self) -> FxtResult<()> { tracing::info!("Tearing down performance test environment"); // Shutdown clients if let Some(client) = self.trading_client.take() { client.shutdown().await; } if let Some(client) = self.backtesting_client.take() { client.shutdown().await; } // Stop mock servers for server in &mut self.mock_servers { let _ = server.stop(); } self.mock_servers.clear(); tracing::info!("Performance test environment teardown complete"); Ok(()) } pub async fn get_metrics(&self) -> PerformanceMetrics { self.metrics.lock().await.clone() } pub async fn record_request(&self, latency: Duration, success: bool) { let mut metrics = self.metrics.lock().await; metrics.add_request_result(latency, success); } } fn create_high_performance_trading_config() -> TradingClientConfig { TradingClientConfig { service_name: "trading_service".to_string(), request_timeout: Duration::from_millis(500), order_validation: OrderValidationConfig { enable_pre_trade_checks: true, max_order_value: 10000000.0, require_confirmation: false, }, risk_management: RiskManagementConfig { enable_position_limits: true, max_position_size: 100000.0, max_daily_loss: 500000.0, }, market_data: MarketDataConfig { subscription_timeout: Duration::from_secs(10), reconnect_interval: Duration::from_millis(500), max_reconnect_attempts: 3, }, monitoring: MonitoringConfig { enable_health_checks: false, // Disable for performance testing health_check_interval: Duration::from_secs(60), enable_circuit_breaker: false, // Disable for performance testing circuit_breaker_threshold: 10, }, event_streaming: EventStreamConfig { buffer_size: 10000, reconnect_policy: ReconnectPolicy::Immediate, max_reconnect_attempts: 1, }, } } fn create_high_performance_backtesting_config() -> BacktestingClientConfig { BacktestingClientConfig { service_name: "backtesting_service".to_string(), request_timeout: Duration::from_secs(10), long_running_timeout: Duration::from_secs(60), retry_config: RetryConfig { max_attempts: 1, // Minimal retries for performance initial_delay: Duration::from_millis(10), max_delay: Duration::from_millis(100), backoff_multiplier: 1.5, }, } } /// Concurrent request performance tests #[cfg(test)] mod concurrent_request_tests { use super::*; #[tokio::test] async fn test_concurrent_order_submission() { let config = PerformanceTestConfig { max_concurrent_requests: 50, test_duration: Duration::from_secs(10), ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let trading_client = test_env .trading_client .as_ref() .expect("Trading client not available"); // Warmup period tracing::info!("Starting warmup period"); let warmup_start = Instant::now(); while warmup_start.elapsed() < config.warmup_duration { let order_request = create_test_order_request(); let _ = trading_client.submit_order(order_request).await; sleep(Duration::from_millis(10)).await; } tracing::info!("Starting concurrent order submission test"); let semaphore = Arc::new(Semaphore::new(config.max_concurrent_requests)); let start_time = Instant::now(); let mut handles = Vec::new(); // Start metrics collection { let mut metrics = test_env.metrics.lock().await; metrics.start_time = start_time; } while start_time.elapsed() < config.test_duration { let permit = semaphore.clone().acquire_owned().await.unwrap(); let client = trading_client.clone(); let metrics = test_env.metrics.clone(); let handle = tokio::spawn(async move { let request_start = Instant::now(); let order_request = create_test_order_request(); let result = client.submit_order(order_request).await; let latency = request_start.elapsed(); { let mut m = metrics.lock().await; m.add_request_result(latency, result.is_ok()); } drop(permit); }); handles.push(handle); // Control request rate sleep(Duration::from_micros(100)).await; } // Wait for all requests to complete tracing::info!( "Waiting for {} concurrent requests to complete", handles.len() ); join_all(handles).await; // Finalize metrics { let mut metrics = test_env.metrics.lock().await; metrics.finalize(); } let final_metrics = test_env.get_metrics().await; // Performance assertions assert!( final_metrics.total_requests > 0, "Should have processed requests" ); assert!( final_metrics.success_rate() > 0.95, "Success rate should be > 95%" ); if let Some(p99_latency) = final_metrics.percentile_latency(99.0) { assert!( p99_latency < config.target_latency_p99, "P99 latency {} should be < target {}", p99_latency.as_millis(), config.target_latency_p99.as_millis() ); } tracing::info!("Concurrent test results:"); tracing::info!(" Total requests: {}", final_metrics.total_requests); tracing::info!( " Success rate: {:.2}%", final_metrics.success_rate() * 100.0 ); tracing::info!(" Throughput: {:.2} RPS", final_metrics.throughput_rps()); tracing::info!(" Average latency: {:?}", final_metrics.average_latency()); tracing::info!( " P99 latency: {:?}", final_metrics.percentile_latency(99.0) ); test_env .teardown() .await .expect("Failed to teardown test environment"); } #[tokio::test] async fn test_mixed_operation_load() { let config = PerformanceTestConfig { max_concurrent_requests: 30, test_duration: Duration::from_secs(15), ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let trading_client = test_env .trading_client .as_ref() .expect("Trading client not available"); tracing::info!("Starting mixed operation load test"); let semaphore = Arc::new(Semaphore::new(config.max_concurrent_requests)); let start_time = Instant::now(); let mut handles = Vec::new(); // Operation counters let order_count = Arc::new(AtomicUsize::new(0)); let position_count = Arc::new(AtomicUsize::new(0)); let analytics_count = Arc::new(AtomicUsize::new(0)); { let mut metrics = test_env.metrics.lock().await; metrics.start_time = start_time; } while start_time.elapsed() < config.test_duration { let permit = semaphore.clone().acquire_owned().await.unwrap(); let client = trading_client.clone(); let metrics = test_env.metrics.clone(); let order_counter = order_count.clone(); let position_counter = position_count.clone(); let analytics_counter = analytics_count.clone(); let handle = tokio::spawn(async move { let request_start = Instant::now(); // Randomly choose operation type let operation_type = rand::random::() % 3; let result = match operation_type { 0 => { // Order submission (60% of operations) order_counter.fetch_add(1, Ordering::Relaxed); let order_request = create_test_order_request(); client.submit_order(order_request).await.map(|_| ()) } 1 => { // Position query (30% of operations) position_counter.fetch_add(1, Ordering::Relaxed); let position_request = GetPositionsRequest { account_id: Some("test_account".to_string()), symbol_filter: None, include_zero_positions: false, }; client.get_positions(position_request).await.map(|_| ()) } 2 => { // Portfolio analytics (10% of operations) analytics_counter.fetch_add(1, Ordering::Relaxed); let analytics_request = PortfolioAnalyticsRequest { account_id: "test_account".to_string(), calculation_date: chrono::Utc::now().timestamp(), include_realized_pnl: true, include_unrealized_pnl: true, include_risk_metrics: false, // Disable for performance }; client .get_portfolio_analytics(analytics_request) .await .map(|_| ()) } _ => unreachable!(), }; let latency = request_start.elapsed(); { let mut m = metrics.lock().await; m.add_request_result(latency, result.is_ok()); } drop(permit); }); handles.push(handle); sleep(Duration::from_micros(200)).await; } // Wait for completion join_all(handles).await; let final_metrics = test_env.get_metrics().await; let final_order_count = order_count.load(Ordering::Relaxed); let final_position_count = position_count.load(Ordering::Relaxed); let final_analytics_count = analytics_count.load(Ordering::Relaxed); tracing::info!("Mixed operation test results:"); tracing::info!(" Order operations: {}", final_order_count); tracing::info!(" Position queries: {}", final_position_count); tracing::info!(" Analytics queries: {}", final_analytics_count); tracing::info!(" Total operations: {}", final_metrics.total_requests); tracing::info!( " Success rate: {:.2}%", final_metrics.success_rate() * 100.0 ); tracing::info!(" Throughput: {:.2} RPS", final_metrics.throughput_rps()); assert!( final_metrics.success_rate() > 0.90, "Mixed operation success rate should be > 90%" ); assert!(final_order_count > 0, "Should have processed orders"); assert!( final_position_count > 0, "Should have processed position queries" ); test_env .teardown() .await .expect("Failed to teardown test environment"); } fn create_test_order_request() -> SubmitOrderRequest { let symbols = ["AAPL", "GOOGL", "MSFT", "TSLA", "AMZN"]; let symbol = symbols[rand::random::() % symbols.len()]; SubmitOrderRequest { symbol: TestUtilities::generate_test_symbol(symbol), side: if rand::random::() { OrderSide::Buy } else { OrderSide::Sell } as i32, order_type: OrderType::Market as i32, quantity: (rand::random::() % 1000 + 1) as f64, price: Some(100.0 + (rand::random::() * 100.0)), stop_price: None, time_in_force: "DAY".to_string(), client_order_id: Uuid::new_v4().to_string(), } } } /// High-frequency trading simulation tests #[cfg(test)] mod hft_simulation_tests { use super::*; #[tokio::test] async fn test_high_frequency_order_flow() { let config = PerformanceTestConfig { max_concurrent_requests: 20, test_duration: Duration::from_secs(5), target_latency_p99: Duration::from_millis(10), // Very low latency requirement target_throughput_rps: 2000.0, ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let trading_client = test_env .trading_client .as_ref() .expect("Trading client not available"); tracing::info!("Starting high-frequency trading simulation"); let start_time = Instant::now(); let mut handles = Vec::new(); { let mut metrics = test_env.metrics.lock().await; metrics.start_time = start_time; } // Simulate HFT pattern: rapid order submission and cancellation while start_time.elapsed() < config.test_duration { // Submit order let client = trading_client.clone(); let metrics = test_env.metrics.clone(); let submit_handle = tokio::spawn(async move { let request_start = Instant::now(); let order_request = create_hft_order_request(); let result = client.submit_order(order_request).await; let latency = request_start.elapsed(); { let mut m = metrics.lock().await; m.add_request_result(latency, result.is_ok()); } result }); handles.push(submit_handle); // Minimal delay for HFT simulation sleep(Duration::from_micros(500)).await; } // Wait for all operations to complete let results = join_all(handles).await; let final_metrics = test_env.get_metrics().await; // HFT performance requirements assert!( final_metrics.success_rate() > 0.98, "HFT success rate should be > 98%" ); assert!( final_metrics.throughput_rps() > config.target_throughput_rps * 0.8, "Throughput should be within 80% of target" ); if let Some(p99_latency) = final_metrics.percentile_latency(99.0) { tracing::info!("HFT P99 latency: {:?}", p99_latency); // Note: In real systems, this would be much stricter (microseconds) } tracing::info!("HFT simulation results:"); tracing::info!(" Total operations: {}", final_metrics.total_requests); tracing::info!( " Success rate: {:.3}%", final_metrics.success_rate() * 100.0 ); tracing::info!(" Throughput: {:.2} RPS", final_metrics.throughput_rps()); tracing::info!(" Average latency: {:?}", final_metrics.average_latency()); test_env .teardown() .await .expect("Failed to teardown test environment"); } #[tokio::test] async fn test_market_data_streaming_performance() { let config = PerformanceTestConfig { test_duration: Duration::from_secs(10), ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let trading_client = test_env .trading_client .as_ref() .expect("Trading client not available"); // Subscribe to high-frequency market data let subscription_request = MarketDataSubscriptionRequest { symbols: vec![ "AAPL".to_string(), "GOOGL".to_string(), "MSFT".to_string(), "TSLA".to_string(), "AMZN".to_string(), "META".to_string(), ], data_types: vec![MarketDataType::Quote as i32, MarketDataType::Trade as i32], include_level2: true, }; let mut stream = trading_client .subscribe_market_data(subscription_request) .await .expect("Failed to subscribe to market data"); let start_time = Instant::now(); let mut updates_received = 0; let mut total_latency = Duration::from_nanos(0); tracing::info!("Starting market data streaming performance test"); while start_time.elapsed() < config.test_duration { match timeout(Duration::from_millis(100), stream.recv()).await { Ok(Some(update)) => { updates_received += 1; // Calculate latency (mock server timestamps should be close to current time) let update_time = chrono::DateTime::from_timestamp_nanos(update.timestamp); if let Some(update_time) = update_time { let latency = chrono::Utc::now().signed_duration_since(update_time); if latency.num_milliseconds() >= 0 { total_latency += Duration::from_millis(latency.num_milliseconds() as u64); } } // Log progress periodically if updates_received % 1000 == 0 { tracing::info!("Received {} market data updates", updates_received); } } Ok(None) => break, Err(_) => { // Timeout - continue continue; } } } let duration = start_time.elapsed(); let updates_per_second = updates_received as f64 / duration.as_secs_f64(); let average_latency = if updates_received > 0 { total_latency / updates_received as u32 } else { Duration::from_nanos(0) }; tracing::info!("Market data streaming results:"); tracing::info!(" Updates received: {}", updates_received); tracing::info!(" Updates per second: {:.2}", updates_per_second); tracing::info!(" Average latency: {:?}", average_latency); assert!( updates_received > 0, "Should have received market data updates" ); assert!( updates_per_second > 100.0, "Should process > 100 updates/second" ); test_env .teardown() .await .expect("Failed to teardown test environment"); } fn create_hft_order_request() -> SubmitOrderRequest { SubmitOrderRequest { symbol: "AAPL".to_string(), // Use consistent symbol for HFT side: if rand::random::() { OrderSide::Buy } else { OrderSide::Sell } as i32, order_type: OrderType::Limit as i32, quantity: 100.0, // Standard lot size price: Some(150.0 + (rand::random::() - 0.5) * 0.20), // Tight price range stop_price: None, time_in_force: "IOC".to_string(), // Immediate or Cancel for HFT client_order_id: Uuid::new_v4().to_string(), } } } /// Stress testing and resource limits #[cfg(test)] mod stress_tests { use super::*; #[tokio::test] async fn test_memory_usage_under_load() { let config = PerformanceTestConfig { max_concurrent_requests: 100, test_duration: Duration::from_secs(20), memory_limit_mb: 256, ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let trading_client = test_env .trading_client .as_ref() .expect("Trading client not available"); tracing::info!("Starting memory usage stress test"); // Monitor initial memory usage let initial_memory = get_memory_usage_mb(); tracing::info!("Initial memory usage: {:.2} MB", initial_memory); let start_time = Instant::now(); let mut handles = Vec::new(); { let mut metrics = test_env.metrics.lock().await; metrics.start_time = start_time; } // Generate sustained load while start_time.elapsed() < config.test_duration { // Batch of concurrent requests for _ in 0..config.max_concurrent_requests { let client = trading_client.clone(); let metrics = test_env.metrics.clone(); let handle = tokio::spawn(async move { let request_start = Instant::now(); // Create large order batch to test memory usage let mut batch_orders = Vec::new(); for i in 0..10 { batch_orders.push(SubmitOrderRequest { symbol: format!("TEST{:04}", i), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 1000.0, price: Some(100.0 + i as f64), stop_price: None, time_in_force: "DAY".to_string(), client_order_id: Uuid::new_v4().to_string(), }); } let batch_request = SubmitBatchOrdersRequest { orders: batch_orders, all_or_none: false, max_acceptable_failures: 2, }; let result = client.submit_batch_orders(batch_request).await; let latency = request_start.elapsed(); { let mut m = metrics.lock().await; m.add_request_result(latency, result.is_ok()); } }); handles.push(handle); } // Check memory usage periodically let current_memory = get_memory_usage_mb(); tracing::debug!("Current memory usage: {:.2} MB", current_memory); if current_memory > config.memory_limit_mb as f64 { tracing::warn!( "Memory usage {} MB exceeds limit {} MB", current_memory, config.memory_limit_mb ); } // Wait for batch to complete before starting next batch let batch_timeout = timeout(Duration::from_secs(5), join_all(handles.drain(..))).await; if batch_timeout.is_err() { tracing::warn!("Batch requests timed out"); break; } sleep(Duration::from_millis(100)).await; } let final_memory = get_memory_usage_mb(); let final_metrics = test_env.get_metrics().await; tracing::info!("Memory stress test results:"); tracing::info!(" Initial memory: {:.2} MB", initial_memory); tracing::info!(" Final memory: {:.2} MB", final_memory); tracing::info!(" Memory increase: {:.2} MB", final_memory - initial_memory); tracing::info!(" Total requests: {}", final_metrics.total_requests); tracing::info!( " Success rate: {:.2}%", final_metrics.success_rate() * 100.0 ); // Memory usage should be reasonable let memory_increase = final_memory - initial_memory; assert!( memory_increase < config.memory_limit_mb as f64 * 0.5, "Memory increase should be < 50% of limit" ); test_env .teardown() .await .expect("Failed to teardown test environment"); } #[tokio::test] async fn test_connection_pool_limits() { let config = PerformanceTestConfig { max_concurrent_requests: 200, // Exceed typical connection pool limits test_duration: Duration::from_secs(10), ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let trading_client = test_env .trading_client .as_ref() .expect("Trading client not available"); tracing::info!("Starting connection pool stress test"); let start_time = Instant::now(); let mut handles = Vec::new(); let connection_errors = Arc::new(AtomicUsize::new(0)); { let mut metrics = test_env.metrics.lock().await; metrics.start_time = start_time; } // Launch many concurrent requests to stress connection pool for i in 0..config.max_concurrent_requests { let client = trading_client.clone(); let metrics = test_env.metrics.clone(); let error_counter = connection_errors.clone(); let handle = tokio::spawn(async move { let request_start = Instant::now(); let order_request = SubmitOrderRequest { symbol: format!("STRESS{:03}", i % 50), // Cycle through symbols side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: Some(100.0), stop_price: None, time_in_force: "DAY".to_string(), client_order_id: format!("stress_order_{}", i), }; let result = client.submit_order(order_request).await; let latency = request_start.elapsed(); let success = match &result { Ok(_) => true, Err(FxtError::Connection(_)) => { error_counter.fetch_add(1, Ordering::Relaxed); false } Err(_) => false, }; { let mut m = metrics.lock().await; m.add_request_result(latency, success); } // Hold connection briefly to stress pool sleep(Duration::from_millis(50)).await; }); handles.push(handle); // Small delay to control request rate sleep(Duration::from_millis(2)).await; } // Wait for all requests to complete join_all(handles).await; let final_metrics = test_env.get_metrics().await; let final_connection_errors = connection_errors.load(Ordering::Relaxed); tracing::info!("Connection pool stress test results:"); tracing::info!(" Total requests: {}", final_metrics.total_requests); tracing::info!( " Success rate: {:.2}%", final_metrics.success_rate() * 100.0 ); tracing::info!(" Connection rejections: {}", final_connection_errors); tracing::info!(" Throughput: {:.2} RPS", final_metrics.throughput_rps()); // Should handle connection pool pressure gracefully assert!( final_metrics.success_rate() > 0.80, "Should maintain > 80% success rate under stress" ); assert!( final_connection_errors < config.max_concurrent_requests / 2, "Connection errors should be < 50% of requests" ); test_env .teardown() .await .expect("Failed to teardown test environment"); } fn get_memory_usage_mb() -> f64 { // In a real implementation, this would use system APIs to get actual memory usage // For testing, we'll simulate with a simple estimation use std::alloc::{GlobalAlloc, Layout, System}; // This is a simplified approximation // In production, you'd use platform-specific APIs or libraries like `sysinfo` // Estimate based on allocator (very rough approximation) static mut ALLOCATED: usize = 0; // SAFETY: Allocator operations use valid layout with correct alignment and size unsafe { // This is just for demonstration - real memory tracking would be more sophisticated let layout = Layout::from_size_align(1024, 8).unwrap(); let ptr = System.alloc(layout); if !ptr.is_null() { ALLOCATED += 1024; System.dealloc(ptr, layout); } (ALLOCATED as f64) / (1024.0 * 1024.0) + 50.0 // Base memory usage } } } /// Backtesting performance tests #[cfg(test)] mod backtesting_performance_tests { use super::*; #[tokio::test] async fn test_concurrent_backtest_execution() { let config = PerformanceTestConfig { max_concurrent_requests: 10, // Backtests are resource-intensive test_duration: Duration::from_secs(30), ..Default::default() }; let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let backtesting_client = test_env .backtesting_client .as_ref() .expect("Backtesting client not available"); tracing::info!("Starting concurrent backtest execution test"); let start_time = Instant::now(); let mut handles = Vec::new(); let completed_backtests = Arc::new(AtomicUsize::new(0)); // Create multiple concurrent backtests for i in 0..config.max_concurrent_requests { let client = backtesting_client.clone(); let completed_counter = completed_backtests.clone(); let handle = tokio::spawn(async move { let backtest_request = CreateBacktestRequest { name: format!("Concurrent Backtest {}", i), strategy_id: "performance_test_strategy".to_string(), start_date: "2024-01-01".to_string(), end_date: "2024-01-31".to_string(), initial_capital: 100000.0, symbols: vec![ format!("SYM{:02}", i % 10), // Distribute across symbols ], parameters: std::collections::HashMap::from([ ("param1".to_string(), format!("{}", i * 10)), ("param2".to_string(), "test_value".to_string()), ]), }; // Create backtest match client.create_backtest(backtest_request).await { Ok(create_response) => { if create_response.success { let backtest_id = create_response.backtest_id; // Start backtest let start_request = StartBacktestRequest { backtest_id: backtest_id.clone(), async_execution: true, }; if let Ok(start_response) = client.start_backtest(start_request).await { if start_response.success { // Monitor until completion (simplified) let mut checks = 0; while checks < 20 { let status_request = GetBacktestStatusRequest { backtest_id: backtest_id.clone(), }; if let Ok(status_response) = client.get_backtest_status(status_request).await { if status_response.status == BacktestStatus::Completed as i32 { completed_counter.fetch_add(1, Ordering::Relaxed); break; } } checks += 1; sleep(Duration::from_millis(100)).await; } } } } } Err(e) => { tracing::warn!("Backtest creation failed: {:?}", e); } } }); handles.push(handle); sleep(Duration::from_millis(100)).await; // Stagger backtest creation } // Wait for all backtests to complete or timeout let completion_timeout = timeout(Duration::from_secs(60), join_all(handles)).await; let final_completed = completed_backtests.load(Ordering::Relaxed); let duration = start_time.elapsed(); tracing::info!("Concurrent backtest test results:"); tracing::info!(" Backtests started: {}", config.max_concurrent_requests); tracing::info!(" Backtests completed: {}", final_completed); tracing::info!( " Completion rate: {:.1}%", (final_completed as f64 / config.max_concurrent_requests as f64) * 100.0 ); tracing::info!(" Total duration: {:?}", duration); // Should complete at least some backtests assert!(final_completed > 0, "Should complete at least one backtest"); assert!(completion_timeout.is_ok(), "Should not timeout"); test_env .teardown() .await .expect("Failed to teardown test environment"); } #[tokio::test] async fn test_large_dataset_backtest_performance() { let config = PerformanceTestConfig::default(); let mut test_env = PerformanceTestEnvironment::new(config.clone()); test_env .setup() .await .expect("Failed to setup test environment"); let backtesting_client = test_env .backtesting_client .as_ref() .expect("Backtesting client not available"); tracing::info!("Starting large dataset backtest performance test"); // Create backtest with large dataset let backtest_request = CreateBacktestRequest { name: "Large Dataset Performance Test".to_string(), strategy_id: "performance_test_strategy".to_string(), start_date: "2023-01-01".to_string(), end_date: "2024-12-31".to_string(), // 2 years of data initial_capital: 1000000.0, symbols: vec![ "AAPL".to_string(), "GOOGL".to_string(), "MSFT".to_string(), "TSLA".to_string(), "AMZN".to_string(), "META".to_string(), "NVDA".to_string(), "NFLX".to_string(), "AMD".to_string(), "CRM".to_string(), // 10 symbols for comprehensive test ], parameters: std::collections::HashMap::from([ ("lookback_days".to_string(), "252".to_string()), // 1 year lookback ("rebalance_freq".to_string(), "weekly".to_string()), ]), }; let creation_start = Instant::now(); let create_response = backtesting_client .create_backtest(backtest_request) .await .expect("Failed to create large backtest"); assert!( create_response.success, "Large backtest creation should succeed" ); let creation_time = creation_start.elapsed(); let backtest_id = create_response.backtest_id; // Start backtest execution let start_request = StartBacktestRequest { backtest_id: backtest_id.clone(), async_execution: true, }; let execution_start = Instant::now(); let start_response = backtesting_client .start_backtest(start_request) .await .expect("Failed to start large backtest"); assert!( start_response.success, "Large backtest start should succeed" ); // Monitor execution progress let mut progress_checks = 0; let max_progress_checks = 100; // Extended timeout for large dataset let mut last_progress = 0.0; while progress_checks < max_progress_checks { let status_request = GetBacktestStatusRequest { backtest_id: backtest_id.clone(), }; match backtesting_client.get_backtest_status(status_request).await { Ok(status_response) => { let progress = status_response.progress_percentage; if progress > last_progress { tracing::info!("Backtest progress: {:.1}%", progress); last_progress = progress; } if status_response.status == BacktestStatus::Completed as i32 { break; } if status_response.status == BacktestStatus::Failed as i32 { panic!( "Large backtest failed: {}", status_response.error_message.unwrap_or_default() ); } } Err(e) => { tracing::warn!("Status check failed: {:?}", e); } } progress_checks += 1; sleep(Duration::from_millis(200)).await; } let execution_time = execution_start.elapsed(); tracing::info!("Large dataset backtest performance results:"); tracing::info!(" Creation time: {:?}", creation_time); tracing::info!(" Execution time: {:?}", execution_time); tracing::info!(" Progress checks: {}", progress_checks); // Performance expectations for large dataset assert!( creation_time < Duration::from_secs(10), "Creation should be < 10 seconds" ); assert!( execution_time < Duration::from_secs(120), "Execution should be < 2 minutes for test" ); assert!( progress_checks < max_progress_checks, "Should complete within timeout" ); test_env .teardown() .await .expect("Failed to teardown test environment"); } }