1use crate::dictionary::Term;
9use crate::error::{Result, TdbError};
10use parking_lot::RwLock;
11use std::collections::VecDeque;
12use std::sync::atomic::{AtomicU64, Ordering};
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15
16#[derive(Debug, Clone)]
18pub struct QueryMonitorConfig {
19 pub default_timeout: Duration,
21 pub slow_query_threshold: Duration,
23 pub max_slow_query_history: usize,
25 pub enabled: bool,
27}
28
29impl Default for QueryMonitorConfig {
30 fn default() -> Self {
31 Self {
32 default_timeout: Duration::from_secs(30),
33 slow_query_threshold: Duration::from_millis(100),
34 max_slow_query_history: 100,
35 enabled: true,
36 }
37 }
38}
39
40pub struct QueryExecution {
42 id: u64,
44 pattern: QueryPattern,
46 start_time: Instant,
48 deadline: Option<Instant>,
50 running: Arc<AtomicU64>, }
53
54impl QueryExecution {
55 fn new(id: u64, pattern: QueryPattern, timeout: Option<Duration>) -> Self {
57 let start_time = Instant::now();
58 let deadline = timeout.map(|t| start_time + t);
59
60 Self {
61 id,
62 pattern,
63 start_time,
64 deadline,
65 running: Arc::new(AtomicU64::new(1)),
66 }
67 }
68
69 pub fn is_timed_out(&self) -> bool {
71 if let Some(deadline) = self.deadline {
72 Instant::now() >= deadline
73 } else {
74 false
75 }
76 }
77
78 pub fn elapsed(&self) -> Duration {
80 self.start_time.elapsed()
81 }
82
83 fn complete(&self) {
85 self.running.store(0, Ordering::Relaxed);
86 }
87
88 pub fn is_running(&self) -> bool {
90 self.running.load(Ordering::Relaxed) == 1
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct QueryPattern {
97 pub subject: Option<String>,
99 pub predicate: Option<String>,
101 pub object: Option<String>,
103}
104
105impl QueryPattern {
106 pub fn from_terms(
108 subject: Option<&Term>,
109 predicate: Option<&Term>,
110 object: Option<&Term>,
111 ) -> Self {
112 Self {
113 subject: subject.map(|t| format!("{:?}", t)),
114 predicate: predicate.map(|t| format!("{:?}", t)),
115 object: object.map(|t| format!("{:?}", t)),
116 }
117 }
118
119 pub fn describe(&self) -> String {
121 format!(
122 "({}, {}, {})",
123 self.subject.as_deref().unwrap_or("?"),
124 self.predicate.as_deref().unwrap_or("?"),
125 self.object.as_deref().unwrap_or("?")
126 )
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct SlowQueryRecord {
133 pub timestamp: Instant,
135 pub pattern: QueryPattern,
137 pub execution_time: Duration,
139 pub result_count: usize,
141 pub timed_out: bool,
143}
144
145pub struct QueryMonitor {
147 config: QueryMonitorConfig,
149 next_id: AtomicU64,
151 active_queries: Arc<RwLock<Vec<Arc<QueryExecution>>>>,
153 slow_queries: Arc<RwLock<VecDeque<SlowQueryRecord>>>,
155 stats: QueryMonitorStats,
157}
158
159#[derive(Debug, Default)]
161pub struct QueryMonitorStats {
162 pub total_queries: AtomicU64,
164 pub timed_out_queries: AtomicU64,
166 pub slow_queries: AtomicU64,
168 pub total_execution_time_us: AtomicU64,
170}
171
172impl QueryMonitorStats {
173 pub fn avg_execution_time_us(&self) -> u64 {
175 let total = self.total_queries.load(Ordering::Relaxed);
176 self.total_execution_time_us
177 .load(Ordering::Relaxed)
178 .checked_div(total)
179 .unwrap_or(0)
180 }
181
182 pub fn timeout_rate(&self) -> f64 {
184 let total = self.total_queries.load(Ordering::Relaxed) as f64;
185 if total == 0.0 {
186 0.0
187 } else {
188 let timeouts = self.timed_out_queries.load(Ordering::Relaxed) as f64;
189 (timeouts / total) * 100.0
190 }
191 }
192
193 pub fn slow_query_rate(&self) -> f64 {
195 let total = self.total_queries.load(Ordering::Relaxed) as f64;
196 if total == 0.0 {
197 0.0
198 } else {
199 let slow = self.slow_queries.load(Ordering::Relaxed) as f64;
200 (slow / total) * 100.0
201 }
202 }
203}
204
205impl QueryMonitor {
206 pub fn new(config: QueryMonitorConfig) -> Self {
208 Self {
209 config,
210 next_id: AtomicU64::new(1),
211 active_queries: Arc::new(RwLock::new(Vec::new())),
212 slow_queries: Arc::new(RwLock::new(VecDeque::new())),
213 stats: QueryMonitorStats::default(),
214 }
215 }
216
217 pub fn begin_query(
219 &self,
220 subject: Option<&Term>,
221 predicate: Option<&Term>,
222 object: Option<&Term>,
223 timeout_override: Option<Duration>,
224 ) -> Arc<QueryExecution> {
225 if !self.config.enabled {
226 let pattern = QueryPattern::from_terms(subject, predicate, object);
228 return Arc::new(QueryExecution::new(0, pattern, None));
229 }
230
231 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
232 let pattern = QueryPattern::from_terms(subject, predicate, object);
233
234 let timeout = timeout_override.or({
236 if self.config.default_timeout.as_secs() > 0 {
237 Some(self.config.default_timeout)
238 } else {
239 None
240 }
241 });
242
243 let execution = Arc::new(QueryExecution::new(id, pattern, timeout));
244
245 self.active_queries.write().push(Arc::clone(&execution));
247
248 execution
249 }
250
251 pub fn end_query(&self, execution: Arc<QueryExecution>, result_count: usize) -> Result<()> {
253 if !self.config.enabled {
254 return Ok(());
255 }
256
257 execution.complete();
258
259 let timed_out = execution.is_timed_out();
261 if timed_out {
262 self.stats.timed_out_queries.fetch_add(1, Ordering::Relaxed);
263 return Err(TdbError::Other(format!(
264 "Query timed out after {:?}",
265 execution.elapsed()
266 )));
267 }
268
269 let execution_time = execution.elapsed();
270
271 self.stats.total_queries.fetch_add(1, Ordering::Relaxed);
273 self.stats
274 .total_execution_time_us
275 .fetch_add(execution_time.as_micros() as u64, Ordering::Relaxed);
276
277 if execution_time >= self.config.slow_query_threshold {
279 self.log_slow_query(
280 execution.pattern.clone(),
281 execution_time,
282 result_count,
283 timed_out,
284 );
285 }
286
287 self.active_queries.write().retain(|e| e.id != execution.id);
289
290 Ok(())
291 }
292
293 pub fn check_timeout(&self, execution: &Arc<QueryExecution>) -> Result<()> {
295 if !self.config.enabled {
296 return Ok(());
297 }
298
299 if execution.is_timed_out() {
300 Err(TdbError::Other(format!(
301 "Query timeout: exceeded {:?}",
302 execution.elapsed()
303 )))
304 } else {
305 Ok(())
306 }
307 }
308
309 fn log_slow_query(
311 &self,
312 pattern: QueryPattern,
313 execution_time: Duration,
314 result_count: usize,
315 timed_out: bool,
316 ) {
317 self.stats.slow_queries.fetch_add(1, Ordering::Relaxed);
318
319 let record = SlowQueryRecord {
320 timestamp: Instant::now(),
321 pattern: pattern.clone(),
322 execution_time,
323 result_count,
324 timed_out,
325 };
326
327 let mut slow_queries = self.slow_queries.write();
329 slow_queries.push_back(record);
330
331 while slow_queries.len() > self.config.max_slow_query_history {
333 slow_queries.pop_front();
334 }
335
336 log::warn!(
338 "Slow query detected: pattern={}, time={:?}, results={}{}",
339 pattern.describe(),
340 execution_time,
341 result_count,
342 if timed_out { " (TIMED OUT)" } else { "" }
343 );
344 }
345
346 pub fn slow_query_history(&self) -> Vec<SlowQueryRecord> {
348 self.slow_queries.read().iter().cloned().collect()
349 }
350
351 pub fn active_queries(&self) -> Vec<Arc<QueryExecution>> {
353 self.active_queries.read().clone()
354 }
355
356 pub fn stats(&self) -> &QueryMonitorStats {
358 &self.stats
359 }
360
361 pub fn clear_slow_query_history(&self) {
363 self.slow_queries.write().clear();
364 }
365
366 pub fn active_query_count(&self) -> usize {
368 self.active_queries.read().len()
369 }
370
371 pub fn cancel_all(&self) {
373 let active = self.active_queries.read();
374 for query in active.iter() {
375 query.complete();
376 }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use crate::dictionary::Term;
384 use std::thread;
385
386 #[test]
387 fn test_query_monitor_creation() {
388 let config = QueryMonitorConfig::default();
389 let monitor = QueryMonitor::new(config);
390
391 assert_eq!(monitor.active_query_count(), 0);
392 assert_eq!(monitor.slow_query_history().len(), 0);
393 }
394
395 #[test]
396 fn test_begin_and_end_query() {
397 let monitor = QueryMonitor::new(QueryMonitorConfig::default());
398
399 let s = Term::Iri("http://example.org/s".to_string());
400 let execution = monitor.begin_query(Some(&s), None, None, None);
401
402 assert_eq!(monitor.active_query_count(), 1);
403
404 monitor.end_query(execution, 10).unwrap();
405
406 assert_eq!(monitor.active_query_count(), 0);
407 assert_eq!(monitor.stats().total_queries.load(Ordering::Relaxed), 1);
408 }
409
410 #[test]
411 #[ignore] fn test_query_timeout() {
413 let config = QueryMonitorConfig {
414 default_timeout: Duration::from_millis(50),
415 ..Default::default()
416 };
417 let monitor = QueryMonitor::new(config);
418
419 let execution = monitor.begin_query(None, None, None, None);
420
421 thread::sleep(Duration::from_millis(100));
423
424 assert!(execution.is_timed_out());
426
427 let result = monitor.end_query(execution, 0);
428 assert!(result.is_err());
429 assert_eq!(monitor.stats().timed_out_queries.load(Ordering::Relaxed), 1);
430 }
431
432 #[test]
433 fn test_slow_query_logging() {
434 let config = QueryMonitorConfig {
435 slow_query_threshold: Duration::from_millis(10),
436 default_timeout: Duration::from_secs(10),
437 ..Default::default()
438 };
439 let monitor = QueryMonitor::new(config);
440
441 let execution = monitor.begin_query(None, None, None, None);
442
443 thread::sleep(Duration::from_millis(20));
445
446 monitor.end_query(execution, 5).unwrap();
447
448 assert_eq!(monitor.stats().slow_queries.load(Ordering::Relaxed), 1);
450 assert_eq!(monitor.slow_query_history().len(), 1);
451 }
452
453 #[test]
454 fn test_timeout_override() {
455 let monitor = QueryMonitor::new(QueryMonitorConfig::default());
456
457 let custom_timeout = Duration::from_millis(50);
458 let execution = monitor.begin_query(None, None, None, Some(custom_timeout));
459
460 assert!(execution.deadline.is_some());
462 }
463
464 #[test]
465 fn test_multiple_active_queries() {
466 let monitor = QueryMonitor::new(QueryMonitorConfig::default());
467
468 let exec1 = monitor.begin_query(None, None, None, None);
469 let exec2 = monitor.begin_query(None, None, None, None);
470 let exec3 = monitor.begin_query(None, None, None, None);
471
472 assert_eq!(monitor.active_query_count(), 3);
473
474 monitor.end_query(exec1, 1).unwrap();
475 assert_eq!(monitor.active_query_count(), 2);
476
477 monitor.end_query(exec2, 2).unwrap();
478 monitor.end_query(exec3, 3).unwrap();
479 assert_eq!(monitor.active_query_count(), 0);
480 }
481
482 #[test]
483 fn test_slow_query_history_limit() {
484 let config = QueryMonitorConfig {
485 slow_query_threshold: Duration::from_millis(1),
486 max_slow_query_history: 5,
487 default_timeout: Duration::from_secs(10),
488 ..Default::default()
489 };
490 let monitor = QueryMonitor::new(config);
491
492 for _ in 0..10 {
494 let execution = monitor.begin_query(None, None, None, None);
495 thread::sleep(Duration::from_millis(2));
496 monitor.end_query(execution, 1).unwrap();
497 }
498
499 assert_eq!(monitor.slow_query_history().len(), 5);
501 }
502
503 #[test]
504 fn test_query_pattern_describe() {
505 let s = Term::Iri("http://example.org/s".to_string());
506 let p = Term::Iri("http://example.org/p".to_string());
507
508 let pattern = QueryPattern::from_terms(Some(&s), Some(&p), None);
509 let desc = pattern.describe();
510
511 assert!(desc.contains("Iri"));
512 }
513
514 #[test]
515 fn test_stats_calculations() {
516 let monitor = QueryMonitor::new(QueryMonitorConfig::default());
517
518 for _ in 0..10 {
520 let execution = monitor.begin_query(None, None, None, None);
521 monitor.end_query(execution, 1).unwrap();
522 }
523
524 let stats = monitor.stats();
525 assert_eq!(stats.total_queries.load(Ordering::Relaxed), 10);
526 let _ = stats.avg_execution_time_us(); }
529
530 #[test]
531 #[ignore] fn test_timeout_rate() {
533 let config = QueryMonitorConfig {
534 default_timeout: Duration::from_millis(50),
535 ..Default::default()
536 };
537 let monitor = QueryMonitor::new(config);
538
539 for i in 0..10 {
541 let execution = monitor.begin_query(None, None, None, None);
542
543 if i < 5 {
544 thread::sleep(Duration::from_millis(100));
546 }
547
548 let _ = monitor.end_query(execution, 1);
549 }
550
551 let stats = monitor.stats();
552 assert!(
554 stats.timeout_rate() > 30.0,
555 "Expected some timeouts but got rate: {}%",
556 stats.timeout_rate()
557 );
558 }
559
560 #[test]
561 fn test_disabled_monitor() {
562 let config = QueryMonitorConfig {
563 enabled: false,
564 ..Default::default()
565 };
566 let monitor = QueryMonitor::new(config);
567
568 let execution = monitor.begin_query(None, None, None, None);
569 monitor.end_query(execution, 10).unwrap();
570
571 assert_eq!(monitor.stats().total_queries.load(Ordering::Relaxed), 0);
573 }
574
575 #[test]
576 fn test_clear_slow_query_history() {
577 let config = QueryMonitorConfig {
578 slow_query_threshold: Duration::from_millis(1),
579 ..Default::default()
580 };
581 let monitor = QueryMonitor::new(config);
582
583 for _ in 0..5 {
584 let execution = monitor.begin_query(None, None, None, None);
585 thread::sleep(Duration::from_millis(2));
586 monitor.end_query(execution, 1).unwrap();
587 }
588
589 assert_eq!(monitor.slow_query_history().len(), 5);
590
591 monitor.clear_slow_query_history();
592
593 assert_eq!(monitor.slow_query_history().len(), 0);
594 }
595
596 #[test]
597 fn test_cancel_all() {
598 let monitor = QueryMonitor::new(QueryMonitorConfig::default());
599
600 let exec1 = monitor.begin_query(None, None, None, None);
601 let exec2 = monitor.begin_query(None, None, None, None);
602
603 assert!(exec1.is_running());
604 assert!(exec2.is_running());
605
606 monitor.cancel_all();
607
608 assert!(!exec1.is_running());
609 assert!(!exec2.is_running());
610 }
611}