1#![allow(clippy::cast_precision_loss)]
3
4use std::time::Duration;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8use humantime::format_duration;
9use log::info;
10#[cfg(feature = "profiling")]
11use log::{debug, error};
12use serde_derive::Serialize;
13#[cfg(feature = "profiling")]
14use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
15#[cfg(target_arch = "wasm32")]
16use wasm_bindgen::prelude::*;
17
18#[cfg(target_arch = "wasm32")]
19#[wasm_bindgen]
20pub fn get_high_res_time() -> f64 {
23 use web_sys::js_sys::Reflect;
24 let global = web_sys::js_sys::global();
25 let performance = Reflect::get(&global, &"performance".into())
26 .ok()
27 .and_then(|v: JsValue| v.dyn_into::<web_sys::Performance>().ok());
28 match performance {
29 Some(perf) => perf.now(),
30 None => 0.0,
31 }
32}
33
34#[derive(Serialize)]
36pub struct ExecutionStatistics {
37 pub max_memory_usage: u64,
38 pub max_plans_in_flight: u64,
39 pub max_plan_queue_memory_in_use: u64,
40 pub cpu_time: Duration,
41 pub wall_time: Duration,
42}
43
44#[cfg(feature = "profiling")]
45#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
46const REFRESH_INTERVAL: Duration = Duration::from_secs(1);
48
49#[cfg(feature = "profiling")]
50#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
51pub(crate) struct ExecutionProfilingCollector {
52 #[cfg(not(target_arch = "wasm32"))]
54 start_time: Instant,
55 #[cfg(target_arch = "wasm32")]
56 start_time: f64,
57 #[cfg(not(target_arch = "wasm32"))]
60 last_refresh: Instant,
61 #[cfg(target_arch = "wasm32")]
62 last_refresh: f64,
63 start_cpu_time: u64,
66 max_memory_usage: u64,
70 system: System,
72 process_id: Option<Pid>,
74}
75
76#[cfg(feature = "profiling")]
77impl ExecutionProfilingCollector {
78 pub fn new() -> ExecutionProfilingCollector {
79 let process_id = sysinfo::get_current_pid().ok();
80 #[cfg(target_arch = "wasm32")]
81 let now = get_high_res_time();
82 #[cfg(not(target_arch = "wasm32"))]
83 let now = Instant::now();
84
85 let mut new_stats = ExecutionProfilingCollector {
86 start_time: now,
87 last_refresh: now,
88 start_cpu_time: 0,
89 max_memory_usage: 0,
90 system: System::new(),
91 process_id,
92 };
93 if let Some(process_id) = process_id {
95 debug!("Process ID: {}", process_id);
96 let process_refresh_kind = ProcessRefreshKind::nothing().with_cpu().with_memory();
97 new_stats.update_system_info(process_refresh_kind);
98
99 let process = new_stats.system.process(process_id).unwrap();
100
101 new_stats.max_memory_usage = process.memory();
102 new_stats.start_cpu_time = process.accumulated_cpu_time();
103 }
104
105 new_stats
106 }
107
108 #[inline]
112 pub fn refresh(&mut self) {
113 #[cfg(not(target_arch = "wasm32"))]
114 if self.last_refresh.elapsed() >= REFRESH_INTERVAL {
115 self.poll_memory();
116 self.last_refresh = Instant::now();
117 }
118 }
119
120 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
121 fn poll_memory(&mut self) {
124 if let Some(pid) = self.process_id {
125 self.update_system_info(ProcessRefreshKind::nothing().with_memory());
127 let process = self.system.process(pid).unwrap();
128 self.max_memory_usage = self.max_memory_usage.max(process.memory());
129 }
130 }
131
132 #[allow(unused)]
134 pub fn cpu_time(&mut self) -> u64 {
135 if let Some(process_id) = self.process_id {
136 self.update_system_info(ProcessRefreshKind::nothing().with_cpu());
138
139 let process = self.system.process(process_id).unwrap();
140 process.accumulated_cpu_time() - self.start_cpu_time
141 } else {
142 0
143 }
144 }
145
146 #[inline]
149 fn update_system_info(&mut self, process_refresh_kind: ProcessRefreshKind) {
150 if let Some(pid) = self.process_id {
151 if self.system.refresh_processes_specifics(
152 ProcessesToUpdate::Some(&[pid]),
153 true,
154 process_refresh_kind,
155 ) < 1
156 {
157 error!("could not refresh process statistics");
158 }
159 }
160 }
161
162 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
164 let mut cpu_time_millis = 0;
165
166 if let Some(pid) = self.process_id {
167 self.update_system_info(ProcessRefreshKind::nothing().with_cpu().with_memory());
169 let process = self.system.process(pid).unwrap();
170
171 self.max_memory_usage = self.max_memory_usage.max(process.memory());
172 cpu_time_millis = process.accumulated_cpu_time() - self.start_cpu_time;
173 }
174
175 let cpu_time = Duration::from_millis(cpu_time_millis);
177 #[cfg(target_arch = "wasm32")]
178 let wall_time = get_high_res_time() - self.start_time;
179 #[cfg(not(target_arch = "wasm32"))]
180 let wall_time = self.start_time.elapsed();
181
182 #[cfg(target_arch = "wasm32")]
183 let wall_time = Duration::from_millis(wall_time as u64);
184
185 ExecutionStatistics {
186 max_memory_usage: self.max_memory_usage,
187 max_plans_in_flight: 0,
188 max_plan_queue_memory_in_use: 0,
189 cpu_time,
190 wall_time,
191 }
192 }
193}
194
195#[cfg(not(feature = "profiling"))]
196pub(crate) struct ExecutionProfilingCollector {
197 #[cfg(not(target_arch = "wasm32"))]
198 start_time: Instant,
199 #[cfg(target_arch = "wasm32")]
200 start_time: f64,
201}
202
203#[cfg(not(feature = "profiling"))]
204impl ExecutionProfilingCollector {
205 pub fn new() -> ExecutionProfilingCollector {
206 #[cfg(target_arch = "wasm32")]
207 let now = get_high_res_time();
208 #[cfg(not(target_arch = "wasm32"))]
209 let now = Instant::now();
210
211 ExecutionProfilingCollector { start_time: now }
212 }
213
214 #[inline]
215 pub fn refresh(&mut self) {}
216
217 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
218 #[cfg(target_arch = "wasm32")]
219 let wall_time = get_high_res_time() - self.start_time;
220 #[cfg(not(target_arch = "wasm32"))]
221 let wall_time = self.start_time.elapsed();
222
223 #[cfg(target_arch = "wasm32")]
224 let wall_time = Duration::from_millis(wall_time as u64);
225
226 ExecutionStatistics {
227 max_memory_usage: 0,
228 max_plans_in_flight: 0,
229 max_plan_queue_memory_in_use: 0,
230 cpu_time: Duration::ZERO,
231 wall_time,
232 }
233 }
234}
235
236pub fn print_execution_statistics(summary: &ExecutionStatistics) {
240 println!("━━━━ Execution Summary ━━━━");
241 if summary.max_memory_usage == 0 {
242 println!("Memory and CPU statistics are not available on your platform.");
243 } else {
244 #[cfg(feature = "profiling")]
245 {
246 println!(
247 "{:<25}{}",
248 "Max memory usage:",
249 bytesize::ByteSize::b(summary.max_memory_usage)
250 );
251 println!(
252 "{:<25}{}",
253 "Max plans in flight:", summary.max_plans_in_flight
254 );
255 println!(
256 "{:<25}{}",
257 "Max plan queue memory:",
258 bytesize::ByteSize::b(summary.max_plan_queue_memory_in_use)
259 );
260 println!("{:<25}{}", "CPU time:", format_duration(summary.cpu_time));
261 }
262 }
263
264 println!("{:<25}{}", "Wall time:", format_duration(summary.wall_time));
265}
266
267pub fn log_execution_statistics(stats: &ExecutionStatistics) {
271 info!("Execution complete.");
272 if stats.max_memory_usage == 0 {
273 info!("Memory and CPU statistics are not available on your platform.");
274 } else {
275 #[cfg(feature = "profiling")]
276 {
277 info!(
278 "Max memory usage: {}",
279 bytesize::ByteSize::b(stats.max_memory_usage)
280 );
281 info!("Max plans in flight: {}", stats.max_plans_in_flight);
282 info!(
283 "Max plan queue memory: {}",
284 bytesize::ByteSize::b(stats.max_plan_queue_memory_in_use)
285 );
286 info!("CPU time: {}", format_duration(stats.cpu_time));
287 }
288 }
289 info!("Wall time: {}", format_duration(stats.wall_time));
290}
291
292#[cfg(all(test, feature = "profiling"))]
293mod tests {
294 use std::thread;
295 use std::time::Duration;
296
297 use super::*;
298
299 #[test]
300 fn test_collector_initialization() {
301 let collector = ExecutionProfilingCollector::new();
302
303 assert!(collector.max_memory_usage > 0);
305 }
306
307 #[test]
308 fn test_refresh_respects_interval() {
309 let mut collector = ExecutionProfilingCollector::new();
310 let before = collector.max_memory_usage;
311
312 collector.refresh();
314 let after = collector.max_memory_usage;
315 assert_eq!(before, after);
316
317 thread::sleep(Duration::from_secs(2));
319 collector.refresh();
320 assert!(collector.max_memory_usage >= before);
322 }
323
324 #[test]
325 fn test_compute_final_statistics_structure() {
326 let mut collector = ExecutionProfilingCollector::new();
327
328 thread::sleep(Duration::from_millis(100));
329 let stats = collector.compute_final_statistics();
330
331 assert!(stats.max_memory_usage > 0);
333 assert_eq!(stats.max_plans_in_flight, 0);
334 assert_eq!(stats.max_plan_queue_memory_in_use, 0);
335 assert!(stats.wall_time > Duration::ZERO);
336 }
337
338 #[test]
339 fn test_cpu_time_increases_over_time() {
340 let mut collector = ExecutionProfilingCollector::new();
341
342 let start = Instant::now();
345 while start.elapsed().as_millis() < 30u128 {
346 std::hint::black_box(0); }
348
349 let cpu_time_1 = collector.cpu_time();
350
351 let start = Instant::now();
353 while start.elapsed().as_millis() < 50u128 {
354 std::hint::black_box(0); }
356
357 let cpu_time_2 = collector.cpu_time();
358 assert!(cpu_time_2 > cpu_time_1);
359 }
360}