featherdb_query/
metrics.rs1use std::sync::atomic::{AtomicU64, Ordering};
7
8#[derive(Debug, Default)]
10pub struct QueryMetrics {
11 queries_executed: AtomicU64,
13 total_parse_time_us: AtomicU64,
15 total_plan_time_us: AtomicU64,
17 total_exec_time_us: AtomicU64,
19 rows_scanned: AtomicU64,
21 rows_returned: AtomicU64,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct QueryMetricsSnapshot {
28 pub queries_executed: u64,
30 pub total_parse_time_us: u64,
32 pub total_plan_time_us: u64,
34 pub total_exec_time_us: u64,
36 pub rows_scanned: u64,
38 pub rows_returned: u64,
40}
41
42impl QueryMetrics {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn record_parse_time(&self, micros: u64) {
50 self.total_parse_time_us
51 .fetch_add(micros, Ordering::Relaxed);
52 }
53
54 pub fn record_plan_time(&self, micros: u64) {
56 self.total_plan_time_us.fetch_add(micros, Ordering::Relaxed);
57 }
58
59 pub fn record_exec_time(&self, micros: u64) {
61 self.total_exec_time_us.fetch_add(micros, Ordering::Relaxed);
62 }
63
64 pub fn record_rows_scanned(&self, count: u64) {
66 self.rows_scanned.fetch_add(count, Ordering::Relaxed);
67 }
68
69 pub fn record_rows_returned(&self, count: u64) {
71 self.rows_returned.fetch_add(count, Ordering::Relaxed);
72 }
73
74 pub fn increment_queries(&self) {
76 self.queries_executed.fetch_add(1, Ordering::Relaxed);
77 }
78
79 pub fn snapshot(&self) -> QueryMetricsSnapshot {
81 QueryMetricsSnapshot {
82 queries_executed: self.queries_executed.load(Ordering::Relaxed),
83 total_parse_time_us: self.total_parse_time_us.load(Ordering::Relaxed),
84 total_plan_time_us: self.total_plan_time_us.load(Ordering::Relaxed),
85 total_exec_time_us: self.total_exec_time_us.load(Ordering::Relaxed),
86 rows_scanned: self.rows_scanned.load(Ordering::Relaxed),
87 rows_returned: self.rows_returned.load(Ordering::Relaxed),
88 }
89 }
90
91 pub fn reset(&self) {
93 self.queries_executed.store(0, Ordering::Relaxed);
94 self.total_parse_time_us.store(0, Ordering::Relaxed);
95 self.total_plan_time_us.store(0, Ordering::Relaxed);
96 self.total_exec_time_us.store(0, Ordering::Relaxed);
97 self.rows_scanned.store(0, Ordering::Relaxed);
98 self.rows_returned.store(0, Ordering::Relaxed);
99 }
100}
101
102impl QueryMetricsSnapshot {
103 pub fn avg_parse_time_us(&self) -> f64 {
105 if self.queries_executed == 0 {
106 0.0
107 } else {
108 self.total_parse_time_us as f64 / self.queries_executed as f64
109 }
110 }
111
112 pub fn avg_plan_time_us(&self) -> f64 {
114 if self.queries_executed == 0 {
115 0.0
116 } else {
117 self.total_plan_time_us as f64 / self.queries_executed as f64
118 }
119 }
120
121 pub fn avg_exec_time_us(&self) -> f64 {
123 if self.queries_executed == 0 {
124 0.0
125 } else {
126 self.total_exec_time_us as f64 / self.queries_executed as f64
127 }
128 }
129
130 pub fn avg_total_time_us(&self) -> f64 {
132 if self.queries_executed == 0 {
133 0.0
134 } else {
135 let total =
136 self.total_parse_time_us + self.total_plan_time_us + self.total_exec_time_us;
137 total as f64 / self.queries_executed as f64
138 }
139 }
140
141 pub fn avg_rows_scanned(&self) -> f64 {
143 if self.queries_executed == 0 {
144 0.0
145 } else {
146 self.rows_scanned as f64 / self.queries_executed as f64
147 }
148 }
149
150 pub fn avg_rows_returned(&self) -> f64 {
152 if self.queries_executed == 0 {
153 0.0
154 } else {
155 self.rows_returned as f64 / self.queries_executed as f64
156 }
157 }
158
159 pub fn selectivity_ratio(&self) -> f64 {
161 if self.rows_scanned == 0 {
162 0.0
163 } else {
164 self.rows_returned as f64 / self.rows_scanned as f64
165 }
166 }
167
168 pub fn total_query_time_us(&self) -> u64 {
170 self.total_parse_time_us + self.total_plan_time_us + self.total_exec_time_us
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_metrics_basic() {
180 let metrics = QueryMetrics::new();
181
182 metrics.increment_queries();
184 metrics.record_parse_time(100);
185 metrics.record_plan_time(200);
186 metrics.record_exec_time(500);
187 metrics.record_rows_scanned(1000);
188 metrics.record_rows_returned(50);
189
190 let snapshot = metrics.snapshot();
191 assert_eq!(snapshot.queries_executed, 1);
192 assert_eq!(snapshot.total_parse_time_us, 100);
193 assert_eq!(snapshot.total_plan_time_us, 200);
194 assert_eq!(snapshot.total_exec_time_us, 500);
195 assert_eq!(snapshot.rows_scanned, 1000);
196 assert_eq!(snapshot.rows_returned, 50);
197 }
198
199 #[test]
200 fn test_metrics_accumulation() {
201 let metrics = QueryMetrics::new();
202
203 metrics.increment_queries();
205 metrics.record_parse_time(100);
206 metrics.record_plan_time(200);
207 metrics.record_exec_time(500);
208 metrics.record_rows_scanned(1000);
209 metrics.record_rows_returned(50);
210
211 metrics.increment_queries();
212 metrics.record_parse_time(150);
213 metrics.record_plan_time(250);
214 metrics.record_exec_time(600);
215 metrics.record_rows_scanned(2000);
216 metrics.record_rows_returned(100);
217
218 let snapshot = metrics.snapshot();
219 assert_eq!(snapshot.queries_executed, 2);
220 assert_eq!(snapshot.total_parse_time_us, 250);
221 assert_eq!(snapshot.total_plan_time_us, 450);
222 assert_eq!(snapshot.total_exec_time_us, 1100);
223 assert_eq!(snapshot.rows_scanned, 3000);
224 assert_eq!(snapshot.rows_returned, 150);
225 }
226
227 #[test]
228 fn test_metrics_averages() {
229 let metrics = QueryMetrics::new();
230
231 metrics.increment_queries();
233 metrics.record_parse_time(100);
234 metrics.record_plan_time(200);
235 metrics.record_exec_time(500);
236 metrics.record_rows_scanned(1000);
237 metrics.record_rows_returned(50);
238
239 metrics.increment_queries();
240 metrics.record_parse_time(200);
241 metrics.record_plan_time(400);
242 metrics.record_exec_time(700);
243 metrics.record_rows_scanned(2000);
244 metrics.record_rows_returned(150);
245
246 let snapshot = metrics.snapshot();
247 assert_eq!(snapshot.avg_parse_time_us(), 150.0);
248 assert_eq!(snapshot.avg_plan_time_us(), 300.0);
249 assert_eq!(snapshot.avg_exec_time_us(), 600.0);
250 assert_eq!(snapshot.avg_total_time_us(), 1050.0);
251 assert_eq!(snapshot.avg_rows_scanned(), 1500.0);
252 assert_eq!(snapshot.avg_rows_returned(), 100.0);
253 }
254
255 #[test]
256 fn test_selectivity_ratio() {
257 let metrics = QueryMetrics::new();
258
259 metrics.increment_queries();
260 metrics.record_rows_scanned(1000);
261 metrics.record_rows_returned(100);
262
263 let snapshot = metrics.snapshot();
264 assert_eq!(snapshot.selectivity_ratio(), 0.1);
265 }
266
267 #[test]
268 fn test_metrics_reset() {
269 let metrics = QueryMetrics::new();
270
271 metrics.increment_queries();
272 metrics.record_parse_time(100);
273 metrics.record_plan_time(200);
274 metrics.record_exec_time(500);
275 metrics.record_rows_scanned(1000);
276 metrics.record_rows_returned(50);
277
278 let snapshot_before = metrics.snapshot();
279 assert_eq!(snapshot_before.queries_executed, 1);
280
281 metrics.reset();
282
283 let snapshot_after = metrics.snapshot();
284 assert_eq!(snapshot_after.queries_executed, 0);
285 assert_eq!(snapshot_after.total_parse_time_us, 0);
286 assert_eq!(snapshot_after.total_plan_time_us, 0);
287 assert_eq!(snapshot_after.total_exec_time_us, 0);
288 assert_eq!(snapshot_after.rows_scanned, 0);
289 assert_eq!(snapshot_after.rows_returned, 0);
290 }
291
292 #[test]
293 fn test_zero_queries_averages() {
294 let metrics = QueryMetrics::new();
295 let snapshot = metrics.snapshot();
296
297 assert_eq!(snapshot.avg_parse_time_us(), 0.0);
299 assert_eq!(snapshot.avg_plan_time_us(), 0.0);
300 assert_eq!(snapshot.avg_exec_time_us(), 0.0);
301 assert_eq!(snapshot.avg_total_time_us(), 0.0);
302 assert_eq!(snapshot.avg_rows_scanned(), 0.0);
303 assert_eq!(snapshot.avg_rows_returned(), 0.0);
304 assert_eq!(snapshot.selectivity_ratio(), 0.0);
305 }
306
307 #[test]
308 fn test_total_query_time() {
309 let metrics = QueryMetrics::new();
310
311 metrics.increment_queries();
312 metrics.record_parse_time(100);
313 metrics.record_plan_time(200);
314 metrics.record_exec_time(500);
315
316 let snapshot = metrics.snapshot();
317 assert_eq!(snapshot.total_query_time_us(), 800);
318 }
319}