Skip to main content

linkrs_metrics/
error_stats.rs

1//! Error Statistics Module
2//!
3//! Provide functions for error type identification, error stage determination, and error statistics.
4
5use parking_lot::Mutex;
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, VecDeque};
8use std::sync::atomic::{AtomicU64, Ordering};
9
10/// Number of variants in the ErrorType enum (used for determining the array size)
11const ERROR_TYPE_COUNT: usize = 11;
12/// Number of variants in the QueryPhase enumeration (used for determining the array size)
13const QUERY_PHASE_COUNT: usize = 5;
14/// Maximum number of recent errors to keep
15const MAX_RECENT_ERRORS: usize = 100;
16
17/// Convert ErrorType to an array index.
18pub fn error_type_to_index(error_type: ErrorType) -> usize {
19    match error_type {
20        ErrorType::ParseError => 0,
21        ErrorType::ValidationError => 1,
22        ErrorType::PlanningError => 2,
23        ErrorType::OptimizationError => 3,
24        ErrorType::ExecutionError => 4,
25        ErrorType::StorageError => 5,
26        ErrorType::TimeoutError => 6,
27        ErrorType::MemoryLimitError => 7,
28        ErrorType::PermissionError => 8,
29        ErrorType::SessionError => 9,
30        ErrorType::OtherError => 10,
31    }
32}
33
34/// Convert the array index to an ErrorType.
35pub fn index_to_error_type(index: usize) -> Option<ErrorType> {
36    match index {
37        0 => Some(ErrorType::ParseError),
38        1 => Some(ErrorType::ValidationError),
39        2 => Some(ErrorType::PlanningError),
40        3 => Some(ErrorType::OptimizationError),
41        4 => Some(ErrorType::ExecutionError),
42        5 => Some(ErrorType::StorageError),
43        6 => Some(ErrorType::TimeoutError),
44        7 => Some(ErrorType::MemoryLimitError),
45        8 => Some(ErrorType::PermissionError),
46        9 => Some(ErrorType::SessionError),
47        10 => Some(ErrorType::OtherError),
48        _ => None,
49    }
50}
51
52/// Convert QueryPhase to an array index.
53pub fn query_phase_to_index(phase: QueryPhase) -> usize {
54    match phase {
55        QueryPhase::Parse => 0,
56        QueryPhase::Validate => 1,
57        QueryPhase::Plan => 2,
58        QueryPhase::Optimize => 3,
59        QueryPhase::Execute => 4,
60    }
61}
62
63/// Convert the array index to a QueryPhase.
64pub fn index_to_query_phase(index: usize) -> Option<QueryPhase> {
65    match index {
66        0 => Some(QueryPhase::Parse),
67        1 => Some(QueryPhase::Validate),
68        2 => Some(QueryPhase::Plan),
69        3 => Some(QueryPhase::Optimize),
70        4 => Some(QueryPhase::Execute),
71        _ => None,
72    }
73}
74
75/// Query Execution Phase
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77pub enum QueryPhase {
78    Parse,
79    Validate,
80    Plan,
81    Optimize,
82    Execute,
83}
84
85impl std::fmt::Display for QueryPhase {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            QueryPhase::Parse => write!(f, "parse"),
89            QueryPhase::Validate => write!(f, "validate"),
90            QueryPhase::Plan => write!(f, "plan"),
91            QueryPhase::Optimize => write!(f, "optimize"),
92            QueryPhase::Execute => write!(f, "execute"),
93        }
94    }
95}
96
97/// Error type
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub enum ErrorType {
100    ParseError,
101    ValidationError,
102    PlanningError,
103    OptimizationError,
104    ExecutionError,
105    StorageError,
106    TimeoutError,
107    MemoryLimitError,
108    PermissionError,
109    SessionError,
110    OtherError,
111}
112
113impl std::fmt::Display for ErrorType {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            ErrorType::ParseError => write!(f, "parse_error"),
117            ErrorType::ValidationError => write!(f, "validation_error"),
118            ErrorType::PlanningError => write!(f, "planning_error"),
119            ErrorType::OptimizationError => write!(f, "optimization_error"),
120            ErrorType::ExecutionError => write!(f, "execution_error"),
121            ErrorType::StorageError => write!(f, "storage_error"),
122            ErrorType::TimeoutError => write!(f, "timeout_error"),
123            ErrorType::MemoryLimitError => write!(f, "memory_limit_error"),
124            ErrorType::PermissionError => write!(f, "permission_error"),
125            ErrorType::SessionError => write!(f, "session_error"),
126            ErrorType::OtherError => write!(f, "other_error"),
127        }
128    }
129}
130
131/// Extended error message
132#[derive(Debug, Clone)]
133pub struct ErrorInfo {
134    pub error_type: ErrorType,
135    pub error_phase: QueryPhase,
136    pub error_message: String,
137    pub error_details: Option<String>,
138}
139
140/// Recent error record with timestamp and query context
141#[derive(Debug, Clone)]
142pub struct RecentError {
143    pub timestamp: u64,
144    pub error_type: ErrorType,
145    pub error_phase: QueryPhase,
146    pub message: String,
147    pub query_text: Option<String>,
148}
149
150impl RecentError {
151    pub fn new(error_info: &ErrorInfo, query_text: Option<String>) -> Self {
152        Self {
153            timestamp: std::time::SystemTime::now()
154                .duration_since(std::time::UNIX_EPOCH)
155                .unwrap_or_default()
156                .as_secs(),
157            error_type: error_info.error_type,
158            error_phase: error_info.error_phase,
159            message: error_info.error_message.clone(),
160            query_text,
161        }
162    }
163}
164
165impl ErrorInfo {
166    pub fn new(
167        error_type: ErrorType,
168        error_phase: QueryPhase,
169        error_message: impl Into<String>,
170    ) -> Self {
171        Self {
172            error_type,
173            error_phase,
174            error_message: error_message.into(),
175            error_details: None,
176        }
177    }
178
179    pub fn with_details(mut self, details: impl Into<String>) -> Self {
180        self.error_details = Some(details.into());
181        self
182    }
183}
184
185/// Error Statistics Summary
186#[derive(Debug, Clone)]
187pub struct ErrorSummary {
188    pub total_errors: u64,
189    pub errors_by_type: HashMap<ErrorType, u64>,
190    pub errors_by_phase: HashMap<QueryPhase, u64>,
191}
192
193/// Error statistics manager
194#[derive(Debug)]
195pub struct ErrorStatsManager {
196    error_counts: [AtomicU64; ERROR_TYPE_COUNT],
197    error_by_phase: [AtomicU64; QUERY_PHASE_COUNT],
198    recent_errors: Mutex<VecDeque<RecentError>>,
199}
200
201impl ErrorStatsManager {
202    pub fn new() -> Self {
203        Self {
204            error_counts: std::array::from_fn(|_| AtomicU64::new(0)),
205            error_by_phase: std::array::from_fn(|_| AtomicU64::new(0)),
206            recent_errors: Mutex::new(VecDeque::with_capacity(MAX_RECENT_ERRORS)),
207        }
208    }
209
210    pub fn record_error(&self, error_type: ErrorType, phase: QueryPhase) {
211        let index = error_type_to_index(error_type);
212        self.error_counts[index].fetch_add(1, Ordering::Relaxed);
213
214        let phase_index = query_phase_to_index(phase);
215        self.error_by_phase[phase_index].fetch_add(1, Ordering::Relaxed);
216
217        log::warn!("Query error: type={}, phase={}", error_type, phase);
218    }
219
220    pub fn record_error_with_context(&self, error_info: &ErrorInfo, query_text: Option<String>) {
221        self.record_error(error_info.error_type, error_info.error_phase);
222
223        let mut recent = self.recent_errors.lock();
224        let error = RecentError::new(error_info, query_text);
225        recent.push_back(error);
226
227        // Keep only the most recent errors
228        while recent.len() > MAX_RECENT_ERRORS {
229            recent.pop_front();
230        }
231    }
232
233    pub fn get_error_count(&self, error_type: ErrorType) -> u64 {
234        let index = error_type_to_index(error_type);
235        self.error_counts[index].load(Ordering::Relaxed)
236    }
237
238    pub fn get_error_count_by_phase(&self, phase: QueryPhase) -> u64 {
239        let index = query_phase_to_index(phase);
240        self.error_by_phase[index].load(Ordering::Relaxed)
241    }
242
243    pub fn get_all_error_counts(&self) -> HashMap<ErrorType, u64> {
244        let mut result = HashMap::new();
245        for i in 0..ERROR_TYPE_COUNT {
246            if let Some(error_type) = index_to_error_type(i) {
247                let count = self.error_counts[i].load(Ordering::Relaxed);
248                if count > 0 {
249                    result.insert(error_type, count);
250                }
251            }
252        }
253        result
254    }
255
256    pub fn get_all_error_counts_by_phase(&self) -> HashMap<QueryPhase, u64> {
257        let mut result = HashMap::new();
258        for i in 0..QUERY_PHASE_COUNT {
259            if let Some(phase) = index_to_query_phase(i) {
260                let count = self.error_by_phase[i].load(Ordering::Relaxed);
261                if count > 0 {
262                    result.insert(phase, count);
263                }
264            }
265        }
266        result
267    }
268
269    pub fn reset_error_counts(&self) {
270        for counter in &self.error_counts {
271            counter.store(0, Ordering::Relaxed);
272        }
273        for counter in &self.error_by_phase {
274            counter.store(0, Ordering::Relaxed);
275        }
276    }
277
278    pub fn get_error_summary(&self) -> ErrorSummary {
279        ErrorSummary {
280            total_errors: self
281                .error_counts
282                .iter()
283                .map(|c| c.load(Ordering::Relaxed))
284                .sum(),
285            errors_by_type: self.get_all_error_counts(),
286            errors_by_phase: self.get_all_error_counts_by_phase(),
287        }
288    }
289
290    pub fn get_recent_errors(&self, limit: usize) -> Vec<RecentError> {
291        let recent = self.recent_errors.lock();
292        recent.iter().rev().take(limit).cloned().collect()
293    }
294}
295
296impl Default for ErrorStatsManager {
297    fn default() -> Self {
298        Self::new()
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_error_stats_recording() {
308        let stats = ErrorStatsManager::new();
309
310        stats.record_error(ErrorType::ParseError, QueryPhase::Parse);
311        stats.record_error(ErrorType::ParseError, QueryPhase::Parse);
312        stats.record_error(ErrorType::ExecutionError, QueryPhase::Execute);
313
314        assert_eq!(stats.get_error_count(ErrorType::ParseError), 2);
315        assert_eq!(stats.get_error_count(ErrorType::ExecutionError), 1);
316        assert_eq!(stats.get_error_count(ErrorType::StorageError), 0);
317
318        assert_eq!(stats.get_error_count_by_phase(QueryPhase::Parse), 2);
319        assert_eq!(stats.get_error_count_by_phase(QueryPhase::Execute), 1);
320        assert_eq!(stats.get_error_count_by_phase(QueryPhase::Validate), 0);
321    }
322
323    #[test]
324    fn test_error_stats_summary() {
325        let stats = ErrorStatsManager::new();
326
327        stats.record_error(ErrorType::ParseError, QueryPhase::Parse);
328        stats.record_error(ErrorType::ValidationError, QueryPhase::Validate);
329        stats.record_error(ErrorType::ExecutionError, QueryPhase::Execute);
330
331        let summary = stats.get_error_summary();
332        assert_eq!(summary.total_errors, 3);
333        assert_eq!(summary.errors_by_type.get(&ErrorType::ParseError), Some(&1));
334        assert_eq!(summary.errors_by_phase.get(&QueryPhase::Parse), Some(&1));
335    }
336
337    #[test]
338    fn test_error_stats_reset() {
339        let stats = ErrorStatsManager::new();
340
341        stats.record_error(ErrorType::ParseError, QueryPhase::Parse);
342        assert_eq!(stats.get_error_count(ErrorType::ParseError), 1);
343
344        stats.reset_error_counts();
345        assert_eq!(stats.get_error_count(ErrorType::ParseError), 0);
346    }
347
348    #[test]
349    fn test_recent_errors() {
350        let stats = ErrorStatsManager::new();
351
352        let error1 = ErrorInfo::new(ErrorType::ParseError, QueryPhase::Parse, "Error 1");
353        let error2 = ErrorInfo::new(ErrorType::ExecutionError, QueryPhase::Execute, "Error 2");
354
355        stats.record_error_with_context(&error1, Some("SELECT * FROM t1".to_string()));
356        stats.record_error_with_context(&error2, Some("INSERT INTO t2 VALUES (1)".to_string()));
357
358        let recent = stats.get_recent_errors(10);
359        assert_eq!(recent.len(), 2);
360        assert_eq!(recent[0].error_type, ErrorType::ExecutionError);
361        assert_eq!(recent[1].error_type, ErrorType::ParseError);
362        assert!(recent[0].query_text.is_some());
363    }
364}