scirs2_core/profiling/
profiler.rs

1//! Main profiler implementation for collecting performance metrics
2
3use crate::profiling::entries::{MemoryEntry, TimingEntry};
4use crate::profiling::memory::MemoryTracker;
5use crate::profiling::timer::Timer;
6use std::collections::HashMap;
7use std::sync::Mutex;
8use std::time::{Duration, Instant};
9
10/// Profiler for collecting performance metrics
11#[derive(Debug)]
12pub struct Profiler {
13    /// Timing measurements
14    timings: HashMap<String, TimingEntry>,
15    /// Memory measurements
16    memory: HashMap<String, MemoryEntry>,
17    /// Currently active timers
18    active_timers: HashMap<String, Instant>,
19    /// Whether the profiler is currently running
20    running: bool,
21}
22
23impl Profiler {
24    /// Create a new profiler
25    pub fn new() -> Self {
26        Self {
27            timings: HashMap::new(),
28            memory: HashMap::new(),
29            active_timers: HashMap::new(),
30            running: false,
31        }
32    }
33
34    /// Get the global profiler instance
35    pub fn global() -> &'static Mutex<Profiler> {
36        static GLOBAL_PROFILER: once_cell::sync::Lazy<Mutex<Profiler>> =
37            once_cell::sync::Lazy::new(|| Mutex::new(Profiler::new()));
38        &GLOBAL_PROFILER
39    }
40
41    /// Start the profiler
42    pub fn start(&mut self) {
43        self.running = true;
44        self.timings.clear();
45        self.memory.clear();
46        self.active_timers.clear();
47    }
48
49    /// Stop the profiler
50    pub fn stop(&mut self) {
51        self.running = false;
52    }
53
54    /// Reset the profiler
55    pub fn reset(&mut self) {
56        self.timings.clear();
57        self.memory.clear();
58        self.active_timers.clear();
59    }
60
61    /// Register the start of a timer
62    pub fn register_timer_start(&mut self, timer: &Timer) {
63        if !self.running {
64            return;
65        }
66
67        self.active_timers
68            .insert(timer.name().to_string(), timer.start_time());
69
70        // Register the parent-child relationship
71        if let Some(parent) = timer.parent() {
72            if let Some(entry) = self.timings.get_mut(parent) {
73                entry.add_child(timer.name());
74            }
75        }
76    }
77
78    /// Register the stop of a timer
79    pub fn register_timer_stop(&mut self, name: &str, duration: Duration, parent: Option<&str>) {
80        if !self.running {
81            return;
82        }
83
84        // Remove from active timers
85        self.active_timers.remove(name);
86
87        // Update the timing entry
88        match self.timings.get_mut(name) {
89            Some(entry) => {
90                entry.add_measurement(duration);
91            }
92            None => {
93                let entry = TimingEntry::new(duration, parent);
94                self.timings.insert(name.to_string(), entry);
95            }
96        }
97
98        // Register the parent-child relationship
99        if let Some(parent) = parent {
100            if let Some(entry) = self.timings.get_mut(parent) {
101                entry.add_child(name);
102            }
103        }
104    }
105
106    /// Register the start of a memory tracker
107    pub fn register_memory_tracker_start(&mut self, _tracker: &MemoryTracker) {
108        if !self.running {
109            // Nothing to do at start, just ensure the method exists for symmetry
110        }
111    }
112
113    /// Register the stop of a memory tracker
114    pub fn register_memory_tracker_stop(&mut self, name: &str, delta: usize) {
115        if !self.running {
116            return;
117        }
118
119        // Update the memory entry
120        match self.memory.get_mut(name) {
121            Some(entry) => {
122                entry.add_measurement(delta);
123            }
124            None => {
125                let entry = MemoryEntry::new(delta);
126                self.memory.insert(name.to_string(), entry);
127            }
128        }
129    }
130
131    /// Print a report of the profiling results
132    pub fn print_report(&self) {
133        if self.timings.is_empty() && self.memory.is_empty() {
134            println!("No profiling data collected.");
135            return;
136        }
137
138        if !self.timings.is_empty() {
139            println!("\n=== Timing Report ===");
140            println!(
141                "{:<30} {:<10} {:<15} {:<15} {:<15}",
142                "Operation", "Calls", "Total (ms)", "Average (ms)", "Max (ms)"
143            );
144            println!("{}", "-".repeat(90));
145
146            // Sort by total duration
147            let mut entries: Vec<(&String, &TimingEntry)> = self.timings.iter().collect();
148            entries.sort_by(|a, b| b.1.total_duration().cmp(&a.1.total_duration()));
149
150            for (name, entry) in entries {
151                println!(
152                    "{:<30} {:<10} {:<15.2} {:<15.2} {:<15.2}",
153                    name,
154                    entry.calls(),
155                    entry.total_duration().as_secs_f64() * 1000.0,
156                    entry.average_duration().as_secs_f64() * 1000.0,
157                    entry.max_duration().as_secs_f64() * 1000.0
158                );
159            }
160        }
161
162        if !self.memory.is_empty() {
163            println!("\n=== Memory Report ===");
164            println!(
165                "{:<30} {:<10} {:<15} {:<15}",
166                "Operation", "Counts", "Total (KB)", "Max (KB)"
167            );
168            println!("{}", "-".repeat(75));
169
170            // Sort by total memory delta
171            let mut entries: Vec<(&String, &MemoryEntry)> = self.memory.iter().collect();
172            entries.sort_by(|a, b| b.1.total_delta().abs().cmp(&a.1.total_delta().abs()));
173
174            for (name, entry) in entries {
175                println!(
176                    "{:<30} {:<10} {:<15.2} {:<15.2}",
177                    name,
178                    entry.allocations(),
179                    entry.total_delta() as f64 / 1024.0,
180                    entry.max_delta() as f64 / 1024.0
181                );
182            }
183        }
184    }
185
186    /// Get a report of the profiling results as a string
187    pub fn get_report(&self) -> String {
188        use std::fmt::Write;
189        let mut report = String::new();
190
191        if self.timings.is_empty() && self.memory.is_empty() {
192            writeln!(report, "No profiling data collected.").expect("Operation failed");
193            return report;
194        }
195
196        if !self.timings.is_empty() {
197            writeln!(report, "\n=== Timing Report ===").expect("Operation failed");
198            writeln!(
199                report,
200                "{:<30} {:<10} {:<15} {:<15} {:<15}",
201                "Operation", "Calls", "Total (ms)", "Average (ms)", "Max (ms)"
202            )
203            .expect("Operation failed");
204            writeln!(report, "{}", "-".repeat(90)).expect("Operation failed");
205
206            // Sort by total duration
207            let mut entries: Vec<(&String, &TimingEntry)> = self.timings.iter().collect();
208            entries.sort_by(|a, b| b.1.total_duration().cmp(&a.1.total_duration()));
209
210            for (name, entry) in entries {
211                writeln!(
212                    report,
213                    "{:<30} {:<10} {:<15.2} {:<15.2} {:<15.2}",
214                    name,
215                    entry.calls(),
216                    entry.total_duration().as_secs_f64() * 1000.0,
217                    entry.average_duration().as_secs_f64() * 1000.0,
218                    entry.max_duration().as_secs_f64() * 1000.0
219                )
220                .expect("Operation failed");
221            }
222        }
223
224        if !self.memory.is_empty() {
225            writeln!(report, "\n=== Memory Report ===").expect("Operation failed");
226            writeln!(
227                report,
228                "{:<30} {:<10} {:<15} {:<15}",
229                "Operation", "Counts", "Total (KB)", "Max (KB)"
230            )
231            .expect("Operation failed");
232            writeln!(report, "{}", "-".repeat(75)).expect("Operation failed");
233
234            // Sort by total memory delta
235            let mut entries: Vec<(&String, &MemoryEntry)> = self.memory.iter().collect();
236            entries.sort_by(|a, b| b.1.total_delta().abs().cmp(&a.1.total_delta().abs()));
237
238            for (name, entry) in entries {
239                writeln!(
240                    report,
241                    "{:<30} {:<10} {:<15.2} {:<15.2}",
242                    name,
243                    entry.allocations(),
244                    entry.total_delta() as f64 / 1024.0,
245                    entry.max_delta() as f64 / 1024.0
246                )
247                .expect("Operation failed");
248            }
249        }
250
251        report
252    }
253
254    /// Get timing statistics for a specific operation
255    pub fn get_timing_stats(&self, name: &str) -> Option<(usize, Duration, Duration, Duration)> {
256        self.timings.get(name).map(|entry| {
257            (
258                entry.calls(),
259                entry.total_duration(),
260                entry.average_duration(),
261                entry.max_duration(),
262            )
263        })
264    }
265
266    /// Get memory statistics for a specific operation
267    pub fn get_memory_stats(&self, name: &str) -> Option<(usize, isize, usize)> {
268        self.memory
269            .get(name)
270            .map(|entry| (entry.allocations(), entry.total_delta(), entry.max_delta()))
271    }
272
273    /// Check if the profiler is running
274    pub fn is_running(&self) -> bool {
275        self.running
276    }
277
278    /// Get access to timing data
279    pub fn timings(&self) -> &HashMap<String, TimingEntry> {
280        &self.timings
281    }
282
283    /// Get access to memory data
284    pub fn memory(&self) -> &HashMap<String, MemoryEntry> {
285        &self.memory
286    }
287}
288
289impl Default for Profiler {
290    fn default() -> Self {
291        Self::new()
292    }
293}