Skip to main content

torsh_profiler/core/
scope.rs

1//! Scope-based profiling utilities
2//!
3//! This module provides RAII-based profiling scope guards that automatically
4//! capture timing information when entering and leaving scopes.
5
6use crate::{core::profiler::global_profiler, ProfileEvent};
7use backtrace::Backtrace;
8use std::time::Instant;
9
10/// RAII scope guard for automatic profiling
11pub struct ScopeGuard {
12    name: String,
13    category: String,
14    start: Instant,
15}
16
17impl ScopeGuard {
18    /// Create a new scope guard with default category
19    pub fn new(name: &str) -> Self {
20        Self::with_category(name, "general")
21    }
22
23    /// Create a new scope guard with specified category
24    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    /// Get the current duration of this scope
33    pub fn elapsed(&self) -> std::time::Duration {
34        self.start.elapsed()
35    }
36
37    /// Get the scope name
38    pub fn name(&self) -> &str {
39        &self.name
40    }
41
42    /// Get the scope category
43    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        // Capture stack trace if enabled with overhead tracking
54        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, // This will be adjusted by the profiler
72            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        // Update overhead stats if tracking is enabled
81        {
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
93/// Get the current thread ID as a number
94fn 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
104/// Capture stack trace if enabled
105fn capture_stack_trace() -> Option<String> {
106    // Only capture if backtrace is available and not in release mode for performance
107    #[cfg(debug_assertions)]
108    {
109        let bt = Backtrace::new();
110        Some(format!("{:?}", bt))
111    }
112    #[cfg(not(debug_assertions))]
113    None
114}
115
116/// Capture stack trace with overhead measurement
117fn 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
124/// Profile a function with automatic scope management
125pub 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
133/// Profile a function with category and automatic scope management
134pub 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
142/// Enhanced scope guard with custom metrics
143pub struct MetricsScope {
144    guard: ScopeGuard,
145    operation_count: Option<u64>,
146    flops: Option<u64>,
147    bytes_transferred: Option<u64>,
148}
149
150impl MetricsScope {
151    /// Create a new metrics scope
152    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    /// Create a new metrics scope with category
162    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    /// Set the number of operations performed
172    pub fn set_operation_count(&mut self, count: u64) {
173        self.operation_count = Some(count);
174    }
175
176    /// Set the number of floating point operations
177    pub fn set_flops(&mut self, flops: u64) {
178        self.flops = Some(flops);
179    }
180
181    /// Set the number of bytes transferred
182    pub fn set_bytes_transferred(&mut self, bytes: u64) {
183        self.bytes_transferred = Some(bytes);
184    }
185
186    /// Add to operation count
187    pub fn add_operations(&mut self, count: u64) {
188        self.operation_count = Some(self.operation_count.unwrap_or(0) + count);
189    }
190
191    /// Add to FLOPS count
192    pub fn add_flops(&mut self, flops: u64) {
193        self.flops = Some(self.flops.unwrap_or(0) + flops);
194    }
195
196    /// Add to bytes transferred
197    pub fn add_bytes(&mut self, bytes: u64) {
198        self.bytes_transferred = Some(self.bytes_transferred.unwrap_or(0) + bytes);
199    }
200
201    /// Get current metrics
202    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        // Capture stack trace if enabled
213        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, // Will be adjusted by profiler
231            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        // Update overhead stats and add event
240        {
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/// Macros for convenient scope profiling
253#[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); // Should have at least one event
304
305        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); // Should have at least 2 events
400
401        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        // Thread IDs should be different
410        assert_ne!(id1, id2);
411        // Both should be valid numbers
412        assert!(id1 > 0);
413        assert!(id2 > 0);
414    }
415}