1use std::time::Instant;
6
7use super::error_stats::{ErrorInfo, ErrorType, QueryPhase};
8use super::executor_stats::ExecutorStats;
9use super::utils::micros_to_millis;
10
11#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum QueryStatus {
92 Success,
93 Failed,
94}
95
96#[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}