Skip to main content

oxiz_sat/
profiling.rs

1//! Performance Profiling Utilities
2//!
3//! Tools for profiling and analyzing SAT solver performance.
4//! Includes timers, operation counters, and bottleneck detection.
5
6use crate::prelude::HashMap;
7#[allow(unused_imports)]
8use crate::prelude::*;
9use std::time::{Duration, Instant};
10
11/// A named timer for profiling code sections
12#[derive(Debug)]
13pub struct ScopedTimer {
14    /// Timer name
15    name: String,
16    /// Start time
17    start: Instant,
18    /// Whether the timer is running
19    running: bool,
20    /// Total elapsed time
21    elapsed: Duration,
22}
23
24impl ScopedTimer {
25    /// Create a new timer
26    #[must_use]
27    pub fn new(name: impl Into<String>) -> Self {
28        Self {
29            name: name.into(),
30            start: Instant::now(),
31            running: true,
32            elapsed: Duration::ZERO,
33        }
34    }
35
36    /// Stop the timer
37    pub fn stop(&mut self) {
38        if self.running {
39            self.elapsed += self.start.elapsed();
40            self.running = false;
41        }
42    }
43
44    /// Restart the timer
45    pub fn restart(&mut self) {
46        if !self.running {
47            self.start = Instant::now();
48            self.running = true;
49        }
50    }
51
52    /// Get the elapsed time
53    #[must_use]
54    pub fn elapsed(&self) -> Duration {
55        if self.running {
56            self.elapsed + self.start.elapsed()
57        } else {
58            self.elapsed
59        }
60    }
61
62    /// Get the timer name
63    #[must_use]
64    pub fn name(&self) -> &str {
65        &self.name
66    }
67}
68
69impl Drop for ScopedTimer {
70    fn drop(&mut self) {
71        if self.running {
72            self.stop();
73        }
74    }
75}
76
77/// Profiler for tracking multiple timers and operation counts
78#[derive(Debug, Default)]
79pub struct Profiler {
80    /// Named timers
81    timers: HashMap<String, Duration>,
82    /// Operation counters
83    counters: HashMap<String, u64>,
84    /// Active timers
85    active: HashMap<String, Instant>,
86}
87
88impl Profiler {
89    /// Create a new profiler
90    #[must_use]
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Start timing an operation
96    pub fn start_timer(&mut self, name: impl Into<String>) {
97        let name = name.into();
98        self.active.insert(name, Instant::now());
99    }
100
101    /// Stop timing an operation
102    pub fn stop_timer(&mut self, name: &str) {
103        if let Some(start) = self.active.remove(name) {
104            let elapsed = start.elapsed();
105            *self
106                .timers
107                .entry(name.to_string())
108                .or_insert(Duration::ZERO) += elapsed;
109        }
110    }
111
112    /// Increment a counter
113    pub fn increment(&mut self, name: impl Into<String>) {
114        self.increment_by(name, 1);
115    }
116
117    /// Increment a counter by a specific amount
118    pub fn increment_by(&mut self, name: impl Into<String>, amount: u64) {
119        *self.counters.entry(name.into()).or_insert(0) += amount;
120    }
121
122    /// Get timer duration
123    #[must_use]
124    pub fn get_timer(&self, name: &str) -> Option<Duration> {
125        self.timers.get(name).copied()
126    }
127
128    /// Get counter value
129    #[must_use]
130    pub fn get_counter(&self, name: &str) -> Option<u64> {
131        self.counters.get(name).copied()
132    }
133
134    /// Get all timers
135    #[must_use]
136    pub fn timers(&self) -> &HashMap<String, Duration> {
137        &self.timers
138    }
139
140    /// Get all counters
141    #[must_use]
142    pub fn counters(&self) -> &HashMap<String, u64> {
143        &self.counters
144    }
145
146    /// Clear all profiling data
147    pub fn clear(&mut self) {
148        self.timers.clear();
149        self.counters.clear();
150        self.active.clear();
151    }
152
153    /// Get total profiled time
154    #[must_use]
155    pub fn total_time(&self) -> Duration {
156        self.timers.values().sum()
157    }
158
159    /// Display profiling report
160    #[must_use]
161    pub fn report(&self) -> String {
162        let mut output = String::new();
163
164        output.push_str("═══════════════════════════════════════════════════════════════\n");
165        output.push_str("                    PROFILING REPORT                           \n");
166        output.push_str("═══════════════════════════════════════════════════════════════\n\n");
167
168        if !self.timers.is_empty() {
169            output.push_str("TIMERS:\n");
170            let mut timer_vec: Vec<_> = self.timers.iter().collect();
171            timer_vec.sort_by(|a, b| b.1.cmp(a.1)); // Sort by duration descending
172
173            let total = self.total_time();
174
175            for (name, duration) in timer_vec {
176                let pct = if total > Duration::ZERO {
177                    100.0 * duration.as_secs_f64() / total.as_secs_f64()
178                } else {
179                    0.0
180                };
181
182                output.push_str(&format!(
183                    "  {:<30} {:>10.3}s  ({:>5.1}%)\n",
184                    name,
185                    duration.as_secs_f64(),
186                    pct
187                ));
188            }
189
190            output.push_str(&format!("\n  Total Time: {:.3}s\n", total.as_secs_f64()));
191            output.push('\n');
192        }
193
194        if !self.counters.is_empty() {
195            output.push_str("COUNTERS:\n");
196            let mut counter_vec: Vec<_> = self.counters.iter().collect();
197            counter_vec.sort_by(|a, b| b.1.cmp(a.1)); // Sort by count descending
198
199            for (name, count) in counter_vec {
200                output.push_str(&format!("  {:<30} {:>12}\n", name, count));
201            }
202            output.push('\n');
203        }
204
205        output.push_str("═══════════════════════════════════════════════════════════════\n");
206
207        output
208    }
209}
210
211/// RAII timer that automatically reports on drop
212pub struct AutoTimer {
213    name: String,
214    start: Instant,
215}
216
217impl AutoTimer {
218    /// Create a new auto timer
219    #[must_use]
220    pub fn new(name: impl Into<String>) -> Self {
221        Self {
222            name: name.into(),
223            start: Instant::now(),
224        }
225    }
226
227    /// Get elapsed time without dropping
228    #[must_use]
229    pub fn elapsed(&self) -> Duration {
230        self.start.elapsed()
231    }
232}
233
234impl Drop for AutoTimer {
235    fn drop(&mut self) {
236        tracing::debug!("{}: {:.3}s", self.name, self.start.elapsed().as_secs_f64());
237    }
238}
239
240/// Macro for creating a scoped timer that logs on drop
241#[macro_export]
242macro_rules! time_scope {
243    ($name:expr) => {
244        let _timer = $crate::profiling::AutoTimer::new($name);
245    };
246}
247
248/// Performance metrics tracker
249#[derive(Debug, Default, Clone)]
250pub struct PerformanceMetrics {
251    /// Total operations performed
252    pub total_ops: u64,
253    /// Total time spent
254    pub total_time: Duration,
255    /// Peak operations per second
256    pub peak_ops_per_sec: f64,
257    /// Average operations per second
258    pub avg_ops_per_sec: f64,
259}
260
261impl PerformanceMetrics {
262    /// Create new metrics
263    #[must_use]
264    pub fn new() -> Self {
265        Self::default()
266    }
267
268    /// Update metrics with new data
269    pub fn update(&mut self, ops: u64, time: Duration) {
270        self.total_ops += ops;
271        self.total_time += time;
272
273        let secs = time.as_secs_f64();
274        if secs > 0.0 {
275            let ops_per_sec = ops as f64 / secs;
276            if ops_per_sec > self.peak_ops_per_sec {
277                self.peak_ops_per_sec = ops_per_sec;
278            }
279        }
280
281        let total_secs = self.total_time.as_secs_f64();
282        if total_secs > 0.0 {
283            self.avg_ops_per_sec = self.total_ops as f64 / total_secs;
284        }
285    }
286
287    /// Reset all metrics
288    pub fn reset(&mut self) {
289        self.total_ops = 0;
290        self.total_time = Duration::ZERO;
291        self.peak_ops_per_sec = 0.0;
292        self.avg_ops_per_sec = 0.0;
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299    use std::thread;
300
301    #[test]
302    fn test_scoped_timer() {
303        let mut timer = ScopedTimer::new("test");
304        thread::sleep(Duration::from_millis(10));
305        timer.stop();
306
307        assert!(timer.elapsed() >= Duration::from_millis(10));
308        assert_eq!(timer.name(), "test");
309    }
310
311    #[test]
312    fn test_scoped_timer_restart() {
313        let mut timer = ScopedTimer::new("test");
314        thread::sleep(Duration::from_millis(5));
315        timer.stop();
316
317        let first_elapsed = timer.elapsed();
318
319        timer.restart();
320        thread::sleep(Duration::from_millis(5));
321        timer.stop();
322
323        assert!(timer.elapsed() >= first_elapsed);
324    }
325
326    #[test]
327    fn test_profiler_timers() {
328        let mut profiler = Profiler::new();
329
330        profiler.start_timer("operation1");
331        thread::sleep(Duration::from_millis(10));
332        profiler.stop_timer("operation1");
333
334        let elapsed = profiler.get_timer("operation1");
335        assert!(elapsed.is_some());
336        assert!(elapsed.expect("Timer must have elapsed duration") >= Duration::from_millis(10));
337    }
338
339    #[test]
340    fn test_profiler_counters() {
341        let mut profiler = Profiler::new();
342
343        profiler.increment("counter1");
344        profiler.increment("counter1");
345        profiler.increment_by("counter2", 5);
346
347        assert_eq!(profiler.get_counter("counter1"), Some(2));
348        assert_eq!(profiler.get_counter("counter2"), Some(5));
349    }
350
351    #[test]
352    fn test_profiler_clear() {
353        let mut profiler = Profiler::new();
354
355        profiler.increment("counter");
356        profiler.start_timer("timer");
357        profiler.stop_timer("timer");
358
359        profiler.clear();
360
361        assert_eq!(profiler.counters().len(), 0);
362        assert_eq!(profiler.timers().len(), 0);
363    }
364
365    #[test]
366    fn test_profiler_report() {
367        let mut profiler = Profiler::new();
368
369        profiler.start_timer("test_timer");
370        profiler.stop_timer("test_timer");
371        profiler.increment("test_counter");
372
373        let report = profiler.report();
374        assert!(report.contains("PROFILING REPORT"));
375        assert!(report.contains("TIMERS:"));
376        assert!(report.contains("COUNTERS:"));
377    }
378
379    #[test]
380    fn test_auto_timer() {
381        let timer = AutoTimer::new("test");
382        thread::sleep(Duration::from_millis(10));
383        assert!(timer.elapsed() >= Duration::from_millis(10));
384    }
385
386    #[test]
387    fn test_performance_metrics() {
388        let mut metrics = PerformanceMetrics::new();
389
390        metrics.update(1000, Duration::from_secs(1));
391        assert_eq!(metrics.total_ops, 1000);
392        assert!(metrics.avg_ops_per_sec > 0.0);
393
394        metrics.update(2000, Duration::from_secs(1));
395        assert_eq!(metrics.total_ops, 3000);
396    }
397
398    #[test]
399    fn test_performance_metrics_reset() {
400        let mut metrics = PerformanceMetrics::new();
401
402        metrics.update(1000, Duration::from_secs(1));
403        metrics.reset();
404
405        assert_eq!(metrics.total_ops, 0);
406        assert_eq!(metrics.total_time, Duration::ZERO);
407    }
408}