vtcode_core/metrics/
discovery_metrics.rs1use 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}