use prometheus::{ Counter, Histogram, Gauge, IntCounter, IntGauge, register_counter, register_histogram, register_gauge, register_int_counter, register_int_gauge, Opts, HistogramOpts, Registry, Encoder, TextEncoder }; use std::collections::HashMap; use std::sync::Arc; use lazy_static::lazy_static; use tracing::{error, info, warn}; /// Core HFT trading metrics for Foxhunt system /// Optimized for high-frequency data collection with minimal latency impact lazy_static! { // Order processing metrics static ref ORDER_COUNTER: Counter = register_counter!( "foxhunt_orders_total", "Total orders processed by the trading system" ).expect("Failed to register orders counter"); static ref ORDER_FILL_COUNTER: Counter = register_counter!( "foxhunt_order_fills_total", "Total order fills executed" ).expect("Failed to register order fills counter"); static ref ORDER_REJECTION_COUNTER: Counter = register_counter!( "foxhunt_order_rejections_total", "Total order rejections" ).expect("Failed to register order rejections counter"); // Latency metrics - critical for HFT performance static ref LATENCY_HISTOGRAM: Histogram = register_histogram!( HistogramOpts::new( "foxhunt_latency_microseconds", "Latency distribution in microseconds" ).buckets(vec![1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0]) ).expect("Failed to register latency histogram"); static ref ORDER_PROCESSING_LATENCY: Histogram = register_histogram!( HistogramOpts::new( "foxhunt_order_processing_latency_microseconds", "Order processing latency from receipt to exchange submission" ).buckets(vec![1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0]) ).expect("Failed to register order processing latency histogram"); static ref MARKET_DATA_LATENCY: Histogram = register_histogram!( HistogramOpts::new( "foxhunt_market_data_latency_microseconds", "Market data processing latency" ).buckets(vec![0.1, 0.5, 1.0, 5.0, 10.0, 25.0, 50.0, 100.0]) ).expect("Failed to register market data latency histogram"); // Risk metrics static ref RISK_BREACH_COUNTER: Counter = register_counter!( "foxhunt_risk_breaches_total", "Total risk limit breaches" ).expect("Failed to register risk breaches counter"); static ref POSITION_VALUE_GAUGE: Gauge = register_gauge!( "foxhunt_position_value_usd", "Current total position value in USD" ).expect("Failed to register position value gauge"); static ref VAR_GAUGE: Gauge = register_gauge!( "foxhunt_var_usd", "Current Value at Risk in USD" ).expect("Failed to register VaR gauge"); // Trading engine metrics static ref ACTIVE_ORDERS_GAUGE: IntGauge = register_int_gauge!( "foxhunt_active_orders", "Number of currently active orders" ).expect("Failed to register active orders gauge"); static ref TRADING_SESSIONS_GAUGE: IntGauge = register_int_gauge!( "foxhunt_trading_sessions_active", "Number of active trading sessions" ).expect("Failed to register trading sessions gauge"); // Market data metrics static ref MARKET_DATA_MESSAGES_COUNTER: Counter = register_counter!( "foxhunt_market_data_messages_total", "Total market data messages received" ).expect("Failed to register market data messages counter"); static ref MARKET_DATA_DROPS_COUNTER: Counter = register_counter!( "foxhunt_market_data_drops_total", "Total market data messages dropped" ).expect("Failed to register market data drops counter"); // AI/ML metrics static ref ML_PREDICTIONS_COUNTER: Counter = register_counter!( "foxhunt_ml_predictions_total", "Total ML model predictions generated" ).expect("Failed to register ML predictions counter"); static ref ML_MODEL_ACCURACY_GAUGE: Gauge = register_gauge!( "foxhunt_ml_model_accuracy", "Current ML model accuracy percentage" ).expect("Failed to register ML model accuracy gauge"); // System health metrics static ref CPU_USAGE_GAUGE: Gauge = register_gauge!( "foxhunt_cpu_usage_percent", "Current CPU usage percentage" ).expect("Failed to register CPU usage gauge"); static ref MEMORY_USAGE_GAUGE: Gauge = register_gauge!( "foxhunt_memory_usage_bytes", "Current memory usage in bytes" ).expect("Failed to register memory usage gauge"); // Broker connectivity metrics static ref BROKER_CONNECTIONS_GAUGE: IntGauge = register_int_gauge!( "foxhunt_broker_connections", "Number of active broker connections" ).expect("Failed to register broker connections gauge"); static ref BROKER_DISCONNECTS_COUNTER: Counter = register_counter!( "foxhunt_broker_disconnects_total", "Total broker disconnection events" ).expect("Failed to register broker disconnects counter"); // Performance metrics static ref THROUGHPUT_GAUGE: Gauge = register_gauge!( "foxhunt_throughput_ops_per_second", "Current system throughput in operations per second" ).expect("Failed to register throughput gauge"); } /// Metrics collector for the Foxhunt HFT system #[derive(Debug)] pub struct FoxhuntMetrics { registry: Arc, custom_counters: HashMap, custom_histograms: HashMap, custom_gauges: HashMap, } impl Default for FoxhuntMetrics { fn default() -> Self { Self::new() } } impl FoxhuntMetrics { /// Create a new metrics collector instance pub fn new() -> Self { Self { registry: Arc::new(Registry::new()), custom_counters: HashMap::new(), custom_histograms: HashMap::new(), custom_gauges: HashMap::new(), } } /// Record an order being processed #[inline(always)] pub fn record_order() { ORDER_COUNTER.inc(); } /// Record an order fill #[inline(always)] pub fn record_order_fill() { ORDER_FILL_COUNTER.inc(); } /// Record an order rejection #[inline(always)] pub fn record_order_rejection() { ORDER_REJECTION_COUNTER.inc(); } /// Record latency measurement in microseconds #[inline(always)] pub fn record_latency(latency_us: f64) { LATENCY_HISTOGRAM.observe(latency_us); } /// Record order processing latency in microseconds #[inline(always)] pub fn record_order_processing_latency(latency_us: f64) { ORDER_PROCESSING_LATENCY.observe(latency_us); } /// Record market data latency in microseconds #[inline(always)] pub fn record_market_data_latency(latency_us: f64) { MARKET_DATA_LATENCY.observe(latency_us); } /// Record a risk breach event #[inline(always)] pub fn record_risk_breach() { RISK_BREACH_COUNTER.inc(); warn!("Risk breach recorded in metrics"); } /// Update current position value #[inline(always)] pub fn update_position_value(value_usd: f64) { POSITION_VALUE_GAUGE.set(value_usd); } /// Update Value at Risk #[inline(always)] pub fn update_var(var_usd: f64) { VAR_GAUGE.set(var_usd); } /// Update active orders count #[inline(always)] pub fn update_active_orders(count: i64) { ACTIVE_ORDERS_GAUGE.set(count); } /// Update trading sessions count #[inline(always)] pub fn update_trading_sessions(count: i64) { TRADING_SESSIONS_GAUGE.set(count); } /// Record market data message received #[inline(always)] pub fn record_market_data_message() { MARKET_DATA_MESSAGES_COUNTER.inc(); } /// Record market data message dropped #[inline(always)] pub fn record_market_data_drop() { MARKET_DATA_DROPS_COUNTER.inc(); } /// Record ML prediction generated #[inline(always)] pub fn record_ml_prediction() { ML_PREDICTIONS_COUNTER.inc(); } /// Update ML model accuracy #[inline(always)] pub fn update_ml_accuracy(accuracy: f64) { ML_MODEL_ACCURACY_GAUGE.set(accuracy); } /// Update CPU usage percentage #[inline(always)] pub fn update_cpu_usage(percentage: f64) { CPU_USAGE_GAUGE.set(percentage); } /// Update memory usage in bytes #[inline(always)] pub fn update_memory_usage(bytes: f64) { MEMORY_USAGE_GAUGE.set(bytes); } /// Update broker connections count #[inline(always)] pub fn update_broker_connections(count: i64) { BROKER_CONNECTIONS_GAUGE.set(count); } /// Record broker disconnection #[inline(always)] pub fn record_broker_disconnect() { BROKER_DISCONNECTS_COUNTER.inc(); } /// Update system throughput #[inline(always)] pub fn update_throughput(ops_per_second: f64) { THROUGHPUT_GAUGE.set(ops_per_second); } /// Get metrics in Prometheus text format pub fn export_metrics() -> Result> { let encoder = TextEncoder::new(); let metric_families = prometheus::gather(); let mut buffer = Vec::new(); encoder.encode(&metric_families, &mut buffer)?; Ok(String::from_utf8(buffer)?) } /// Create a custom counter metric pub fn create_custom_counter(&mut self, name: &str, help: &str) -> Result<(), Box> { let counter = Counter::new(name, help)?; self.registry.register(Box::new(counter.clone()))?; self.custom_counters.insert(name.to_string(), counter); Ok(()) } /// Increment a custom counter pub fn increment_custom_counter(&self, name: &str) { if let Some(counter) = self.custom_counters.get(name) { counter.inc(); } else { error!("Custom counter '{}' not found", name); } } /// Reset all metrics (for testing purposes) #[cfg(test)] pub fn reset_all() { // Reset all static metrics to zero ORDER_COUNTER.reset(); ORDER_FILL_COUNTER.reset(); ORDER_REJECTION_COUNTER.reset(); RISK_BREACH_COUNTER.reset(); MARKET_DATA_MESSAGES_COUNTER.reset(); MARKET_DATA_DROPS_COUNTER.reset(); ML_PREDICTIONS_COUNTER.reset(); BROKER_DISCONNECTS_COUNTER.reset(); // Reset gauges to zero POSITION_VALUE_GAUGE.set(0.0); VAR_GAUGE.set(0.0); ACTIVE_ORDERS_GAUGE.set(0); TRADING_SESSIONS_GAUGE.set(0); ML_MODEL_ACCURACY_GAUGE.set(0.0); CPU_USAGE_GAUGE.set(0.0); MEMORY_USAGE_GAUGE.set(0.0); BROKER_CONNECTIONS_GAUGE.set(0); THROUGHPUT_GAUGE.set(0.0); } /// Log current metrics summary pub fn log_metrics_summary() { info!( "Metrics Summary - Orders: {}, Fills: {}, Rejections: {}, Active Orders: {}, Risk Breaches: {}", ORDER_COUNTER.get(), ORDER_FILL_COUNTER.get(), ORDER_REJECTION_COUNTER.get(), ACTIVE_ORDERS_GAUGE.get(), RISK_BREACH_COUNTER.get() ); } } /// Convenience functions for direct metric recording /// These are optimized for hot path usage with minimal overhead /// Record order with timing #[inline(always)] pub fn record_order() { FoxhuntMetrics::record_order(); } /// Record order fill #[inline(always)] pub fn record_order_fill() { FoxhuntMetrics::record_order_fill(); } /// Record order rejection #[inline(always)] pub fn record_order_rejection() { FoxhuntMetrics::record_order_rejection(); } /// Record latency measurement #[inline(always)] pub fn record_latency(latency_us: f64) { FoxhuntMetrics::record_latency(latency_us); } /// Record order processing latency #[inline(always)] pub fn record_order_processing_latency(latency_us: f64) { FoxhuntMetrics::record_order_processing_latency(latency_us); } /// Record market data latency #[inline(always)] pub fn record_market_data_latency(latency_us: f64) { FoxhuntMetrics::record_market_data_latency(latency_us); } /// Record risk breach #[inline(always)] pub fn record_risk_breach() { FoxhuntMetrics::record_risk_breach(); } /// Update position value #[inline(always)] pub fn update_position_value(value_usd: f64) { FoxhuntMetrics::update_position_value(value_usd); } /// Update VaR #[inline(always)] pub fn update_var(var_usd: f64) { FoxhuntMetrics::update_var(var_usd); } /// Update active orders count #[inline(always)] pub fn update_active_orders(count: i64) { FoxhuntMetrics::update_active_orders(count); } /// Record market data message #[inline(always)] pub fn record_market_data_message() { FoxhuntMetrics::record_market_data_message(); } /// Record ML prediction #[inline(always)] pub fn record_ml_prediction() { FoxhuntMetrics::record_ml_prediction(); } /// Update system throughput #[inline(always)] pub fn update_throughput(ops_per_second: f64) { FoxhuntMetrics::update_throughput(ops_per_second); } #[cfg(test)] mod tests { use super::*; #[test] fn test_order_metrics() { FoxhuntMetrics::reset_all(); record_order(); record_order(); record_order_fill(); assert_eq!(ORDER_COUNTER.get(), 2.0); assert_eq!(ORDER_FILL_COUNTER.get(), 1.0); } #[test] fn test_latency_metrics() { record_latency(25.5); record_order_processing_latency(15.2); record_market_data_latency(2.1); // Histograms don't have direct getters, but we can verify they accept values // In a real environment, these would be scraped by Prometheus } #[test] fn test_risk_metrics() { FoxhuntMetrics::reset_all(); record_risk_breach(); update_position_value(150000.0); update_var(5000.0); assert_eq!(RISK_BREACH_COUNTER.get(), 1.0); assert_eq!(POSITION_VALUE_GAUGE.get(), 150000.0); assert_eq!(VAR_GAUGE.get(), 5000.0); } #[test] fn test_custom_metrics() { let mut metrics = FoxhuntMetrics::new(); metrics.create_custom_counter("test_counter", "Test counter").unwrap(); metrics.increment_custom_counter("test_counter"); // Custom counter incremented successfully } #[test] fn test_metrics_export() { record_order(); let exported = FoxhuntMetrics::export_metrics().unwrap(); assert!(exported.contains("foxhunt_orders_total")); } }