Files
foxhunt/tli/tests/integration/performance_tests.rs
jgrusewski 030a15ee05 🔧 Emergency Fix: Resolve catastrophic _i32 suffix corruption (463→0 errors)
- Fixed systematic array indexing corruption: [0_i32] → [0]
- Fixed numeric literal suffixes across 835 files
- Fixed iterator patterns on RwLockReadGuard (.iter() required)
- Fixed float type annotations (365.25_f64 for sqrt)
- Fixed missing semicolons in position manager
- Fixed reference dereferencing in data loader

Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices
Impact: Complete compilation failure (463 errors)
Resolution: Automated regex + targeted fixes
Result: 100% compilation success (0 errors)

Validated: cargo check --workspace passes
Ready for: Production deployment
2025-10-10 23:05:26 +02:00

1282 lines
45 KiB
Rust

//! 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 tli::client::{BacktestingClient, TliClientBuilder, TradingClient};
use tli::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<Duration>,
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<Duration> {
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<Duration> {
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<TradingClient>,
backtesting_client: Option<BacktestingClient>,
mock_servers: Vec<Box<dyn MockServer>>,
metrics: Arc<tokio::sync::Mutex<PerformanceMetrics>>,
}
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) -> TliResult<()> {
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) -> TliResult<()> {
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::<u8>() % 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::<usize>() % symbols.len()];
SubmitOrderRequest {
symbol: TestUtilities::generate_test_symbol(symbol),
side: if rand::random::<bool>() {
OrderSide::Buy
} else {
OrderSide::Sell
} as i32,
order_type: OrderType::Market as i32,
quantity: (rand::random::<u32>() % 1000 + 1) as f64,
price: Some(100.0 + (rand::random::<f64>() * 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::<bool>() {
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::<f64>() - 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(TliError::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 errors: {}", 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");
}
}