use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Clone)]
pub struct MetricsCollector {
state: Arc<Mutex<MetricsState>>,
}
struct MetricsState {
total_api_calls: u64,
successful_calls: u64,
failed_calls: u64,
rate_limited_calls: u64,
cache_hits: u64,
cache_misses: u64,
response_latencies: Vec<u64>,
min_latency_ms: u64,
max_latency_ms: u64,
error_counts: HashMap<String, u64>,
circuit_breaker_trips: u64,
circuit_breaker_recoveries: u64,
created_at: Instant,
last_reset: Instant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsSnapshot {
pub total_api_calls: u64,
pub successful_calls: u64,
pub failed_calls: u64,
pub rate_limited_calls: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub cache_hit_rate: f64,
pub avg_latency_ms: f64,
pub min_latency_ms: u64,
pub max_latency_ms: u64,
pub error_counts: HashMap<String, u64>,
pub circuit_breaker_trips: u64,
pub circuit_breaker_recoveries: u64,
pub uptime_secs: u64,
}
impl MetricsCollector {
pub fn new() -> Self {
MetricsCollector {
state: Arc::new(Mutex::new(MetricsState {
total_api_calls: 0,
successful_calls: 0,
failed_calls: 0,
rate_limited_calls: 0,
cache_hits: 0,
cache_misses: 0,
response_latencies: Vec::new(),
min_latency_ms: u64::MAX,
max_latency_ms: 0,
error_counts: HashMap::new(),
circuit_breaker_trips: 0,
circuit_breaker_recoveries: 0,
created_at: Instant::now(),
last_reset: Instant::now(),
})),
}
}
pub fn record_api_success(&self, latency_ms: u64) {
if let Ok(mut state) = self.state.lock() {
state.total_api_calls += 1;
state.successful_calls += 1;
state.response_latencies.push(latency_ms);
state.min_latency_ms = state.min_latency_ms.min(latency_ms);
state.max_latency_ms = state.max_latency_ms.max(latency_ms);
}
}
pub fn record_api_failure(&self, error_type: &str, latency_ms: u64) {
if let Ok(mut state) = self.state.lock() {
state.total_api_calls += 1;
state.failed_calls += 1;
state.response_latencies.push(latency_ms);
state.min_latency_ms = state.min_latency_ms.min(latency_ms);
state.max_latency_ms = state.max_latency_ms.max(latency_ms);
*state.error_counts.entry(error_type.to_string()).or_insert(0) += 1;
}
}
pub fn record_rate_limited(&self) {
if let Ok(mut state) = self.state.lock() {
state.rate_limited_calls += 1;
}
}
pub fn record_cache_hit(&self) {
if let Ok(mut state) = self.state.lock() {
state.cache_hits += 1;
}
}
pub fn record_cache_miss(&self) {
if let Ok(mut state) = self.state.lock() {
state.cache_misses += 1;
}
}
pub fn record_circuit_break(&self) {
if let Ok(mut state) = self.state.lock() {
state.circuit_breaker_trips += 1;
}
}
pub fn record_circuit_recovery(&self) {
if let Ok(mut state) = self.state.lock() {
state.circuit_breaker_recoveries += 1;
}
}
pub fn snapshot(&self) -> MetricsSnapshot {
if let Ok(state) = self.state.lock() {
let total_cache = state.cache_hits + state.cache_misses;
let cache_hit_rate = if total_cache > 0 {
state.cache_hits as f64 / total_cache as f64
} else {
0.0
};
let avg_latency_ms = if !state.response_latencies.is_empty() {
state.response_latencies.iter().sum::<u64>() as f64
/ state.response_latencies.len() as f64
} else {
0.0
};
let uptime_secs = state.created_at.elapsed().as_secs();
MetricsSnapshot {
total_api_calls: state.total_api_calls,
successful_calls: state.successful_calls,
failed_calls: state.failed_calls,
rate_limited_calls: state.rate_limited_calls,
cache_hits: state.cache_hits,
cache_misses: state.cache_misses,
cache_hit_rate,
avg_latency_ms,
min_latency_ms: if state.min_latency_ms == u64::MAX {
0
} else {
state.min_latency_ms
},
max_latency_ms: state.max_latency_ms,
error_counts: state.error_counts.clone(),
circuit_breaker_trips: state.circuit_breaker_trips,
circuit_breaker_recoveries: state.circuit_breaker_recoveries,
uptime_secs,
}
} else {
MetricsSnapshot::default()
}
}
pub fn reset(&self) {
if let Ok(mut state) = self.state.lock() {
state.total_api_calls = 0;
state.successful_calls = 0;
state.failed_calls = 0;
state.rate_limited_calls = 0;
state.cache_hits = 0;
state.cache_misses = 0;
state.response_latencies.clear();
state.min_latency_ms = u64::MAX;
state.max_latency_ms = 0;
state.error_counts.clear();
state.circuit_breaker_trips = 0;
state.circuit_breaker_recoveries = 0;
state.last_reset = Instant::now();
}
}
pub fn print_report(&self) {
let snapshot = self.snapshot();
println!("\n╔════════════════════════════════════════════════════════════╗");
println!("║ MARKET DATA METRICS REPORT ║");
println!("╚════════════════════════════════════════════════════════════╝");
println!("\n📊 API Call Metrics:");
println!(" Total API Calls: {}", snapshot.total_api_calls);
println!(" Successful: {}", snapshot.successful_calls);
println!(" Failed: {}", snapshot.failed_calls);
println!(" Rate Limited: {}", snapshot.rate_limited_calls);
println!("\n💾 Cache Performance:");
println!(" Cache Hits: {}", snapshot.cache_hits);
println!(" Cache Misses: {}", snapshot.cache_misses);
println!(" Hit Rate: {:.2}%", snapshot.cache_hit_rate * 100.0);
println!("\n⚡ Response Latency:");
println!(" Average: {:.2} ms", snapshot.avg_latency_ms);
println!(" Min: {} ms", snapshot.min_latency_ms);
println!(" Max: {} ms", snapshot.max_latency_ms);
if !snapshot.error_counts.is_empty() {
println!("\n❌ Error Breakdown:");
for (error_type, count) in &snapshot.error_counts {
println!(" {}: {}", error_type, count);
}
}
println!("\n🔄 Circuit Breaker:");
println!(" Trips: {}", snapshot.circuit_breaker_trips);
println!(" Recoveries: {}", snapshot.circuit_breaker_recoveries);
println!("\n⏱️ Uptime: {} seconds", snapshot.uptime_secs);
println!("╚════════════════════════════════════════════════════════════╝\n");
}
}
impl Default for MetricsSnapshot {
fn default() -> Self {
MetricsSnapshot {
total_api_calls: 0,
successful_calls: 0,
failed_calls: 0,
rate_limited_calls: 0,
cache_hits: 0,
cache_misses: 0,
cache_hit_rate: 0.0,
avg_latency_ms: 0.0,
min_latency_ms: 0,
max_latency_ms: 0,
error_counts: HashMap::new(),
circuit_breaker_trips: 0,
circuit_breaker_recoveries: 0,
uptime_secs: 0,
}
}
}
impl Default for MetricsCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_collection() {
let metrics = MetricsCollector::new();
metrics.record_api_success(100);
metrics.record_cache_hit();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.total_api_calls, 1);
assert_eq!(snapshot.successful_calls, 1);
assert_eq!(snapshot.cache_hits, 1);
}
#[test]
fn test_cache_hit_rate() {
let metrics = MetricsCollector::new();
metrics.record_cache_hit();
metrics.record_cache_hit();
metrics.record_cache_miss();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.cache_hit_rate, 2.0 / 3.0);
}
}