Files
foxhunt/tli/tests/performance_tests.rs
jgrusewski ecaa146c04 🏗️ MAJOR ARCHITECTURAL FIXES: Resolve critical compilation errors and architectural violations
 FIXED CRITICAL COMPILATION ERRORS:
- ProductionBenzingaProvider: Added missing Debug trait
- Trading Service: Fixed Option<f64> to f64 conversion in order book levels
- TLS Config: Fixed certificate ownership and lifetime issues
- Repository Impl: Fixed unused variable warnings with underscore prefix
- Config Database: Fixed sqlx lifetime parameter errors
- Common Types: Removed invalid Side import causing compilation failure

🔧 ARCHITECTURAL COMPLIANCE ACHIEVED:
- Config Crate Centralization: All vault access properly routed through config crate
- TLI Pure Client: No server components, clean gRPC client architecture
- Service Independence: Trading/Backtesting/ML services properly decoupled
- Repository Pattern: Clean dependency injection without database coupling

🎯 DEPENDENCY MANAGEMENT CORRECTED:
- Fixed circular dependencies between services
- Centralized configuration through config crate only
- Removed direct vault dependencies outside config crate
- Clean import structure across all services

📊 COMPILATION PROGRESS:
- From 100+ critical errors to manageable type imports
- Core architectural violations resolved
- Clean service boundaries established
- Repository interfaces properly abstracted

🚀 NEXT PHASE READY:
- Common type exports need completion
- Final import reconciliation pending
- Zero errors target within reach

🎉 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 20:13:41 +02:00

1008 lines
35 KiB
Rust

//! Performance tests for TLI system
//!
//! This module provides comprehensive performance testing to validate:
//! - Sub-50μs latency claims for critical trading operations
//! - 10,000+ orders/sec throughput capabilities
//! - Lock-free ring buffer performance
//! - Event storage and retrieval performance
//! - Memory allocation and garbage collection impact
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Barrier};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tokio::sync::{Mutex, RwLock, Semaphore};
use tokio::time::timeout;
use uuid::Uuid;
use tli::client::{
ClientStats, ConnectionConfig, ConnectionManager, EventStreamConfig, EventStreamManager,
OrderContext, TradingClient, TradingClientConfig,
};
// Database imports removed - TLI is pure client
use tli::error::{TliError, TliResult};
use tli::prelude::*;
use tli::types::*;
// Performance testing utilities
use criterion::{black_box, BenchmarkId, Criterion, Throughput};
use tempfile::TempDir;
use tracing_test::traced_test;
#[cfg(test)]
mod latency_performance_tests {
use super::*;
/// Test order submission latency targeting sub-50μs
#[tokio::test]
#[traced_test]
async fn test_order_submission_latency() {
let connection_config = ConnectionConfig::default();
let connection_manager = Arc::new(ConnectionManager::new(connection_config));
let trading_config = TradingClientConfig::default();
let client = TradingClient::new(connection_manager, trading_config);
// Create test order request
let order_request = SubmitOrderRequest {
symbol: "AAPL".to_string(),
side: OrderSide::Buy as i32,
order_type: OrderType::Market as i32,
quantity: 100.0,
client_order_id: Uuid::new_v4().to_string(),
price: Some(150.0),
time_in_force: TimeInForce::Day as i32,
..Default::default()
};
// Warm up the system with a few operations
for _ in 0..10 {
let _ = black_box(order_request.clone());
}
// Measure latency for order validation (client-side only)
let iterations = 1000;
let mut latencies = Vec::with_capacity(iterations);
for _ in 0..iterations {
let start = Instant::now();
// Simulate order validation logic that would happen client-side
let validation_result = validate_order_locally(&order_request);
black_box(validation_result);
let latency = start.elapsed();
latencies.push(latency);
}
// Calculate statistics
latencies.sort();
let min_latency = latencies[0];
let max_latency = latencies[iterations - 1];
let median_latency = latencies[iterations / 2];
let p95_latency = latencies[(iterations as f64 * 0.95) as usize];
let p99_latency = latencies[(iterations as f64 * 0.99) as usize];
let avg_latency = latencies.iter().sum::<Duration>() / iterations as u32;
println!("Order Validation Latency Statistics:");
println!(" Min: {:?}", min_latency);
println!(" Max: {:?}", max_latency);
println!(" Median: {:?}", median_latency);
println!(" Average: {:?}", avg_latency);
println!(" P95: {:?}", p95_latency);
println!(" P99: {:?}", p99_latency);
// Performance assertions
assert!(
avg_latency.as_micros() < 50,
"Average latency {} μs exceeds 50μs target",
avg_latency.as_micros()
);
assert!(
p95_latency.as_micros() < 100,
"P95 latency {} μs exceeds 100μs threshold",
p95_latency.as_micros()
);
assert!(
p99_latency.as_micros() < 200,
"P99 latency {} μs exceeds 200μs threshold",
p99_latency.as_micros()
);
}
/// Helper function for local order validation
fn validate_order_locally(request: &SubmitOrderRequest) -> bool {
// Basic validation checks that would be done client-side
!request.symbol.is_empty()
&& request.quantity > 0.0
&& request.quantity < 1_000_000.0
&& !request.client_order_id.is_empty()
}
/// Test timestamp conversion performance (critical for HFT)
#[tokio::test]
#[traced_test]
async fn test_timestamp_conversion_latency() {
let iterations = 10_000;
let mut latencies = Vec::with_capacity(iterations);
// Test current timestamp generation performance
for _ in 0..iterations {
let start = Instant::now();
let timestamp = black_box(current_unix_nanos());
let system_time = black_box(unix_nanos_to_system_time(timestamp));
let converted_back = black_box(system_time_to_unix_nanos(system_time));
let latency = start.elapsed();
latencies.push(latency);
}
latencies.sort();
let avg_latency = latencies.iter().sum::<Duration>() / iterations as u32;
let p99_latency = latencies[(iterations as f64 * 0.99) as usize];
println!("Timestamp Conversion Latency:");
println!(" Average: {:?}", avg_latency);
println!(" P99: {:?}", p99_latency);
// Should be extremely fast - under 1μs
assert!(
avg_latency.as_nanos() < 1_000,
"Average timestamp conversion {} ns exceeds 1μs",
avg_latency.as_nanos()
);
}
/// Test type conversion performance
#[tokio::test]
#[traced_test]
async fn test_type_conversion_latency() {
let iterations = 10_000;
let test_data = vec![
("AAPL", "BUY", "MARKET", "NEW"),
("MSFT", "SELL", "LIMIT", "FILLED"),
("GOOGL", "BUY", "STOP", "CANCELLED"),
];
let mut total_latency = Duration::new(0, 0);
for _ in 0..iterations {
for (symbol, side, order_type, status) in &test_data {
let start = Instant::now();
// Test all type conversions
let _symbol_valid = black_box(validate_symbol(symbol));
let _order_side = black_box(string_to_order_side(side));
let _order_type_conv = black_box(string_to_order_type(order_type));
let _status_conv = black_box(string_to_order_status(status));
total_latency += start.elapsed();
}
}
let avg_latency = total_latency / (iterations * test_data.len()) as u32;
println!("Type Conversion Average Latency: {:?}", avg_latency);
// Should be very fast - under 100ns per conversion
assert!(
avg_latency.as_nanos() < 100,
"Average type conversion {} ns exceeds 100ns",
avg_latency.as_nanos()
);
}
/// Test memory allocation impact on latency
#[tokio::test]
#[traced_test]
async fn test_memory_allocation_latency() {
let iterations = 1_000;
let mut latencies_with_alloc = Vec::with_capacity(iterations);
let mut latencies_without_alloc = Vec::with_capacity(iterations);
// Pre-allocate structures to test without allocation
let mut pre_allocated_orders = Vec::with_capacity(iterations);
for i in 0..iterations {
pre_allocated_orders.push(SubmitOrderRequest {
symbol: "TEST".to_string(),
side: OrderSide::Buy as i32,
order_type: OrderType::Market as i32,
quantity: 100.0 + i as f64,
client_order_id: format!("order_{}", i),
..Default::default()
});
}
// Test with allocation
for i in 0..iterations {
let start = Instant::now();
let _order = black_box(SubmitOrderRequest {
symbol: "TEST".to_string(),
side: OrderSide::Buy as i32,
order_type: OrderType::Market as i32,
quantity: 100.0 + i as f64,
client_order_id: format!("order_{}", i),
..Default::default()
});
latencies_with_alloc.push(start.elapsed());
}
// Test without allocation (using pre-allocated)
for i in 0..iterations {
let start = Instant::now();
let _order = black_box(&pre_allocated_orders[i]);
latencies_without_alloc.push(start.elapsed());
}
let avg_with_alloc = latencies_with_alloc.iter().sum::<Duration>() / iterations as u32;
let avg_without_alloc =
latencies_without_alloc.iter().sum::<Duration>() / iterations as u32;
let allocation_overhead = avg_with_alloc.saturating_sub(avg_without_alloc);
println!("Memory Allocation Impact:");
println!(" With allocation: {:?}", avg_with_alloc);
println!(" Without allocation: {:?}", avg_without_alloc);
println!(" Allocation overhead: {:?}", allocation_overhead);
// Allocation overhead should be minimal for HFT systems
assert!(
allocation_overhead.as_micros() < 10,
"Allocation overhead {} μs too high for HFT",
allocation_overhead.as_micros()
);
}
}
#[cfg(test)]
mod throughput_performance_tests {
use super::*;
/// Test order processing throughput targeting 10,000+ orders/sec
#[tokio::test]
#[traced_test]
async fn test_order_processing_throughput() {
let connection_config = ConnectionConfig::default();
let connection_manager = Arc::new(ConnectionManager::new(connection_config));
let trading_config = TradingClientConfig::default();
let client = Arc::new(TradingClient::new(connection_manager, trading_config));
let test_duration = Duration::from_secs(1);
let order_counter = Arc::new(AtomicU64::new(0));
let start_time = Instant::now();
// Spawn multiple concurrent order processors
let num_workers = 8;
let barrier = Arc::new(Barrier::new(num_workers));
let mut handles = Vec::new();
for worker_id in 0..num_workers {
let client_clone = client.clone();
let counter_clone = order_counter.clone();
let barrier_clone = barrier.clone();
let handle = tokio::spawn(async move {
// Wait for all workers to start
barrier_clone.wait();
let worker_start = Instant::now();
let mut local_count = 0u64;
while worker_start.elapsed() < test_duration {
// Create order request
let order_request = SubmitOrderRequest {
symbol: "TEST".to_string(),
side: OrderSide::Buy as i32,
order_type: OrderType::Market as i32,
quantity: 100.0,
client_order_id: format!("worker_{}_{}", worker_id, local_count),
..Default::default()
};
// Process order (validation only, no actual gRPC call)
let _is_valid = validate_order_locally(&order_request);
black_box(order_request);
local_count += 1;
}
counter_clone.fetch_add(local_count, Ordering::Relaxed);
local_count
});
handles.push(handle);
}
// Wait for all workers to complete
let worker_results = futures::future::join_all(handles).await;
let total_duration = start_time.elapsed();
let total_orders = order_counter.load(Ordering::Relaxed);
let orders_per_second = total_orders as f64 / total_duration.as_secs_f64();
println!("Order Processing Throughput Results:");
println!(" Total orders: {}", total_orders);
println!(" Duration: {:?}", total_duration);
println!(" Orders/sec: {:.2}", orders_per_second);
println!(" Workers: {}", num_workers);
for (i, result) in worker_results.iter().enumerate() {
if let Ok(count) = result {
println!(" Worker {}: {} orders", i, count);
}
}
// Performance assertion
assert!(
orders_per_second >= 10_000.0,
"Throughput {} orders/sec below 10,000 target",
orders_per_second
);
}
/// Test event processing throughput
#[tokio::test]
#[traced_test]
async fn test_event_processing_throughput() {
let event_config = EventStreamConfig {
buffer_size: 100_000,
reconnect_delay: Duration::from_millis(100),
max_reconnect_attempts: 3,
enable_compression: false,
batch_size: 1000,
flush_interval: Duration::from_millis(1),
};
let (event_manager, _event_receiver) = EventStreamManager::new(event_config);
let events_processed = Arc::new(AtomicU64::new(0));
let test_duration = Duration::from_secs(1);
// Generate events at high rate
let num_producers = 4;
let mut handles = Vec::new();
for producer_id in 0..num_producers {
let counter = events_processed.clone();
let handle = tokio::spawn(async move {
let start = Instant::now();
let mut local_count = 0u64;
while start.elapsed() < test_duration {
let event = TliEvent {
event_id: Uuid::new_v4().to_string(),
event_type: EventType::MarketData,
source_service: format!("producer_{}", producer_id),
timestamp: current_unix_nanos(),
data: serde_json::json!({
"symbol": "TEST",
"price": 100.0 + local_count as f64 * 0.01,
"volume": 1000 + local_count
}),
metadata: HashMap::new(),
};
// Process event (serialization and validation)
let _serialized = black_box(serde_json::to_string(&event).unwrap());
black_box(event);
local_count += 1;
}
counter.fetch_add(local_count, Ordering::Relaxed);
local_count
});
handles.push(handle);
}
let start_time = Instant::now();
let results = futures::future::join_all(handles).await;
let total_duration = start_time.elapsed();
let total_events = events_processed.load(Ordering::Relaxed);
let events_per_second = total_events as f64 / total_duration.as_secs_f64();
println!("Event Processing Throughput Results:");
println!(" Total events: {}", total_events);
println!(" Duration: {:?}", total_duration);
println!(" Events/sec: {:.2}", events_per_second);
println!(" Producers: {}", num_producers);
// Should handle at least 100,000 events per second
assert!(
events_per_second >= 100_000.0,
"Event throughput {} events/sec below 100,000 target",
events_per_second
);
event_manager.shutdown().await;
}
// Database throughput test removed - TLI is pure client
}
#[cfg(test)]
mod lock_free_performance_tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
/// Test lock-free ring buffer performance
#[tokio::test]
#[traced_test]
async fn test_lock_free_ring_buffer_performance() {
const BUFFER_SIZE: usize = 65536; // Power of 2 for efficient modulo
const NUM_PRODUCERS: usize = 4;
const NUM_CONSUMERS: usize = 2;
const TEST_DURATION_SECS: u64 = 1;
// Simple lock-free ring buffer simulation using atomic operations
struct LockFreeRingBuffer {
buffer: Vec<AtomicU64>,
head: AtomicUsize,
tail: AtomicUsize,
capacity: usize,
}
impl LockFreeRingBuffer {
fn new(capacity: usize) -> Self {
let mut buffer = Vec::with_capacity(capacity);
for _ in 0..capacity {
buffer.push(AtomicU64::new(0));
}
Self {
buffer,
head: AtomicUsize::new(0),
tail: AtomicUsize::new(0),
capacity,
}
}
fn try_push(&self, value: u64) -> bool {
let current_tail = self.tail.load(Ordering::Acquire);
let next_tail = (current_tail + 1) % self.capacity;
let current_head = self.head.load(Ordering::Acquire);
if next_tail == current_head {
return false; // Buffer full
}
self.buffer[current_tail].store(value, Ordering::Release);
self.tail.store(next_tail, Ordering::Release);
true
}
fn try_pop(&self) -> Option<u64> {
let current_head = self.head.load(Ordering::Acquire);
let current_tail = self.tail.load(Ordering::Acquire);
if current_head == current_tail {
return None; // Buffer empty
}
let value = self.buffer[current_head].load(Ordering::Acquire);
let next_head = (current_head + 1) % self.capacity;
self.head.store(next_head, Ordering::Release);
Some(value)
}
}
let ring_buffer = Arc::new(LockFreeRingBuffer::new(BUFFER_SIZE));
let items_produced = Arc::new(AtomicU64::new(0));
let items_consumed = Arc::new(AtomicU64::new(0));
let test_duration = Duration::from_secs(TEST_DURATION_SECS);
let mut handles = Vec::new();
// Start producers
for producer_id in 0..NUM_PRODUCERS {
let buffer = ring_buffer.clone();
let produced_counter = items_produced.clone();
let handle = tokio::spawn(async move {
let start = Instant::now();
let mut local_produced = 0u64;
while start.elapsed() < test_duration {
let value = (producer_id as u64) << 32 | local_produced;
if buffer.try_push(value) {
local_produced += 1;
} else {
tokio::task::yield_now().await; // Buffer full, yield
}
}
produced_counter.fetch_add(local_produced, Ordering::Relaxed);
local_produced
});
handles.push(handle);
}
// Start consumers
for _consumer_id in 0..NUM_CONSUMERS {
let buffer = ring_buffer.clone();
let consumed_counter = items_consumed.clone();
let handle = tokio::spawn(async move {
let start = Instant::now();
let mut local_consumed = 0u64;
while start.elapsed() < test_duration {
if let Some(_value) = buffer.try_pop() {
local_consumed += 1;
} else {
tokio::task::yield_now().await; // Buffer empty, yield
}
}
consumed_counter.fetch_add(local_consumed, Ordering::Relaxed);
local_consumed
});
handles.push(handle);
}
let start_time = Instant::now();
let results = futures::future::join_all(handles).await;
let total_duration = start_time.elapsed();
let total_produced = items_produced.load(Ordering::Relaxed);
let total_consumed = items_consumed.load(Ordering::Relaxed);
let production_rate = total_produced as f64 / total_duration.as_secs_f64();
let consumption_rate = total_consumed as f64 / total_duration.as_secs_f64();
println!("Lock-Free Ring Buffer Performance:");
println!(" Buffer size: {}", BUFFER_SIZE);
println!(" Producers: {}", NUM_PRODUCERS);
println!(" Consumers: {}", NUM_CONSUMERS);
println!(" Duration: {:?}", total_duration);
println!(" Items produced: {}", total_produced);
println!(" Items consumed: {}", total_consumed);
println!(" Production rate: {:.2} items/sec", production_rate);
println!(" Consumption rate: {:.2} items/sec", consumption_rate);
// Should achieve high throughput with lock-free operations
assert!(
production_rate >= 1_000_000.0,
"Production rate {} items/sec below 1M target",
production_rate
);
assert!(
consumption_rate >= 1_000_000.0,
"Consumption rate {} items/sec below 1M target",
consumption_rate
);
// Buffer should not lose significant data
let loss_rate = (total_produced - total_consumed) as f64 / total_produced as f64;
assert!(
loss_rate < 0.1,
"Data loss rate {:.2}% too high",
loss_rate * 100.0
);
}
/// Test atomic operations performance for order sequencing
#[tokio::test]
#[traced_test]
async fn test_atomic_order_sequencing_performance() {
let order_sequence = Arc::new(AtomicU64::new(0));
let num_threads = 8;
let operations_per_thread = 100_000;
let mut handles = Vec::new();
for thread_id in 0..num_threads {
let sequence = order_sequence.clone();
let handle = tokio::spawn(async move {
let start = Instant::now();
let mut local_sequences = Vec::with_capacity(operations_per_thread);
for _ in 0..operations_per_thread {
// Get next sequence number atomically
let seq = sequence.fetch_add(1, Ordering::AcqRel);
local_sequences.push(seq);
}
let duration = start.elapsed();
(thread_id, local_sequences, duration)
});
handles.push(handle);
}
let start_time = Instant::now();
let results = futures::future::join_all(handles).await;
let total_duration = start_time.elapsed();
let total_operations = num_threads * operations_per_thread;
let ops_per_second = total_operations as f64 / total_duration.as_secs_f64();
println!("Atomic Sequencing Performance:");
println!(" Threads: {}", num_threads);
println!(" Operations per thread: {}", operations_per_thread);
println!(" Total operations: {}", total_operations);
println!(" Duration: {:?}", total_duration);
println!(" Operations/sec: {:.2}", ops_per_second);
// Verify no duplicate sequence numbers
let mut all_sequences = Vec::new();
for result in results {
if let Ok((_thread_id, sequences, _duration)) = result {
all_sequences.extend(sequences);
}
}
all_sequences.sort_unstable();
for i in 1..all_sequences.len() {
assert!(
all_sequences[i] != all_sequences[i - 1],
"Duplicate sequence number found: {}",
all_sequences[i]
);
}
// Should achieve very high atomic operation throughput
assert!(
ops_per_second >= 10_000_000.0,
"Atomic operations {} ops/sec below 10M target",
ops_per_second
);
}
}
#[cfg(test)]
mod memory_performance_tests {
use super::*;
/// Test memory allocation patterns for order processing
#[tokio::test]
#[traced_test]
async fn test_memory_allocation_patterns() {
const NUM_ORDERS: usize = 10_000;
// Test 1: Allocate orders individually (bad pattern)
let start = Instant::now();
let mut individual_orders = Vec::new();
for i in 0..NUM_ORDERS {
let order = SubmitOrderRequest {
symbol: format!("SYMBOL_{}", i % 100),
side: if i % 2 == 0 {
OrderSide::Buy
} else {
OrderSide::Sell
} as i32,
order_type: OrderType::Market as i32,
quantity: 100.0 + i as f64,
client_order_id: format!("order_{}", i),
..Default::default()
};
individual_orders.push(order);
}
let individual_duration = start.elapsed();
// Test 2: Pre-allocate and reuse (good pattern)
let start = Instant::now();
let mut batch_orders = Vec::with_capacity(NUM_ORDERS);
for i in 0..NUM_ORDERS {
let order = SubmitOrderRequest {
symbol: format!("SYMBOL_{}", i % 100),
side: if i % 2 == 0 {
OrderSide::Buy
} else {
OrderSide::Sell
} as i32,
order_type: OrderType::Market as i32,
quantity: 100.0 + i as f64,
client_order_id: format!("order_{}", i),
..Default::default()
};
batch_orders.push(order);
}
let batch_duration = start.elapsed();
// Test 3: Object pooling simulation
let start = Instant::now();
let mut order_pool = Vec::with_capacity(1000);
for _ in 0..1000 {
order_pool.push(SubmitOrderRequest::default());
}
let mut pooled_orders = Vec::with_capacity(NUM_ORDERS);
for i in 0..NUM_ORDERS {
let mut order = if let Some(pooled) = order_pool.pop() {
pooled
} else {
SubmitOrderRequest::default()
};
order.symbol = format!("SYMBOL_{}", i % 100);
order.side = if i % 2 == 0 {
OrderSide::Buy
} else {
OrderSide::Sell
} as i32;
order.order_type = OrderType::Market as i32;
order.quantity = 100.0 + i as f64;
order.client_order_id = format!("order_{}", i);
pooled_orders.push(order);
}
let pooled_duration = start.elapsed();
println!("Memory Allocation Pattern Performance:");
println!(" Individual allocation: {:?}", individual_duration);
println!(" Batch allocation: {:?}", batch_duration);
println!(" Object pooling: {:?}", pooled_duration);
let individual_ns_per_op = individual_duration.as_nanos() / NUM_ORDERS as u128;
let batch_ns_per_op = batch_duration.as_nanos() / NUM_ORDERS as u128;
let pooled_ns_per_op = pooled_duration.as_nanos() / NUM_ORDERS as u128;
println!(" Individual: {} ns/order", individual_ns_per_op);
println!(" Batch: {} ns/order", batch_ns_per_op);
println!(" Pooled: {} ns/order", pooled_ns_per_op);
// Object pooling should be fastest
assert!(pooled_ns_per_op <= individual_ns_per_op);
assert!(batch_ns_per_op <= individual_ns_per_op);
// All should be under 10μs per order for HFT
assert!(individual_ns_per_op < 10_000);
assert!(batch_ns_per_op < 10_000);
assert!(pooled_ns_per_op < 10_000);
}
/// Test garbage collection impact simulation
#[tokio::test]
#[traced_test]
async fn test_gc_impact_simulation() {
const ITERATIONS: usize = 1_000;
const ALLOCATION_SIZE: usize = 10_000;
let mut gc_simulation_times = Vec::with_capacity(ITERATIONS);
for iteration in 0..ITERATIONS {
let start = Instant::now();
// Simulate large allocation that might trigger GC
let mut temp_data = Vec::with_capacity(ALLOCATION_SIZE);
for i in 0..ALLOCATION_SIZE {
temp_data.push(format!("data_{}_{}", iteration, i));
}
// Simulate some processing
black_box(&temp_data);
// Drop the data (simulating GC)
drop(temp_data);
let duration = start.elapsed();
gc_simulation_times.push(duration);
}
gc_simulation_times.sort();
let median = gc_simulation_times[ITERATIONS / 2];
let p95 = gc_simulation_times[(ITERATIONS as f64 * 0.95) as usize];
let p99 = gc_simulation_times[(ITERATIONS as f64 * 0.99) as usize];
let max = gc_simulation_times[ITERATIONS - 1];
println!("GC Impact Simulation:");
println!(" Median: {:?}", median);
println!(" P95: {:?}", p95);
println!(" P99: {:?}", p99);
println!(" Max: {:?}", max);
// For HFT systems, even P99 should be under 1ms
assert!(
p99.as_millis() < 1,
"P99 GC impact {} ms exceeds 1ms threshold",
p99.as_millis()
);
}
}
// Encryption performance tests removed - TLI is pure client, encryption handled by services
// Criterion benchmark functions (for use with `cargo bench`)
#[cfg(test)]
mod criterion_benchmarks {
use super::*;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
pub fn benchmark_order_validation(c: &mut Criterion) {
let order_request = SubmitOrderRequest {
symbol: "AAPL".to_string(),
side: OrderSide::Buy as i32,
order_type: OrderType::Market as i32,
quantity: 100.0,
client_order_id: "test_order".to_string(),
..Default::default()
};
c.bench_function("order_validation", |b| {
b.iter(|| black_box(validate_order_locally(&order_request)))
});
}
pub fn benchmark_timestamp_operations(c: &mut Criterion) {
c.bench_function("current_unix_nanos", |b| {
b.iter(|| black_box(current_unix_nanos()))
});
c.bench_function("timestamp_conversion", |b| {
b.iter(|| {
let timestamp = current_unix_nanos();
let system_time = unix_nanos_to_system_time(timestamp);
black_box(system_time_to_unix_nanos(system_time))
})
});
}
pub fn benchmark_type_conversions(c: &mut Criterion) {
let mut group = c.benchmark_group("type_conversions");
group.bench_function("string_to_order_side", |b| {
b.iter(|| black_box(string_to_order_side("BUY")))
});
group.bench_function("string_to_order_type", |b| {
b.iter(|| black_box(string_to_order_type("MARKET")))
});
group.bench_function("validate_symbol", |b| {
b.iter(|| black_box(validate_symbol("AAPL")))
});
group.finish();
}
criterion_group!(
benches,
benchmark_order_validation,
benchmark_timestamp_operations,
benchmark_type_conversions
);
criterion_main!(benches);
/// Helper function for local order validation
fn validate_order_locally(request: &SubmitOrderRequest) -> bool {
!request.symbol.is_empty()
&& request.quantity > 0.0
&& request.quantity < 1_000_000.0
&& !request.client_order_id.is_empty()
}
}
// Performance test utilities
#[cfg(test)]
mod performance_test_utils {
use super::*;
/// Performance test configuration
pub struct PerformanceTestConfig {
pub target_latency_micros: u64,
pub target_throughput_ops_per_sec: f64,
pub test_duration_secs: u64,
pub num_workers: usize,
pub warmup_iterations: usize,
}
impl Default for PerformanceTestConfig {
fn default() -> Self {
Self {
target_latency_micros: 50,
target_throughput_ops_per_sec: 10_000.0,
test_duration_secs: 1,
num_workers: 8,
warmup_iterations: 100,
}
}
}
/// Run a latency benchmark and validate results
pub async fn run_latency_benchmark<F, Fut>(
name: &str,
config: PerformanceTestConfig,
operation: F,
) where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: futures::Future<Output = ()> + Send,
{
// Warmup
for _ in 0..config.warmup_iterations {
operation().await;
}
// Measure latencies
let iterations = 1000;
let mut latencies = Vec::with_capacity(iterations);
for _ in 0..iterations {
let start = Instant::now();
operation().await;
latencies.push(start.elapsed());
}
// Calculate statistics
latencies.sort();
let avg = latencies.iter().sum::<Duration>() / iterations as u32;
let p95 = latencies[(iterations as f64 * 0.95) as usize];
let p99 = latencies[(iterations as f64 * 0.99) as usize];
println!("{} Latency Benchmark:", name);
println!(" Average: {:?}", avg);
println!(" P95: {:?}", p95);
println!(" P99: {:?}", p99);
assert!(
avg.as_micros() <= config.target_latency_micros as u128,
"{} average latency {} μs exceeds target {} μs",
name,
avg.as_micros(),
config.target_latency_micros
);
}
/// Run a throughput benchmark and validate results
pub async fn run_throughput_benchmark<F, Fut>(
name: &str,
config: PerformanceTestConfig,
operation: F,
) where
F: Fn() -> Fut + Send + Sync + Clone + 'static,
Fut: futures::Future<Output = ()> + Send,
{
let operations_completed = Arc::new(AtomicU64::new(0));
let test_duration = Duration::from_secs(config.test_duration_secs);
let mut handles = Vec::new();
for _ in 0..config.num_workers {
let op = operation.clone();
let counter = operations_completed.clone();
let handle = tokio::spawn(async move {
let start = Instant::now();
let mut local_ops = 0u64;
while start.elapsed() < test_duration {
op().await;
local_ops += 1;
}
counter.fetch_add(local_ops, Ordering::Relaxed);
});
handles.push(handle);
}
let start_time = Instant::now();
futures::future::join_all(handles).await;
let actual_duration = start_time.elapsed();
let total_ops = operations_completed.load(Ordering::Relaxed);
let ops_per_second = total_ops as f64 / actual_duration.as_secs_f64();
println!("{} Throughput Benchmark:", name);
println!(" Operations: {}", total_ops);
println!(" Duration: {:?}", actual_duration);
println!(" Ops/sec: {:.2}", ops_per_second);
assert!(
ops_per_second >= config.target_throughput_ops_per_sec,
"{} throughput {:.2} ops/sec below target {:.2} ops/sec",
name,
ops_per_second,
config.target_throughput_ops_per_sec
);
}
}