ARCHITECTURAL ACHIEVEMENTS: ✅ Zero compilation errors across entire workspace ✅ Complete elimination of circular dependencies ✅ Proper configuration architecture with centralized config crate ✅ Fixed all type mismatches and missing fields ✅ Restored proper crate structure (config at root level) MAJOR FIXES: - Fixed 19 critical data crate compilation errors - Resolved configuration struct field mismatches - Fixed enum variant naming (CSV → Csv) - Corrected type conversions (FromPrimitive, compression types) - Fixed HashMap key types (u32 vs usize) - Resolved TLOBProcessor constructor issues WORKSPACE STATUS: - All services compile successfully - Trading Service: ✅ Ready - Backtesting Service: ✅ Ready - ML Training Service: ✅ Ready - TLI Client: ✅ Ready Only documentation warnings remain (3,316 warnings to be addressed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
746 lines
28 KiB
Rust
746 lines
28 KiB
Rust
use std::collections::VecDeque;
|
|
use std::sync::Arc;
|
|
use tokio::time::{timeout, Duration};
|
|
use trading_engine::{
|
|
infrastructure::{PerformanceAnalyzer, ResourceMonitor, ThroughputMonitor},
|
|
lockfree::{AtomicCounter, RingBuffer},
|
|
prelude::*,
|
|
simd::SimdProcessor,
|
|
timing::{HardwareTimestamp, LatencyTracker, PrecisionTimer},
|
|
trading::{OrderManager, PositionManager},
|
|
types::{PerformanceMetrics, Price, Quantity, Symbol},
|
|
};
|
|
|
|
/// Comprehensive performance validation and benchmarking tests
|
|
pub struct PerformanceValidationTests {
|
|
latency_tracker: Arc<LatencyTracker>,
|
|
throughput_monitor: Arc<ThroughputMonitor>,
|
|
resource_monitor: Arc<ResourceMonitor>,
|
|
performance_analyzer: Arc<PerformanceAnalyzer>,
|
|
order_manager: Arc<OrderManager>,
|
|
position_manager: Arc<PositionManager>,
|
|
simd_processor: Arc<SimdProcessor>,
|
|
ring_buffer: Arc<RingBuffer<u64>>,
|
|
}
|
|
|
|
impl PerformanceValidationTests {
|
|
pub async fn new() -> Result<Self> {
|
|
let config = load_test_config().await?;
|
|
|
|
let latency_tracker = Arc::new(LatencyTracker::new());
|
|
let throughput_monitor = Arc::new(ThroughputMonitor::new());
|
|
let resource_monitor = Arc::new(ResourceMonitor::new());
|
|
let performance_analyzer = Arc::new(PerformanceAnalyzer::new(config.clone()));
|
|
let order_manager = Arc::new(OrderManager::new(config.clone()).await?);
|
|
let position_manager = Arc::new(PositionManager::new(config.clone()).await?);
|
|
let simd_processor = Arc::new(SimdProcessor::new());
|
|
let ring_buffer = Arc::new(RingBuffer::new(65536)); // 64k entries
|
|
|
|
Ok(Self {
|
|
latency_tracker,
|
|
throughput_monitor,
|
|
resource_monitor,
|
|
performance_analyzer,
|
|
order_manager,
|
|
position_manager,
|
|
simd_processor,
|
|
ring_buffer,
|
|
})
|
|
}
|
|
|
|
/// Test 1: Critical path sub-50μs latency validation
|
|
/// Steps: 12 comprehensive latency measurement phases
|
|
pub async fn test_critical_path_latency_validation(&self) -> Result<WorkflowTestResult> {
|
|
let mut result = WorkflowTestResult::new("Critical Path Latency Validation");
|
|
|
|
// Step 1: Hardware timing calibration
|
|
result.add_step("Hardware Timing Calibration").await;
|
|
let calibration_start = HardwareTimestamp::now();
|
|
let calibration_samples: Vec<u64> = (0..10000)
|
|
.map(|_| {
|
|
let start = HardwareTimestamp::now();
|
|
std::hint::black_box(42); // Prevent optimization
|
|
start.elapsed_nanos()
|
|
})
|
|
.collect();
|
|
|
|
let calibration_time = calibration_start.elapsed_nanos();
|
|
let min_resolution = calibration_samples
|
|
.iter()
|
|
.filter(|&&x| x > 0)
|
|
.min()
|
|
.unwrap_or(&1);
|
|
let avg_resolution =
|
|
calibration_samples.iter().sum::<u64>() as f64 / calibration_samples.len() as f64;
|
|
|
|
assert!(
|
|
*min_resolution <= 50,
|
|
"Hardware timing resolution should be ≤50ns, got {}ns",
|
|
min_resolution
|
|
);
|
|
result.add_metric("hardware_resolution_ns", *min_resolution as f64);
|
|
result.add_metric("avg_resolution_ns", avg_resolution);
|
|
result.add_metric("calibration_time_ns", calibration_time as f64);
|
|
|
|
// Step 2: RDTSC precision measurement
|
|
result.add_step("RDTSC Precision Measurement").await;
|
|
let rdtsc_samples: Vec<u64> = (0..1000)
|
|
.map(|_| {
|
|
let start = HardwareTimestamp::rdtsc_start();
|
|
// Minimal operation to measure
|
|
let _dummy = 1u64.wrapping_add(2);
|
|
HardwareTimestamp::rdtsc_end(start)
|
|
})
|
|
.collect();
|
|
|
|
let rdtsc_p50 = percentile(&rdtsc_samples, 50.0);
|
|
let rdtsc_p95 = percentile(&rdtsc_samples, 95.0);
|
|
let rdtsc_p99 = percentile(&rdtsc_samples, 99.0);
|
|
|
|
assert!(
|
|
rdtsc_p95 <= 100,
|
|
"RDTSC P95 should be ≤100ns, got {}ns",
|
|
rdtsc_p95
|
|
);
|
|
result.add_metric("rdtsc_p50_ns", rdtsc_p50 as f64);
|
|
result.add_metric("rdtsc_p95_ns", rdtsc_p95 as f64);
|
|
result.add_metric("rdtsc_p99_ns", rdtsc_p99 as f64);
|
|
|
|
// Step 3: Order creation latency measurement
|
|
result.add_step("Order Creation Latency").await;
|
|
let order_creation_samples: Vec<u64> = (0..10000)
|
|
.map(|i| {
|
|
let start = HardwareTimestamp::now();
|
|
let _order = Order::new(
|
|
OrderId::from_u64(i as u64),
|
|
Symbol::new("EURUSD"),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
);
|
|
start.elapsed_nanos()
|
|
})
|
|
.collect();
|
|
|
|
let creation_p50 = percentile(&order_creation_samples, 50.0);
|
|
let creation_p95 = percentile(&order_creation_samples, 95.0);
|
|
let creation_p99 = percentile(&order_creation_samples, 99.0);
|
|
|
|
assert!(
|
|
creation_p95 <= 5_000,
|
|
"Order creation P95 should be ≤5μs, got {}ns",
|
|
creation_p95
|
|
);
|
|
result.add_metric("order_creation_p50_ns", creation_p50 as f64);
|
|
result.add_metric("order_creation_p95_ns", creation_p95 as f64);
|
|
|
|
// Step 4: Risk validation latency
|
|
result.add_step("Risk Validation Latency").await;
|
|
let symbol = Symbol::new("EURUSD");
|
|
let test_order = Order::new(
|
|
OrderId::new(),
|
|
symbol.clone(),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
)?;
|
|
|
|
let risk_samples: Vec<u64> = {
|
|
let mut samples = Vec::with_capacity(1000);
|
|
for _ in 0..1000 {
|
|
let start = HardwareTimestamp::now();
|
|
let _risk_result = self.order_manager.validate_risk_fast(&test_order).await;
|
|
let elapsed = start.elapsed_nanos();
|
|
samples.push(elapsed);
|
|
}
|
|
samples
|
|
};
|
|
|
|
let risk_p50 = percentile(&risk_samples, 50.0);
|
|
let risk_p95 = percentile(&risk_samples, 95.0);
|
|
|
|
assert!(
|
|
risk_p95 <= 10_000,
|
|
"Risk validation P95 should be ≤10μs, got {}ns",
|
|
risk_p95
|
|
);
|
|
result.add_metric("risk_validation_p50_ns", risk_p50 as f64);
|
|
result.add_metric("risk_validation_p95_ns", risk_p95 as f64);
|
|
|
|
// Step 5: Position update latency
|
|
result.add_step("Position Update Latency").await;
|
|
let position_samples: Vec<u64> = {
|
|
let mut samples = Vec::with_capacity(1000);
|
|
for i in 0..1000 {
|
|
let start = HardwareTimestamp::now();
|
|
let _result = self
|
|
.position_manager
|
|
.update_position_atomic(&symbol, Quantity::from(i as i64 * 100))
|
|
.await;
|
|
let elapsed = start.elapsed_nanos();
|
|
samples.push(elapsed);
|
|
}
|
|
samples
|
|
};
|
|
|
|
let position_p50 = percentile(&position_samples, 50.0);
|
|
let position_p95 = percentile(&position_samples, 95.0);
|
|
|
|
assert!(
|
|
position_p95 <= 8_000,
|
|
"Position update P95 should be ≤8μs, got {}ns",
|
|
position_p95
|
|
);
|
|
result.add_metric("position_update_p50_ns", position_p50 as f64);
|
|
result.add_metric("position_update_p95_ns", position_p95 as f64);
|
|
|
|
// Step 6: Lock-free data structure performance
|
|
result.add_step("Lock-free Structure Performance").await;
|
|
let lockfree_samples: Vec<u64> = (0..10000)
|
|
.map(|i| {
|
|
let start = HardwareTimestamp::now();
|
|
let success = self.ring_buffer.try_push(i);
|
|
let elapsed = start.elapsed_nanos();
|
|
assert!(success, "Ring buffer push should succeed");
|
|
elapsed
|
|
})
|
|
.collect();
|
|
|
|
let lockfree_p50 = percentile(&lockfree_samples, 50.0);
|
|
let lockfree_p95 = percentile(&lockfree_samples, 95.0);
|
|
|
|
assert!(
|
|
lockfree_p95 <= 500,
|
|
"Lock-free push P95 should be ≤500ns, got {}ns",
|
|
lockfree_p95
|
|
);
|
|
result.add_metric("lockfree_push_p50_ns", lockfree_p50 as f64);
|
|
result.add_metric("lockfree_push_p95_ns", lockfree_p95 as f64);
|
|
|
|
// Step 7: SIMD operation performance
|
|
result.add_step("SIMD Operation Performance").await;
|
|
let simd_data: Vec<f64> = (0..1000).map(|i| i as f64 * 1.1).collect();
|
|
let simd_samples: Vec<u64> = (0..1000)
|
|
.map(|_| {
|
|
let start = HardwareTimestamp::now();
|
|
let _result = self.simd_processor.vectorized_multiply(&simd_data, 2.0);
|
|
start.elapsed_nanos()
|
|
})
|
|
.collect();
|
|
|
|
let simd_p50 = percentile(&simd_samples, 50.0);
|
|
let simd_p95 = percentile(&simd_samples, 95.0);
|
|
|
|
assert!(
|
|
simd_p95 <= 2_000,
|
|
"SIMD operation P95 should be ≤2μs, got {}ns",
|
|
simd_p95
|
|
);
|
|
result.add_metric("simd_operation_p50_ns", simd_p50 as f64);
|
|
result.add_metric("simd_operation_p95_ns", simd_p95 as f64);
|
|
|
|
// Step 8: End-to-end critical path measurement
|
|
result.add_step("End-to-End Critical Path").await;
|
|
let e2e_samples: Vec<u64> = {
|
|
let mut samples = Vec::with_capacity(1000);
|
|
for i in 0..1000 {
|
|
let start = HardwareTimestamp::now();
|
|
|
|
// Critical path: Order creation -> Risk check -> Position update -> Submit
|
|
let order = Order::new(
|
|
OrderId::from_u64(10000 + i as u64),
|
|
symbol.clone(),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
)?;
|
|
|
|
let _risk_check = self.order_manager.validate_risk_fast(&order).await;
|
|
let _position_update = self
|
|
.position_manager
|
|
.update_position_atomic(&symbol, Quantity::from(100_000))
|
|
.await;
|
|
let _submission = self.order_manager.submit_order_fast(order).await;
|
|
|
|
let elapsed = start.elapsed_nanos();
|
|
samples.push(elapsed);
|
|
}
|
|
Result::<Vec<u64>>::Ok(samples)
|
|
}?;
|
|
|
|
let e2e_p50 = percentile(&e2e_samples, 50.0);
|
|
let e2e_p95 = percentile(&e2e_samples, 95.0);
|
|
let e2e_p99 = percentile(&e2e_samples, 99.0);
|
|
|
|
// THE CRITICAL REQUIREMENT: Sub-50μs end-to-end
|
|
assert!(
|
|
e2e_p95 < 50_000,
|
|
"End-to-end critical path too slow: P95={}ns > 50μs",
|
|
e2e_p95
|
|
);
|
|
result.add_metric("e2e_critical_p50_ns", e2e_p50 as f64);
|
|
result.add_metric("e2e_critical_p95_ns", e2e_p95 as f64);
|
|
result.add_metric("e2e_critical_p99_ns", e2e_p99 as f64);
|
|
|
|
// Step 9: Jitter analysis
|
|
result.add_step("Jitter Analysis").await;
|
|
let jitter_samples: Vec<u64> = e2e_samples
|
|
.windows(2)
|
|
.map(|pair| (pair[1] as i64 - pair[0] as i64).abs() as u64)
|
|
.collect();
|
|
|
|
let jitter_p95 = percentile(&jitter_samples, 95.0);
|
|
let jitter_max = jitter_samples.iter().max().unwrap_or(&0);
|
|
|
|
assert!(
|
|
jitter_p95 < 10_000,
|
|
"Jitter P95 should be <10μs, got {}ns",
|
|
jitter_p95
|
|
);
|
|
result.add_metric("jitter_p95_ns", jitter_p95 as f64);
|
|
result.add_metric("jitter_max_ns", *jitter_max as f64);
|
|
|
|
// Step 10: Temperature and throttling monitoring
|
|
result.add_step("Thermal Performance").await;
|
|
let thermal_metrics = self.resource_monitor.get_thermal_metrics().await?;
|
|
assert!(
|
|
thermal_metrics.cpu_temperature_celsius < 80.0,
|
|
"CPU temperature too high: {}°C",
|
|
thermal_metrics.cpu_temperature_celsius
|
|
);
|
|
assert!(
|
|
!thermal_metrics.is_throttling,
|
|
"CPU should not be throttling"
|
|
);
|
|
result.add_metric("cpu_temperature", thermal_metrics.cpu_temperature_celsius);
|
|
|
|
// Step 11: Cache performance analysis
|
|
result.add_step("Cache Performance Analysis").await;
|
|
let cache_metrics = self.resource_monitor.get_cache_metrics().await?;
|
|
assert!(
|
|
cache_metrics.l1_hit_rate > 0.95,
|
|
"L1 cache hit rate should be >95%"
|
|
);
|
|
assert!(
|
|
cache_metrics.l2_hit_rate > 0.90,
|
|
"L2 cache hit rate should be >90%"
|
|
);
|
|
result.add_metric("l1_hit_rate", cache_metrics.l1_hit_rate);
|
|
result.add_metric("l2_hit_rate", cache_metrics.l2_hit_rate);
|
|
|
|
// Step 12: Sustained performance validation
|
|
result.add_step("Sustained Performance").await;
|
|
let sustained_start = HardwareTimestamp::now();
|
|
let mut sustained_samples = Vec::with_capacity(10000);
|
|
|
|
// Run for 10 seconds at high frequency
|
|
let test_duration = Duration::from_secs(10);
|
|
let test_end = sustained_start.add_duration(test_duration);
|
|
|
|
while HardwareTimestamp::now() < test_end {
|
|
let sample_start = HardwareTimestamp::now();
|
|
|
|
let order = Order::new(
|
|
OrderId::new(),
|
|
symbol.clone(),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
)?;
|
|
|
|
let _risk_check = self.order_manager.validate_risk_fast(&order).await;
|
|
let elapsed = sample_start.elapsed_nanos();
|
|
sustained_samples.push(elapsed);
|
|
}
|
|
|
|
let sustained_p95 = percentile(&sustained_samples, 95.0);
|
|
let sustained_degradation = (sustained_p95 as f64 / e2e_p95 as f64) - 1.0;
|
|
|
|
assert!(
|
|
sustained_degradation < 0.20,
|
|
"Sustained performance degradation should be <20%, got {:.1}%",
|
|
sustained_degradation * 100.0
|
|
);
|
|
result.add_metric("sustained_samples", sustained_samples.len() as f64);
|
|
result.add_metric("sustained_p95_ns", sustained_p95 as f64);
|
|
result.add_metric("performance_degradation", sustained_degradation);
|
|
|
|
result.mark_success();
|
|
Ok(result)
|
|
}
|
|
|
|
/// Test 2: Throughput and scalability benchmarks
|
|
/// Steps: 10 comprehensive throughput measurement phases
|
|
pub async fn test_throughput_scalability_benchmarks(&self) -> Result<WorkflowTestResult> {
|
|
let mut result = WorkflowTestResult::new("Throughput Scalability Benchmarks");
|
|
|
|
// Step 1: Single-threaded baseline throughput
|
|
result.add_step("Single-threaded Baseline").await;
|
|
let single_thread_start = HardwareTimestamp::now();
|
|
let mut operations_completed = 0u64;
|
|
let test_duration = Duration::from_secs(5);
|
|
let end_time = single_thread_start.add_duration(test_duration);
|
|
|
|
while HardwareTimestamp::now() < end_time {
|
|
let order = Order::new(
|
|
OrderId::from_u64(operations_completed),
|
|
Symbol::new("EURUSD"),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
)?;
|
|
|
|
let _validation = self.order_manager.validate_risk_fast(&order).await;
|
|
operations_completed += 1;
|
|
}
|
|
|
|
let actual_duration = single_thread_start.elapsed_nanos() as f64 / 1_000_000_000.0;
|
|
let single_thread_ops_per_sec = operations_completed as f64 / actual_duration;
|
|
|
|
assert!(
|
|
single_thread_ops_per_sec > 100_000.0,
|
|
"Single-thread should exceed 100k ops/sec, got {:.0}",
|
|
single_thread_ops_per_sec
|
|
);
|
|
result.add_metric("single_thread_ops_per_sec", single_thread_ops_per_sec);
|
|
result.add_metric("single_thread_total_ops", operations_completed as f64);
|
|
|
|
// Step 2: Multi-threaded throughput scaling
|
|
result.add_step("Multi-threaded Scaling").await;
|
|
let thread_counts = vec![2, 4, 8, 16];
|
|
let mut scaling_results = Vec::new();
|
|
|
|
for thread_count in thread_counts {
|
|
let mt_start = HardwareTimestamp::now();
|
|
let operations_per_thread = Arc::new(AtomicCounter::new());
|
|
|
|
let handles: Vec<_> = (0..thread_count)
|
|
.map(|thread_id| {
|
|
let counter = operations_per_thread.clone();
|
|
let order_manager = self.order_manager.clone();
|
|
|
|
tokio::spawn(async move {
|
|
let thread_start = HardwareTimestamp::now();
|
|
let thread_duration = Duration::from_secs(3);
|
|
let thread_end = thread_start.add_duration(thread_duration);
|
|
|
|
let mut local_ops = 0u64;
|
|
while HardwareTimestamp::now() < thread_end {
|
|
let order = Order::new(
|
|
OrderId::from_u64((thread_id as u64) << 32 | local_ops),
|
|
Symbol::new("EURUSD"),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
)?;
|
|
|
|
let _validation = order_manager.validate_risk_fast(&order).await;
|
|
local_ops += 1;
|
|
}
|
|
|
|
counter.add(local_ops);
|
|
Result::<u64>::Ok(local_ops)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let thread_results = futures::future::join_all(handles).await;
|
|
let mt_duration = mt_start.elapsed_nanos() as f64 / 1_000_000_000.0;
|
|
let total_mt_ops = operations_per_thread.get();
|
|
let mt_ops_per_sec = total_mt_ops as f64 / mt_duration;
|
|
|
|
scaling_results.push((thread_count, mt_ops_per_sec));
|
|
|
|
// Validate scaling efficiency
|
|
let scaling_efficiency =
|
|
mt_ops_per_sec / (single_thread_ops_per_sec * thread_count as f64);
|
|
result.add_metric(
|
|
&format!("mt_{}_threads_ops_per_sec", thread_count),
|
|
mt_ops_per_sec,
|
|
);
|
|
result.add_metric(
|
|
&format!("mt_{}_threads_efficiency", thread_count),
|
|
scaling_efficiency,
|
|
);
|
|
|
|
// Should maintain at least 70% efficiency up to 8 threads
|
|
if thread_count <= 8 {
|
|
assert!(
|
|
scaling_efficiency > 0.70,
|
|
"Scaling efficiency for {} threads too low: {:.1}%",
|
|
thread_count,
|
|
scaling_efficiency * 100.0
|
|
);
|
|
}
|
|
}
|
|
|
|
// Step 3: Memory bandwidth saturation test
|
|
result.add_step("Memory Bandwidth Saturation").await;
|
|
let memory_test_data: Vec<f64> = (0..1_000_000).map(|i| i as f64 * 1.1).collect();
|
|
let memory_start = HardwareTimestamp::now();
|
|
|
|
let memory_operations = 1000;
|
|
for _ in 0..memory_operations {
|
|
let _result = self.simd_processor.vectorized_sum(&memory_test_data);
|
|
}
|
|
|
|
let memory_duration = memory_start.elapsed_nanos() as f64 / 1_000_000.0; // ms
|
|
let memory_bandwidth_gbps = (memory_test_data.len() * 8 * memory_operations) as f64
|
|
/ 1_000_000_000.0
|
|
/ (memory_duration / 1000.0);
|
|
|
|
result.add_metric("memory_bandwidth_gbps", memory_bandwidth_gbps);
|
|
assert!(
|
|
memory_bandwidth_gbps > 10.0,
|
|
"Memory bandwidth should exceed 10 GB/s"
|
|
);
|
|
|
|
// Step 4: Queue depth and batching optimization
|
|
result.add_step("Queue Depth Optimization").await;
|
|
let batch_sizes = vec![1, 8, 32, 128, 512];
|
|
let mut batch_results = Vec::new();
|
|
|
|
for batch_size in batch_sizes {
|
|
let batch_start = HardwareTimestamp::now();
|
|
let total_batches = 1000;
|
|
|
|
for batch_idx in 0..total_batches {
|
|
let mut batch_orders = Vec::with_capacity(batch_size);
|
|
|
|
for i in 0..batch_size {
|
|
let order = Order::new(
|
|
OrderId::from_u64((batch_idx * batch_size + i) as u64),
|
|
Symbol::new("EURUSD"),
|
|
OrderType::Market,
|
|
OrderSide::Buy,
|
|
Quantity::from(100_000),
|
|
None,
|
|
)?;
|
|
batch_orders.push(order);
|
|
}
|
|
|
|
let _batch_result = self.order_manager.validate_risk_batch(&batch_orders).await;
|
|
}
|
|
|
|
let batch_duration = batch_start.elapsed_nanos() as f64 / 1_000_000_000.0;
|
|
let batch_ops_per_sec = (total_batches * batch_size) as f64 / batch_duration;
|
|
|
|
batch_results.push((batch_size, batch_ops_per_sec));
|
|
result.add_metric(
|
|
&format!("batch_size_{}_ops_per_sec", batch_size),
|
|
batch_ops_per_sec,
|
|
);
|
|
}
|
|
|
|
// Find optimal batch size
|
|
let optimal_batch = batch_results
|
|
.iter()
|
|
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
|
.unwrap();
|
|
result.add_metric("optimal_batch_size", optimal_batch.0 as f64);
|
|
result.add_metric("optimal_batch_ops_per_sec", optimal_batch.1);
|
|
|
|
// Step 5: Network I/O throughput simulation
|
|
result.add_step("Network I/O Throughput").await;
|
|
let network_start = HardwareTimestamp::now();
|
|
let message_count = 100_000;
|
|
let message_size = 256; // bytes
|
|
|
|
for i in 0..message_count {
|
|
let message = vec![0u8; message_size];
|
|
let _serialized = self.order_manager.serialize_order_message(&message).await;
|
|
}
|
|
|
|
let network_duration = network_start.elapsed_nanos() as f64 / 1_000_000_000.0;
|
|
let network_messages_per_sec = message_count as f64 / network_duration;
|
|
let network_mbps = (message_count * message_size) as f64 / 1_000_000.0 / network_duration;
|
|
|
|
result.add_metric("network_messages_per_sec", network_messages_per_sec);
|
|
result.add_metric("network_throughput_mbps", network_mbps);
|
|
assert!(
|
|
network_messages_per_sec > 50_000.0,
|
|
"Network message rate should exceed 50k/sec"
|
|
);
|
|
|
|
// Step 6: Database write throughput
|
|
result.add_step("Database Write Throughput").await;
|
|
let db_start = HardwareTimestamp::now();
|
|
let db_writes = 10_000;
|
|
|
|
for i in 0..db_writes {
|
|
let trade_record = create_test_trade_record(i);
|
|
let _db_result = self.order_manager.persist_trade_record(&trade_record).await;
|
|
}
|
|
|
|
let db_duration = db_start.elapsed_nanos() as f64 / 1_000_000_000.0;
|
|
let db_writes_per_sec = db_writes as f64 / db_duration;
|
|
|
|
result.add_metric("db_writes_per_sec", db_writes_per_sec);
|
|
assert!(
|
|
db_writes_per_sec > 5_000.0,
|
|
"Database writes should exceed 5k/sec"
|
|
);
|
|
|
|
// Step 7: CPU utilization under load
|
|
result.add_step("CPU Utilization Analysis").await;
|
|
let cpu_start = HardwareTimestamp::now();
|
|
let baseline_cpu = self.resource_monitor.get_cpu_usage().await?;
|
|
|
|
// Generate high load
|
|
let high_load_duration = Duration::from_secs(5);
|
|
let load_end = cpu_start.add_duration(high_load_duration);
|
|
|
|
let _load_task = tokio::spawn(async move {
|
|
while HardwareTimestamp::now() < load_end {
|
|
// Simulate trading workload
|
|
let _computation = (0..1000).map(|x| x * x).sum::<i32>();
|
|
}
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
let load_cpu = self.resource_monitor.get_cpu_usage().await?;
|
|
|
|
let cpu_utilization = load_cpu.user_percent + load_cpu.system_percent;
|
|
result.add_metric("cpu_utilization_percent", cpu_utilization);
|
|
result.add_metric("cpu_user_percent", load_cpu.user_percent);
|
|
result.add_metric("cpu_system_percent", load_cpu.system_percent);
|
|
|
|
assert!(
|
|
cpu_utilization < 90.0,
|
|
"CPU utilization should stay below 90%"
|
|
);
|
|
|
|
// Step 8: Memory allocation and GC pressure
|
|
result.add_step("Memory Allocation Analysis").await;
|
|
let memory_start = self.resource_monitor.get_memory_usage().await?;
|
|
|
|
// Allocate and deallocate memory to test pressure
|
|
let allocation_cycles = 1000;
|
|
for _ in 0..allocation_cycles {
|
|
let large_allocation: Vec<u64> = (0..10_000).collect();
|
|
std::hint::black_box(&large_allocation); // Prevent optimization
|
|
}
|
|
|
|
let memory_end = self.resource_monitor.get_memory_usage().await?;
|
|
let memory_growth = memory_end.used_mb - memory_start.used_mb;
|
|
|
|
result.add_metric("memory_growth_mb", memory_growth);
|
|
result.add_metric("memory_utilization_percent", memory_end.utilization_percent);
|
|
|
|
// Memory growth should be reasonable (not a major leak)
|
|
assert!(
|
|
memory_growth < 100.0,
|
|
"Memory growth should be <100MB for test workload"
|
|
);
|
|
|
|
// Step 9: I/O wait and disk performance
|
|
result.add_step("I/O Performance Analysis").await;
|
|
let io_metrics = self.resource_monitor.get_io_metrics().await?;
|
|
result.add_metric("disk_read_mbps", io_metrics.read_mbps);
|
|
result.add_metric("disk_write_mbps", io_metrics.write_mbps);
|
|
result.add_metric("io_wait_percent", io_metrics.io_wait_percent);
|
|
|
|
assert!(io_metrics.io_wait_percent < 20.0, "I/O wait should be <20%");
|
|
|
|
// Step 10: Overall system performance score
|
|
result.add_step("System Performance Score").await;
|
|
let perf_score = self
|
|
.performance_analyzer
|
|
.calculate_overall_score(
|
|
single_thread_ops_per_sec,
|
|
optimal_batch.1,
|
|
network_messages_per_sec,
|
|
db_writes_per_sec,
|
|
)
|
|
.await?;
|
|
|
|
result.add_metric("overall_performance_score", perf_score.total_score);
|
|
result.add_metric("latency_score", perf_score.latency_score);
|
|
result.add_metric("throughput_score", perf_score.throughput_score);
|
|
result.add_metric(
|
|
"resource_efficiency_score",
|
|
perf_score.resource_efficiency_score,
|
|
);
|
|
|
|
assert!(
|
|
perf_score.total_score > 85.0,
|
|
"Overall performance score should exceed 85/100"
|
|
);
|
|
|
|
result.mark_success();
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
// Helper functions for performance testing
|
|
fn percentile(samples: &[u64], percentile: f64) -> u64 {
|
|
if samples.is_empty() {
|
|
return 0;
|
|
}
|
|
|
|
let mut sorted = samples.to_vec();
|
|
sorted.sort_unstable();
|
|
|
|
let index = ((percentile / 100.0) * (sorted.len() - 1) as f64) as usize;
|
|
sorted[index]
|
|
}
|
|
|
|
fn create_test_trade_record(id: u64) -> TradeRecord {
|
|
TradeRecord {
|
|
trade_id: TradeId::from_u64(id),
|
|
symbol: Symbol::new("EURUSD"),
|
|
quantity: Quantity::from(100_000),
|
|
price: Price::from_f64(1.1050).unwrap(),
|
|
side: OrderSide::Buy,
|
|
timestamp: HardwareTimestamp::now(),
|
|
venue: "TEST_VENUE".to_string(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_critical_path_latency_integration() {
|
|
let test_suite = PerformanceValidationTests::new().await.unwrap();
|
|
let result = test_suite
|
|
.test_critical_path_latency_validation()
|
|
.await
|
|
.unwrap();
|
|
assert!(result.success, "Critical path latency test failed");
|
|
assert!(result.steps.len() == 12, "Should have 12 steps");
|
|
|
|
// Verify critical performance requirements
|
|
let e2e_p95 = result.metrics.get("e2e_critical_p95_ns").unwrap();
|
|
assert!(
|
|
*e2e_p95 < 50_000.0,
|
|
"End-to-end P95 latency requirement failed"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_throughput_benchmarks_integration() {
|
|
let test_suite = PerformanceValidationTests::new().await.unwrap();
|
|
let result = test_suite
|
|
.test_throughput_scalability_benchmarks()
|
|
.await
|
|
.unwrap();
|
|
assert!(result.success, "Throughput benchmarks test failed");
|
|
assert!(result.steps.len() == 10, "Should have 10 steps");
|
|
|
|
// Verify throughput requirements
|
|
let single_thread_ops = result.metrics.get("single_thread_ops_per_sec").unwrap();
|
|
assert!(
|
|
*single_thread_ops > 100_000.0,
|
|
"Single-thread throughput requirement failed"
|
|
);
|
|
}
|
|
}
|