Files
foxhunt/testing/integration/performance/hft_benchmarks.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

989 lines
37 KiB
Rust

//! Performance Tests for Foxhunt HFT Trading System
//!
//! This module tests that the system meets strict High-Frequency Trading (HFT)
//! performance requirements. All tests validate sub-50μs latency targets and
//! ensure the system can handle the throughput demands of live trading.
//!
//! # Performance Test Coverage
//!
//! - **Latency Validation** (Sub-50μs end-to-end trading paths)
//! - **Throughput Testing** (Orders per second, market data processing)
//! - **Memory Performance** (Allocation patterns, cache efficiency)
//! - **CPU Utilization** (Core affinity, SIMD optimization)
//! - **Network Performance** (Market data ingestion, order routing)
//! - **Database Performance** (Position updates, trade recording)
//! - **ML Inference Speed** (Model prediction latency)
//! - **Concurrent Performance** (Multi-threaded safety and speed)
//!
//! # Test Philosophy
//!
//! Performance tests are designed to validate that the system meets production
//! HFT requirements under various load conditions. They measure actual latency
//! and throughput rather than relying on theoretical calculations.
// anyhow not available - using simple Result type
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
use std::time::{Duration, Instant};
use std::sync::{Arc, atomic::{AtomicU64, AtomicUsize, Ordering}};
use std::collections::HashMap;
use tokio::time::timeout;
// Import unified types
// Import risk and ML systems
// use risk::prelude::*; // REMOVED - prelude does not exist
use ml::prelude::*;
// Import common test utilities
use crate::common::{*, test_config::*, test_utils::*, assertions::*};
use common::*;
use common::test_config::*;
use common::test_utils::*;
use common::assertions::*;
/// Performance test configuration
#[derive(Debug, Clone)]
struct PerformanceTestConfig {
/// Maximum allowed latency for critical operations (microseconds)
max_critical_latency_us: u64,
/// Maximum allowed latency for non-critical operations (microseconds)
max_standard_latency_us: u64,
/// Target throughput (operations per second)
target_throughput_ops: u64,
/// Test duration for sustained load testing
sustained_test_duration_ms: u64,
/// Number of concurrent operations for load testing
concurrent_operations: usize,
/// Enable CPU-intensive optimizations testing
test_simd_optimizations: bool,
/// Enable memory performance testing
test_memory_performance: bool,
/// Enable network simulation testing
test_network_performance: bool,
/// Sample size for statistical measurements
measurement_samples: usize,
}
impl Default for PerformanceTestConfig {
fn default() -> Self {
Self {
max_critical_latency_us: 50, // HFT requirement: sub-50μs
max_standard_latency_us: 100, // Non-critical: sub-100μs
target_throughput_ops: 10000, // 10k ops/sec target
sustained_test_duration_ms: 5000, // 5 second sustained tests
concurrent_operations: 100, // 100 concurrent operations
test_simd_optimizations: true,
test_memory_performance: true,
test_network_performance: false, // Disabled by default (requires network setup)
measurement_samples: 1000, // 1000 samples for statistics
}
}
}
/// Performance measurement result
#[derive(Debug, Clone)]
struct PerformanceMeasurement {
operation_name: String,
samples: Vec<Duration>,
min_latency: Duration,
max_latency: Duration,
avg_latency: Duration,
p50_latency: Duration,
p95_latency: Duration,
p99_latency: Duration,
throughput_ops_per_sec: f64,
success_rate: f64,
memory_allocations: u64,
cpu_utilization: f64,
}
impl PerformanceMeasurement {
fn new(operation_name: String, mut samples: Vec<Duration>) -> Self {
samples.sort();
let len = samples.len();
let min_latency = samples.first().copied().unwrap_or(Duration::ZERO);
let max_latency = samples.last().copied().unwrap_or(Duration::ZERO);
let avg_latency = if !samples.is_empty() {
let total: Duration = samples.iter().sum();
total / len as u32
} else {
Duration::ZERO
};
let p50_latency = samples.get(len / 2).copied().unwrap_or(Duration::ZERO);
let p95_latency = samples.get((len * 95) / 100).copied().unwrap_or(Duration::ZERO);
let p99_latency = samples.get((len * 99) / 100).copied().unwrap_or(Duration::ZERO);
let throughput_ops_per_sec = if avg_latency.as_secs_f64() > 0.0 {
1.0 / avg_latency.as_secs_f64()
} else {
0.0
};
Self {
operation_name,
samples,
min_latency,
max_latency,
avg_latency,
p50_latency,
p95_latency,
p99_latency,
throughput_ops_per_sec,
success_rate: 1.0, // Will be updated based on actual results
memory_allocations: 0, // Placeholder
cpu_utilization: 0.0, // Placeholder
}
}
fn meets_latency_requirement(&self, max_latency_us: u64) -> bool {
self.p99_latency.as_micros() <= max_latency_us as u128
}
fn meets_throughput_requirement(&self, min_throughput: f64) -> bool {
self.throughput_ops_per_sec >= min_throughput
}
}
/// Performance test suite
struct PerformanceTestSuite {
config: PerformanceTestConfig,
risk_engine: Option<RiskEngine>,
ml_registry: Option<Arc<ModelRegistry>>,
measurements: HashMap<String, PerformanceMeasurement>,
total_operations: Arc<AtomicU64>,
successful_operations: Arc<AtomicU64>,
failed_operations: Arc<AtomicU64>,
}
impl PerformanceTestSuite {
fn new() -> Self {
setup_test_tracing();
Self {
config: PerformanceTestConfig::default(),
risk_engine: None,
ml_registry: None,
measurements: HashMap::new(),
total_operations: Arc::new(AtomicU64::new(0)),
successful_operations: Arc::new(AtomicU64::new(0)),
failed_operations: Arc::new(AtomicU64::new(0)),
}
}
async fn setup(&mut self) -> Result<()> {
// Initialize components for performance testing
let risk_config = RiskConfig {
max_position_size: Price::from_f64(100000.0)?,
max_daily_loss: Price::from_f64(10000.0)?,
var_confidence_level: 0.95,
var_lookback_days: 252,
enable_kill_switch: false, // Disabled for performance testing
enable_circuit_breakers: false, // Disabled for clean measurements
redis_url: "redis://localhost:6379".to_string(),
};
// Initialize risk engine (may fail if Redis not available)
match RiskEngine::new(risk_config).await {
Ok(engine) => self.risk_engine = Some(engine),
Err(_) => tracing::warn!("Risk engine unavailable for performance testing"),
}
// Initialize ML registry
let registry = get_global_registry();
// Try to register models for performance testing
if let Ok(tlob_model) = ml::model_factory::create_tlob_wrapper() {
let _ = registry.register(Arc::from(tlob_model)).await;
}
if let Ok(dqn_model) = ml::model_factory::create_dqn_wrapper() {
let _ = registry.register(Arc::from(dqn_model)).await;
}
self.ml_registry = Some(registry);
Ok(())
}
/// Record operation metrics
fn record_operation(&self, success: bool) {
self.total_operations.fetch_add(1, Ordering::SeqCst);
if success {
self.successful_operations.fetch_add(1, Ordering::SeqCst);
} else {
self.failed_operations.fetch_add(1, Ordering::SeqCst);
}
}
/// Measure operation latency
async fn measure_operation<F, R>(&self, operation_name: &str, operation: F) -> Result<(R, Duration)>
where
F: std::future::Future<Output = Result<R>>,
{
let start_time = Instant::now();
let result = operation.await;
let latency = start_time.elapsed();
self.record_operation(result.is_ok());
Ok((result?, latency))
}
/// Measure multiple samples of an operation
async fn measure_operation_samples<F, Fut>(&self, operation_name: &str, operation_factory: F, samples: usize) -> Result<PerformanceMeasurement>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<()>>,
{
let mut latencies = Vec::with_capacity(samples);
let mut success_count = 0;
for _ in 0..samples {
let start_time = Instant::now();
let result = operation_factory().await;
let latency = start_time.elapsed();
latencies.push(latency);
if result.is_ok() {
success_count += 1;
}
self.record_operation(result.is_ok());
}
let mut measurement = PerformanceMeasurement::new(operation_name.to_string(), latencies);
measurement.success_rate = success_count as f64 / samples as f64;
Ok(measurement)
}
/// Create test market data for performance testing
fn create_performance_market_data(&self, index: usize) -> Result<TestMarketData> {
TestMarketData::new(
&format!("PERF_SYMBOL_{}", index % 100), // Cycle through 100 symbols
50000.0 + (index as f64 % 1000.0), // Varying prices
1000.0 + (index as f64 % 500.0), // Varying volumes
)
}
/// Test order processing latency
async fn test_order_processing_latency(&self) -> Result<PerformanceMeasurement> {
let measurement = self.measure_operation_samples(
"order_processing",
|| async {
// Create test order
let symbol = Symbol::from("PERF_BTC");
let quantity = Quantity::from_f64(1.0)?;
let price = Price::from_f64(50000.0)?;
let order = Order::limit(symbol, Side::Buy, quantity, price);
// Simulate order validation
if order.quantity.to_f64() > 0.0 && order.symbol.as_str().len() > 0 {
Ok(())
} else {
Err(anyhow::anyhow!("Invalid order"))
}
},
self.config.measurement_samples,
).await?;
Ok(measurement)
}
/// Test risk calculation latency
async fn test_risk_calculation_latency(&self) -> Result<PerformanceMeasurement> {
let measurement = self.measure_operation_samples(
"risk_calculation",
|| async {
// Create order info for risk calculation
let order_info = OrderInfo {
symbol: Symbol::from("RISK_TEST"),
side: Side::Buy,
quantity: Quantity::from_f64(100.0)?,
price: Price::from_f64(50000.0)?,
};
// Test risk calculation with or without risk engine
if let Some(ref risk_engine) = self.risk_engine {
match risk_engine.validate_order(&order_info).await {
Ok(_) => Ok(()),
Err(_) => Ok(()), // Risk rejection is valid for performance test
}
} else {
// Fallback risk calculation
let order_value = order_info.quantity.to_f64() * order_info.price.to_f64();
if order_value < 100000.0 { // Simple limit check
Ok(())
} else {
Err(anyhow::anyhow!("Position limit exceeded"))
}
}
},
self.config.measurement_samples,
).await?;
Ok(measurement)
}
/// Test ML inference latency
async fn test_ml_inference_latency(&self) -> Result<PerformanceMeasurement> {
let measurement = self.measure_operation_samples(
"ml_inference",
|| async {
if let Some(ref registry) = self.ml_registry {
// Create test features
let features = Features::new(
vec![50000.0, 1000.0, 49999.0, 50001.0, 2.0, 1234567890.0],
vec!["price".to_string(), "volume".to_string(), "bid".to_string(),
"ask".to_string(), "spread".to_string(), "timestamp".to_string()],
);
// Test prediction with all available models
let predictions = registry.predict_all(&features).await;
// Consider it successful if at least one model responds
let success_count = predictions.iter().filter(|p| p.is_ok()).count();
if success_count > 0 {
Ok(())
} else {
Err(anyhow::anyhow!("No successful ML predictions"))
}
} else {
// Simulate ML inference without models
let input_sum: f64 = vec![50000.0, 1000.0, 49999.0, 50001.0, 2.0]
.iter().sum();
let _prediction = input_sum / 5.0; // Simple average
Ok(())
}
},
self.config.measurement_samples,
).await?;
Ok(measurement)
}
/// Test memory allocation performance
async fn test_memory_performance(&self) -> Result<PerformanceMeasurement> {
let measurement = self.measure_operation_samples(
"memory_allocation",
|| async {
// Test various memory allocation patterns
// 1. Small allocations (typical for trading data)
let mut small_vec: Vec<f64> = Vec::with_capacity(10);
for i in 0..10 {
small_vec.push(i as f64);
}
// 2. Medium allocations (order book data)
let mut medium_vec: Vec<Price> = Vec::with_capacity(100);
for i in 0..100 {
medium_vec.push(Price::from_f64(50000.0 + i as f64)?);
}
// 3. HashMap operations (symbol lookups)
let mut symbol_map: HashMap<String, f64> = HashMap::new();
for i in 0..50 {
symbol_map.insert(format!("SYMBOL_{}", i), i as f64);
}
// 4. String operations (logging, serialization)
let log_message = format!("Trade executed: {} shares at ${:.2}",
small_vec.len(), medium_vec.len() as f64);
// Verify allocations were successful
if !small_vec.is_empty() && !medium_vec.is_empty() &&
!symbol_map.is_empty() && !log_message.is_empty() {
Ok(())
} else {
Err(anyhow::anyhow!("Memory allocation failed"))
}
},
self.config.measurement_samples,
).await?;
Ok(measurement)
}
/// Test concurrent performance
async fn test_concurrent_performance(&self) -> Result<PerformanceMeasurement> {
let start_time = Instant::now();
let mut latencies = Vec::new();
let concurrent_ops = self.config.concurrent_operations;
// Create concurrent tasks
let mut tasks = Vec::with_capacity(concurrent_ops);
let total_ops = Arc::new(AtomicU64::new(0));
let successful_ops = Arc::new(AtomicU64::new(0));
for i in 0..concurrent_ops {
let total_ops_clone = Arc::clone(&total_ops);
let successful_ops_clone = Arc::clone(&successful_ops);
let task = tokio::spawn(async move {
let task_start = Instant::now();
// Simulate concurrent trading operations
let symbol = format!("CONCURRENT_{}", i % 10);
let quantity = 100.0 + (i as f64 % 900.0);
let price = 50000.0 + (i as f64 % 1000.0);
// Create order
let order_result = Order::limit(
Symbol::from(symbol.as_str()),
if i % 2 == 0 { Side::Buy } else { Side::Sell },
Quantity::from_f64(quantity)?,
Price::from_f64(price)?,
);
total_ops_clone.fetch_add(1, Ordering::SeqCst);
// Simulate order processing
tokio::time::sleep(Duration::from_micros(10)).await;
let task_latency = task_start.elapsed();
successful_ops_clone.fetch_add(1, Ordering::SeqCst);
Ok::<Duration, anyhow::Error>(task_latency)
});
tasks.push(task);
}
// Wait for all tasks and collect latencies
let results = futures::future::join_all(tasks).await;
for result in results {
match result {
Ok(Ok(latency)) => latencies.push(latency),
Ok(Err(_)) => {}, // Task failed
Err(_) => {}, // Task panicked
}
}
let total_time = start_time.elapsed();
let successful_count = successful_ops.load(Ordering::SeqCst);
let mut measurement = PerformanceMeasurement::new("concurrent_operations".to_string(), latencies);
measurement.success_rate = successful_count as f64 / concurrent_ops as f64;
measurement.throughput_ops_per_sec = successful_count as f64 / total_time.as_secs_f64();
Ok(measurement)
}
/// Test sustained throughput
async fn test_sustained_throughput(&self) -> Result<PerformanceMeasurement> {
let test_duration = Duration::from_millis(self.config.sustained_test_duration_ms);
let start_time = Instant::now();
let mut operation_count = 0;
let mut latencies = Vec::new();
while start_time.elapsed() < test_duration {
let op_start = Instant::now();
// Perform a representative trading operation
let market_data = self.create_performance_market_data(operation_count)?;
// Simulate signal generation
let signal_strength = (market_data.price % 100.0) / 100.0;
let side = if signal_strength > 0.5 { Side::Buy } else { Side::Sell };
// Create order
let _order = Order::market(
market_data.symbol,
side,
Quantity::from_f64(signal_strength * 100.0)?,
);
let op_latency = op_start.elapsed();
latencies.push(op_latency);
operation_count += 1;
// Small delay to prevent CPU saturation
if operation_count % 100 == 0 {
tokio::task::yield_now().await;
}
}
let total_time = start_time.elapsed();
let ops_per_second = operation_count as f64 / total_time.as_secs_f64();
let mut measurement = PerformanceMeasurement::new("sustained_throughput".to_string(), latencies);
measurement.throughput_ops_per_sec = ops_per_second;
measurement.success_rate = 1.0; // All operations completed
Ok(measurement)
}
/// Store measurement result
fn store_measurement(&mut self, measurement: PerformanceMeasurement) {
self.measurements.insert(measurement.operation_name.clone(), measurement);
}
/// Get performance summary
fn get_performance_summary(&self) -> PerformanceSummary {
let total_ops = self.total_operations.load(Ordering::SeqCst);
let successful_ops = self.successful_operations.load(Ordering::SeqCst);
let failed_ops = self.failed_operations.load(Ordering::SeqCst);
PerformanceSummary {
total_operations: total_ops,
successful_operations: successful_ops,
failed_operations: failed_ops,
overall_success_rate: if total_ops > 0 {
successful_ops as f64 / total_ops as f64
} else {
0.0
},
measurements: self.measurements.clone(),
}
}
}
/// Performance test summary
#[derive(Debug, Clone)]
struct PerformanceSummary {
total_operations: u64,
successful_operations: u64,
failed_operations: u64,
overall_success_rate: f64,
measurements: HashMap<String, PerformanceMeasurement>,
}
impl PerformanceSummary {
fn meets_hft_requirements(&self, config: &PerformanceTestConfig) -> bool {
for measurement in self.measurements.values() {
if !measurement.meets_latency_requirement(config.max_critical_latency_us) {
return false;
}
}
self.overall_success_rate >= 0.95 // 95% success rate requirement
}
fn log_summary(&self) {
tracing::info!("=== PERFORMANCE TEST SUMMARY ===");
tracing::info!("Total operations: {}", self.total_operations);
tracing::info!("Successful: {}, Failed: {}", self.successful_operations, self.failed_operations);
tracing::info!("Overall success rate: {:.2}%", self.overall_success_rate * 100.0);
for measurement in self.measurements.values() {
tracing::info!("--- {} ---", measurement.operation_name);
tracing::info!(" Avg latency: {}μs", measurement.avg_latency.as_micros());
tracing::info!(" P95 latency: {}μs", measurement.p95_latency.as_micros());
tracing::info!(" P99 latency: {}μs", measurement.p99_latency.as_micros());
tracing::info!(" Throughput: {:.0} ops/sec", measurement.throughput_ops_per_sec);
tracing::info!(" Success rate: {:.2}%", measurement.success_rate * 100.0);
}
}
}
/// Test market data structure for performance testing
#[derive(Debug, Clone)]
struct TestMarketData {
symbol: Symbol,
price: f64,
volume: f64,
timestamp: u64,
}
impl TestMarketData {
fn new(symbol: &str, price: f64, volume: f64) -> Result<Self> {
Ok(Self {
symbol: Symbol::from(symbol),
price,
volume,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_micros() as u64,
})
}
}
// ========== PERFORMANCE TESTS ==========
#[tokio::test]
async fn test_order_processing_performance() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
let measurement = test_suite.test_order_processing_latency().await?;
test_suite.store_measurement(measurement.clone());
// Validate latency requirements
assert!(measurement.meets_latency_requirement(test_suite.config.max_critical_latency_us),
"Order processing P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_critical_latency_us);
// Validate success rate
assert!(measurement.success_rate >= 0.95,
"Order processing success rate {:.2}% below 95% requirement",
measurement.success_rate * 100.0);
Ok(())
}
#[tokio::test]
async fn test_risk_calculation_performance() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
let measurement = test_suite.test_risk_calculation_latency().await?;
test_suite.store_measurement(measurement.clone());
// Validate latency requirements
assert!(measurement.meets_latency_requirement(test_suite.config.max_critical_latency_us),
"Risk calculation P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_critical_latency_us);
// Log performance metrics
tracing::info!("Risk calculation performance:");
tracing::info!(" Average latency: {}μs", measurement.avg_latency.as_micros());
tracing::info!(" P99 latency: {}μs", measurement.p99_latency.as_micros());
tracing::info!(" Success rate: {:.2}%", measurement.success_rate * 100.0);
Ok(())
}
#[tokio::test]
async fn test_ml_inference_performance() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
let measurement = test_suite.test_ml_inference_latency().await?;
test_suite.store_measurement(measurement.clone());
// ML inference may have slightly higher latency tolerance
assert!(measurement.meets_latency_requirement(test_suite.config.max_standard_latency_us),
"ML inference P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_standard_latency_us);
// Validate that ML models are responsive
assert!(measurement.success_rate > 0.0,
"ML inference should have some successful predictions");
Ok(())
}
#[tokio::test]
async fn test_memory_allocation_performance() -> Result<()> {
if !PerformanceTestConfig::default().test_memory_performance {
return Ok(());
}
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
let measurement = test_suite.test_memory_performance().await?;
test_suite.store_measurement(measurement.clone());
// Memory operations should be very fast
assert!(measurement.meets_latency_requirement(test_suite.config.max_critical_latency_us),
"Memory allocation P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_critical_latency_us);
// All memory operations should succeed
assert_eq!(measurement.success_rate, 1.0,
"Memory allocation success rate should be 100%");
Ok(())
}
#[tokio::test]
async fn test_concurrent_operation_performance() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
let measurement = test_suite.test_concurrent_performance().await?;
test_suite.store_measurement(measurement.clone());
// Concurrent operations may have slightly higher latency
assert!(measurement.meets_latency_requirement(test_suite.config.max_standard_latency_us),
"Concurrent operations P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_standard_latency_us);
// Validate high success rate under concurrency
assert!(measurement.success_rate >= 0.90,
"Concurrent operations success rate {:.2}% below 90% requirement",
measurement.success_rate * 100.0);
// Validate throughput
assert!(measurement.throughput_ops_per_sec >= 1000.0,
"Concurrent throughput {:.0} ops/sec below 1000 ops/sec requirement",
measurement.throughput_ops_per_sec);
Ok(())
}
#[tokio::test]
async fn test_sustained_throughput_performance() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
let measurement = test_suite.test_sustained_throughput().await?;
test_suite.store_measurement(measurement.clone());
// Validate sustained throughput meets requirements
assert!(measurement.throughput_ops_per_sec >= test_suite.config.target_throughput_ops as f64,
"Sustained throughput {:.0} ops/sec below target {} ops/sec",
measurement.throughput_ops_per_sec, test_suite.config.target_throughput_ops);
// Validate latency remains acceptable under sustained load
assert!(measurement.meets_latency_requirement(test_suite.config.max_standard_latency_us),
"Sustained operations P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_standard_latency_us);
tracing::info!("Sustained throughput test completed: {:.0} ops/sec over {}ms",
measurement.throughput_ops_per_sec, test_suite.config.sustained_test_duration_ms);
Ok(())
}
#[tokio::test]
async fn test_end_to_end_trading_performance() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
// Test complete trading pipeline performance
let measurement = test_suite.measure_operation_samples(
"end_to_end_trading",
|| async {
// 1. Market data processing
let market_data = test_suite.create_performance_market_data(0)?;
// 2. Signal generation (ML inference)
let features = Features::new(
vec![market_data.price, market_data.volume, market_data.timestamp as f64],
vec!["price".to_string(), "volume".to_string(), "timestamp".to_string()],
);
let mut signal_strength = 0.5; // Default signal
if let Some(ref registry) = test_suite.ml_registry {
let predictions = registry.predict_all(&features).await;
if let Some(Ok(first_prediction)) = predictions.into_iter().next() {
signal_strength = (first_prediction.value + 1.0) / 2.0; // Normalize to 0-1
}
}
// 3. Risk validation
let order_info = OrderInfo {
symbol: market_data.symbol.clone(),
side: if signal_strength > 0.5 { Side::Buy } else { Side::Sell },
quantity: Quantity::from_f64(signal_strength * 100.0)?,
price: Price::from_f64(market_data.price)?,
};
let risk_approved = if let Some(ref risk_engine) = test_suite.risk_engine {
risk_engine.validate_order(&order_info).await.unwrap_or_else(|_| RiskCheckResult {
approved: false,
risk_score: 1.0,
violations: Vec::new(),
metadata: HashMap::new(),
}).approved
} else {
// Simple risk check
order_info.quantity.to_f64() * order_info.price.to_f64() < 10000.0
};
// 4. Order creation and submission
if risk_approved {
let _order = Order::limit(
order_info.symbol,
order_info.side,
order_info.quantity,
order_info.price,
);
Ok(())
} else {
// Risk rejection is a valid outcome
Ok(())
}
},
test_suite.config.measurement_samples / 2, // Fewer samples for complex operation
).await?;
test_suite.store_measurement(measurement.clone());
// End-to-end should meet critical latency requirements
assert!(measurement.meets_latency_requirement(test_suite.config.max_critical_latency_us),
"End-to-end trading P99 latency {}μs exceeds requirement {}μs",
measurement.p99_latency.as_micros(), test_suite.config.max_critical_latency_us);
Ok(())
}
#[tokio::test]
async fn test_system_resource_utilization() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
// Test resource utilization under load
let start_time = Instant::now();
let initial_memory = get_approximate_memory_usage();
// Perform operations that stress different system resources
let operations = vec![
test_suite.test_order_processing_latency(),
test_suite.test_risk_calculation_latency(),
test_suite.test_ml_inference_latency(),
test_suite.test_memory_performance(),
];
// Run all operations concurrently
let (order_result, risk_result, ml_result, memory_result) =
futures::future::try_join4(
operations[0],
operations[1],
operations[2],
operations[3],
).await?;
let total_time = start_time.elapsed();
let final_memory = get_approximate_memory_usage();
// Store all measurements
test_suite.store_measurement(order_result);
test_suite.store_measurement(risk_result);
test_suite.store_measurement(ml_result);
test_suite.store_measurement(memory_result);
// Validate resource usage
let memory_growth = final_memory.saturating_sub(initial_memory);
assert!(memory_growth < 100 * 1024 * 1024, // 100MB limit
"Memory usage grew by {}MB, exceeding 100MB limit", memory_growth / (1024 * 1024));
// Validate total execution time
assert!(total_time.as_secs() < 30,
"Resource utilization test took {}s, exceeding 30s limit", total_time.as_secs());
tracing::info!("Resource utilization test completed in {}ms with {}MB memory growth",
total_time.as_millis(), memory_growth / (1024 * 1024));
Ok(())
}
#[tokio::test]
async fn test_comprehensive_performance_validation() -> Result<()> {
let mut test_suite = PerformanceTestSuite::new();
test_suite.setup().await?;
// Run comprehensive performance test suite
let tests = vec![
("order_processing", test_suite.test_order_processing_latency()),
("risk_calculation", test_suite.test_risk_calculation_latency()),
("ml_inference", test_suite.test_ml_inference_latency()),
("concurrent_ops", test_suite.test_concurrent_performance()),
("sustained_throughput", test_suite.test_sustained_throughput()),
];
for (test_name, test_future) in tests {
let measurement = test_future.await?;
test_suite.store_measurement(measurement);
tracing::info!("Completed performance test: {}", test_name);
}
// Generate comprehensive summary
let summary = test_suite.get_performance_summary();
summary.log_summary();
// Validate overall HFT requirements
assert!(summary.meets_hft_requirements(&test_suite.config),
"System does not meet HFT performance requirements");
// Validate individual critical operations
for (operation_name, measurement) in &summary.measurements {
if operation_name.contains("order") || operation_name.contains("risk") {
assert!(measurement.meets_latency_requirement(test_suite.config.max_critical_latency_us),
"Critical operation '{}' P99 latency {}μs exceeds {}μs requirement",
operation_name, measurement.p99_latency.as_micros(), test_suite.config.max_critical_latency_us);
}
}
tracing::info!("🎉 All performance tests passed! System meets HFT requirements.");
Ok(())
}
// ========== UTILITY FUNCTIONS ==========
/// Approximate memory usage (placeholder implementation)
fn get_approximate_memory_usage() -> usize {
// This is a placeholder - in a real implementation you'd use system APIs
// to get actual memory usage
std::process::id() as usize * 1024 // Rough approximation
}
/// Create performance test dataset
fn create_performance_dataset(size: usize) -> Result<Vec<TestMarketData>> {
let mut dataset = Vec::with_capacity(size);
for i in 0..size {
dataset.push(TestMarketData::new(
&format!("PERF_{}", i % 100),
50000.0 + (i as f64 % 1000.0),
1000.0 + (i as f64 % 500.0),
)?);
}
Ok(dataset)
}
/// Validate performance requirements for production deployment
fn validate_production_readiness(summary: &PerformanceSummary) -> Result<()> {
// Critical requirements for production deployment
let requirements = vec![
("Overall success rate", summary.overall_success_rate >= 0.99),
("Total operations", summary.total_operations >= 1000),
];
for (requirement, passes) in requirements {
if !passes {
return Err(anyhow::anyhow!("Production requirement failed: {}", requirement));
}
}
// Validate each measurement meets production standards
for (operation, measurement) in &summary.measurements {
if measurement.p99_latency.as_micros() > 100 {
tracing::warn!("Operation '{}' has high P99 latency: {}μs",
operation, measurement.p99_latency.as_micros());
}
}
Ok(())
}
/// Performance test configuration for different environments
impl PerformanceTestConfig {
fn for_development() -> Self {
Self {
max_critical_latency_us: 100, // Relaxed for development
max_standard_latency_us: 200,
target_throughput_ops: 1000, // Lower target
sustained_test_duration_ms: 2000, // Shorter tests
concurrent_operations: 50, // Fewer concurrent ops
measurement_samples: 100, // Fewer samples
..Default::default()
}
}
fn for_production() -> Self {
Self {
max_critical_latency_us: 50, // Strict production requirement
max_standard_latency_us: 100,
target_throughput_ops: 10000, // Full production target
sustained_test_duration_ms: 10000, // Longer stress tests
concurrent_operations: 200, // High concurrency
measurement_samples: 2000, // More samples for accuracy
..Default::default()
}
}
}