Skip to main content

vtcode_core/metrics/
discovery_metrics.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct DiscoveryMetrics {
7    pub total_queries: u64,
8    pub successful_queries: u64,
9    pub failed_queries: u64,
10    pub total_time_ms: u64,
11    pub cache_hits: u64,
12    pub recent_queries: VecDeque<QueryRecord>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct QueryRecord {
17    pub keyword: String,
18    pub result_count: u64,
19    pub response_time_ms: u64,
20    pub timestamp: DateTime<Utc>,
21    pub success: bool,
22}
23
24impl DiscoveryMetrics {
25    pub fn new() -> Self {
26        Self {
27            total_queries: 0,
28            successful_queries: 0,
29            failed_queries: 0,
30            total_time_ms: 0,
31            cache_hits: 0,
32            recent_queries: VecDeque::with_capacity(100),
33        }
34    }
35
36    pub fn record_query(&mut self, keyword: String, result_count: u64, response_time_ms: u64) {
37        self.total_queries += 1;
38        self.successful_queries += 1;
39        self.total_time_ms += response_time_ms;
40
41        let record = QueryRecord {
42            keyword,
43            result_count,
44            response_time_ms,
45            timestamp: Utc::now(),
46            success: true,
47        };
48
49        if self.recent_queries.len() >= 100 {
50            self.recent_queries.pop_front();
51        }
52        self.recent_queries.push_back(record);
53    }
54
55    pub fn record_failure(&mut self, keyword: String) {
56        self.total_queries += 1;
57        self.failed_queries += 1;
58
59        let record = QueryRecord {
60            keyword,
61            result_count: 0,
62            response_time_ms: 0,
63            timestamp: Utc::now(),
64            success: false,
65        };
66
67        if self.recent_queries.len() >= 100 {
68            self.recent_queries.pop_front();
69        }
70        self.recent_queries.push_back(record);
71    }
72
73    pub fn record_cache_hit(&mut self) {
74        self.cache_hits += 1;
75    }
76
77    pub fn avg_response_time_ms(&self) -> u64 {
78        self.total_time_ms.checked_div(self.successful_queries).unwrap_or(0)
79    }
80
81    pub fn hit_rate(&self) -> f64 {
82        if self.total_queries > 0 {
83            self.successful_queries as f64 / self.total_queries as f64
84        } else {
85            0.0
86        }
87    }
88
89    pub fn cache_hit_rate(&self) -> f64 {
90        if self.total_queries > 0 {
91            self.cache_hits as f64 / self.total_queries as f64
92        } else {
93            0.0
94        }
95    }
96}
97
98impl Default for DiscoveryMetrics {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_record_query() {
110        let mut metrics = DiscoveryMetrics::new();
111        metrics.record_query("file".to_owned(), 5, 50);
112
113        assert_eq!(metrics.total_queries, 1);
114        assert_eq!(metrics.successful_queries, 1);
115        assert_eq!(metrics.total_time_ms, 50);
116        assert_eq!(metrics.avg_response_time_ms(), 50);
117    }
118
119    #[test]
120    fn test_record_failure() {
121        let mut metrics = DiscoveryMetrics::new();
122        metrics.record_failure("invalid".to_owned());
123
124        assert_eq!(metrics.total_queries, 1);
125        assert_eq!(metrics.failed_queries, 1);
126        assert!((metrics.hit_rate() - 0.0).abs() < f64::EPSILON);
127    }
128
129    #[test]
130    fn test_hit_rate() {
131        let mut metrics = DiscoveryMetrics::new();
132        metrics.record_query("test".to_owned(), 3, 30);
133        metrics.record_query("test2".to_owned(), 2, 25);
134        metrics.record_failure("test3".to_owned());
135
136        assert_eq!(metrics.total_queries, 3);
137        assert!((metrics.hit_rate() - 2.0 / 3.0).abs() < f64::EPSILON);
138    }
139
140    #[test]
141    fn test_cache_hit_rate() {
142        let mut metrics = DiscoveryMetrics::new();
143        metrics.record_query("test".to_owned(), 3, 30);
144        metrics.record_cache_hit();
145        metrics.record_cache_hit();
146
147        assert_eq!(metrics.cache_hits, 2);
148        assert!(metrics.cache_hit_rate() > 0.0);
149    }
150
151    #[test]
152    fn test_recent_queries_limit() {
153        let mut metrics = DiscoveryMetrics::new();
154        for i in 0..150 {
155            metrics.record_query(format!("query_{i}"), 1, 10);
156        }
157
158        assert_eq!(metrics.total_queries, 150);
159        assert_eq!(metrics.recent_queries.len(), 100);
160    }
161}