1use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5use crate::profile::QueryProfile;
6use crate::utils::micros_to_millis;
7
8use super::core::StatsManager;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SlowQueryStats {
13 pub total: u64,
14 pub min_duration_secs: f64,
15 pub max_duration_secs: f64,
16 pub avg_duration_secs: f64,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ExecutorSummary {
22 pub executor_type: String,
23 pub count: u64,
24 pub total_time_ms: u64,
25 pub total_rows: u64,
26 pub avg_time_ms: f64,
27}
28
29impl StatsManager {
30 pub fn record_query_profile(&self, profile: QueryProfile) {
31 if !self.monitoring_enabled {
32 return;
33 }
34
35 let is_failed = matches!(profile.status, crate::profile::QueryStatus::Failed);
36 let is_slow = profile.total_duration_us >= self.slow_query_threshold_us;
37 if is_slow {
38 self.write_slow_query_log(&profile);
39 } else {
40 self.aggregated_stats.record_query(&profile, false);
41 }
42 self.record_timeseries_query(profile.total_duration_us, is_failed);
43
44 let mut profiles = self.query_profiles.write();
45 if profiles.len() >= self.profile_cache_size {
46 profiles.pop_front();
47 }
48 profiles.push_back(profile);
49 }
50
51 pub(crate) fn write_slow_query_log(&self, profile: &QueryProfile) {
52 self.aggregated_stats.record_query(profile, true);
54
55 if let Some(ref logger) = self.slow_query_logger {
56 logger.log(profile);
57 } else {
58 self.write_slow_query_log_fallback(profile);
59 }
60 }
61
62 fn write_slow_query_log_fallback(&self, profile: &QueryProfile) {
63 let executor_summary: Vec<String> = profile
64 .executor_stats
65 .iter()
66 .map(|stat| {
67 format!(
68 "{}[id={}, {}ms, rows={}, mem={}]",
69 stat.executor_type,
70 stat.executor_id,
71 stat.duration_ms(),
72 stat.rows_processed(),
73 stat.memory_used()
74 )
75 })
76 .collect();
77
78 let error_str = if let Some(ref info) = profile.error_info {
79 format!(
80 " [error={} phase={}]: {}",
81 info.error_type, info.error_phase, info.error_message
82 )
83 } else if let Some(ref msg) = profile.error_message {
84 format!(" [error]: {}", msg)
85 } else {
86 String::new()
87 };
88
89 log::warn!(
90 "Slow query [trace_id={}] [session_id={}] [duration={}ms] [status={}]\n\
91Queries: {}\n\
92Stage statistics: parse={}ms validate={}ms plan={}ms optimize={}ms execute={}ms\n\
93Number of results: {} Number of executors: {} Total executor time: {}ms\n\\\
94Executor details: {}{}",
95 profile.trace_id,
96 profile.session_id,
97 micros_to_millis(profile.total_duration_us),
98 match profile.status {
99 crate::profile::QueryStatus::Success => "success",
100 crate::profile::QueryStatus::Failed => "failed",
101 },
102 profile.query_text,
103 profile.stages.parse_ms(),
104 profile.stages.validate_ms(),
105 profile.stages.plan_ms(),
106 profile.stages.optimize_ms(),
107 profile.stages.execute_ms(),
108 profile.result_count,
109 profile.executor_stats.len(),
110 profile.total_executor_time_ms(),
111 executor_summary.join(", "),
112 error_str
113 );
114 }
115
116 pub fn get_recent_queries(&self, limit: usize) -> Vec<QueryProfile> {
117 let profiles = self.query_profiles.read();
118 profiles.iter().rev().take(limit).cloned().collect()
119 }
120
121 pub fn get_slow_queries(&self, limit: usize) -> Vec<QueryProfile> {
122 let profiles = self.query_profiles.read();
123 profiles
124 .iter()
125 .filter(|p| p.total_duration_us >= self.slow_query_threshold_us)
126 .rev()
127 .take(limit)
128 .cloned()
129 .collect()
130 }
131
132 pub fn get_all_slow_queries(&self, limit: usize) -> SlowQueryStats {
133 let profiles = self.query_profiles.read();
134 let slow_queries: Vec<_> = profiles
135 .iter()
136 .filter(|p| p.total_duration_us >= self.slow_query_threshold_us)
137 .take(limit)
138 .cloned()
139 .collect();
140
141 let total = slow_queries.len() as u64;
142 let durations: Vec<f64> = slow_queries
143 .iter()
144 .map(|p| p.total_duration_us as f64 / 1_000_000.0)
145 .collect();
146
147 let (min, max, avg) = if durations.is_empty() {
148 (0.0, 0.0, 0.0)
149 } else {
150 let min = durations.iter().cloned().fold(f64::INFINITY, f64::min);
151 let max = durations.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
152 let avg = durations.iter().sum::<f64>() / durations.len() as f64;
153 (min, max, avg)
154 };
155
156 SlowQueryStats {
157 total,
158 min_duration_secs: min,
159 max_duration_secs: max,
160 avg_duration_secs: avg,
161 }
162 }
163
164 pub fn get_query_profile(&self, trace_id: &str) -> Option<QueryProfile> {
165 let profiles = self.query_profiles.read();
166 profiles.iter().find(|p| p.trace_id == trace_id).cloned()
167 }
168
169 pub fn get_session_queries(&self, session_id: i64, limit: usize) -> Vec<QueryProfile> {
170 let profiles = self.query_profiles.read();
171 profiles
172 .iter()
173 .filter(|p| p.session_id == session_id)
174 .rev()
175 .take(limit)
176 .cloned()
177 .collect()
178 }
179
180 pub fn get_executor_stats_summary(&self) -> HashMap<String, (u64, u64, usize)> {
181 let profiles = self.query_profiles.read();
182 let mut stats: HashMap<String, (u64, u64, usize)> = HashMap::new();
183
184 for profile in profiles.iter() {
185 for exec_stat in &profile.executor_stats {
186 let entry = stats
187 .entry(exec_stat.executor_type.clone())
188 .or_insert((0, 0, 0));
189 entry.0 += exec_stat.duration_ms() as u64;
190 entry.1 += exec_stat.rows_processed() as u64;
191 entry.2 += 1;
192 }
193 }
194
195 stats
196 }
197
198 pub fn executor_summary_snapshot(&self) -> Vec<ExecutorSummary> {
199 let mut out: Vec<ExecutorSummary> = self
200 .get_executor_stats_summary()
201 .into_iter()
202 .map(|(executor_type, (total_time_ms, total_rows, count))| {
203 let avg_time_ms = if count == 0 {
204 0.0
205 } else {
206 total_time_ms as f64 / count as f64
207 };
208 ExecutorSummary {
209 executor_type,
210 count: count as u64,
211 total_time_ms,
212 total_rows,
213 avg_time_ms,
214 }
215 })
216 .collect();
217 out.sort_by_key(|p| std::cmp::Reverse(p.total_time_ms));
218 out
219 }
220
221 pub fn profiles_in_window(
222 &self,
223 from_secs: Option<u64>,
224 to_secs: Option<u64>,
225 limit: usize,
226 ) -> Vec<QueryProfile> {
227 let profiles = self.query_profiles.read();
228 profiles
229 .iter()
230 .rev()
231 .filter(|p| {
232 if let Some(from) = from_secs {
233 if p.started_at_secs < from {
234 return false;
235 }
236 }
237 if let Some(to) = to_secs {
238 if p.started_at_secs > to {
239 return false;
240 }
241 }
242 true
243 })
244 .take(limit)
245 .cloned()
246 .collect()
247 }
248
249 pub fn clear_query_cache(&self) {
250 let mut profiles = self.query_profiles.write();
251 profiles.clear();
252 }
253
254 pub fn query_cache_size(&self) -> usize {
255 let profiles = self.query_profiles.read();
256 profiles.len()
257 }
258}