use crate::cache::CacheStats;
use std::sync::RwLock;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct ExecutionMetrics {
pub execution_count: usize,
pub total_duration: Duration,
pub average_duration: Duration,
pub min_duration: Duration,
pub max_duration: Duration,
}
impl Default for ExecutionMetrics {
fn default() -> Self {
Self {
execution_count: 0,
total_duration: Duration::ZERO,
average_duration: Duration::ZERO,
min_duration: Duration::MAX,
max_duration: Duration::ZERO,
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleMetrics {
pub load_count: usize,
pub cache_hits: usize,
pub cache_misses: usize,
pub hit_rate: f64,
}
impl Default for ModuleMetrics {
fn default() -> Self {
Self {
load_count: 0,
cache_hits: 0,
cache_misses: 0,
hit_rate: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct MetricsSnapshot {
pub execution: ExecutionMetrics,
pub modules: ModuleMetrics,
pub trace_entries: usize,
pub module_cache_size: usize,
pub ast_cache: CacheStats,
}
pub struct MetricsCollector {
enabled: RwLock<bool>,
execution_start: RwLock<Option<Instant>>,
execution: RwLock<ExecutionMetrics>,
modules: RwLock<ModuleMetrics>,
module_loads: RwLock<std::collections::HashMap<String, usize>>,
}
impl MetricsCollector {
pub fn new() -> Self {
Self {
enabled: RwLock::new(false),
execution_start: RwLock::new(None),
execution: RwLock::new(ExecutionMetrics::default()),
modules: RwLock::new(ModuleMetrics::default()),
module_loads: RwLock::new(std::collections::HashMap::new()),
}
}
pub fn enable(&self) {
*self.enabled.write().unwrap() = true;
}
pub fn disable(&self) {
*self.enabled.write().unwrap() = false;
}
pub fn is_enabled(&self) -> bool {
*self.enabled.read().unwrap()
}
pub fn record_execution_start(&self) {
if !self.is_enabled() {
return;
}
*self.execution_start.write().unwrap() = Some(Instant::now());
}
pub fn record_execution_end(&self) {
if !self.is_enabled() {
return;
}
let start = self.execution_start.write().unwrap().take();
if let Some(start_time) = start {
let duration = start_time.elapsed();
let mut exec = self.execution.write().unwrap();
exec.execution_count += 1;
exec.total_duration += duration;
exec.average_duration = exec.total_duration / exec.execution_count as u32;
exec.min_duration = exec.min_duration.min(duration);
exec.max_duration = exec.max_duration.max(duration);
}
}
pub fn record_module_load(&self, module_id: &str, cached: bool) {
if !self.is_enabled() {
return;
}
let mut modules = self.modules.write().unwrap();
modules.load_count += 1;
if cached {
modules.cache_hits += 1;
} else {
modules.cache_misses += 1;
}
if modules.load_count > 0 {
modules.hit_rate = modules.cache_hits as f64 / modules.load_count as f64;
}
let mut loads = self.module_loads.write().unwrap();
*loads.entry(module_id.to_string()).or_insert(0) += 1;
}
pub fn snapshot(
&self,
trace_entries: usize,
module_cache_size: usize,
ast_cache: &CacheStats,
) -> MetricsSnapshot {
MetricsSnapshot {
execution: self.execution.read().unwrap().clone(),
modules: self.modules.read().unwrap().clone(),
trace_entries,
module_cache_size,
ast_cache: ast_cache.clone(),
}
}
pub fn reset(&self) {
*self.execution.write().unwrap() = ExecutionMetrics::default();
*self.modules.write().unwrap() = ModuleMetrics::default();
self.module_loads.write().unwrap().clear();
}
pub fn module_load_count(&self, module_id: &str) -> usize {
self.module_loads
.read()
.unwrap()
.get(module_id)
.copied()
.unwrap_or(0)
}
pub fn all_module_loads(&self) -> std::collections::HashMap<String, usize> {
self.module_loads.read().unwrap().clone()
}
}
impl Default for MetricsCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_collector_enable_disable() {
let collector = MetricsCollector::new();
assert!(!collector.is_enabled());
collector.enable();
assert!(collector.is_enabled());
collector.disable();
assert!(!collector.is_enabled());
}
#[test]
fn test_execution_metrics() {
let collector = MetricsCollector::new();
collector.enable();
collector.record_execution_start();
std::thread::sleep(Duration::from_millis(10));
collector.record_execution_end();
let snapshot = collector.snapshot(
0,
0,
&CacheStats {
size: 0,
max_size: 0,
hits: 0,
misses: 0,
hit_rate: 0.0,
},
);
assert_eq!(snapshot.execution.execution_count, 1);
assert!(snapshot.execution.total_duration.as_millis() >= 10);
assert_eq!(
snapshot.execution.min_duration,
snapshot.execution.max_duration
);
}
#[test]
fn test_module_metrics() {
let collector = MetricsCollector::new();
collector.enable();
collector.record_module_load("test_module", false); collector.record_module_load("test_module", true); collector.record_module_load("other_module", true);
let snapshot = collector.snapshot(
0,
0,
&CacheStats {
size: 0,
max_size: 0,
hits: 0,
misses: 0,
hit_rate: 0.0,
},
);
assert_eq!(snapshot.modules.load_count, 3);
assert_eq!(snapshot.modules.cache_hits, 2);
assert_eq!(snapshot.modules.cache_misses, 1);
assert!((snapshot.modules.hit_rate - 0.666).abs() < 0.01); }
#[test]
fn test_metrics_disabled_no_collect() {
let collector = MetricsCollector::new();
collector.record_execution_start();
collector.record_execution_end();
collector.record_module_load("test", true);
let snapshot = collector.snapshot(
0,
0,
&CacheStats {
size: 0,
max_size: 0,
hits: 0,
misses: 0,
hit_rate: 0.0,
},
);
assert_eq!(snapshot.execution.execution_count, 0);
assert_eq!(snapshot.modules.load_count, 0);
}
#[test]
fn test_metrics_reset() {
let collector = MetricsCollector::new();
collector.enable();
collector.record_execution_start();
collector.record_execution_end();
collector.record_module_load("test", true);
collector.reset();
let snapshot = collector.snapshot(
0,
0,
&CacheStats {
size: 0,
max_size: 0,
hits: 0,
misses: 0,
hit_rate: 0.0,
},
);
assert_eq!(snapshot.execution.execution_count, 0);
assert_eq!(snapshot.modules.load_count, 0);
assert_eq!(collector.all_module_loads().len(), 0);
}
#[test]
fn test_module_load_counts() {
let collector = MetricsCollector::new();
collector.enable();
collector.record_module_load("module_a", false);
collector.record_module_load("module_a", false);
collector.record_module_load("module_b", true);
assert_eq!(collector.module_load_count("module_a"), 2);
assert_eq!(collector.module_load_count("module_b"), 1);
assert_eq!(collector.module_load_count("module_c"), 0);
let loads = collector.all_module_loads();
assert_eq!(loads.len(), 2);
}
}