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 if self.successful_queries > 0 {
79 self.total_time_ms / self.successful_queries
80 } else {
81 0
82 }
83 }
84
85 pub fn hit_rate(&self) -> f64 {
86 if self.total_queries > 0 {
87 self.successful_queries as f64 / self.total_queries as f64
88 } else {
89 0.0
90 }
91 }
92
93 pub fn cache_hit_rate(&self) -> f64 {
94 if self.total_queries > 0 {
95 self.cache_hits as f64 / self.total_queries as f64
96 } else {
97 0.0
98 }
99 }
100}
101
102impl Default for DiscoveryMetrics {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_record_query() {
114 let mut metrics = DiscoveryMetrics::new();
115 metrics.record_query("file".to_owned(), 5, 50);
116
117 assert_eq!(metrics.total_queries, 1);
118 assert_eq!(metrics.successful_queries, 1);
119 assert_eq!(metrics.total_time_ms, 50);
120 assert_eq!(metrics.avg_response_time_ms(), 50);
121 }
122
123 #[test]
124 fn test_record_failure() {
125 let mut metrics = DiscoveryMetrics::new();
126 metrics.record_failure("invalid".to_owned());
127
128 assert_eq!(metrics.total_queries, 1);
129 assert_eq!(metrics.failed_queries, 1);
130 assert_eq!(metrics.hit_rate(), 0.0);
131 }
132
133 #[test]
134 fn test_hit_rate() {
135 let mut metrics = DiscoveryMetrics::new();
136 metrics.record_query("test".to_owned(), 3, 30);
137 metrics.record_query("test2".to_owned(), 2, 25);
138 metrics.record_failure("test3".to_owned());
139
140 assert_eq!(metrics.total_queries, 3);
141 assert_eq!(metrics.hit_rate(), 2.0 / 3.0);
142 }
143
144 #[test]
145 fn test_cache_hit_rate() {
146 let mut metrics = DiscoveryMetrics::new();
147 metrics.record_query("test".to_owned(), 3, 30);
148 metrics.record_cache_hit();
149 metrics.record_cache_hit();
150
151 assert_eq!(metrics.cache_hits, 2);
152 assert!(metrics.cache_hit_rate() > 0.0);
153 }
154
155 #[test]
156 fn test_recent_queries_limit() {
157 let mut metrics = DiscoveryMetrics::new();
158 for i in 0..150 {
159 metrics.record_query(format!("query_{}", i), 1, 10);
160 }
161
162 assert_eq!(metrics.total_queries, 150);
163 assert_eq!(metrics.recent_queries.len(), 100);
164 }
165}