use std::sync::Arc;
use tokio::sync::Mutex;
use super::config::StreamClientConfig;
use super::constants::*;
#[derive(Debug, Clone)]
pub struct EventMetrics {
pub process_count: u64,
pub events_processed: u64,
pub events_per_second: f64,
pub events_in_window: u64,
pub window_start_time: std::time::Instant,
}
impl EventMetrics {
fn new(now: std::time::Instant) -> Self {
Self {
process_count: 0,
events_processed: 0,
events_per_second: 0.0,
events_in_window: 0,
window_start_time: now,
}
}
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub start_time: std::time::Instant,
pub event_metrics: [EventMetrics; 3], pub average_processing_time_ms: f64,
pub min_processing_time_ms: f64,
pub max_processing_time_ms: f64,
pub last_update_time: std::time::Instant,
}
impl Default for PerformanceMetrics {
fn default() -> Self {
Self::new()
}
}
pub enum MetricsEventType {
Tx,
Account,
BlockMeta,
}
impl MetricsEventType {
fn as_index(&self) -> usize {
match self {
MetricsEventType::Tx => 0,
MetricsEventType::Account => 1,
MetricsEventType::BlockMeta => 2,
}
}
}
impl PerformanceMetrics {
pub fn new() -> Self {
let now = std::time::Instant::now();
Self {
start_time: now,
event_metrics: [EventMetrics::new(now), EventMetrics::new(now), EventMetrics::new(now)],
average_processing_time_ms: 0.0,
min_processing_time_ms: 0.0,
max_processing_time_ms: 0.0,
last_update_time: now,
}
}
fn update_window_metrics(
&mut self,
event_type: &MetricsEventType,
now: std::time::Instant,
window_duration: std::time::Duration,
) {
let index = event_type.as_index();
let event_metric = &mut self.event_metrics[index];
if now.duration_since(event_metric.window_start_time) >= window_duration {
let window_seconds = now.duration_since(event_metric.window_start_time).as_secs_f64();
event_metric.events_per_second = if window_seconds > 0.001 {
event_metric.events_in_window as f64 / window_seconds
} else {
0.0 };
event_metric.events_in_window = 0;
event_metric.window_start_time = now;
}
}
fn calculate_real_time_events_per_second(
&self,
event_type: &MetricsEventType,
now: std::time::Instant,
) -> f64 {
let index = event_type.as_index();
let event_metric = &self.event_metrics[index];
let current_window_duration =
now.duration_since(event_metric.window_start_time).as_secs_f64();
if current_window_duration > 1.0 && event_metric.events_in_window > 0 {
event_metric.events_in_window as f64 / current_window_duration
}
else if event_metric.events_per_second > 0.0 {
event_metric.events_per_second
}
else {
let total_duration = now.duration_since(self.start_time).as_secs_f64();
if total_duration > 1.0 && event_metric.events_processed > 0 {
event_metric.events_processed as f64 / total_duration
} else {
0.0
}
}
}
}
pub struct MetricsManager {
metrics: Arc<Mutex<PerformanceMetrics>>,
config: Arc<StreamClientConfig>,
stream_name: String,
}
impl MetricsManager {
pub fn new(
metrics: Arc<Mutex<PerformanceMetrics>>,
config: Arc<StreamClientConfig>,
stream_name: String,
) -> Self {
Self { metrics, config, stream_name }
}
pub async fn get_metrics(&self) -> PerformanceMetrics {
let metrics = self.metrics.lock().await;
metrics.clone()
}
pub async fn print_metrics(&self) {
let metrics = self.get_metrics().await;
let event_names = ["TX", "Account", "Block Meta"];
let event_types =
[MetricsEventType::Tx, MetricsEventType::Account, MetricsEventType::BlockMeta];
let now = std::time::Instant::now();
println!("\n📊 {} Performance Metrics", self.stream_name);
println!(" Run Time: {:?}", metrics.start_time.elapsed());
println!("┌─────────────┬──────────────┬──────────────────┬─────────────────┐");
println!("│ Event Type │ Process Count│ Events Processed │ Events/Second │");
println!("├─────────────┼──────────────┼──────────────────┼─────────────────┤");
for (i, name) in event_names.iter().enumerate() {
let event_metric = &metrics.event_metrics[i];
let real_time_eps = metrics.calculate_real_time_events_per_second(&event_types[i], now);
println!(
"│ {:11} │ {:12} │ {:16} │ {:13.2} │",
name,
event_metric.process_count,
event_metric.events_processed,
real_time_eps
);
}
println!("└─────────────┴──────────────┴──────────────────┴─────────────────┘");
println!("\n⏱️ Processing Time Statistics");
println!("┌─────────────────────┬─────────────┐");
println!("│ Metric │ Value (ms) │");
println!("├─────────────────────┼─────────────┤");
println!("│ Average │ {:9.2} │", metrics.average_processing_time_ms);
println!("│ Minimum │ {:9.2} │", metrics.min_processing_time_ms);
println!("│ Maximum │ {:9.2} │", metrics.max_processing_time_ms);
println!("└─────────────────────┴─────────────┘");
println!();
}
pub async fn start_auto_monitoring(&self) -> Option<tokio::task::JoinHandle<()>> {
if !self.config.enable_metrics {
return None; }
let metrics_manager = self.clone();
let handle = tokio::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(
DEFAULT_METRICS_PRINT_INTERVAL_SECONDS,
));
loop {
interval.tick().await;
metrics_manager.print_metrics().await;
}
});
Some(handle)
}
pub async fn add_process_count(&self, event_type: MetricsEventType) {
if !self.config.enable_metrics {
return;
}
let mut metrics = self.metrics.lock().await;
metrics.event_metrics[event_type.as_index()].process_count += 1;
}
pub async fn add_tx_process_count(&self) {
self.add_process_count(MetricsEventType::Tx).await;
}
pub async fn add_account_process_count(&self) {
self.add_process_count(MetricsEventType::Account).await;
}
pub async fn add_block_meta_process_count(&self) {
self.add_process_count(MetricsEventType::BlockMeta).await;
}
pub async fn update_metrics(
&self,
event_type: MetricsEventType,
events_processed: u64,
processing_time_ms: f64,
) {
if !self.config.enable_metrics {
return;
}
let mut metrics = self.metrics.lock().await;
let now = std::time::Instant::now();
let index = event_type.as_index();
metrics.event_metrics[index].events_processed += events_processed;
metrics.event_metrics[index].events_in_window += events_processed;
metrics.last_update_time = now;
if processing_time_ms < metrics.min_processing_time_ms
|| metrics.min_processing_time_ms == 0.0
{
metrics.min_processing_time_ms = processing_time_ms;
}
if processing_time_ms > metrics.max_processing_time_ms {
metrics.max_processing_time_ms = processing_time_ms;
}
let total_events = metrics.event_metrics[index].events_processed;
if total_events > 0 {
let total_events_f64 = total_events as f64;
let old_total = (total_events_f64 - events_processed as f64).max(0.0);
metrics.average_processing_time_ms = if old_total > 0.0 {
(metrics.average_processing_time_ms * old_total
+ processing_time_ms * events_processed as f64)
/ total_events_f64
} else {
processing_time_ms
};
}
let window_duration = std::time::Duration::from_secs(DEFAULT_METRICS_WINDOW_SECONDS);
metrics.update_window_metrics(&event_type, now, window_duration);
}
pub fn log_slow_processing(&self, processing_time_ms: f64, event_count: usize) {
if processing_time_ms > SLOW_PROCESSING_THRESHOLD_MS {
log::warn!(
"{} slow processing: {processing_time_ms}ms for {event_count} events",
self.stream_name
);
}
}
}
impl Clone for MetricsManager {
fn clone(&self) -> Self {
Self {
metrics: self.metrics.clone(),
config: self.config.clone(),
stream_name: self.stream_name.clone(),
}
}
}