Skip to main content

safer_ring/
perf.rs

1//! Performance profiling and optimization utilities.
2
3use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7/// Performance counter for tracking operation metrics.
8#[derive(Debug)]
9pub struct PerfCounter {
10    /// Total number of operations
11    count: AtomicU64,
12    /// Total time spent in operations (nanoseconds)
13    total_time_ns: AtomicU64,
14    /// Minimum operation time (nanoseconds)
15    min_time_ns: AtomicU64,
16    /// Maximum operation time (nanoseconds)
17    max_time_ns: AtomicU64,
18}
19
20impl PerfCounter {
21    /// Create a new performance counter.
22    pub fn new() -> Self {
23        Self {
24            count: AtomicU64::new(0),
25            total_time_ns: AtomicU64::new(0),
26            min_time_ns: AtomicU64::new(u64::MAX),
27            max_time_ns: AtomicU64::new(0),
28        }
29    }
30
31    /// Record an operation duration.
32    pub fn record(&self, duration: Duration) {
33        let nanos = duration.as_nanos() as u64;
34
35        self.count.fetch_add(1, Ordering::Relaxed);
36        self.total_time_ns.fetch_add(nanos, Ordering::Relaxed);
37
38        // Update min (with compare-and-swap loop)
39        let mut current_min = self.min_time_ns.load(Ordering::Relaxed);
40        while nanos < current_min {
41            match self.min_time_ns.compare_exchange_weak(
42                current_min,
43                nanos,
44                Ordering::Relaxed,
45                Ordering::Relaxed,
46            ) {
47                Ok(_) => break,
48                Err(x) => current_min = x,
49            }
50        }
51
52        // Update max (with compare-and-swap loop)
53        let mut current_max = self.max_time_ns.load(Ordering::Relaxed);
54        while nanos > current_max {
55            match self.max_time_ns.compare_exchange_weak(
56                current_max,
57                nanos,
58                Ordering::Relaxed,
59                Ordering::Relaxed,
60            ) {
61                Ok(_) => break,
62                Err(x) => current_max = x,
63            }
64        }
65    }
66
67    /// Get performance statistics.
68    pub fn stats(&self) -> PerfStats {
69        let count = self.count.load(Ordering::Relaxed);
70        let total_ns = self.total_time_ns.load(Ordering::Relaxed);
71        let min_ns = self.min_time_ns.load(Ordering::Relaxed);
72        let max_ns = self.max_time_ns.load(Ordering::Relaxed);
73
74        let avg_ns = if count > 0 { total_ns / count } else { 0 };
75
76        PerfStats {
77            count,
78            total_time: Duration::from_nanos(total_ns),
79            avg_time: Duration::from_nanos(avg_ns),
80            min_time: if min_ns == u64::MAX {
81                Duration::ZERO
82            } else {
83                Duration::from_nanos(min_ns)
84            },
85            max_time: Duration::from_nanos(max_ns),
86        }
87    }
88
89    /// Reset all counters.
90    pub fn reset(&self) {
91        self.count.store(0, Ordering::Relaxed);
92        self.total_time_ns.store(0, Ordering::Relaxed);
93        self.min_time_ns.store(u64::MAX, Ordering::Relaxed);
94        self.max_time_ns.store(0, Ordering::Relaxed);
95    }
96}
97
98impl Default for PerfCounter {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104/// Performance statistics snapshot.
105#[derive(Debug, Clone)]
106pub struct PerfStats {
107    /// Number of operations recorded
108    pub count: u64,
109    /// Total time across all operations
110    pub total_time: Duration,
111    /// Average time per operation
112    pub avg_time: Duration,
113    /// Minimum operation time
114    pub min_time: Duration,
115    /// Maximum operation time
116    pub max_time: Duration,
117}
118
119impl PerfStats {
120    /// Calculate operations per second.
121    pub fn ops_per_sec(&self) -> f64 {
122        if self.total_time.is_zero() {
123            0.0
124        } else {
125            self.count as f64 / self.total_time.as_secs_f64()
126        }
127    }
128
129    /// Calculate throughput in bytes per second.
130    pub fn throughput_bps(&self, bytes_per_op: u64) -> f64 {
131        self.ops_per_sec() * bytes_per_op as f64
132    }
133}
134
135/// RAII timer for automatic performance measurement.
136pub struct PerfTimer<'a> {
137    counter: &'a PerfCounter,
138    start: Instant,
139}
140
141impl<'a> PerfTimer<'a> {
142    /// Start timing an operation.
143    pub fn new(counter: &'a PerfCounter) -> Self {
144        Self {
145            counter,
146            start: Instant::now(),
147        }
148    }
149}
150
151impl<'a> Drop for PerfTimer<'a> {
152    fn drop(&mut self) {
153        let duration = self.start.elapsed();
154        self.counter.record(duration);
155    }
156}
157
158/// Memory usage tracker.
159#[derive(Debug)]
160pub struct MemoryTracker {
161    /// Current allocated bytes
162    allocated: AtomicUsize,
163    /// Peak allocated bytes
164    peak: AtomicUsize,
165    /// Total allocations
166    total_allocs: AtomicU64,
167    /// Total deallocations
168    total_deallocs: AtomicU64,
169}
170
171impl MemoryTracker {
172    /// Create a new memory tracker.
173    pub fn new() -> Self {
174        Self {
175            allocated: AtomicUsize::new(0),
176            peak: AtomicUsize::new(0),
177            total_allocs: AtomicU64::new(0),
178            total_deallocs: AtomicU64::new(0),
179        }
180    }
181
182    /// Record an allocation.
183    pub fn record_alloc(&self, size: usize) {
184        let new_allocated = self.allocated.fetch_add(size, Ordering::Relaxed) + size;
185        self.total_allocs.fetch_add(1, Ordering::Relaxed);
186
187        // Update peak
188        let mut current_peak = self.peak.load(Ordering::Relaxed);
189        while new_allocated > current_peak {
190            match self.peak.compare_exchange_weak(
191                current_peak,
192                new_allocated,
193                Ordering::Relaxed,
194                Ordering::Relaxed,
195            ) {
196                Ok(_) => break,
197                Err(x) => current_peak = x,
198            }
199        }
200    }
201
202    /// Record a deallocation.
203    pub fn record_dealloc(&self, size: usize) {
204        self.allocated.fetch_sub(size, Ordering::Relaxed);
205        self.total_deallocs.fetch_add(1, Ordering::Relaxed);
206    }
207
208    /// Get current memory usage.
209    pub fn current_usage(&self) -> usize {
210        self.allocated.load(Ordering::Relaxed)
211    }
212
213    /// Get peak memory usage.
214    pub fn peak_usage(&self) -> usize {
215        self.peak.load(Ordering::Relaxed)
216    }
217
218    /// Get memory statistics.
219    pub fn stats(&self) -> MemoryStats {
220        MemoryStats {
221            current: self.allocated.load(Ordering::Relaxed),
222            peak: self.peak.load(Ordering::Relaxed),
223            total_allocs: self.total_allocs.load(Ordering::Relaxed),
224            total_deallocs: self.total_deallocs.load(Ordering::Relaxed),
225        }
226    }
227
228    /// Reset all counters.
229    pub fn reset(&self) {
230        self.allocated.store(0, Ordering::Relaxed);
231        self.peak.store(0, Ordering::Relaxed);
232        self.total_allocs.store(0, Ordering::Relaxed);
233        self.total_deallocs.store(0, Ordering::Relaxed);
234    }
235}
236
237impl Default for MemoryTracker {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243/// Memory usage statistics.
244#[derive(Debug, Clone)]
245pub struct MemoryStats {
246    /// Current allocated bytes
247    pub current: usize,
248    /// Peak allocated bytes
249    pub peak: usize,
250    /// Total number of allocations
251    pub total_allocs: u64,
252    /// Total number of deallocations
253    pub total_deallocs: u64,
254}
255
256impl MemoryStats {
257    /// Calculate allocation rate (allocs per second).
258    pub fn alloc_rate(&self, duration: Duration) -> f64 {
259        if duration.is_zero() {
260            0.0
261        } else {
262            self.total_allocs as f64 / duration.as_secs_f64()
263        }
264    }
265
266    /// Calculate average allocation size.
267    pub fn avg_alloc_size(&self) -> f64 {
268        if self.total_allocs > 0 {
269            self.peak as f64 / self.total_allocs as f64
270        } else {
271            0.0
272        }
273    }
274}
275
276/// Global performance registry for collecting metrics across the library.
277pub struct PerfRegistry {
278    counters: std::collections::HashMap<String, Arc<PerfCounter>>,
279    memory_tracker: Arc<MemoryTracker>,
280}
281
282impl PerfRegistry {
283    /// Create a new performance registry.
284    pub fn new() -> Self {
285        Self {
286            counters: std::collections::HashMap::new(),
287            memory_tracker: Arc::new(MemoryTracker::new()),
288        }
289    }
290
291    /// Get or create a performance counter.
292    pub fn counter(&mut self, name: &str) -> Arc<PerfCounter> {
293        self.counters
294            .entry(name.to_string())
295            .or_insert_with(|| Arc::new(PerfCounter::new()))
296            .clone()
297    }
298
299    /// Get the memory tracker.
300    pub fn memory_tracker(&self) -> Arc<MemoryTracker> {
301        self.memory_tracker.clone()
302    }
303
304    /// Get all counter statistics.
305    pub fn all_stats(&self) -> std::collections::HashMap<String, PerfStats> {
306        self.counters
307            .iter()
308            .map(|(name, counter)| (name.clone(), counter.stats()))
309            .collect()
310    }
311
312    /// Reset all counters.
313    pub fn reset_all(&self) {
314        for counter in self.counters.values() {
315            counter.reset();
316        }
317        self.memory_tracker.reset();
318    }
319}
320
321impl Default for PerfRegistry {
322    fn default() -> Self {
323        Self::new()
324    }
325}
326
327/// Macro for easy performance timing.
328#[macro_export]
329macro_rules! time_operation {
330    ($counter:expr, $operation:expr) => {{
331        let _timer = $crate::perf::PerfTimer::new($counter);
332        $operation
333    }};
334}
335
336/// Macro for timing async operations.
337#[macro_export]
338macro_rules! time_async_operation {
339    ($counter:expr, $operation:expr) => {{
340        let start = std::time::Instant::now();
341        let result = $operation.await;
342        $counter.record(start.elapsed());
343        result
344    }};
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    use std::thread;
351    use std::time::Duration;
352
353    #[test]
354    fn test_perf_counter() {
355        let counter = PerfCounter::new();
356
357        counter.record(Duration::from_millis(10));
358        counter.record(Duration::from_millis(20));
359        counter.record(Duration::from_millis(5));
360
361        let stats = counter.stats();
362        assert_eq!(stats.count, 3);
363        assert_eq!(stats.min_time, Duration::from_millis(5));
364        assert_eq!(stats.max_time, Duration::from_millis(20));
365        assert!(stats.avg_time >= Duration::from_millis(11));
366        assert!(stats.avg_time <= Duration::from_millis(12));
367    }
368
369    #[test]
370    fn test_perf_timer() {
371        let counter = PerfCounter::new();
372
373        {
374            let _timer = PerfTimer::new(&counter);
375            thread::sleep(Duration::from_millis(10));
376        }
377
378        let stats = counter.stats();
379        assert_eq!(stats.count, 1);
380        assert!(stats.total_time >= Duration::from_millis(9));
381    }
382
383    #[test]
384    fn test_memory_tracker() {
385        let tracker = MemoryTracker::new();
386
387        tracker.record_alloc(1024);
388        assert_eq!(tracker.current_usage(), 1024);
389        assert_eq!(tracker.peak_usage(), 1024);
390
391        tracker.record_alloc(2048);
392        assert_eq!(tracker.current_usage(), 3072);
393        assert_eq!(tracker.peak_usage(), 3072);
394
395        tracker.record_dealloc(1024);
396        assert_eq!(tracker.current_usage(), 2048);
397        assert_eq!(tracker.peak_usage(), 3072); // Peak doesn't decrease
398    }
399
400    #[test]
401    fn test_perf_registry() {
402        let mut registry = PerfRegistry::new();
403
404        let counter1 = registry.counter("test1");
405        let counter2 = registry.counter("test2");
406
407        counter1.record(Duration::from_millis(10));
408        counter2.record(Duration::from_millis(20));
409
410        let all_stats = registry.all_stats();
411        assert_eq!(all_stats.len(), 2);
412        assert!(all_stats.contains_key("test1"));
413        assert!(all_stats.contains_key("test2"));
414    }
415
416    #[test]
417    fn test_concurrent_perf_counter() {
418        let counter = Arc::new(PerfCounter::new());
419        let mut handles = vec![];
420
421        for _ in 0..10 {
422            let counter_clone = counter.clone();
423            handles.push(thread::spawn(move || {
424                for _ in 0..100 {
425                    counter_clone.record(Duration::from_nanos(1000));
426                }
427            }));
428        }
429
430        for handle in handles {
431            handle.join().unwrap();
432        }
433
434        let stats = counter.stats();
435        assert_eq!(stats.count, 1000);
436    }
437}