oxirs_vec/sparql_integration/
monitoring.rs1use parking_lot::RwLock;
4use std::collections::HashMap;
5use std::sync::Arc;
6use std::time::Duration;
7
8#[derive(Debug, Clone)]
10pub struct PerformanceMonitor {
11 pub query_stats: Arc<RwLock<QueryStats>>,
12 pub operation_timings: Arc<RwLock<HashMap<String, Vec<Duration>>>>,
13 pub cache_stats: Arc<RwLock<CacheStats>>,
14}
15
16#[derive(Debug, Clone, Default)]
17pub struct QueryStats {
18 pub total_queries: u64,
19 pub successful_queries: u64,
20 pub failed_queries: u64,
21 pub avg_response_time: Duration,
22 pub max_response_time: Duration,
23 pub min_response_time: Duration,
24}
25
26#[derive(Debug, Clone, Default)]
27pub struct CacheStats {
28 pub cache_hits: u64,
29 pub cache_misses: u64,
30 pub cache_size: usize,
31 pub cache_capacity: usize,
32}
33
34impl PerformanceMonitor {
35 pub fn new() -> Self {
36 Self {
37 query_stats: Arc::new(RwLock::new(QueryStats::default())),
38 operation_timings: Arc::new(RwLock::new(HashMap::new())),
39 cache_stats: Arc::new(RwLock::new(CacheStats::default())),
40 }
41 }
42
43 pub fn record_query(&self, duration: Duration, success: bool) {
44 let mut stats = self.query_stats.write();
45 stats.total_queries += 1;
46
47 if success {
48 stats.successful_queries += 1;
49 } else {
50 stats.failed_queries += 1;
51 }
52
53 if stats.total_queries == 1 {
54 stats.avg_response_time = duration;
55 stats.max_response_time = duration;
56 stats.min_response_time = duration;
57 } else {
58 let total_time = stats
60 .avg_response_time
61 .mul_f64(stats.total_queries as f64 - 1.0)
62 + duration;
63 stats.avg_response_time = total_time.div_f64(stats.total_queries as f64);
64
65 if duration > stats.max_response_time {
66 stats.max_response_time = duration;
67 }
68 if duration < stats.min_response_time {
69 stats.min_response_time = duration;
70 }
71 }
72 }
73
74 pub fn record_operation(&self, operation: &str, duration: Duration) {
75 let mut timings = self.operation_timings.write();
76 timings
77 .entry(operation.to_string())
78 .or_default()
79 .push(duration);
80 }
81
82 pub fn record_cache_hit(&self) {
83 let mut stats = self.cache_stats.write();
84 stats.cache_hits += 1;
85 }
86
87 pub fn record_cache_miss(&self) {
88 let mut stats = self.cache_stats.write();
89 stats.cache_misses += 1;
90 }
91
92 pub fn update_cache_size(&self, size: usize, capacity: usize) {
93 let mut stats = self.cache_stats.write();
94 stats.cache_size = size;
95 stats.cache_capacity = capacity;
96 }
97
98 pub fn get_stats(&self) -> (QueryStats, CacheStats) {
99 let query_stats = self.query_stats.read().clone();
100 let cache_stats = self.cache_stats.read().clone();
101 (query_stats, cache_stats)
102 }
103
104 pub fn get_operation_timings(&self) -> HashMap<String, Vec<Duration>> {
105 self.operation_timings.read().clone()
106 }
107
108 pub fn reset_stats(&self) {
109 *self.query_stats.write() = QueryStats::default();
110 *self.operation_timings.write() = HashMap::new();
111 *self.cache_stats.write() = CacheStats::default();
112 }
113
114 pub fn generate_report(&self) -> PerformanceReport {
116 let (query_stats, cache_stats) = self.get_stats();
117 let operation_timings = self.get_operation_timings();
118
119 let mut operation_summaries = HashMap::new();
120 for (op, timings) in operation_timings {
121 if !timings.is_empty() {
122 let total_time: Duration = timings.iter().sum();
123 let avg_time = total_time / timings.len() as u32;
124 let max_time = timings.iter().max().copied().unwrap_or_default();
125 let min_time = timings.iter().min().copied().unwrap_or_default();
126
127 operation_summaries.insert(
128 op,
129 OperationSummary {
130 count: timings.len(),
131 total_time,
132 avg_time,
133 max_time,
134 min_time,
135 },
136 );
137 }
138 }
139
140 PerformanceReport {
141 query_stats,
142 cache_stats,
143 operation_summaries,
144 report_time: std::time::SystemTime::now(),
145 }
146 }
147}
148
149impl Default for PerformanceMonitor {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct PerformanceReport {
158 pub query_stats: QueryStats,
159 pub cache_stats: CacheStats,
160 pub operation_summaries: HashMap<String, OperationSummary>,
161 pub report_time: std::time::SystemTime,
162}
163
164#[derive(Debug, Clone)]
165pub struct OperationSummary {
166 pub count: usize,
167 pub total_time: Duration,
168 pub avg_time: Duration,
169 pub max_time: Duration,
170 pub min_time: Duration,
171}
172
173impl PerformanceReport {
174 pub fn cache_hit_ratio(&self) -> f64 {
176 let total_requests = self.cache_stats.cache_hits + self.cache_stats.cache_misses;
177 if total_requests == 0 {
178 0.0
179 } else {
180 (self.cache_stats.cache_hits as f64 / total_requests as f64) * 100.0
181 }
182 }
183
184 pub fn query_success_ratio(&self) -> f64 {
186 if self.query_stats.total_queries == 0 {
187 0.0
188 } else {
189 (self.query_stats.successful_queries as f64 / self.query_stats.total_queries as f64)
190 * 100.0
191 }
192 }
193
194 pub fn format_summary(&self) -> String {
196 format!(
197 "Performance Report (generated at {:?})\n\
198 Query Statistics:\n\
199 - Total queries: {}\n\
200 - Successful: {} ({:.1}%)\n\
201 - Failed: {}\n\
202 - Avg response time: {:?}\n\
203 - Min/Max response time: {:?}/{:?}\n\
204 Cache Statistics:\n\
205 - Cache hits: {}\n\
206 - Cache misses: {}\n\
207 - Hit ratio: {:.1}%\n\
208 - Cache utilization: {}/{} ({:.1}%)\n\
209 Operation Summaries: {} operations tracked",
210 self.report_time,
211 self.query_stats.total_queries,
212 self.query_stats.successful_queries,
213 self.query_success_ratio(),
214 self.query_stats.failed_queries,
215 self.query_stats.avg_response_time,
216 self.query_stats.min_response_time,
217 self.query_stats.max_response_time,
218 self.cache_stats.cache_hits,
219 self.cache_stats.cache_misses,
220 self.cache_hit_ratio(),
221 self.cache_stats.cache_size,
222 self.cache_stats.cache_capacity,
223 if self.cache_stats.cache_capacity > 0 {
224 (self.cache_stats.cache_size as f64 / self.cache_stats.cache_capacity as f64)
225 * 100.0
226 } else {
227 0.0
228 },
229 self.operation_summaries.len()
230 )
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use std::time::Duration;
238
239 #[test]
240 fn test_performance_monitor_basic() {
241 let monitor = PerformanceMonitor::new();
242
243 monitor.record_query(Duration::from_millis(100), true);
245 monitor.record_query(Duration::from_millis(200), true);
246 monitor.record_query(Duration::from_millis(50), false);
247
248 let (query_stats, _) = monitor.get_stats();
249 assert_eq!(query_stats.total_queries, 3);
250 assert_eq!(query_stats.successful_queries, 2);
251 assert_eq!(query_stats.failed_queries, 1);
252 }
253
254 #[test]
255 fn test_cache_stats() {
256 let monitor = PerformanceMonitor::new();
257
258 monitor.record_cache_hit();
259 monitor.record_cache_hit();
260 monitor.record_cache_miss();
261
262 let report = monitor.generate_report();
263 assert_eq!(report.cache_stats.cache_hits, 2);
264 assert_eq!(report.cache_stats.cache_misses, 1);
265 assert!((report.cache_hit_ratio() - 66.67).abs() < 0.1);
266 }
267
268 #[test]
269 fn test_operation_timings() {
270 let monitor = PerformanceMonitor::new();
271
272 monitor.record_operation("search", Duration::from_millis(100));
273 monitor.record_operation("search", Duration::from_millis(200));
274 monitor.record_operation("embed", Duration::from_millis(50));
275
276 let report = monitor.generate_report();
277 assert_eq!(report.operation_summaries.len(), 2);
278
279 let search_summary = report.operation_summaries.get("search").unwrap();
280 assert_eq!(search_summary.count, 2);
281 assert_eq!(search_summary.avg_time, Duration::from_millis(150));
282 }
283}