Skip to main content

geographdb_core/
telemetry.rs

1//! Telemetry module for race detection and loop guards
2//!
3//! This module provides runtime verification for:
4//! - Race condition detection in concurrent access patterns
5//! - Loop/infinite recursion detection
6//!
7//! Enabled via the `telemetry` feature flag.
8
9#[cfg(feature = "telemetry")]
10use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
11
12/// Operation tracer for detecting redundant calls
13#[cfg(feature = "telemetry")]
14pub struct OpTracer {
15    call_counts: dashmap::DashMap<String, AtomicU64>,
16}
17
18#[cfg(feature = "telemetry")]
19impl OpTracer {
20    pub fn new() -> Self {
21        Self {
22            call_counts: dashmap::DashMap::new(),
23        }
24    }
25
26    pub fn trace(&self, op: &str, file: &str, line: u32) {
27        let key = format!("{}:{}:{}", file, line, op);
28        let count = self
29            .call_counts
30            .entry(key.clone())
31            .or_insert(AtomicU64::new(0));
32        let new_count = count.fetch_add(1, Ordering::SeqCst) + 1;
33
34        // Warn on suspicious patterns
35        if new_count > 100 {
36            tracing::warn!(
37                "Operation {} called {} times - possible loop",
38                key,
39                new_count
40            );
41        }
42    }
43}
44
45#[cfg(feature = "telemetry")]
46impl Default for OpTracer {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52#[cfg(not(feature = "telemetry"))]
53impl Default for OpTracer {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59/// Loop guard for preventing infinite recursion/iteration
60#[cfg(feature = "telemetry")]
61pub struct LoopGuard {
62    name: &'static str,
63    max_iterations: usize,
64    counter: AtomicUsize,
65}
66
67#[cfg(feature = "telemetry")]
68impl LoopGuard {
69    pub fn new(name: &'static str, max_iterations: usize) -> Self {
70        Self {
71            name,
72            max_iterations,
73            counter: AtomicUsize::new(0),
74        }
75    }
76
77    pub fn check(&self) -> Result<(), String> {
78        let count = self.counter.fetch_add(1, Ordering::SeqCst);
79        if count >= self.max_iterations {
80            return Err(format!(
81                "LoopGuard '{}' exceeded max iterations: {}",
82                self.name, self.max_iterations
83            ));
84        }
85        Ok(())
86    }
87}
88
89#[cfg(feature = "telemetry")]
90impl Drop for LoopGuard {
91    fn drop(&mut self) {
92        let count = self.counter.load(Ordering::SeqCst);
93        if count > self.max_iterations / 2 {
94            tracing::info!("LoopGuard '{}' iterations: {}", self.name, count);
95        }
96    }
97}
98
99/// No-op implementations when telemetry is disabled
100#[cfg(not(feature = "telemetry"))]
101pub struct OpTracer;
102
103#[cfg(not(feature = "telemetry"))]
104impl OpTracer {
105    pub fn new() -> Self {
106        Self
107    }
108    pub fn trace(&self, _op: &str, _file: &str, _line: u32) {}
109}
110
111#[cfg(not(feature = "telemetry"))]
112pub struct LoopGuard;
113
114#[cfg(not(feature = "telemetry"))]
115impl LoopGuard {
116    pub fn new(_name: &'static str, _max_iterations: usize) -> Self {
117        Self
118    }
119    pub fn check(&self) -> Result<(), String> {
120        Ok(())
121    }
122}