1#![allow(clippy::cast_precision_loss)]
3
4use std::time::Duration;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8use bytesize::ByteSize;
9use humantime::format_duration;
10use log::{debug, error, info};
11use serde_derive::Serialize;
12use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
13#[cfg(target_arch = "wasm32")]
14use wasm_bindgen::prelude::*;
15#[cfg(target_arch = "wasm32")]
16use web_sys::window;
17
18#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
19const REFRESH_INTERVAL: Duration = Duration::from_secs(1);
21
22#[cfg(target_arch = "wasm32")]
23#[wasm_bindgen]
24pub fn get_high_res_time() -> f64 {
26 let perf = window().unwrap().performance().unwrap();
27 perf.now() }
29
30#[derive(Serialize)]
32pub struct ExecutionStatistics {
33 pub max_memory_usage: u64,
34 pub cpu_time: Duration,
35 pub wall_time: Duration,
36}
37
38#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
39pub(crate) struct ExecutionProfilingCollector {
40 #[cfg(not(target_arch = "wasm32"))]
42 start_time: Instant,
43 #[cfg(target_arch = "wasm32")]
44 start_time: f64,
45 #[cfg(not(target_arch = "wasm32"))]
48 last_refresh: Instant,
49 #[cfg(target_arch = "wasm32")]
50 last_refresh: f64,
51 start_cpu_time: u64,
54 max_memory_usage: u64,
58 system: System,
60 process_id: Option<Pid>,
62}
63
64impl ExecutionProfilingCollector {
65 pub fn new() -> ExecutionProfilingCollector {
66 let process_id = sysinfo::get_current_pid().ok();
67 #[cfg(target_arch = "wasm32")]
68 let now = get_high_res_time();
69 #[cfg(not(target_arch = "wasm32"))]
70 let now = Instant::now();
71
72 let mut new_stats = ExecutionProfilingCollector {
73 start_time: now,
74 last_refresh: now,
75 start_cpu_time: 0,
76 max_memory_usage: 0,
77 system: System::new(),
78 process_id,
79 };
80 if let Some(process_id) = process_id {
82 debug!("Process ID: {}", process_id);
83 let process_refresh_kind = ProcessRefreshKind::nothing().with_cpu().with_memory();
84 new_stats.update_system_info(process_refresh_kind);
85
86 let process = new_stats.system.process(process_id).unwrap();
87
88 new_stats.max_memory_usage = process.memory();
89 new_stats.start_cpu_time = process.accumulated_cpu_time();
90 }
91
92 new_stats
93 }
94
95 #[inline]
99 pub fn refresh(&mut self) {
100 #[cfg(not(target_arch = "wasm32"))]
101 if self.last_refresh.elapsed() >= REFRESH_INTERVAL {
102 self.poll_memory();
103 self.last_refresh = Instant::now();
104 }
105 }
106
107 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
108 fn poll_memory(&mut self) {
111 if let Some(pid) = self.process_id {
112 self.update_system_info(ProcessRefreshKind::nothing().with_memory());
114 let process = self.system.process(pid).unwrap();
115 self.max_memory_usage = self.max_memory_usage.max(process.memory());
116 }
117 }
118
119 #[allow(unused)]
121 pub fn cpu_time(&mut self) -> u64 {
122 if let Some(process_id) = self.process_id {
123 self.update_system_info(ProcessRefreshKind::nothing().with_cpu());
125
126 let process = self.system.process(process_id).unwrap();
127 process.accumulated_cpu_time() - self.start_cpu_time
128 } else {
129 0
130 }
131 }
132
133 #[inline]
136 fn update_system_info(&mut self, process_refresh_kind: ProcessRefreshKind) {
137 if let Some(pid) = self.process_id {
138 if self.system.refresh_processes_specifics(
139 ProcessesToUpdate::Some(&[pid]),
140 true,
141 process_refresh_kind,
142 ) < 1
143 {
144 error!("could not refresh process statistics");
145 }
146 }
147 }
148
149 pub fn compute_final_statistics(&mut self) -> ExecutionStatistics {
151 let mut cpu_time_millis = 0;
152
153 if let Some(pid) = self.process_id {
154 self.update_system_info(ProcessRefreshKind::nothing().with_cpu().with_memory());
156 let process = self.system.process(pid).unwrap();
157
158 self.max_memory_usage = self.max_memory_usage.max(process.memory());
159 cpu_time_millis = process.accumulated_cpu_time() - self.start_cpu_time;
160 }
161
162 let cpu_time = Duration::from_millis(cpu_time_millis);
164 #[cfg(target_arch = "wasm32")]
165 let wall_time = get_high_res_time() - self.start_time;
166 #[cfg(not(target_arch = "wasm32"))]
167 let wall_time = self.start_time.elapsed();
168
169 #[cfg(target_arch = "wasm32")]
170 let wall_time = Duration::from_millis(wall_time as u64);
171
172 ExecutionStatistics {
173 max_memory_usage: self.max_memory_usage,
174 cpu_time,
175 wall_time,
176 }
177 }
178}
179
180pub fn print_execution_statistics(summary: &ExecutionStatistics) {
184 println!("━━━━ Execution Summary ━━━━");
185 if summary.max_memory_usage == 0 {
186 println!("Memory and CPU statistics are not available on your platform.");
187 } else {
188 println!(
189 "{:<25}{}",
190 "Max memory usage:",
191 ByteSize::b(summary.max_memory_usage)
192 );
193 println!("{:<25}{}", "CPU time:", format_duration(summary.cpu_time));
194 }
195
196 println!("{:<25}{}", "Wall time:", format_duration(summary.wall_time));
197}
198
199pub fn log_execution_statistics(stats: &ExecutionStatistics) {
203 info!("Execution complete.");
204 if stats.max_memory_usage == 0 {
205 info!("Memory and CPU statistics are not available on your platform.");
206 } else {
207 info!("Max memory usage: {}", ByteSize::b(stats.max_memory_usage));
208 info!("CPU time: {}", format_duration(stats.cpu_time));
209 }
210 info!("Wall time: {}", format_duration(stats.wall_time));
211}
212
213#[cfg(test)]
214mod tests {
215 use std::thread;
216 use std::time::Duration;
217
218 use super::*;
219
220 #[test]
221 fn test_collector_initialization() {
222 let collector = ExecutionProfilingCollector::new();
223
224 assert!(collector.max_memory_usage > 0);
226 }
227
228 #[test]
229 fn test_refresh_respects_interval() {
230 let mut collector = ExecutionProfilingCollector::new();
231 let before = collector.max_memory_usage;
232
233 collector.refresh();
235 let after = collector.max_memory_usage;
236 assert_eq!(before, after);
237
238 thread::sleep(Duration::from_secs(2));
240 collector.refresh();
241 assert!(collector.max_memory_usage >= before);
243 }
244
245 #[test]
246 fn test_compute_final_statistics_structure() {
247 let mut collector = ExecutionProfilingCollector::new();
248
249 thread::sleep(Duration::from_millis(100));
250 let stats = collector.compute_final_statistics();
251
252 assert!(stats.max_memory_usage > 0);
254 assert!(stats.wall_time > Duration::ZERO);
255 }
256
257 #[test]
258 fn test_cpu_time_increases_over_time() {
259 let mut collector = ExecutionProfilingCollector::new();
260
261 let start = Instant::now();
264 while start.elapsed().as_millis() < 30u128 {
265 std::hint::black_box(0); }
267
268 let cpu_time_1 = collector.cpu_time();
269
270 let start = Instant::now();
272 while start.elapsed().as_millis() < 50u128 {
273 std::hint::black_box(0); }
275
276 let cpu_time_2 = collector.cpu_time();
277 assert!(cpu_time_2 > cpu_time_1);
278 }
279}