linkrs_query/executor/explain/
execution_stats_context.rs1use std::collections::HashMap;
7use std::time::Instant;
8
9use parking_lot::Mutex;
10
11use crate::executor::base::ExecutorStats;
12use linkrs_metrics::utils::micros_to_millis;
13
14#[derive(Debug, Clone)]
16pub struct NodeExecutionStats {
17 pub node_id: i64,
18 pub executor_stats: ExecutorStats,
19 pub startup_time_us: u64,
20}
21
22impl NodeExecutionStats {
23 pub fn new(node_id: i64) -> Self {
24 Self {
25 node_id,
26 executor_stats: ExecutorStats::default(),
27 startup_time_us: 0,
28 }
29 }
30
31 pub fn actual_rows(&self) -> usize {
32 self.executor_stats.num_rows
33 }
34
35 pub fn actual_time_us(&self) -> u64 {
36 self.executor_stats.exec_time_us
37 }
38
39 pub fn actual_time_ms(&self) -> f64 {
40 micros_to_millis(self.executor_stats.exec_time_us)
41 }
42
43 pub fn memory_used(&self) -> usize {
44 self.executor_stats.memory_peak
45 }
46}
47
48impl Default for NodeExecutionStats {
49 fn default() -> Self {
50 Self::new(0)
51 }
52}
53
54#[derive(Debug, Clone, Default)]
56pub struct GlobalExecutionStats {
57 pub planning_time_us: u64,
58 pub execution_time_us: u64,
59 pub total_rows: usize,
60 pub peak_memory: usize,
61}
62
63impl GlobalExecutionStats {
64 pub fn planning_time_ms(&self) -> f64 {
65 micros_to_millis(self.planning_time_us)
66 }
67
68 pub fn execution_time_ms(&self) -> f64 {
69 micros_to_millis(self.execution_time_us)
70 }
71}
72
73pub struct ExecutionStatsContext {
78 node_stats: Mutex<HashMap<i64, NodeExecutionStats>>,
79 global_stats: Mutex<GlobalExecutionStats>,
80 start_time: Instant,
81}
82
83impl ExecutionStatsContext {
84 pub fn new() -> Self {
85 Self {
86 node_stats: Mutex::new(HashMap::new()),
87 global_stats: Mutex::new(GlobalExecutionStats::default()),
88 start_time: Instant::now(),
89 }
90 }
91
92 pub fn with_planning_time(planning_time_ms: f64) -> Self {
93 let ctx = Self::new();
94 ctx.global_stats.lock().planning_time_us = (planning_time_ms * 1000.0) as u64;
95 ctx
96 }
97
98 pub fn on_node_start(&self, node_id: i64) {
99 let mut stats = self.node_stats.lock();
100 stats
101 .entry(node_id)
102 .or_insert_with(|| NodeExecutionStats::new(node_id));
103 }
104
105 pub fn on_node_complete(&self, node_id: i64, executor_stats: ExecutorStats) {
106 let mut stats = self.node_stats.lock();
107 let node_stats = NodeExecutionStats {
108 node_id,
109 executor_stats,
110 startup_time_us: 0,
111 };
112 stats.insert(node_id, node_stats);
113 }
114
115 pub fn record_node_rows(&self, node_id: i64, rows: usize) {
116 let mut stats = self.node_stats.lock();
117 if let Some(s) = stats.get_mut(&node_id) {
118 s.executor_stats.num_rows = rows;
119 }
120 }
121
122 pub fn record_node_time(&self, node_id: i64, time_us: u64) {
123 let mut stats = self.node_stats.lock();
124 if let Some(s) = stats.get_mut(&node_id) {
125 s.executor_stats.exec_time_us = time_us;
126 }
127 }
128
129 pub fn record_startup_time(&self, node_id: i64, startup_time_us: u64) {
130 let mut stats = self.node_stats.lock();
131 if let Some(s) = stats.get_mut(&node_id) {
132 s.startup_time_us = startup_time_us;
133 }
134 }
135
136 pub fn record_global_execution_time(&self, time_us: u64) {
137 self.global_stats.lock().execution_time_us = time_us;
138 }
139
140 pub fn collect_stats(&self) -> HashMap<i64, NodeExecutionStats> {
141 self.node_stats.lock().clone()
142 }
143
144 pub fn get_node_stats(&self, node_id: i64) -> Option<NodeExecutionStats> {
145 self.node_stats.lock().get(&node_id).cloned()
146 }
147
148 pub fn get_global_stats(&self) -> GlobalExecutionStats {
149 self.global_stats.lock().clone()
150 }
151
152 pub fn total_elapsed_ms(&self) -> f64 {
153 self.start_time.elapsed().as_micros() as f64 / 1000.0
154 }
155}
156
157impl Default for ExecutionStatsContext {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_execution_stats_context() {
169 let ctx = ExecutionStatsContext::new();
170
171 ctx.on_node_start(1);
172 let exec_stats = ExecutorStats {
173 num_rows: 100,
174 exec_time_us: 5500,
175 ..ExecutorStats::default()
176 };
177 ctx.on_node_complete(1, exec_stats);
178
179 let collected = ctx.collect_stats();
180 assert_eq!(collected.get(&1).unwrap().actual_rows(), 100);
181 assert!((collected.get(&1).unwrap().actual_time_ms() - 5.5).abs() < 0.001);
182 }
183}