Skip to main content

pulith_fetch/
perf.rs

1//! Performance measurement utilities for pulith-fetch.
2//!
3//! This module provides tools for measuring and tracking performance metrics
4//! including memory usage, throughput, and timing measurements.
5
6use std::sync::Arc;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::{Duration, Instant};
9
10/// Memory usage tracker for monitoring allocation patterns.
11#[derive(Debug, Default)]
12pub struct MemoryTracker {
13    /// Total bytes allocated
14    pub total_allocated: AtomicU64,
15    /// Peak memory usage in bytes
16    pub peak_usage: AtomicU64,
17    /// Current allocation count
18    pub allocation_count: AtomicU64,
19}
20
21impl MemoryTracker {
22    /// Create a new memory tracker.
23    pub fn new() -> Arc<Self> {
24        Arc::new(Self::default())
25    }
26
27    /// Record an allocation of the specified size.
28    pub fn record_allocation(&self, size: u64) {
29        self.total_allocated.fetch_add(size, Ordering::Relaxed);
30        self.allocation_count.fetch_add(1, Ordering::Relaxed);
31
32        let current = self.total_allocated.load(Ordering::Relaxed);
33        let mut peak = self.peak_usage.load(Ordering::Relaxed);
34        while current > peak {
35            match self.peak_usage.compare_exchange_weak(
36                peak,
37                current,
38                Ordering::Relaxed,
39                Ordering::Relaxed,
40            ) {
41                Ok(_) => break,
42                Err(actual) => peak = actual,
43            }
44        }
45    }
46
47    /// Record a deallocation of the specified size.
48    pub fn record_deallocation(&self, size: u64) {
49        self.total_allocated.fetch_sub(size, Ordering::Relaxed);
50    }
51
52    /// Get current memory usage statistics.
53    pub fn get_stats(&self) -> MemoryStats {
54        MemoryStats {
55            current_usage: self.total_allocated.load(Ordering::Relaxed),
56            peak_usage: self.peak_usage.load(Ordering::Relaxed),
57            allocation_count: self.allocation_count.load(Ordering::Relaxed),
58        }
59    }
60
61    /// Reset all statistics.
62    pub fn reset(&self) {
63        self.total_allocated.store(0, Ordering::Relaxed);
64        self.peak_usage.store(0, Ordering::Relaxed);
65        self.allocation_count.store(0, Ordering::Relaxed);
66    }
67}
68
69/// Memory usage statistics.
70#[derive(Debug, Clone)]
71pub struct MemoryStats {
72    /// Current memory usage in bytes
73    pub current_usage: u64,
74    /// Peak memory usage in bytes
75    pub peak_usage: u64,
76    /// Total number of allocations
77    pub allocation_count: u64,
78}
79
80/// Throughput measurement helper for tracking data transfer rates.
81#[derive(Debug)]
82pub struct ThroughputMeter {
83    start_time: Instant,
84    bytes_transferred: Arc<AtomicU64>,
85}
86
87impl Default for ThroughputMeter {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl ThroughputMeter {
94    /// Create a new throughput meter.
95    pub fn new() -> Self {
96        Self {
97            start_time: Instant::now(),
98            bytes_transferred: Arc::new(AtomicU64::new(0)),
99        }
100    }
101
102    /// Record bytes transferred.
103    pub fn record_bytes(&self, bytes: u64) {
104        self.bytes_transferred.fetch_add(bytes, Ordering::Relaxed);
105    }
106
107    /// Get current throughput in bytes per second.
108    pub fn current_throughput(&self) -> f64 {
109        let bytes = self.bytes_transferred.load(Ordering::Relaxed);
110        let elapsed = self.start_time.elapsed().as_secs_f64();
111        if elapsed > 0.0 {
112            bytes as f64 / elapsed
113        } else {
114            0.0
115        }
116    }
117
118    /// Get total bytes transferred.
119    pub fn total_bytes(&self) -> u64 {
120        self.bytes_transferred.load(Ordering::Relaxed)
121    }
122
123    /// Get elapsed time since creation.
124    pub fn elapsed(&self) -> Duration {
125        self.start_time.elapsed()
126    }
127
128    /// Reset the meter.
129    pub fn reset(&self) {
130        self.bytes_transferred.store(0, Ordering::Relaxed);
131    }
132}
133
134/// Timing measurement utility for benchmarking operations.
135#[derive(Debug)]
136pub struct Timer {
137    start_time: Option<Instant>,
138    total_duration: Arc<AtomicU64>,
139}
140
141impl Default for Timer {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl Timer {
148    /// Create a new timer.
149    pub fn new() -> Self {
150        Self {
151            start_time: None,
152            total_duration: Arc::new(AtomicU64::new(0)),
153        }
154    }
155
156    /// Start timing.
157    pub fn start(&mut self) {
158        self.start_time = Some(Instant::now());
159    }
160
161    /// Stop timing and return the elapsed duration.
162    pub fn stop(&mut self) -> Duration {
163        if let Some(start) = self.start_time {
164            let elapsed = start.elapsed();
165            self.total_duration
166                .fetch_add(elapsed.as_nanos() as u64, Ordering::Relaxed);
167            self.start_time = None;
168            elapsed
169        } else {
170            Duration::ZERO
171        }
172    }
173
174    /// Get the total accumulated duration.
175    pub fn total_duration(&self) -> Duration {
176        Duration::from_nanos(self.total_duration.load(Ordering::Relaxed))
177    }
178
179    /// Check if the timer is currently running.
180    pub fn is_running(&self) -> bool {
181        self.start_time.is_some()
182    }
183
184    /// Reset the timer.
185    pub fn reset(&self) {
186        self.total_duration.store(0, Ordering::Relaxed);
187    }
188}
189
190/// Performance profiler that combines multiple measurement tools.
191#[derive(Debug)]
192pub struct Profiler {
193    memory_tracker: Arc<MemoryTracker>,
194    throughput_meter: Arc<ThroughputMeter>,
195    timer: Arc<Timer>,
196}
197
198impl Default for Profiler {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204impl Profiler {
205    /// Create a new profiler.
206    pub fn new() -> Self {
207        Self {
208            memory_tracker: MemoryTracker::new(),
209            throughput_meter: Arc::new(ThroughputMeter::new()),
210            timer: Arc::new(Timer::new()),
211        }
212    }
213
214    /// Get a reference to the memory tracker.
215    pub fn memory_tracker(&self) -> &Arc<MemoryTracker> {
216        &self.memory_tracker
217    }
218
219    /// Get a reference to the throughput meter.
220    pub fn throughput_meter(&self) -> &Arc<ThroughputMeter> {
221        &self.throughput_meter
222    }
223
224    /// Get a reference to the timer.
225    pub fn timer(&self) -> &Arc<Timer> {
226        &self.timer
227    }
228
229    /// Get a comprehensive performance report.
230    pub fn get_report(&self) -> PerformanceReport {
231        PerformanceReport {
232            memory_stats: self.memory_tracker.get_stats(),
233            throughput_bps: self.throughput_meter.current_throughput(),
234            total_bytes: self.throughput_meter.total_bytes(),
235            elapsed_time: self.throughput_meter.elapsed(),
236            total_duration: self.timer.total_duration(),
237        }
238    }
239
240    /// Reset all measurements.
241    pub fn reset(&self) {
242        self.memory_tracker.reset();
243        self.throughput_meter.reset();
244        self.timer.reset();
245    }
246}
247
248/// Comprehensive performance report.
249#[derive(Debug, Clone)]
250pub struct PerformanceReport {
251    /// Memory usage statistics
252    pub memory_stats: MemoryStats,
253    /// Current throughput in bytes per second
254    pub throughput_bps: f64,
255    /// Total bytes transferred
256    pub total_bytes: u64,
257    /// Total elapsed time
258    pub elapsed_time: Duration,
259    /// Accumulated timing duration
260    pub total_duration: Duration,
261}
262
263impl PerformanceReport {
264    /// Get throughput in human-readable format (MB/s).
265    pub fn throughput_mbps(&self) -> f64 {
266        self.throughput_bps / (1024.0 * 1024.0)
267    }
268
269    /// Get memory usage in human-readable format (MB).
270    pub fn memory_usage_mb(&self) -> f64 {
271        self.memory_stats.current_usage as f64 / (1024.0 * 1024.0)
272    }
273
274    /// Get peak memory usage in human-readable format (MB).
275    pub fn peak_memory_mb(&self) -> f64 {
276        self.memory_stats.peak_usage as f64 / (1024.0 * 1024.0)
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use std::thread;
284    use std::time::Duration;
285
286    #[test]
287    fn test_memory_tracker() {
288        let tracker = MemoryTracker::new();
289
290        tracker.record_allocation(1024);
291        tracker.record_allocation(2048);
292
293        let stats = tracker.get_stats();
294        assert_eq!(stats.current_usage, 3072);
295        assert_eq!(stats.allocation_count, 2);
296        assert_eq!(stats.peak_usage, 3072);
297
298        tracker.record_deallocation(1024);
299        let stats = tracker.get_stats();
300        assert_eq!(stats.current_usage, 2048);
301        assert_eq!(stats.peak_usage, 3072);
302    }
303
304    #[test]
305    fn test_throughput_meter() {
306        let meter = ThroughputMeter::new();
307
308        meter.record_bytes(1024);
309        thread::sleep(Duration::from_millis(10));
310        meter.record_bytes(1024);
311
312        assert_eq!(meter.total_bytes(), 2048);
313        assert!(meter.current_throughput() > 0.0);
314        assert!(meter.elapsed() > Duration::ZERO);
315    }
316
317    #[test]
318    fn test_timer() {
319        let mut timer = Timer::new();
320
321        assert!(!timer.is_running());
322        assert_eq!(timer.total_duration(), Duration::ZERO);
323
324        timer.start();
325        assert!(timer.is_running());
326
327        thread::sleep(Duration::from_millis(10));
328        let elapsed = timer.stop();
329
330        assert!(!timer.is_running());
331        assert!(elapsed > Duration::ZERO);
332        assert_eq!(timer.total_duration(), elapsed);
333    }
334
335    #[test]
336    fn test_profiler() {
337        let profiler = Profiler::new();
338
339        profiler.memory_tracker().record_allocation(1024);
340        profiler.throughput_meter().record_bytes(2048);
341
342        let report = profiler.get_report();
343        assert_eq!(report.memory_stats.current_usage, 1024);
344        assert_eq!(report.total_bytes, 2048);
345        assert!(report.elapsed_time >= Duration::ZERO);
346    }
347
348    #[test]
349    fn test_performance_report_formatting() {
350        let report = PerformanceReport {
351            memory_stats: MemoryStats {
352                current_usage: 1024 * 1024,
353                peak_usage: 2 * 1024 * 1024,
354                allocation_count: 10,
355            },
356            throughput_bps: (10 * 1024 * 1024) as f64,
357            total_bytes: 5 * 1024 * 1024,
358            elapsed_time: Duration::from_secs(1),
359            total_duration: Duration::from_millis(500),
360        };
361
362        assert_eq!(report.throughput_mbps(), 10.0);
363        assert_eq!(report.memory_usage_mb(), 1.0);
364        assert_eq!(report.peak_memory_mb(), 2.0);
365    }
366}