1use std::time::Duration;
2#[cfg(not(target_arch = "wasm32"))]
3use std::time::Instant;
4
5use humantime::format_duration;
6use log::info;
7#[cfg(feature = "profiling")]
8use log::{debug, error};
9use serde_derive::Serialize;
10#[cfg(feature = "profiling")]
11use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
12#[cfg(target_arch = "wasm32")]
13use wasm_bindgen::prelude::*;
14
15#[cfg(target_arch = "wasm32")]
16#[wasm_bindgen]
17#[must_use]
18pub fn get_high_res_time() -> f64 {
21 use web_sys::js_sys::Reflect;
22 let global = web_sys::js_sys::global();
23 let performance = Reflect::get(&global, &"performance".into())
24 .ok()
25 .and_then(|v: JsValue| v.dyn_into::<web_sys::Performance>().ok());
26 match performance {
27 Some(perf) => perf.now(),
28 None => 0.0,
29 }
30}
31
32#[derive(Serialize)]
34pub struct ExecutionStatistics {
35 pub max_memory_usage: u64,
36 pub max_plans_in_flight: u64,
37 pub max_plan_queue_memory_in_use: u64,
38 pub cpu_time: Duration,
39 pub wall_time: Duration,
40}
41
42#[cfg(feature = "profiling")]
43#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
44const REFRESH_INTERVAL: Duration = Duration::from_secs(1);
46
47#[cfg(feature = "profiling")]
48#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
49pub(crate) struct ExecutionProfilingCollector {
50 #[cfg(not(target_arch = "wasm32"))]
52 start_time: Instant,
53 #[cfg(target_arch = "wasm32")]
54 start_time: f64,
55 #[cfg(not(target_arch = "wasm32"))]
58 last_refresh: Instant,
59 #[cfg(target_arch = "wasm32")]
60 last_refresh: f64,
61 start_cpu_time: u64,
64 max_memory_usage: u64,
68 system: System,
70 process_id: Option<Pid>,
72}
73
74#[cfg(feature = "profiling")]
75impl ExecutionProfilingCollector {
76 #[must_use]
77 pub fn new() -> ExecutionProfilingCollector {
78 let process_id = sysinfo::get_current_pid().ok();
79 #[cfg(target_arch = "wasm32")]
80 let now = get_high_res_time();
81 #[cfg(not(target_arch = "wasm32"))]
82 let now = Instant::now();
83
84 let mut new_stats = ExecutionProfilingCollector {
85 start_time: now,
86 last_refresh: now,
87 start_cpu_time: 0,
88 max_memory_usage: 0,
89 system: System::new(),
90 process_id,
91 };
92 if let Some(process_id) = process_id {
94 debug!("Process ID: {}", process_id);
95 let process_refresh_kind = ProcessRefreshKind::nothing().with_cpu().with_memory();
96 new_stats.update_system_info(process_refresh_kind);
97
98 let process = new_stats.system.process(process_id).unwrap();
99
100 new_stats.max_memory_usage = process.memory();
101 new_stats.start_cpu_time = process.accumulated_cpu_time();
102 }
103
104 new_stats
105 }
106
107 #[inline]
111 pub fn refresh(&mut self) {
112 #[cfg(not(target_arch = "wasm32"))]
113 if self.last_refresh.elapsed() >= REFRESH_INTERVAL {
114 self.poll_memory();
115 self.last_refresh = Instant::now();
116 }
117 }
118
119 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
120 fn poll_memory(&mut self) {
123 if let Some(pid) = self.process_id {
124 self.update_system_info(ProcessRefreshKind::nothing().with_memory());
126 let process = self.system.process(pid).unwrap();
127 self.max_memory_usage = self.max_memory_usage.max(process.memory());
128 }
129 }
130
131 #[allow(unused)]
133 #[must_use]
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 #[must_use]
164 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
165 let mut cpu_time_millis = 0;
166
167 if let Some(pid) = self.process_id {
168 self.update_system_info(ProcessRefreshKind::nothing().with_cpu().with_memory());
170 let process = self.system.process(pid).unwrap();
171
172 self.max_memory_usage = self.max_memory_usage.max(process.memory());
173 cpu_time_millis = process.accumulated_cpu_time() - self.start_cpu_time;
174 }
175
176 let cpu_time = Duration::from_millis(cpu_time_millis);
178 #[cfg(target_arch = "wasm32")]
179 let wall_time = get_high_res_time() - self.start_time;
180 #[cfg(not(target_arch = "wasm32"))]
181 let wall_time = self.start_time.elapsed();
182
183 #[cfg(target_arch = "wasm32")]
184 let wall_time = Duration::from_millis(wall_time as u64);
185
186 ExecutionStatistics {
187 max_memory_usage: self.max_memory_usage,
188 max_plans_in_flight: 0,
189 max_plan_queue_memory_in_use: 0,
190 cpu_time,
191 wall_time,
192 }
193 }
194}
195
196#[cfg(not(feature = "profiling"))]
197pub(crate) struct ExecutionProfilingCollector {
198 #[cfg(not(target_arch = "wasm32"))]
199 start_time: Instant,
200 #[cfg(target_arch = "wasm32")]
201 start_time: f64,
202}
203
204#[cfg(not(feature = "profiling"))]
205impl ExecutionProfilingCollector {
206 #[must_use]
207 pub fn new() -> ExecutionProfilingCollector {
208 #[cfg(target_arch = "wasm32")]
209 let now = get_high_res_time();
210 #[cfg(not(target_arch = "wasm32"))]
211 let now = Instant::now();
212
213 ExecutionProfilingCollector { start_time: now }
214 }
215
216 #[inline]
217 pub fn refresh(&mut self) {}
218
219 #[must_use]
220 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
221 #[cfg(target_arch = "wasm32")]
222 let wall_time = get_high_res_time() - self.start_time;
223 #[cfg(not(target_arch = "wasm32"))]
224 let wall_time = self.start_time.elapsed();
225
226 #[cfg(target_arch = "wasm32")]
227 let wall_time = Duration::from_millis(wall_time as u64);
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 = Instant::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 = Instant::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}