1use crate::{core::profiler::global_profiler, ProfileEvent};
7use backtrace::Backtrace;
8use std::time::Instant;
9
10pub struct ScopeGuard {
12 name: String,
13 category: String,
14 start: Instant,
15}
16
17impl ScopeGuard {
18 pub fn new(name: &str) -> Self {
20 Self::with_category(name, "general")
21 }
22
23 pub fn with_category(name: &str, category: &str) -> Self {
25 Self {
26 name: name.to_string(),
27 category: category.to_string(),
28 start: Instant::now(),
29 }
30 }
31
32 pub fn elapsed(&self) -> std::time::Duration {
34 self.start.elapsed()
35 }
36
37 pub fn name(&self) -> &str {
39 &self.name
40 }
41
42 pub fn category(&self) -> &str {
44 &self.category
45 }
46}
47
48impl Drop for ScopeGuard {
49 fn drop(&mut self) {
50 let duration = self.start.elapsed();
51 let thread_id = get_thread_id();
52
53 let profiler_arc = global_profiler();
55 let (stack_trace, stack_trace_overhead_ns) = {
56 let profiler = profiler_arc.lock();
57 if profiler.are_stack_traces_enabled() {
58 if profiler.is_overhead_tracking_enabled() {
59 capture_stack_trace_with_overhead()
60 } else {
61 (capture_stack_trace(), 0)
62 }
63 } else {
64 (None, 0)
65 }
66 };
67
68 let event = ProfileEvent {
69 name: self.name.clone(),
70 category: self.category.clone(),
71 start_us: 0, duration_us: duration.as_micros() as u64,
73 thread_id,
74 operation_count: None,
75 flops: None,
76 bytes_transferred: None,
77 stack_trace,
78 };
79
80 {
82 let mut profiler = profiler_arc.lock();
83 if profiler.is_overhead_tracking_enabled() && stack_trace_overhead_ns > 0 {
84 profiler.overhead_stats.stack_trace_time_ns += stack_trace_overhead_ns;
85 profiler.overhead_stats.stack_trace_count += 1;
86 profiler.overhead_stats.total_overhead_ns += stack_trace_overhead_ns;
87 }
88 profiler.add_event(event);
89 }
90 }
91}
92
93fn get_thread_id() -> usize {
95 let thread_id = std::thread::current().id();
96 format!("{thread_id:?}")
97 .chars()
98 .filter(|c| c.is_ascii_digit())
99 .collect::<String>()
100 .parse::<usize>()
101 .unwrap_or(0)
102}
103
104fn capture_stack_trace() -> Option<String> {
106 #[cfg(debug_assertions)]
108 {
109 let bt = Backtrace::new();
110 Some(format!("{:?}", bt))
111 }
112 #[cfg(not(debug_assertions))]
113 None
114}
115
116fn capture_stack_trace_with_overhead() -> (Option<String>, u64) {
118 let start = Instant::now();
119 let stack_trace = capture_stack_trace();
120 let overhead_ns = start.elapsed().as_nanos() as u64;
121 (stack_trace, overhead_ns)
122}
123
124pub fn profile_function<F, R>(name: &str, func: F) -> R
126where
127 F: FnOnce() -> R,
128{
129 let _guard = ScopeGuard::new(name);
130 func()
131}
132
133pub fn profile_function_with_category<F, R>(name: &str, category: &str, func: F) -> R
135where
136 F: FnOnce() -> R,
137{
138 let _guard = ScopeGuard::with_category(name, category);
139 func()
140}
141
142pub struct MetricsScope {
144 guard: ScopeGuard,
145 operation_count: Option<u64>,
146 flops: Option<u64>,
147 bytes_transferred: Option<u64>,
148}
149
150impl MetricsScope {
151 pub fn new(name: &str) -> Self {
153 Self {
154 guard: ScopeGuard::new(name),
155 operation_count: None,
156 flops: None,
157 bytes_transferred: None,
158 }
159 }
160
161 pub fn with_category(name: &str, category: &str) -> Self {
163 Self {
164 guard: ScopeGuard::with_category(name, category),
165 operation_count: None,
166 flops: None,
167 bytes_transferred: None,
168 }
169 }
170
171 pub fn set_operation_count(&mut self, count: u64) {
173 self.operation_count = Some(count);
174 }
175
176 pub fn set_flops(&mut self, flops: u64) {
178 self.flops = Some(flops);
179 }
180
181 pub fn set_bytes_transferred(&mut self, bytes: u64) {
183 self.bytes_transferred = Some(bytes);
184 }
185
186 pub fn add_operations(&mut self, count: u64) {
188 self.operation_count = Some(self.operation_count.unwrap_or(0) + count);
189 }
190
191 pub fn add_flops(&mut self, flops: u64) {
193 self.flops = Some(self.flops.unwrap_or(0) + flops);
194 }
195
196 pub fn add_bytes(&mut self, bytes: u64) {
198 self.bytes_transferred = Some(self.bytes_transferred.unwrap_or(0) + bytes);
199 }
200
201 pub fn metrics(&self) -> (Option<u64>, Option<u64>, Option<u64>) {
203 (self.operation_count, self.flops, self.bytes_transferred)
204 }
205}
206
207impl Drop for MetricsScope {
208 fn drop(&mut self) {
209 let duration = self.guard.start.elapsed();
210 let thread_id = get_thread_id();
211
212 let profiler_arc = global_profiler();
214 let (stack_trace, stack_trace_overhead_ns) = {
215 let profiler = profiler_arc.lock();
216 if profiler.are_stack_traces_enabled() {
217 if profiler.is_overhead_tracking_enabled() {
218 capture_stack_trace_with_overhead()
219 } else {
220 (capture_stack_trace(), 0)
221 }
222 } else {
223 (None, 0)
224 }
225 };
226
227 let event = ProfileEvent {
228 name: self.guard.name.clone(),
229 category: self.guard.category.clone(),
230 start_us: 0, duration_us: duration.as_micros() as u64,
232 thread_id,
233 operation_count: self.operation_count,
234 flops: self.flops,
235 bytes_transferred: self.bytes_transferred,
236 stack_trace,
237 };
238
239 {
241 let mut profiler = profiler_arc.lock();
242 if profiler.is_overhead_tracking_enabled() && stack_trace_overhead_ns > 0 {
243 profiler.overhead_stats.stack_trace_time_ns += stack_trace_overhead_ns;
244 profiler.overhead_stats.stack_trace_count += 1;
245 profiler.overhead_stats.total_overhead_ns += stack_trace_overhead_ns;
246 }
247 profiler.add_event(event);
248 }
249 }
250}
251
252#[macro_export]
254macro_rules! profile_scope {
255 ($name:expr) => {
256 let _guard = $crate::core::scope::ScopeGuard::new($name);
257 };
258 ($name:expr, $category:expr) => {
259 let _guard = $crate::core::scope::ScopeGuard::with_category($name, $category);
260 };
261}
262
263#[macro_export]
264macro_rules! profile_function {
265 ($name:expr, $func:expr) => {
266 $crate::core::scope::profile_function($name, $func)
267 };
268 ($name:expr, $category:expr, $func:expr) => {
269 $crate::core::scope::profile_function_with_category($name, $category, $func)
270 };
271}
272
273#[macro_export]
274macro_rules! profile_metrics {
275 ($name:expr) => {
276 $crate::core::scope::MetricsScope::new($name)
277 };
278 ($name:expr, $category:expr) => {
279 $crate::core::scope::MetricsScope::with_category($name, $category)
280 };
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::core::profiler::{
287 clear_global_events, get_global_stats, start_profiling, stop_profiling,
288 };
289 use std::thread;
290 use std::time::Duration;
291
292 #[test]
293 fn test_scope_guard_basic() {
294 start_profiling();
295 clear_global_events();
296
297 {
298 let _guard = ScopeGuard::new("test_scope");
299 thread::sleep(Duration::from_millis(10));
300 }
301
302 let stats = get_global_stats().unwrap();
303 assert!(stats.0 > 0); stop_profiling();
306 }
307
308 #[test]
309 fn test_scope_guard_with_category() {
310 start_profiling();
311 clear_global_events();
312
313 {
314 let _guard = ScopeGuard::with_category("test_scope", "testing");
315 thread::sleep(Duration::from_millis(5));
316 }
317
318 let stats = get_global_stats().unwrap();
319 assert!(stats.0 > 0);
320
321 stop_profiling();
322 }
323
324 #[test]
325 fn test_profile_function() {
326 start_profiling();
327 clear_global_events();
328
329 let result = profile_function("test_function", || {
330 thread::sleep(Duration::from_millis(5));
331 42
332 });
333
334 assert_eq!(result, 42);
335 let stats = get_global_stats().unwrap();
336 assert!(stats.0 > 0);
337
338 stop_profiling();
339 }
340
341 #[test]
342 fn test_metrics_scope() {
343 start_profiling();
344 clear_global_events();
345
346 {
347 let mut scope = MetricsScope::new("test_metrics");
348 scope.set_operation_count(100);
349 scope.set_flops(500);
350 scope.set_bytes_transferred(1024);
351
352 thread::sleep(Duration::from_millis(5));
353
354 let (ops, flops, bytes) = scope.metrics();
355 assert_eq!(ops, Some(100));
356 assert_eq!(flops, Some(500));
357 assert_eq!(bytes, Some(1024));
358 }
359
360 let stats = get_global_stats().unwrap();
361 assert!(stats.0 > 0);
362
363 stop_profiling();
364 }
365
366 #[test]
367 fn test_metrics_scope_accumulation() {
368 let mut scope = MetricsScope::new("test_accumulation");
369
370 scope.add_operations(50);
371 scope.add_operations(75);
372 scope.add_flops(100);
373 scope.add_flops(200);
374 scope.add_bytes(512);
375 scope.add_bytes(256);
376
377 let (ops, flops, bytes) = scope.metrics();
378 assert_eq!(ops, Some(125));
379 assert_eq!(flops, Some(300));
380 assert_eq!(bytes, Some(768));
381 }
382
383 #[test]
384 fn test_profile_scope_macro() {
385 start_profiling();
386 clear_global_events();
387
388 {
389 profile_scope!("macro_test");
390 thread::sleep(Duration::from_millis(5));
391 }
392
393 {
394 profile_scope!("macro_test_with_category", "testing");
395 thread::sleep(Duration::from_millis(5));
396 }
397
398 let stats = get_global_stats().unwrap();
399 assert!(stats.0 >= 2); stop_profiling();
402 }
403
404 #[test]
405 fn test_thread_id_extraction() {
406 let id1 = get_thread_id();
407 let id2 = thread::spawn(|| get_thread_id()).join().unwrap();
408
409 assert_ne!(id1, id2);
411 assert!(id1 > 0);
413 assert!(id2 > 0);
414 }
415}