Skip to main content

linkrs_metrics/
profile.rs

1//! Querying image data and executing executor statistics
2//!
3//! Query profiles for detailed monitoring and analysis, with microsecond-level accuracy.
4
5use std::time::Instant;
6
7use super::error_stats::{ErrorInfo, ErrorType, QueryPhase};
8use super::executor_stats::ExecutorStats;
9use super::utils::micros_to_millis;
10
11/// Statistics during the query execution phase (in microseconds)
12#[derive(Debug, Clone, Default)]
13pub struct StageMetrics {
14    pub parse_us: u64,
15    pub validate_us: u64,
16    pub plan_us: u64,
17    pub optimize_us: u64,
18    pub execute_us: u64,
19}
20
21impl StageMetrics {
22    pub fn from_query_metrics(metrics: &crate::QueryMetrics) -> Self {
23        Self {
24            parse_us: metrics.parse_time_us,
25            validate_us: metrics.validate_time_us,
26            plan_us: metrics.plan_time_us,
27            optimize_us: metrics.optimize_time_us,
28            execute_us: metrics.execute_time_us,
29        }
30    }
31
32    pub fn total_ms(&self) -> f64 {
33        (self.parse_us + self.validate_us + self.plan_us + self.optimize_us + self.execute_us)
34            as f64
35            / 1000.0
36    }
37
38    pub fn parse_ms(&self) -> f64 {
39        micros_to_millis(self.parse_us)
40    }
41
42    pub fn validate_ms(&self) -> f64 {
43        micros_to_millis(self.validate_us)
44    }
45
46    pub fn plan_ms(&self) -> f64 {
47        micros_to_millis(self.plan_us)
48    }
49
50    pub fn optimize_ms(&self) -> f64 {
51        micros_to_millis(self.optimize_us)
52    }
53
54    pub fn execute_ms(&self) -> f64 {
55        micros_to_millis(self.execute_us)
56    }
57}
58
59/// Actuator statistics
60#[derive(Debug, Clone)]
61pub struct ExecutorStat {
62    pub executor_type: String,
63    pub executor_id: i64,
64    pub stats: ExecutorStats,
65}
66
67impl ExecutorStat {
68    pub fn from_executor(executor_type: String, executor_id: i64, stats: ExecutorStats) -> Self {
69        Self {
70            executor_type,
71            executor_id,
72            stats,
73        }
74    }
75
76    pub fn duration_ms(&self) -> f64 {
77        micros_to_millis(self.stats.exec_time_us)
78    }
79
80    pub fn rows_processed(&self) -> usize {
81        self.stats.num_rows
82    }
83
84    pub fn memory_used(&self) -> usize {
85        self.stats.memory_peak
86    }
87}
88
89/// Query status
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum QueryStatus {
92    Success,
93    Failed,
94}
95
96/// Query the image
97///
98/// Query profiles for detailed monitoring and analysis, with microsecond-level accuracy.
99/// Differences from QueryMetrics:
100/// QueryProfile: Provides detailed monitoring data for internal analysis and logging (in microseconds).
101/// QueryMetrics: A lightweight component designed to provide results to the client in a very short time (within microseconds).
102#[derive(Debug, Clone)]
103pub struct QueryProfile {
104    pub trace_id: String,
105    pub session_id: i64,
106    pub query_text: String,
107    pub start_time: Instant,
108    pub started_at_secs: u64,
109    pub total_duration_us: u64,
110    pub stages: StageMetrics,
111    pub executor_stats: Vec<ExecutorStat>,
112    pub result_count: usize,
113    pub plan_node_count: usize,
114    pub status: QueryStatus,
115    pub error_message: Option<String>,
116    pub error_info: Option<ErrorInfo>,
117}
118
119fn wall_secs_now() -> u64 {
120    std::time::SystemTime::now()
121        .duration_since(std::time::UNIX_EPOCH)
122        .unwrap_or_default()
123        .as_secs()
124}
125
126impl QueryProfile {
127    pub fn new(session_id: i64, query_text: String) -> Self {
128        Self {
129            trace_id: uuid::Uuid::new_v4().to_string(),
130            session_id,
131            query_text,
132            start_time: Instant::now(),
133            started_at_secs: wall_secs_now(),
134            total_duration_us: 0,
135            stages: StageMetrics::default(),
136            executor_stats: Vec::new(),
137            result_count: 0,
138            plan_node_count: 0,
139            status: QueryStatus::Success,
140            error_message: None,
141            error_info: None,
142        }
143    }
144
145    pub fn mark_failed(&mut self, error: String) {
146        self.status = QueryStatus::Failed;
147        self.error_message = Some(error);
148    }
149
150    pub fn mark_failed_with_info(&mut self, error_info: ErrorInfo) {
151        self.status = QueryStatus::Failed;
152        self.error_message = Some(error_info.error_message.clone());
153        self.error_info = Some(error_info);
154    }
155
156    pub fn error_type(&self) -> Option<ErrorType> {
157        self.error_info.as_ref().map(|e| e.error_type)
158    }
159
160    pub fn error_phase(&self) -> Option<QueryPhase> {
161        self.error_info.as_ref().map(|e| e.error_phase)
162    }
163
164    pub fn add_executor_stat(&mut self, stat: ExecutorStat) {
165        self.executor_stats.push(stat);
166    }
167
168    pub fn add_executor_stats_from(
169        &mut self,
170        executor_type: String,
171        executor_id: i64,
172        stats: ExecutorStats,
173    ) {
174        let stat = ExecutorStat::from_executor(executor_type, executor_id, stats);
175        self.executor_stats.push(stat);
176    }
177
178    pub fn set_stage_metrics(&mut self, metrics: crate::QueryMetrics) {
179        self.stages = StageMetrics::from_query_metrics(&metrics);
180    }
181
182    pub fn total_executor_time_us(&self) -> u64 {
183        self.executor_stats
184            .iter()
185            .map(|s| s.stats.exec_time_us)
186            .sum()
187    }
188
189    pub fn total_executor_time_ms(&self) -> f64 {
190        micros_to_millis(self.total_executor_time_us())
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::executor_stats::ExecutorStats;
198
199    #[test]
200    fn test_query_profile_creation() {
201        let profile = QueryProfile::new(123, "MATCH (n) RETURN n".to_string());
202        assert_eq!(profile.session_id, 123);
203        assert_eq!(profile.query_text, "MATCH (n) RETURN n");
204        assert!(!profile.trace_id.is_empty());
205        assert!(matches!(profile.status, QueryStatus::Success));
206    }
207
208    #[test]
209    fn test_query_profile_mark_failed() {
210        let mut profile = QueryProfile::new(123, "MATCH (n) RETURN n".to_string());
211        profile.mark_failed("Syntax error".to_string());
212        assert!(matches!(profile.status, QueryStatus::Failed));
213        assert_eq!(profile.error_message, Some("Syntax error".to_string()));
214    }
215
216    #[test]
217    fn test_query_profile_add_executor_stat() {
218        let mut profile = QueryProfile::new(123, "MATCH (n) RETURN n".to_string());
219        let stats = ExecutorStats {
220            exec_time_us: 100_000,
221            num_rows: 50,
222            memory_peak: 1024,
223            ..ExecutorStats::default()
224        };
225        let stat = ExecutorStat::from_executor("ScanVerticesExecutor".to_string(), 1, stats);
226        profile.add_executor_stat(stat);
227        assert_eq!(profile.executor_stats.len(), 1);
228        assert_eq!(profile.total_executor_time_us(), 100_000);
229        assert!((profile.total_executor_time_ms() - 100.0).abs() < 0.001);
230    }
231
232    #[test]
233    fn test_stage_metrics_default() {
234        let metrics = StageMetrics::default();
235        assert_eq!(metrics.parse_us, 0);
236        assert_eq!(metrics.execute_us, 0);
237        assert!((metrics.total_ms() - 0.0).abs() < 0.001);
238    }
239
240    #[test]
241    fn test_executor_stat_from_executor() {
242        let stats = ExecutorStats {
243            exec_time_us: 1500,
244            num_rows: 100,
245            memory_peak: 2048,
246            ..ExecutorStats::default()
247        };
248
249        let stat = ExecutorStat::from_executor("TestExecutor".to_string(), 1, stats);
250
251        assert!((stat.duration_ms() - 1.5).abs() < 0.001);
252        assert_eq!(stat.rows_processed(), 100);
253        assert_eq!(stat.memory_used(), 2048);
254    }
255
256    #[test]
257    fn test_stage_metrics_from_query_metrics() {
258        let metrics = crate::QueryMetrics {
259            parse_time_us: 100,
260            execute_time_us: 500,
261            ..crate::QueryMetrics::default()
262        };
263
264        let stages = StageMetrics::from_query_metrics(&metrics);
265
266        assert_eq!(stages.parse_us, 100);
267        assert_eq!(stages.execute_us, 500);
268        assert!((stages.parse_ms() - 0.1).abs() < 0.001);
269        assert!((stages.execute_ms() - 0.5).abs() < 0.001);
270    }
271}