1use chrono::{DateTime, Utc};
4use parking_lot::RwLock;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::sync::Arc;
8use std::time::Instant;
9use tracing::debug;
10use uuid::Uuid;
11
12#[derive(Debug, Clone)]
14pub struct QueryContext {
15 pub query_id: Uuid,
16 pub query_type: String,
17 pub query_text: String,
18 pub parameters: Vec<String>,
19 pub start_time: Instant,
20 pub cache_hit: bool,
21 pub result_size: Option<usize>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct QueryPerformanceMetrics {
27 pub query_id: Uuid,
28 pub query_type: String,
29 pub query_text: String,
30 pub execution_time_ms: u64,
31 pub cache_hit: bool,
32 pub result_size: Option<usize>,
33 pub memory_usage_bytes: Option<u64>,
34 pub cpu_usage_percent: Option<f64>,
35 pub timestamp: DateTime<Utc>,
36 pub parameters: Vec<String>,
37 pub optimization_applied: Vec<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct QueryPerformanceStats {
43 pub query_type: String,
44 pub total_executions: u64,
45 pub cache_hits: u64,
46 pub cache_misses: u64,
47 pub average_execution_time_ms: f64,
48 pub min_execution_time_ms: u64,
49 pub max_execution_time_ms: u64,
50 pub p95_execution_time_ms: u64,
51 pub p99_execution_time_ms: u64,
52 pub average_result_size: f64,
53 pub total_memory_usage_bytes: u64,
54 pub average_cpu_usage_percent: f64,
55 pub cache_hit_rate: f64,
56 pub slow_queries_count: u64,
57 pub fast_queries_count: u64,
58 pub last_executed: Option<DateTime<Utc>>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct QueryOptimizationSuggestion {
64 pub query_type: String,
65 pub suggestion_type: OptimizationType,
66 pub description: String,
67 pub potential_improvement_percent: f64,
68 pub priority: OptimizationPriority,
69 pub implementation_effort: ImplementationEffort,
70}
71
72#[non_exhaustive]
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75pub enum OptimizationType {
76 AddIndex,
78 UsePreparedStatement,
80 OptimizeQuery,
82 AddCaching,
84 ReduceResultSet,
86 UseConnectionPooling,
88}
89
90#[non_exhaustive]
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
93pub enum OptimizationPriority {
94 Low,
95 Medium,
96 High,
97 Critical,
98}
99
100#[non_exhaustive]
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
103pub enum ImplementationEffort {
104 Low,
105 Medium,
106 High,
107}
108
109pub struct QueryPerformanceTracker {
111 metrics: Arc<RwLock<Vec<QueryPerformanceMetrics>>>,
113 stats: Arc<RwLock<HashMap<String, QueryPerformanceStats>>>,
115 suggestions: Arc<RwLock<Vec<QueryOptimizationSuggestion>>>,
117 max_metrics: usize,
119 slow_query_threshold_ms: u64,
121 fast_query_threshold_ms: u64,
123}
124
125impl QueryPerformanceTracker {
126 #[must_use]
128 pub fn new(
129 max_metrics: usize,
130 slow_query_threshold_ms: u64,
131 fast_query_threshold_ms: u64,
132 ) -> Self {
133 Self {
134 metrics: Arc::new(RwLock::new(Vec::new())),
135 stats: Arc::new(RwLock::new(HashMap::new())),
136 suggestions: Arc::new(RwLock::new(Vec::new())),
137 max_metrics,
138 slow_query_threshold_ms,
139 fast_query_threshold_ms,
140 }
141 }
142
143 #[must_use]
145 pub fn new_default() -> Self {
146 Self::new(10000, 1000, 100) }
148
149 #[must_use]
151 pub fn start_query(
152 &self,
153 query_type: &str,
154 query_text: &str,
155 parameters: Vec<String>,
156 ) -> QueryContext {
157 QueryContext {
158 query_id: Uuid::new_v4(),
159 query_type: query_type.to_string(),
160 query_text: query_text.to_string(),
161 parameters,
162 start_time: Instant::now(),
163 cache_hit: false,
164 result_size: None,
165 }
166 }
167
168 #[allow(clippy::cast_possible_truncation)]
170 pub fn complete_query(
171 &self,
172 context: QueryContext,
173 cache_hit: bool,
174 result_size: Option<usize>,
175 memory_usage_bytes: Option<u64>,
176 cpu_usage_percent: Option<f64>,
177 optimization_applied: Vec<String>,
178 ) {
179 let execution_time = context.start_time.elapsed();
180 let execution_time_ms = execution_time.as_millis() as u64;
181
182 let metric = QueryPerformanceMetrics {
183 query_id: context.query_id,
184 query_type: context.query_type.clone(),
185 query_text: context.query_text,
186 execution_time_ms,
187 cache_hit,
188 result_size,
189 memory_usage_bytes,
190 cpu_usage_percent,
191 timestamp: Utc::now(),
192 parameters: context.parameters,
193 optimization_applied,
194 };
195
196 {
198 let mut metrics = self.metrics.write();
199 metrics.push(metric.clone());
200
201 if metrics.len() > self.max_metrics {
203 let excess = metrics.len() - self.max_metrics;
204 metrics.drain(0..excess);
205 }
206 }
207
208 self.update_stats(&metric);
210
211 self.generate_optimization_suggestions(&metric);
213
214 debug!(
215 "Query completed: {} ({}ms, cache_hit: {}, size: {:?})",
216 context.query_type, execution_time_ms, cache_hit, result_size
217 );
218 }
219
220 #[must_use]
222 pub fn get_stats(&self, query_type: &str) -> Option<QueryPerformanceStats> {
223 let stats = self.stats.read();
224 stats.get(query_type).cloned()
225 }
226
227 #[must_use]
229 pub fn get_all_stats(&self) -> HashMap<String, QueryPerformanceStats> {
230 let stats = self.stats.read();
231 stats.clone()
232 }
233
234 #[must_use]
236 pub fn get_optimization_suggestions(&self) -> Vec<QueryOptimizationSuggestion> {
237 let suggestions = self.suggestions.read();
238 suggestions.clone()
239 }
240
241 #[must_use]
243 pub fn get_slow_queries(&self) -> Vec<QueryPerformanceMetrics> {
244 let metrics = self.metrics.read();
245 metrics
246 .iter()
247 .filter(|m| m.execution_time_ms >= self.slow_query_threshold_ms)
248 .cloned()
249 .collect()
250 }
251
252 #[must_use]
254 pub fn get_fast_queries(&self) -> Vec<QueryPerformanceMetrics> {
255 let metrics = self.metrics.read();
256 metrics
257 .iter()
258 .filter(|m| m.execution_time_ms <= self.fast_query_threshold_ms)
259 .cloned()
260 .collect()
261 }
262
263 #[must_use]
265 #[allow(clippy::cast_precision_loss)]
266 pub fn get_performance_summary(&self) -> QueryPerformanceSummary {
267 let stats = self.get_all_stats();
268 let suggestions = self.get_optimization_suggestions();
269 let slow_queries = self.get_slow_queries();
270 let fast_queries = self.get_fast_queries();
271
272 let total_queries: u64 = stats.values().map(|s| s.total_executions).sum();
273 let total_cache_hits: u64 = stats.values().map(|s| s.cache_hits).sum();
274 let overall_cache_hit_rate = if total_queries > 0 {
275 total_cache_hits as f64 / total_queries as f64
276 } else {
277 0.0
278 };
279
280 let average_execution_time = if stats.is_empty() {
281 0.0
282 } else {
283 stats
284 .values()
285 .map(|s| s.average_execution_time_ms)
286 .sum::<f64>()
287 / stats.len() as f64
288 };
289
290 QueryPerformanceSummary {
291 timestamp: Utc::now(),
292 total_queries,
293 overall_cache_hit_rate,
294 average_execution_time_ms: average_execution_time,
295 slow_queries_count: slow_queries.len() as u64,
296 fast_queries_count: fast_queries.len() as u64,
297 optimization_suggestions_count: suggestions.len() as u64,
298 stats,
299 suggestions,
300 }
301 }
302
303 #[allow(clippy::cast_precision_loss)]
305 fn update_stats(&self, metric: &QueryPerformanceMetrics) {
306 let mut stats = self.stats.write();
307 let entry =
308 stats
309 .entry(metric.query_type.clone())
310 .or_insert_with(|| QueryPerformanceStats {
311 query_type: metric.query_type.clone(),
312 total_executions: 0,
313 cache_hits: 0,
314 cache_misses: 0,
315 average_execution_time_ms: 0.0,
316 min_execution_time_ms: u64::MAX,
317 max_execution_time_ms: 0,
318 p95_execution_time_ms: 0,
319 p99_execution_time_ms: 0,
320 average_result_size: 0.0,
321 total_memory_usage_bytes: 0,
322 average_cpu_usage_percent: 0.0,
323 cache_hit_rate: 0.0,
324 slow_queries_count: 0,
325 fast_queries_count: 0,
326 last_executed: None,
327 });
328
329 entry.total_executions += 1;
330 entry.last_executed = Some(metric.timestamp);
331
332 if metric.cache_hit {
333 entry.cache_hits += 1;
334 } else {
335 entry.cache_misses += 1;
336 }
337
338 if metric.execution_time_ms < entry.min_execution_time_ms {
340 entry.min_execution_time_ms = metric.execution_time_ms;
341 }
342 if metric.execution_time_ms > entry.max_execution_time_ms {
343 entry.max_execution_time_ms = metric.execution_time_ms;
344 }
345
346 entry.average_execution_time_ms = (entry.average_execution_time_ms
348 * (entry.total_executions - 1) as f64
349 + metric.execution_time_ms as f64)
350 / entry.total_executions as f64;
351
352 if let Some(size) = metric.result_size {
354 entry.average_result_size =
355 (entry.average_result_size * (entry.total_executions - 1) as f64 + size as f64)
356 / entry.total_executions as f64;
357 }
358
359 if let Some(memory) = metric.memory_usage_bytes {
361 entry.total_memory_usage_bytes += memory;
362 }
363
364 if let Some(cpu) = metric.cpu_usage_percent {
366 entry.average_cpu_usage_percent =
367 (entry.average_cpu_usage_percent * (entry.total_executions - 1) as f64 + cpu)
368 / entry.total_executions as f64;
369 }
370
371 entry.cache_hit_rate = if entry.total_executions > 0 {
373 entry.cache_hits as f64 / entry.total_executions as f64
374 } else {
375 0.0
376 };
377
378 if metric.execution_time_ms >= self.slow_query_threshold_ms {
380 entry.slow_queries_count += 1;
381 }
382 if metric.execution_time_ms <= self.fast_query_threshold_ms {
383 entry.fast_queries_count += 1;
384 }
385
386 self.calculate_percentiles(entry);
388 }
389
390 #[allow(
392 clippy::cast_precision_loss,
393 clippy::cast_possible_truncation,
394 clippy::cast_sign_loss
395 )]
396 fn calculate_percentiles(&self, stats: &mut QueryPerformanceStats) {
397 let metrics = self.metrics.read();
399 let mut execution_times: Vec<u64> = metrics
400 .iter()
401 .filter(|m| m.query_type == stats.query_type)
402 .map(|m| m.execution_time_ms)
403 .collect();
404
405 execution_times.sort_unstable();
406
407 if !execution_times.is_empty() {
408 let len = execution_times.len();
409
410 let p95_index = (len as f64 * 0.95) as usize;
412 stats.p95_execution_time_ms = execution_times[p95_index.min(len - 1)];
413
414 let p99_index = (len as f64 * 0.99) as usize;
416 stats.p99_execution_time_ms = execution_times[p99_index.min(len - 1)];
417 }
418 }
419
420 fn generate_optimization_suggestions(&self, metric: &QueryPerformanceMetrics) {
422 let mut suggestions = self.suggestions.write();
423
424 suggestions.retain(|s| s.query_type != metric.query_type);
426
427 let mut new_suggestions = Vec::new();
428
429 if metric.execution_time_ms >= self.slow_query_threshold_ms {
431 new_suggestions.push(QueryOptimizationSuggestion {
432 query_type: metric.query_type.clone(),
433 suggestion_type: OptimizationType::AddIndex,
434 description: format!(
435 "Query is slow ({}ms). Consider adding database indexes.",
436 metric.execution_time_ms
437 ),
438 potential_improvement_percent: 50.0,
439 priority: OptimizationPriority::High,
440 implementation_effort: ImplementationEffort::Medium,
441 });
442
443 new_suggestions.push(QueryOptimizationSuggestion {
444 query_type: metric.query_type.clone(),
445 suggestion_type: OptimizationType::OptimizeQuery,
446 description: "Query structure could be optimized for better performance."
447 .to_string(),
448 potential_improvement_percent: 30.0,
449 priority: OptimizationPriority::Medium,
450 implementation_effort: ImplementationEffort::High,
451 });
452 }
453
454 if !metric.cache_hit {
456 new_suggestions.push(QueryOptimizationSuggestion {
457 query_type: metric.query_type.clone(),
458 suggestion_type: OptimizationType::AddCaching,
459 description:
460 "Query is not cached. Consider implementing caching for better performance."
461 .to_string(),
462 potential_improvement_percent: 80.0,
463 priority: OptimizationPriority::High,
464 implementation_effort: ImplementationEffort::Medium,
465 });
466 }
467
468 if let Some(size) = metric.result_size {
470 if size > 1000 {
471 new_suggestions.push(QueryOptimizationSuggestion {
472 query_type: metric.query_type.clone(),
473 suggestion_type: OptimizationType::ReduceResultSet,
474 description: format!(
475 "Large result set ({size} items). Consider pagination or filtering."
476 ),
477 potential_improvement_percent: 40.0,
478 priority: OptimizationPriority::Medium,
479 implementation_effort: ImplementationEffort::Low,
480 });
481 }
482 }
483
484 if let Some(memory) = metric.memory_usage_bytes {
486 if memory > 10 * 1024 * 1024 {
487 new_suggestions.push(QueryOptimizationSuggestion {
489 query_type: metric.query_type.clone(),
490 suggestion_type: OptimizationType::UsePreparedStatement,
491 description: "High memory usage. Consider using prepared statements."
492 .to_string(),
493 potential_improvement_percent: 20.0,
494 priority: OptimizationPriority::Low,
495 implementation_effort: ImplementationEffort::Low,
496 });
497 }
498 }
499
500 suggestions.extend(new_suggestions);
501 }
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct QueryPerformanceSummary {
507 pub timestamp: DateTime<Utc>,
508 pub total_queries: u64,
509 pub overall_cache_hit_rate: f64,
510 pub average_execution_time_ms: f64,
511 pub slow_queries_count: u64,
512 pub fast_queries_count: u64,
513 pub optimization_suggestions_count: u64,
514 pub stats: HashMap<String, QueryPerformanceStats>,
515 pub suggestions: Vec<QueryOptimizationSuggestion>,
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use std::thread;
522 use std::time::Duration;
523
524 #[test]
525 fn test_query_performance_tracking() {
526 let tracker = QueryPerformanceTracker::new_default();
527
528 let context = tracker.start_query(
530 "test_query",
531 "SELECT * FROM tasks",
532 vec!["param1".to_string()],
533 );
534
535 thread::sleep(Duration::from_millis(100));
537
538 tracker.complete_query(
540 context,
541 false, Some(100), Some(1024), Some(5.0), vec!["index_optimization".to_string()],
546 );
547
548 let stats = tracker.get_stats("test_query");
550 assert!(stats.is_some());
551 let stats = stats.unwrap();
552 assert_eq!(stats.total_executions, 1);
553 assert_eq!(stats.cache_misses, 1);
554 assert_eq!(stats.cache_hits, 0);
555 assert!(stats.average_execution_time_ms >= 100.0);
556 }
557
558 #[test]
559 fn test_optimization_suggestions() {
560 let tracker = QueryPerformanceTracker::new(1000, 50, 10); let context = tracker.start_query("slow_query", "SELECT * FROM tasks", vec![]);
564 thread::sleep(Duration::from_millis(60)); tracker.complete_query(context, false, Some(2000), None, None, vec![]);
566
567 let suggestions = tracker.get_optimization_suggestions();
569 assert!(!suggestions.is_empty());
570 assert!(suggestions.iter().any(|s| s.query_type == "slow_query"));
571 }
572
573 #[test]
574 fn test_performance_summary() {
575 let tracker = QueryPerformanceTracker::new_default();
576
577 for i in 0..5 {
579 let context = tracker.start_query("test_query", "SELECT * FROM tasks", vec![]);
580 thread::sleep(Duration::from_millis(10));
581 tracker.complete_query(
582 context,
583 i % 2 == 0, Some(100),
585 None,
586 None,
587 vec![],
588 );
589 }
590
591 let summary = tracker.get_performance_summary();
592 assert_eq!(summary.total_queries, 5);
593 assert!(summary.overall_cache_hit_rate > 0.0);
594 assert!(summary.average_execution_time_ms > 0.0);
595 }
596}