## Final Metrics (Wave 99) - Compilation errors: 672 → 0 ✅ (100% resolution) - Test compilation: 489 → 0 ✅ (100% resolution) - Warnings: 313 → 124 (60% reduction, target was <50) ## Wave Timeline Wave 82-87: Source code errors (183→0) Wave 88-94: Test compilation (489→0) Wave 95: Import cleanup experiment Wave 96: Import restoration (26 errors fixed) Wave 97: Warning phase 1 (313→188, -40%) Wave 98: Warning phase 2 (188→124, -34%) Wave 99: Warning phase 3 (124→124, target not met) ## Major API Migrations (73+ files) - NewsEvent: 18-field structure with full metadata - ExecutionReport: filled_quantity→executed_quantity - Position: 16-field modernization (avg_cost, market_value, etc) - TradingOrder: account_id field added - TimeInForce: Abbreviated variants (GTC, IOC, FOK) ## Remaining Work - 124 warnings (non-critical: unused variables, dead code, deprecated APIs) - Most are cleanup/style issues, not correctness problems - Recommendation: Accept current state, prioritize test coverage (95% target) ## Production Status ✅ Wave 79 certified: 87.8% production ready ✅ Zero compilation errors maintained ✅ All services compile and tests runnable 🔄 Next: Test coverage measurement (95% target - CLAUDE.md requirement) Co-authored-by: Wave 82-99 Agents (40+ parallel agents deployed)
620 lines
23 KiB
Rust
620 lines
23 KiB
Rust
//! End-to-End Latency Measurement Framework for HFT Order Processing
|
|
//!
|
|
//! This test suite measures complete order processing latency with RDTSC timing:
|
|
//! - Order submission → validation → risk checks → execution → confirmation
|
|
//! - P50, P95, P99 latency distributions
|
|
//! - Per-stage breakdowns and bottleneck identification
|
|
//! - Comparison against HFT targets (<50μs total, <10μs ML, <5μs metrics)
|
|
//!
|
|
//! **Wave 68 Agent 10**: Production latency measurement and optimization validation
|
|
|
|
use std::time::Duration;
|
|
use trading_engine::timing::{
|
|
HardwareTimestamp, calibrate_tsc,
|
|
};
|
|
|
|
/// E2E latency measurement point in the order processing pipeline
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum LatencyCheckpoint {
|
|
OrderSubmission, // Entry point
|
|
ValidationStart, // Pre-validation start
|
|
ValidationComplete, // All validations passed
|
|
RiskCheckStart, // Risk manager invocation
|
|
RiskCheckComplete, // Risk approval received
|
|
ExecutionStart, // Order routing begins
|
|
BrokerSent, // Order sent to exchange
|
|
ExchangeResponse, // Exchange acknowledgment
|
|
ConfirmationSent, // Final confirmation to client
|
|
}
|
|
|
|
/// Comprehensive E2E latency measurement
|
|
#[derive(Debug, Clone)]
|
|
pub struct E2ELatencyTrace {
|
|
/// Order ID for correlation
|
|
pub order_id: String,
|
|
|
|
/// Checkpoints with RDTSC timestamps
|
|
pub checkpoints: Vec<(LatencyCheckpoint, HardwareTimestamp)>,
|
|
|
|
/// Total E2E latency (nanoseconds)
|
|
pub total_latency_ns: u64,
|
|
|
|
/// Per-stage breakdowns
|
|
pub validation_latency_ns: u64,
|
|
pub risk_check_latency_ns: u64,
|
|
pub execution_latency_ns: u64,
|
|
pub exchange_latency_ns: u64,
|
|
pub confirmation_latency_ns: u64,
|
|
|
|
/// Additional overhead measurements
|
|
pub ml_inference_latency_ns: Option<u64>,
|
|
pub metrics_collection_overhead_ns: u64,
|
|
}
|
|
|
|
impl E2ELatencyTrace {
|
|
/// Create new latency trace for an order
|
|
pub fn new(order_id: String) -> Self {
|
|
Self {
|
|
order_id,
|
|
checkpoints: Vec::with_capacity(10),
|
|
total_latency_ns: 0,
|
|
validation_latency_ns: 0,
|
|
risk_check_latency_ns: 0,
|
|
execution_latency_ns: 0,
|
|
exchange_latency_ns: 0,
|
|
confirmation_latency_ns: 0,
|
|
ml_inference_latency_ns: None,
|
|
metrics_collection_overhead_ns: 0,
|
|
}
|
|
}
|
|
|
|
/// Record a checkpoint with RDTSC timing
|
|
pub fn record_checkpoint(&mut self, checkpoint: LatencyCheckpoint) {
|
|
let timestamp = HardwareTimestamp::now();
|
|
self.checkpoints.push((checkpoint, timestamp));
|
|
}
|
|
|
|
/// Calculate all stage latencies from checkpoints
|
|
pub fn calculate_latencies(&mut self) -> Result<(), &'static str> {
|
|
if self.checkpoints.len() < 2 {
|
|
return Err("Insufficient checkpoints for latency calculation");
|
|
}
|
|
|
|
// Find checkpoint positions
|
|
let find_checkpoint = |cp: LatencyCheckpoint| -> Option<usize> {
|
|
self.checkpoints.iter().position(|(c, _)| *c == cp)
|
|
};
|
|
|
|
// Calculate validation latency
|
|
if let (Some(val_start), Some(val_end)) = (
|
|
find_checkpoint(LatencyCheckpoint::ValidationStart),
|
|
find_checkpoint(LatencyCheckpoint::ValidationComplete),
|
|
) {
|
|
self.validation_latency_ns = self.checkpoints[val_end].1
|
|
.latency_ns(&self.checkpoints[val_start].1);
|
|
}
|
|
|
|
// Calculate risk check latency
|
|
if let (Some(risk_start), Some(risk_end)) = (
|
|
find_checkpoint(LatencyCheckpoint::RiskCheckStart),
|
|
find_checkpoint(LatencyCheckpoint::RiskCheckComplete),
|
|
) {
|
|
self.risk_check_latency_ns = self.checkpoints[risk_end].1
|
|
.latency_ns(&self.checkpoints[risk_start].1);
|
|
}
|
|
|
|
// Calculate execution latency
|
|
if let (Some(exec_start), Some(broker_sent)) = (
|
|
find_checkpoint(LatencyCheckpoint::ExecutionStart),
|
|
find_checkpoint(LatencyCheckpoint::BrokerSent),
|
|
) {
|
|
self.execution_latency_ns = self.checkpoints[broker_sent].1
|
|
.latency_ns(&self.checkpoints[exec_start].1);
|
|
}
|
|
|
|
// Calculate exchange response latency
|
|
if let (Some(broker_sent), Some(exchange_resp)) = (
|
|
find_checkpoint(LatencyCheckpoint::BrokerSent),
|
|
find_checkpoint(LatencyCheckpoint::ExchangeResponse),
|
|
) {
|
|
self.exchange_latency_ns = self.checkpoints[exchange_resp].1
|
|
.latency_ns(&self.checkpoints[broker_sent].1);
|
|
}
|
|
|
|
// Calculate confirmation latency
|
|
if let (Some(exchange_resp), Some(confirmation)) = (
|
|
find_checkpoint(LatencyCheckpoint::ExchangeResponse),
|
|
find_checkpoint(LatencyCheckpoint::ConfirmationSent),
|
|
) {
|
|
self.confirmation_latency_ns = self.checkpoints[confirmation].1
|
|
.latency_ns(&self.checkpoints[exchange_resp].1);
|
|
}
|
|
|
|
// Calculate total E2E latency
|
|
if let (Some(first), Some(last)) = (self.checkpoints.first(), self.checkpoints.last()) {
|
|
self.total_latency_ns = last.1.latency_ns(&first.1);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get validation latency in microseconds
|
|
pub fn validation_us(&self) -> f64 {
|
|
self.validation_latency_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Get risk check latency in microseconds
|
|
pub fn risk_check_us(&self) -> f64 {
|
|
self.risk_check_latency_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Get execution latency in microseconds
|
|
pub fn execution_us(&self) -> f64 {
|
|
self.execution_latency_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Get exchange latency in microseconds
|
|
pub fn exchange_us(&self) -> f64 {
|
|
self.exchange_latency_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Get total E2E latency in microseconds
|
|
pub fn total_us(&self) -> f64 {
|
|
self.total_latency_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Check if latency meets HFT targets
|
|
pub fn meets_hft_targets(&self) -> LatencyTargetResult {
|
|
LatencyTargetResult {
|
|
total_target_met: self.total_latency_ns < 50_000, // <50μs
|
|
validation_target_met: self.validation_latency_ns < 5_000, // <5μs
|
|
risk_check_target_met: self.risk_check_latency_ns < 15_000, // <15μs
|
|
execution_target_met: self.execution_latency_ns < 10_000, // <10μs
|
|
ml_inference_target_met: self.ml_inference_latency_ns
|
|
.map(|lat| lat < 10_000)
|
|
.unwrap_or(true), // <10μs if present
|
|
metrics_overhead_target_met: self.metrics_collection_overhead_ns < 5_000, // <5μs
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Result of comparing against HFT latency targets
|
|
#[derive(Debug, Clone)]
|
|
pub struct LatencyTargetResult {
|
|
pub total_target_met: bool,
|
|
pub validation_target_met: bool,
|
|
pub risk_check_target_met: bool,
|
|
pub execution_target_met: bool,
|
|
pub ml_inference_target_met: bool,
|
|
pub metrics_overhead_target_met: bool,
|
|
}
|
|
|
|
impl LatencyTargetResult {
|
|
/// Check if all targets are met
|
|
pub fn all_targets_met(&self) -> bool {
|
|
self.total_target_met
|
|
&& self.validation_target_met
|
|
&& self.risk_check_target_met
|
|
&& self.execution_target_met
|
|
&& self.ml_inference_target_met
|
|
&& self.metrics_overhead_target_met
|
|
}
|
|
}
|
|
|
|
/// Latency distribution statistics
|
|
#[derive(Debug, Clone)]
|
|
pub struct LatencyDistribution {
|
|
pub samples: Vec<u64>,
|
|
pub p50_ns: u64,
|
|
pub p95_ns: u64,
|
|
pub p99_ns: u64,
|
|
pub min_ns: u64,
|
|
pub max_ns: u64,
|
|
pub mean_ns: f64,
|
|
pub stddev_ns: f64,
|
|
}
|
|
|
|
impl LatencyDistribution {
|
|
/// Calculate distribution from latency samples
|
|
pub fn from_samples(mut samples: Vec<u64>) -> Self {
|
|
if samples.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
|
|
samples.sort_unstable();
|
|
|
|
let p50_ns = Self::percentile(&samples, 0.50);
|
|
let p95_ns = Self::percentile(&samples, 0.95);
|
|
let p99_ns = Self::percentile(&samples, 0.99);
|
|
let min_ns = *samples.first().unwrap();
|
|
let max_ns = *samples.last().unwrap();
|
|
|
|
let mean_ns = samples.iter().sum::<u64>() as f64 / samples.len() as f64;
|
|
let variance = samples.iter()
|
|
.map(|&x| {
|
|
let diff = x as f64 - mean_ns;
|
|
diff * diff
|
|
})
|
|
.sum::<f64>() / samples.len() as f64;
|
|
let stddev_ns = variance.sqrt();
|
|
|
|
Self {
|
|
samples,
|
|
p50_ns,
|
|
p95_ns,
|
|
p99_ns,
|
|
min_ns,
|
|
max_ns,
|
|
mean_ns,
|
|
stddev_ns,
|
|
}
|
|
}
|
|
|
|
fn percentile(sorted_samples: &[u64], percentile: f64) -> u64 {
|
|
let index = ((sorted_samples.len() as f64 - 1.0) * percentile) as usize;
|
|
sorted_samples[index]
|
|
}
|
|
|
|
fn empty() -> Self {
|
|
Self {
|
|
samples: Vec::new(),
|
|
p50_ns: 0,
|
|
p95_ns: 0,
|
|
p99_ns: 0,
|
|
min_ns: 0,
|
|
max_ns: 0,
|
|
mean_ns: 0.0,
|
|
stddev_ns: 0.0,
|
|
}
|
|
}
|
|
|
|
/// Get P50 in microseconds
|
|
pub fn p50_us(&self) -> f64 {
|
|
self.p50_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Get P95 in microseconds
|
|
pub fn p95_us(&self) -> f64 {
|
|
self.p95_ns as f64 / 1000.0
|
|
}
|
|
|
|
/// Get P99 in microseconds
|
|
pub fn p99_us(&self) -> f64 {
|
|
self.p99_ns as f64 / 1000.0
|
|
}
|
|
}
|
|
|
|
/// Complete E2E latency analysis results
|
|
#[derive(Debug, Clone)]
|
|
pub struct E2ELatencyAnalysis {
|
|
pub total_orders: usize,
|
|
pub total_latency_dist: LatencyDistribution,
|
|
pub validation_latency_dist: LatencyDistribution,
|
|
pub risk_check_latency_dist: LatencyDistribution,
|
|
pub execution_latency_dist: LatencyDistribution,
|
|
pub exchange_latency_dist: LatencyDistribution,
|
|
pub ml_inference_latency_dist: Option<LatencyDistribution>,
|
|
pub metrics_overhead_dist: LatencyDistribution,
|
|
|
|
/// Percentage of orders meeting each target
|
|
pub total_target_pass_rate: f64,
|
|
pub validation_target_pass_rate: f64,
|
|
pub risk_check_target_pass_rate: f64,
|
|
pub execution_target_pass_rate: f64,
|
|
|
|
/// Bottleneck identification
|
|
pub primary_bottleneck: String,
|
|
pub bottleneck_contribution_pct: f64,
|
|
}
|
|
|
|
impl E2ELatencyAnalysis {
|
|
/// Analyze collection of latency traces
|
|
pub fn analyze(traces: &[E2ELatencyTrace]) -> Self {
|
|
if traces.is_empty() {
|
|
return Self::empty();
|
|
}
|
|
|
|
// Extract latency samples
|
|
let total_samples: Vec<u64> = traces.iter().map(|t| t.total_latency_ns).collect();
|
|
let validation_samples: Vec<u64> = traces.iter().map(|t| t.validation_latency_ns).collect();
|
|
let risk_check_samples: Vec<u64> = traces.iter().map(|t| t.risk_check_latency_ns).collect();
|
|
let execution_samples: Vec<u64> = traces.iter().map(|t| t.execution_latency_ns).collect();
|
|
let exchange_samples: Vec<u64> = traces.iter().map(|t| t.exchange_latency_ns).collect();
|
|
let metrics_samples: Vec<u64> = traces.iter().map(|t| t.metrics_collection_overhead_ns).collect();
|
|
|
|
// Calculate distributions
|
|
let total_latency_dist = LatencyDistribution::from_samples(total_samples);
|
|
let validation_latency_dist = LatencyDistribution::from_samples(validation_samples);
|
|
let risk_check_latency_dist = LatencyDistribution::from_samples(risk_check_samples);
|
|
let execution_latency_dist = LatencyDistribution::from_samples(execution_samples);
|
|
let exchange_latency_dist = LatencyDistribution::from_samples(exchange_samples);
|
|
let metrics_overhead_dist = LatencyDistribution::from_samples(metrics_samples);
|
|
|
|
// ML inference distribution (if present)
|
|
let ml_samples: Vec<u64> = traces.iter()
|
|
.filter_map(|t| t.ml_inference_latency_ns)
|
|
.collect();
|
|
let ml_inference_latency_dist = if !ml_samples.is_empty() {
|
|
Some(LatencyDistribution::from_samples(ml_samples))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Calculate target pass rates
|
|
let total_target_pass_rate = traces.iter()
|
|
.filter(|t| t.meets_hft_targets().total_target_met)
|
|
.count() as f64 / traces.len() as f64 * 100.0;
|
|
|
|
let validation_target_pass_rate = traces.iter()
|
|
.filter(|t| t.meets_hft_targets().validation_target_met)
|
|
.count() as f64 / traces.len() as f64 * 100.0;
|
|
|
|
let risk_check_target_pass_rate = traces.iter()
|
|
.filter(|t| t.meets_hft_targets().risk_check_target_met)
|
|
.count() as f64 / traces.len() as f64 * 100.0;
|
|
|
|
let execution_target_pass_rate = traces.iter()
|
|
.filter(|t| t.meets_hft_targets().execution_target_met)
|
|
.count() as f64 / traces.len() as f64 * 100.0;
|
|
|
|
// Identify primary bottleneck
|
|
let avg_validation = validation_latency_dist.mean_ns;
|
|
let avg_risk_check = risk_check_latency_dist.mean_ns;
|
|
let avg_execution = execution_latency_dist.mean_ns;
|
|
let avg_exchange = exchange_latency_dist.mean_ns;
|
|
|
|
let (primary_bottleneck, max_latency) = [
|
|
("Validation", avg_validation),
|
|
("Risk Check", avg_risk_check),
|
|
("Execution", avg_execution),
|
|
("Exchange", avg_exchange),
|
|
]
|
|
.iter()
|
|
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
|
.map(|(name, lat)| (name.to_string(), *lat))
|
|
.unwrap();
|
|
|
|
let total_avg = total_latency_dist.mean_ns;
|
|
let bottleneck_contribution_pct = if total_avg > 0.0 {
|
|
(max_latency / total_avg) * 100.0
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
Self {
|
|
total_orders: traces.len(),
|
|
total_latency_dist,
|
|
validation_latency_dist,
|
|
risk_check_latency_dist,
|
|
execution_latency_dist,
|
|
exchange_latency_dist,
|
|
ml_inference_latency_dist,
|
|
metrics_overhead_dist,
|
|
total_target_pass_rate,
|
|
validation_target_pass_rate,
|
|
risk_check_target_pass_rate,
|
|
execution_target_pass_rate,
|
|
primary_bottleneck,
|
|
bottleneck_contribution_pct,
|
|
}
|
|
}
|
|
|
|
fn empty() -> Self {
|
|
Self {
|
|
total_orders: 0,
|
|
total_latency_dist: LatencyDistribution::empty(),
|
|
validation_latency_dist: LatencyDistribution::empty(),
|
|
risk_check_latency_dist: LatencyDistribution::empty(),
|
|
execution_latency_dist: LatencyDistribution::empty(),
|
|
exchange_latency_dist: LatencyDistribution::empty(),
|
|
ml_inference_latency_dist: None,
|
|
metrics_overhead_dist: LatencyDistribution::empty(),
|
|
total_target_pass_rate: 0.0,
|
|
validation_target_pass_rate: 0.0,
|
|
risk_check_target_pass_rate: 0.0,
|
|
execution_target_pass_rate: 0.0,
|
|
primary_bottleneck: "Unknown".to_string(),
|
|
bottleneck_contribution_pct: 0.0,
|
|
}
|
|
}
|
|
|
|
/// Generate detailed analysis report
|
|
pub fn generate_report(&self) -> String {
|
|
format!(
|
|
r#"
|
|
═══════════════════════════════════════════════════════════════════
|
|
E2E LATENCY MEASUREMENT REPORT
|
|
Wave 68 Agent 10
|
|
═══════════════════════════════════════════════════════════════════
|
|
|
|
EXECUTIVE SUMMARY
|
|
─────────────────────────────────────────────────────────────────
|
|
Total Orders Measured: {}
|
|
HFT Target (<50μs): {:.1}% pass rate
|
|
|
|
OVERALL LATENCY DISTRIBUTION
|
|
─────────────────────────────────────────────────────────────────
|
|
P50: {:.2} μs
|
|
P95: {:.2} μs
|
|
P99: {:.2} μs
|
|
Mean: {:.2} μs ± {:.2} μs
|
|
Min: {:.2} μs
|
|
Max: {:.2} μs
|
|
|
|
PER-STAGE BREAKDOWN (P95 Latencies)
|
|
─────────────────────────────────────────────────────────────────
|
|
Validation: {:.2} μs ({:.1}% pass rate)
|
|
Risk Check: {:.2} μs ({:.1}% pass rate)
|
|
Execution: {:.2} μs ({:.1}% pass rate)
|
|
Exchange: {:.2} μs
|
|
{}
|
|
Metrics: {:.2} μs
|
|
|
|
BOTTLENECK ANALYSIS
|
|
─────────────────────────────────────────────────────────────────
|
|
Primary Bottleneck: {}
|
|
Contribution: {:.1}% of total latency
|
|
|
|
HFT TARGET COMPLIANCE
|
|
─────────────────────────────────────────────────────────────────
|
|
Total Latency (<50μs): {:.1}%
|
|
Validation (<5μs): {:.1}%
|
|
Risk Check (<15μs): {:.1}%
|
|
Execution (<10μs): {:.1}%
|
|
|
|
RECOMMENDATIONS
|
|
─────────────────────────────────────────────────────────────────
|
|
{}
|
|
|
|
═══════════════════════════════════════════════════════════════════
|
|
"#,
|
|
self.total_orders,
|
|
self.total_target_pass_rate,
|
|
self.total_latency_dist.p50_us(),
|
|
self.total_latency_dist.p95_us(),
|
|
self.total_latency_dist.p99_us(),
|
|
self.total_latency_dist.mean_ns / 1000.0,
|
|
self.total_latency_dist.stddev_ns / 1000.0,
|
|
self.total_latency_dist.min_ns as f64 / 1000.0,
|
|
self.total_latency_dist.max_ns as f64 / 1000.0,
|
|
self.validation_latency_dist.p95_us(),
|
|
self.validation_target_pass_rate,
|
|
self.risk_check_latency_dist.p95_us(),
|
|
self.risk_check_target_pass_rate,
|
|
self.execution_latency_dist.p95_us(),
|
|
self.execution_target_pass_rate,
|
|
self.exchange_latency_dist.p95_us(),
|
|
self.ml_inference_latency_dist.as_ref()
|
|
.map(|dist| format!("ML Inference: {:.2} μs\n", dist.p95_us()))
|
|
.unwrap_or_default(),
|
|
self.metrics_overhead_dist.p95_us(),
|
|
self.primary_bottleneck,
|
|
self.bottleneck_contribution_pct,
|
|
self.total_target_pass_rate,
|
|
self.validation_target_pass_rate,
|
|
self.risk_check_target_pass_rate,
|
|
self.execution_target_pass_rate,
|
|
self.generate_recommendations()
|
|
)
|
|
}
|
|
|
|
fn generate_recommendations(&self) -> String {
|
|
let mut recommendations = Vec::new();
|
|
|
|
if self.total_target_pass_rate < 95.0 {
|
|
recommendations.push("⚠ Overall latency target not met - requires optimization");
|
|
}
|
|
|
|
if self.primary_bottleneck == "Validation" {
|
|
recommendations.push("→ Optimize validation logic - consider parallel checks");
|
|
} else if self.primary_bottleneck == "Risk Check" {
|
|
recommendations.push("→ Optimize risk calculations - consider caching or approximation");
|
|
} else if self.primary_bottleneck == "Execution" {
|
|
recommendations.push("→ Optimize order routing - reduce broker communication overhead");
|
|
} else if self.primary_bottleneck == "Exchange" {
|
|
recommendations.push("→ Exchange latency dominant - consider co-location or venue change");
|
|
}
|
|
|
|
if self.metrics_overhead_dist.p95_ns > 5_000 {
|
|
recommendations.push("→ Reduce metrics collection overhead");
|
|
}
|
|
|
|
if let Some(ref ml_dist) = self.ml_inference_latency_dist {
|
|
if ml_dist.p95_ns > 10_000 {
|
|
recommendations.push("→ ML inference exceeds target - consider model optimization");
|
|
}
|
|
}
|
|
|
|
if recommendations.is_empty() {
|
|
"✓ All HFT targets met - system performing within specifications".to_string()
|
|
} else {
|
|
recommendations.join("\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_latency_trace_creation() {
|
|
let mut trace = E2ELatencyTrace::new("order-001".to_string());
|
|
|
|
trace.record_checkpoint(LatencyCheckpoint::OrderSubmission);
|
|
std::thread::sleep(Duration::from_micros(10));
|
|
trace.record_checkpoint(LatencyCheckpoint::ValidationStart);
|
|
std::thread::sleep(Duration::from_micros(5));
|
|
trace.record_checkpoint(LatencyCheckpoint::ValidationComplete);
|
|
std::thread::sleep(Duration::from_micros(15));
|
|
trace.record_checkpoint(LatencyCheckpoint::RiskCheckStart);
|
|
std::thread::sleep(Duration::from_micros(10));
|
|
trace.record_checkpoint(LatencyCheckpoint::RiskCheckComplete);
|
|
|
|
assert!(trace.calculate_latencies().is_ok());
|
|
assert!(trace.validation_latency_ns > 0);
|
|
assert!(trace.risk_check_latency_ns > 0);
|
|
assert!(trace.total_latency_ns > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_latency_distribution() {
|
|
let samples = vec![1000, 2000, 3000, 4000, 5000, 10000, 15000, 20000, 50000, 100000];
|
|
let dist = LatencyDistribution::from_samples(samples);
|
|
|
|
assert!(dist.p50_ns > 0);
|
|
assert!(dist.p95_ns > dist.p50_ns);
|
|
assert!(dist.p99_ns >= dist.p95_ns);
|
|
assert_eq!(dist.min_ns, 1000);
|
|
assert_eq!(dist.max_ns, 100000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hft_target_validation() {
|
|
let mut trace = E2ELatencyTrace::new("order-001".to_string());
|
|
trace.total_latency_ns = 40_000; // 40μs - meets target
|
|
trace.validation_latency_ns = 3_000; // 3μs - meets target
|
|
trace.risk_check_latency_ns = 12_000; // 12μs - meets target
|
|
trace.execution_latency_ns = 8_000; // 8μs - meets target
|
|
trace.metrics_collection_overhead_ns = 4_000; // 4μs - meets target
|
|
|
|
let result = trace.meets_hft_targets();
|
|
assert!(result.total_target_met);
|
|
assert!(result.validation_target_met);
|
|
assert!(result.risk_check_target_met);
|
|
assert!(result.execution_target_met);
|
|
assert!(result.metrics_overhead_target_met);
|
|
assert!(result.all_targets_met());
|
|
}
|
|
|
|
#[test]
|
|
fn test_e2e_analysis() {
|
|
// Calibrate TSC first
|
|
let _ = calibrate_tsc();
|
|
|
|
let mut traces = Vec::new();
|
|
|
|
for i in 0..100 {
|
|
let mut trace = E2ELatencyTrace::new(format!("order-{:03}", i));
|
|
trace.total_latency_ns = 30_000 + (i * 100); // Simulated latencies
|
|
trace.validation_latency_ns = 2_000 + (i * 10);
|
|
trace.risk_check_latency_ns = 10_000 + (i * 30);
|
|
trace.execution_latency_ns = 8_000 + (i * 20);
|
|
trace.exchange_latency_ns = 5_000 + (i * 15);
|
|
trace.metrics_collection_overhead_ns = 3_000 + (i * 5);
|
|
traces.push(trace);
|
|
}
|
|
|
|
let analysis = E2ELatencyAnalysis::analyze(&traces);
|
|
|
|
assert_eq!(analysis.total_orders, 100);
|
|
assert!(analysis.total_latency_dist.p50_ns > 0);
|
|
assert!(analysis.total_latency_dist.p95_ns > analysis.total_latency_dist.p50_ns);
|
|
assert!(!analysis.primary_bottleneck.is_empty());
|
|
assert!(analysis.bottleneck_contribution_pct > 0.0);
|
|
|
|
// Print report for visual inspection
|
|
println!("{}", analysis.generate_report());
|
|
}
|
|
}
|