eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Performance metrics for monitoring and debugging.
//!
//! Tracks operation timing, cache hit rates, and other performance indicators.

use std::collections::HashMap;
use std::time::{Duration, Instant};

/// Performance metrics collector.
pub struct Metrics {
    /// Operation timings (operation name -> list of durations)
    timings: HashMap<String, Vec<Duration>>,
    /// Cache hit/miss counters
    cache_hits: u64,
    cache_misses: u64,
    /// Start time for uptime tracking
    start_time: Instant,
}

impl Metrics {
    /// Create a new metrics collector.
    pub fn new() -> Self {
        Self {
            timings: HashMap::new(),
            cache_hits: 0,
            cache_misses: 0,
            start_time: Instant::now(),
        }
    }
    
    /// Record an operation timing.
    pub fn record_timing(&mut self, operation: &str, duration: Duration) {
        self.timings
            .entry(operation.to_string())
            .or_insert_with(Vec::new)
            .push(duration);
    }
    
    /// Time an operation using a closure.
    pub fn timed<F, T>(&mut self, operation: &str, f: F) -> T
    where
        F: FnOnce() -> T,
    {
        let start = Instant::now();
        let result = f();
        self.record_timing(operation, start.elapsed());
        result
    }
    
    /// Record a cache hit.
    pub fn record_cache_hit(&mut self) {
        self.cache_hits += 1;
    }
    
    /// Record a cache miss.
    pub fn record_cache_miss(&mut self) {
        self.cache_misses += 1;
    }
    
    /// Get cache hit rate (0.0 to 1.0).
    pub fn cache_hit_rate(&self) -> f64 {
        let total = self.cache_hits + self.cache_misses;
        if total == 0 {
            0.0
        } else {
            self.cache_hits as f64 / total as f64
        }
    }
    
    /// Get average timing for an operation.
    pub fn avg_timing(&self, operation: &str) -> Option<Duration> {
        self.timings.get(operation).map(|durations| {
            if durations.is_empty() {
                Duration::ZERO
            } else {
                let total: Duration = durations.iter().sum();
                total / durations.len() as u32
            }
        })
    }
    
    /// Get uptime.
    pub fn uptime(&self) -> Duration {
        self.start_time.elapsed()
    }
    
    /// Get summary for debugging.
    pub fn summary(&self) -> MetricsSummary {
        MetricsSummary {
            uptime: self.uptime(),
            cache_hit_rate: self.cache_hit_rate(),
            total_cache_ops: self.cache_hits + self.cache_misses,
            operation_counts: self.timings
                .iter()
                .map(|(k, v)| (k.clone(), v.len()))
                .collect(),
        }
    }
    
    /// Reset all metrics.
    pub fn reset(&mut self) {
        self.timings.clear();
        self.cache_hits = 0;
        self.cache_misses = 0;
    }
}

impl Default for Metrics {
    fn default() -> Self {
        Self::new()
    }
}

/// Summary of collected metrics.
#[derive(Debug)]
pub struct MetricsSummary {
    pub uptime: Duration,
    pub cache_hit_rate: f64,
    pub total_cache_ops: u64,
    pub operation_counts: HashMap<String, usize>,
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_cache_hit_rate() {
        let mut metrics = Metrics::new();
        
        metrics.record_cache_hit();
        metrics.record_cache_hit();
        metrics.record_cache_miss();
        
        assert!((metrics.cache_hit_rate() - 0.666).abs() < 0.01);
    }
    
    #[test]
    fn test_timing() {
        let mut metrics = Metrics::new();
        
        let result = metrics.timed("test_op", || {
            std::thread::sleep(Duration::from_millis(10));
            42
        });
        
        assert_eq!(result, 42);
        assert!(metrics.avg_timing("test_op").unwrap() >= Duration::from_millis(10));
    }
}