1use std::time::Duration;
2
3use humantime::format_duration;
4use log::info;
5#[cfg(feature = "profiling")]
6use log::{debug, error};
7use serde_derive::Serialize;
8#[cfg(feature = "profiling")]
9use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
10#[cfg(target_arch = "wasm32")]
11use wasm_bindgen::{JsCast, JsValue};
12
13#[cfg(feature = "profiling")]
14use crate::profiling::QueryProfiler;
15
16#[cfg(not(target_arch = "wasm32"))]
21pub type ProfilingInstant = std::time::Instant;
22
23#[cfg(target_arch = "wasm32")]
28#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
29pub struct ProfilingInstant(f64);
30
31#[cfg(target_arch = "wasm32")]
32impl ProfilingInstant {
33 #[must_use]
34 pub fn now() -> Self {
35 use web_sys::js_sys::Reflect;
36
37 let global = web_sys::js_sys::global();
38 let performance = Reflect::get(&global, &"performance".into())
39 .ok()
40 .and_then(|value: JsValue| value.dyn_into::<web_sys::Performance>().ok());
41
42 Self(performance.map_or(0.0, |performance| performance.now()))
43 }
44
45 #[must_use]
46 pub fn elapsed(&self) -> Duration {
47 let elapsed_milliseconds = Self::now().0 - self.0;
48 if !elapsed_milliseconds.is_finite() || elapsed_milliseconds <= 0.0 {
49 return Duration::ZERO;
50 }
51
52 Duration::from_secs_f64(elapsed_milliseconds / 1000.0)
53 }
54}
55
56#[derive(Serialize)]
58pub struct ExecutionStatistics {
59 pub max_memory_usage: u64,
60 pub max_plans_in_flight: u64,
61 pub max_plan_queue_memory_in_use: u64,
62 pub cpu_time: Duration,
63 pub wall_time: Duration,
64}
65
66#[cfg(feature = "profiling")]
67#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
68const REFRESH_INTERVAL: Duration = Duration::from_secs(1);
70
71#[cfg(feature = "profiling")]
72#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
73pub(crate) struct ExecutionProfilingCollector {
74 pub(crate) query_profiler: QueryProfiler,
76 start_time: ProfilingInstant,
78 last_refresh: ProfilingInstant,
81 start_cpu_time: u64,
84 max_memory_usage: u64,
88 system: System,
90 process_id: Option<Pid>,
92}
93
94#[cfg(feature = "profiling")]
95impl ExecutionProfilingCollector {
96 #[must_use]
97 pub fn new() -> ExecutionProfilingCollector {
98 let process_id = sysinfo::get_current_pid().ok();
99 let now = ProfilingInstant::now();
100
101 let mut new_stats = ExecutionProfilingCollector {
102 query_profiler: QueryProfiler::default(),
103 start_time: now,
104 last_refresh: now,
105 start_cpu_time: 0,
106 max_memory_usage: 0,
107 system: System::new(),
108 process_id,
109 };
110 if let Some(process_id) = process_id {
112 debug!("Process ID: {}", process_id);
113 let process_refresh_kind = ProcessRefreshKind::nothing().with_cpu().with_memory();
114 new_stats.update_system_info(process_refresh_kind);
115
116 let process = new_stats.system.process(process_id).unwrap();
117
118 new_stats.max_memory_usage = process.memory();
119 new_stats.start_cpu_time = process.accumulated_cpu_time();
120 }
121
122 new_stats
123 }
124
125 #[inline]
129 pub fn refresh(&mut self) {
130 #[cfg(not(target_arch = "wasm32"))]
131 if self.last_refresh.elapsed() >= REFRESH_INTERVAL {
132 self.poll_memory();
133 self.last_refresh = ProfilingInstant::now();
134 }
135 }
136
137 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
138 fn poll_memory(&mut self) {
141 if let Some(pid) = self.process_id {
142 self.update_system_info(ProcessRefreshKind::nothing().with_memory());
144 let process = self.system.process(pid).unwrap();
145 self.max_memory_usage = self.max_memory_usage.max(process.memory());
146 }
147 }
148
149 #[allow(unused)]
151 #[must_use]
152 pub fn cpu_time(&mut self) -> u64 {
153 if let Some(process_id) = self.process_id {
154 self.update_system_info(ProcessRefreshKind::nothing().with_cpu());
156
157 let process = self.system.process(process_id).unwrap();
158 process.accumulated_cpu_time() - self.start_cpu_time
159 } else {
160 0
161 }
162 }
163
164 #[inline]
167 fn update_system_info(&mut self, process_refresh_kind: ProcessRefreshKind) {
168 if let Some(pid) = self.process_id {
169 if self.system.refresh_processes_specifics(
170 ProcessesToUpdate::Some(&[pid]),
171 true,
172 process_refresh_kind,
173 ) < 1
174 {
175 error!("could not refresh process statistics");
176 }
177 }
178 }
179
180 #[must_use]
182 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
183 let mut cpu_time_millis = 0;
184
185 if let Some(pid) = self.process_id {
186 self.update_system_info(ProcessRefreshKind::nothing().with_cpu().with_memory());
188 let process = self.system.process(pid).unwrap();
189
190 self.max_memory_usage = self.max_memory_usage.max(process.memory());
191 cpu_time_millis = process.accumulated_cpu_time() - self.start_cpu_time;
192 }
193
194 let cpu_time = Duration::from_millis(cpu_time_millis);
196 let wall_time = self.start_time.elapsed();
197
198 ExecutionStatistics {
199 max_memory_usage: self.max_memory_usage,
200 max_plans_in_flight: 0,
201 max_plan_queue_memory_in_use: 0,
202 cpu_time,
203 wall_time,
204 }
205 }
206}
207
208#[cfg(not(feature = "profiling"))]
209pub(crate) struct ExecutionProfilingCollector {
210 start_time: ProfilingInstant,
211}
212
213#[cfg(not(feature = "profiling"))]
214impl ExecutionProfilingCollector {
215 #[must_use]
216 pub fn new() -> ExecutionProfilingCollector {
217 ExecutionProfilingCollector {
218 start_time: ProfilingInstant::now(),
219 }
220 }
221
222 #[inline]
223 pub fn refresh(&mut self) {}
224
225 #[must_use]
226 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
227 let wall_time = self.start_time.elapsed();
228
229 ExecutionStatistics {
230 max_memory_usage: 0,
231 max_plans_in_flight: 0,
232 max_plan_queue_memory_in_use: 0,
233 cpu_time: Duration::ZERO,
234 wall_time,
235 }
236 }
237}
238
239pub fn print_execution_statistics(summary: &ExecutionStatistics) {
243 println!("━━━━ Execution Summary ━━━━");
244 #[cfg(feature = "profiling")]
245 {
246 if cfg!(target_family = "wasm") {
247 println!("Memory and CPU statistics are not available on your platform.");
248 } else {
249 println!(
250 "{:<25}{}",
251 "Max memory usage:",
252 bytesize::ByteSize::b(summary.max_memory_usage)
253 );
254 println!(
255 "{:<25}{}",
256 "Max plans in flight:", summary.max_plans_in_flight
257 );
258 println!(
259 "{:<25}{}",
260 "Max plan queue memory:",
261 bytesize::ByteSize::b(summary.max_plan_queue_memory_in_use)
262 );
263 println!("{:<25}{}", "CPU time:", format_duration(summary.cpu_time));
264 }
265 }
266
267 println!("{:<25}{}", "Wall time:", format_duration(summary.wall_time));
268}
269
270pub fn log_execution_statistics(stats: &ExecutionStatistics) {
274 info!("Execution complete.");
275 #[cfg(feature = "profiling")]
276 {
277 if cfg!(target_family = "wasm") {
278 info!("Memory and CPU statistics are not available on your platform.");
279 } else {
280 info!(
281 "Max memory usage: {}",
282 bytesize::ByteSize::b(stats.max_memory_usage)
283 );
284 info!("Max plans in flight: {}", stats.max_plans_in_flight);
285 info!(
286 "Max plan queue memory: {}",
287 bytesize::ByteSize::b(stats.max_plan_queue_memory_in_use)
288 );
289 info!("CPU time: {}", format_duration(stats.cpu_time));
290 }
291 }
292 info!("Wall time: {}", format_duration(stats.wall_time));
293}
294
295#[cfg(all(test, feature = "profiling"))]
296mod tests {
297 use std::thread;
298 use std::time::Duration;
299
300 use super::*;
301
302 #[test]
303 fn test_collector_initialization() {
304 let collector = ExecutionProfilingCollector::new();
305
306 assert!(collector.max_memory_usage > 0);
308 }
309
310 #[test]
311 fn test_refresh_respects_interval() {
312 let mut collector = ExecutionProfilingCollector::new();
313 let before = collector.max_memory_usage;
314
315 collector.refresh();
317 let after = collector.max_memory_usage;
318 assert_eq!(before, after);
319
320 thread::sleep(Duration::from_secs(2));
322 collector.refresh();
323 assert!(collector.max_memory_usage >= before);
325 }
326
327 #[test]
328 fn test_compute_final_statistics_structure() {
329 let mut collector = ExecutionProfilingCollector::new();
330
331 thread::sleep(Duration::from_millis(100));
332 let stats = collector.compute_final_statistics();
333
334 assert!(stats.max_memory_usage > 0);
336 assert_eq!(stats.max_plans_in_flight, 0);
337 assert_eq!(stats.max_plan_queue_memory_in_use, 0);
338 assert!(stats.wall_time > Duration::ZERO);
339 }
340
341 #[test]
342 fn test_cpu_time_increases_over_time() {
343 let mut collector = ExecutionProfilingCollector::new();
344
345 let start = ProfilingInstant::now();
348 while start.elapsed().as_millis() < 30u128 {
349 std::hint::black_box(0); }
351
352 let cpu_time_1 = collector.cpu_time();
353
354 let start = ProfilingInstant::now();
356 while start.elapsed().as_millis() < 50u128 {
357 std::hint::black_box(0); }
359
360 let cpu_time_2 = collector.cpu_time();
361 assert!(cpu_time_2 > cpu_time_1);
362 }
363}