Skip to main content

runmat_gc/
stats.rs

1//! Garbage collection statistics and metrics
2//!
3//! Provides detailed statistics about GC performance, memory usage,
4//! and collection behavior for monitoring and tuning.
5
6use runmat_time::Instant;
7use std::collections::VecDeque;
8use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
9use std::time::Duration;
10
11/// Comprehensive GC statistics
12#[derive(Debug)]
13pub struct GcStats {
14    /// Total number of allocations
15    pub total_allocations: AtomicUsize,
16
17    /// Total bytes allocated
18    pub total_allocated_bytes: AtomicU64,
19
20    /// Number of minor collections performed
21    pub minor_collections: AtomicUsize,
22
23    /// Number of major collections performed  
24    pub major_collections: AtomicUsize,
25
26    /// Total time spent in minor collections
27    pub minor_collection_time: AtomicU64,
28
29    /// Total time spent in major collections
30    pub major_collection_time: AtomicU64,
31
32    /// Objects collected in minor collections
33    pub minor_objects_collected: AtomicUsize,
34
35    /// Objects collected in major collections
36    pub major_objects_collected: AtomicUsize,
37
38    /// Objects promoted between generations
39    pub objects_promoted: AtomicUsize,
40
41    /// Peak memory usage
42    pub peak_memory_usage: AtomicUsize,
43
44    /// Current memory usage
45    pub current_memory_usage: AtomicUsize,
46
47    /// Collection history for trend analysis
48    collection_history: parking_lot::Mutex<VecDeque<CollectionEvent>>,
49
50    /// Allocation rate tracking
51    allocation_timestamps: parking_lot::Mutex<VecDeque<Instant>>,
52
53    /// Start time for rate calculations
54    start_time: Instant,
55}
56
57/// Information about a single garbage collection event
58#[derive(Debug, Clone)]
59pub struct CollectionEvent {
60    pub timestamp: Instant,
61    pub collection_type: CollectionType,
62    pub duration: Duration,
63    pub objects_collected: usize,
64    pub bytes_collected: usize,
65    pub heap_size_before: usize,
66    pub heap_size_after: usize,
67    pub promotion_count: usize,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum CollectionType {
72    Minor,
73    Major,
74}
75
76impl GcStats {
77    pub fn new() -> Self {
78        Self {
79            total_allocations: AtomicUsize::new(0),
80            total_allocated_bytes: AtomicU64::new(0),
81            minor_collections: AtomicUsize::new(0),
82            major_collections: AtomicUsize::new(0),
83            minor_collection_time: AtomicU64::new(0),
84            major_collection_time: AtomicU64::new(0),
85            minor_objects_collected: AtomicUsize::new(0),
86            major_objects_collected: AtomicUsize::new(0),
87            objects_promoted: AtomicUsize::new(0),
88            peak_memory_usage: AtomicUsize::new(0),
89            current_memory_usage: AtomicUsize::new(0),
90            collection_history: parking_lot::Mutex::new(VecDeque::new()),
91            allocation_timestamps: parking_lot::Mutex::new(VecDeque::new()),
92            start_time: Instant::now(),
93        }
94    }
95
96    /// Record an allocation
97    pub fn record_allocation(&self, size: usize) {
98        self.total_allocations.fetch_add(1, Ordering::Relaxed);
99        self.total_allocated_bytes
100            .fetch_add(size as u64, Ordering::Relaxed);
101
102        let new_usage = self.current_memory_usage.fetch_add(size, Ordering::Relaxed) + size;
103
104        // Update peak usage
105        let mut peak = self.peak_memory_usage.load(Ordering::Relaxed);
106        while new_usage > peak {
107            match self.peak_memory_usage.compare_exchange_weak(
108                peak,
109                new_usage,
110                Ordering::Relaxed,
111                Ordering::Relaxed,
112            ) {
113                Ok(_) => break,
114                Err(x) => peak = x,
115            }
116        }
117
118        // Track allocation rate
119        let mut timestamps = self.allocation_timestamps.lock();
120        timestamps.push_back(Instant::now());
121
122        // Keep only recent allocations for rate calculation (last 60 seconds)
123        let cutoff = Instant::now() - Duration::from_secs(60);
124        while timestamps.front().is_some_and(|&t| t < cutoff) {
125            timestamps.pop_front();
126        }
127    }
128
129    /// Record a minor collection
130    pub fn record_minor_collection(&self, objects_collected: usize, duration: Duration) {
131        self.minor_collections.fetch_add(1, Ordering::Relaxed);
132        self.minor_objects_collected
133            .fetch_add(objects_collected, Ordering::Relaxed);
134        self.minor_collection_time
135            .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
136
137        self.record_collection_event(CollectionEvent {
138            timestamp: Instant::now(),
139            collection_type: CollectionType::Minor,
140            duration,
141            objects_collected,
142            bytes_collected: objects_collected * 64, // Estimate: avg 64 bytes per object
143            heap_size_before: 0,
144            heap_size_after: 0,
145            promotion_count: 0,
146        });
147    }
148
149    /// Record a major collection
150    pub fn record_major_collection(&self, objects_collected: usize, duration: Duration) {
151        self.major_collections.fetch_add(1, Ordering::Relaxed);
152        self.major_objects_collected
153            .fetch_add(objects_collected, Ordering::Relaxed);
154        self.major_collection_time
155            .fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
156
157        self.record_collection_event(CollectionEvent {
158            timestamp: Instant::now(),
159            collection_type: CollectionType::Major,
160            duration,
161            objects_collected,
162            bytes_collected: 0,
163            heap_size_before: 0,
164            heap_size_after: 0,
165            promotion_count: 0,
166        });
167    }
168
169    /// Record object promotion
170    pub fn record_promotion(&self, count: usize) {
171        self.objects_promoted.fetch_add(count, Ordering::Relaxed);
172    }
173
174    /// Record memory deallocation
175    pub fn record_deallocation(&self, size: usize) {
176        self.current_memory_usage.fetch_sub(size, Ordering::Relaxed);
177    }
178
179    /// Get current allocation rate (allocations per second)
180    pub fn allocation_rate(&self) -> f64 {
181        let timestamps = self.allocation_timestamps.lock();
182        if timestamps.len() < 2 {
183            return 0.0;
184        }
185
186        let duration = timestamps
187            .back()
188            .unwrap()
189            .duration_since(*timestamps.front().unwrap());
190        if duration.as_secs_f64() == 0.0 {
191            return 0.0;
192        }
193
194        timestamps.len() as f64 / duration.as_secs_f64()
195    }
196
197    /// Get average minor collection time
198    pub fn average_minor_collection_time(&self) -> Duration {
199        let total_time = Duration::from_nanos(self.minor_collection_time.load(Ordering::Relaxed));
200        let count = self.minor_collections.load(Ordering::Relaxed);
201
202        if count == 0 {
203            Duration::ZERO
204        } else {
205            total_time / count as u32
206        }
207    }
208
209    /// Get average major collection time
210    pub fn average_major_collection_time(&self) -> Duration {
211        let total_time = Duration::from_nanos(self.major_collection_time.load(Ordering::Relaxed));
212        let count = self.major_collections.load(Ordering::Relaxed);
213
214        if count == 0 {
215            Duration::ZERO
216        } else {
217            total_time / count as u32
218        }
219    }
220
221    /// Get GC overhead as percentage of total runtime
222    pub fn gc_overhead_percentage(&self) -> f64 {
223        let total_gc_time = Duration::from_nanos(
224            self.minor_collection_time.load(Ordering::Relaxed)
225                + self.major_collection_time.load(Ordering::Relaxed),
226        );
227
228        let total_runtime = self.start_time.elapsed();
229
230        if total_runtime.as_nanos() == 0 {
231            0.0
232        } else {
233            (total_gc_time.as_nanos() as f64 / total_runtime.as_nanos() as f64) * 100.0
234        }
235    }
236
237    /// Get memory utilization percentage
238    pub fn memory_utilization(&self) -> f64 {
239        let current = self.current_memory_usage.load(Ordering::Relaxed);
240        let peak = self.peak_memory_usage.load(Ordering::Relaxed);
241
242        if peak == 0 {
243            0.0
244        } else {
245            (current as f64 / peak as f64) * 100.0
246        }
247    }
248
249    /// Get collection frequency (collections per minute)
250    pub fn collection_frequency(&self) -> (f64, f64) {
251        let runtime_minutes = self.start_time.elapsed().as_secs_f64() / 60.0;
252        if runtime_minutes == 0.0 {
253            return (0.0, 0.0);
254        }
255
256        let minor_freq = self.minor_collections.load(Ordering::Relaxed) as f64 / runtime_minutes;
257        let major_freq = self.major_collections.load(Ordering::Relaxed) as f64 / runtime_minutes;
258
259        (minor_freq, major_freq)
260    }
261
262    /// Get recent collection history
263    pub fn recent_collections(&self, limit: usize) -> Vec<CollectionEvent> {
264        let history = self.collection_history.lock();
265        history.iter().rev().take(limit).cloned().collect()
266    }
267
268    /// Record a collection event
269    fn record_collection_event(&self, event: CollectionEvent) {
270        let mut history = self.collection_history.lock();
271        history.push_back(event);
272
273        // Keep only recent history (last 1000 events)
274        while history.len() > 1000 {
275            history.pop_front();
276        }
277    }
278
279    /// Reset all statistics
280    pub fn reset(&self) {
281        self.total_allocations.store(0, Ordering::Relaxed);
282        self.total_allocated_bytes.store(0, Ordering::Relaxed);
283        self.minor_collections.store(0, Ordering::Relaxed);
284        self.major_collections.store(0, Ordering::Relaxed);
285        self.minor_collection_time.store(0, Ordering::Relaxed);
286        self.major_collection_time.store(0, Ordering::Relaxed);
287        self.minor_objects_collected.store(0, Ordering::Relaxed);
288        self.major_objects_collected.store(0, Ordering::Relaxed);
289        self.objects_promoted.store(0, Ordering::Relaxed);
290        self.peak_memory_usage.store(0, Ordering::Relaxed);
291        self.current_memory_usage.store(0, Ordering::Relaxed);
292
293        self.collection_history.lock().clear();
294        self.allocation_timestamps.lock().clear();
295    }
296
297    /// Generate a summary report
298    pub fn summary_report(&self) -> String {
299        let total_allocs = self.total_allocations.load(Ordering::Relaxed);
300        let total_bytes = self.total_allocated_bytes.load(Ordering::Relaxed);
301        let minor_colls = self.minor_collections.load(Ordering::Relaxed);
302        let major_colls = self.major_collections.load(Ordering::Relaxed);
303        let current_mem = self.current_memory_usage.load(Ordering::Relaxed);
304        let peak_mem = self.peak_memory_usage.load(Ordering::Relaxed);
305        let (minor_freq, major_freq) = self.collection_frequency();
306
307        format!(
308            "GC Statistics Summary:\n\
309             Allocations: {} ({} bytes)\n\
310             Current Memory: {} bytes (Peak: {} bytes)\n\
311             Minor Collections: {} (avg {:.2}ms, {:.1}/min)\n\
312             Major Collections: {} (avg {:.2}ms, {:.1}/min)\n\
313             GC Overhead: {:.2}%\n\
314             Allocation Rate: {:.1} allocs/sec\n\
315             Memory Utilization: {:.1}%",
316            total_allocs,
317            total_bytes,
318            current_mem,
319            peak_mem,
320            minor_colls,
321            self.average_minor_collection_time().as_secs_f64() * 1000.0,
322            minor_freq,
323            major_colls,
324            self.average_major_collection_time().as_secs_f64() * 1000.0,
325            major_freq,
326            self.gc_overhead_percentage(),
327            self.allocation_rate(),
328            self.memory_utilization()
329        )
330    }
331}
332
333impl Clone for GcStats {
334    fn clone(&self) -> Self {
335        Self {
336            total_allocations: AtomicUsize::new(self.total_allocations.load(Ordering::Relaxed)),
337            total_allocated_bytes: AtomicU64::new(
338                self.total_allocated_bytes.load(Ordering::Relaxed),
339            ),
340            minor_collections: AtomicUsize::new(self.minor_collections.load(Ordering::Relaxed)),
341            major_collections: AtomicUsize::new(self.major_collections.load(Ordering::Relaxed)),
342            minor_collection_time: AtomicU64::new(
343                self.minor_collection_time.load(Ordering::Relaxed),
344            ),
345            major_collection_time: AtomicU64::new(
346                self.major_collection_time.load(Ordering::Relaxed),
347            ),
348            minor_objects_collected: AtomicUsize::new(
349                self.minor_objects_collected.load(Ordering::Relaxed),
350            ),
351            major_objects_collected: AtomicUsize::new(
352                self.major_objects_collected.load(Ordering::Relaxed),
353            ),
354            objects_promoted: AtomicUsize::new(self.objects_promoted.load(Ordering::Relaxed)),
355            peak_memory_usage: AtomicUsize::new(self.peak_memory_usage.load(Ordering::Relaxed)),
356            current_memory_usage: AtomicUsize::new(
357                self.current_memory_usage.load(Ordering::Relaxed),
358            ),
359            collection_history: parking_lot::Mutex::new(self.collection_history.lock().clone()),
360            allocation_timestamps: parking_lot::Mutex::new(
361                self.allocation_timestamps.lock().clone(),
362            ),
363            start_time: self.start_time,
364        }
365    }
366}
367
368impl Default for GcStats {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374/// Performance metrics for GC tuning
375#[derive(Debug, Clone)]
376pub struct PerformanceMetrics {
377    pub allocation_rate: f64,
378    pub gc_overhead: f64,
379    pub memory_efficiency: f64,
380    pub collection_latency: Duration,
381    pub throughput: f64,
382}
383
384impl PerformanceMetrics {
385    pub fn from_stats(stats: &GcStats) -> Self {
386        Self {
387            allocation_rate: stats.allocation_rate(),
388            gc_overhead: stats.gc_overhead_percentage(),
389            memory_efficiency: stats.memory_utilization(),
390            collection_latency: stats
391                .average_minor_collection_time()
392                .max(stats.average_major_collection_time()),
393            throughput: stats.total_allocations.load(Ordering::Relaxed) as f64
394                / stats.start_time.elapsed().as_secs_f64(),
395        }
396    }
397
398    /// Get an overall performance score (0-100)
399    pub fn performance_score(&self) -> f64 {
400        let latency_score = if self.collection_latency.as_millis() < 10 {
401            100.0
402        } else {
403            (100.0 / (self.collection_latency.as_millis() as f64 / 10.0)).min(100.0)
404        };
405
406        let overhead_score = (100.0 - self.gc_overhead).max(0.0);
407        let efficiency_score = self.memory_efficiency;
408
409        (latency_score + overhead_score + efficiency_score) / 3.0
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416    use std::thread;
417
418    #[test]
419    fn test_stats_basic_operations() {
420        let stats = GcStats::new();
421
422        // Test allocation recording
423        stats.record_allocation(100);
424        assert_eq!(stats.total_allocations.load(Ordering::Relaxed), 1);
425        assert_eq!(stats.total_allocated_bytes.load(Ordering::Relaxed), 100);
426        assert_eq!(stats.current_memory_usage.load(Ordering::Relaxed), 100);
427
428        // Test deallocation
429        stats.record_deallocation(50);
430        assert_eq!(stats.current_memory_usage.load(Ordering::Relaxed), 50);
431    }
432
433    #[test]
434    fn test_collection_recording() {
435        let stats = GcStats::new();
436
437        stats.record_minor_collection(10, Duration::from_millis(5));
438        assert_eq!(stats.minor_collections.load(Ordering::Relaxed), 1);
439        assert_eq!(stats.minor_objects_collected.load(Ordering::Relaxed), 10);
440
441        stats.record_major_collection(50, Duration::from_millis(20));
442        assert_eq!(stats.major_collections.load(Ordering::Relaxed), 1);
443        assert_eq!(stats.major_objects_collected.load(Ordering::Relaxed), 50);
444    }
445
446    #[test]
447    fn test_allocation_rate() {
448        let stats = GcStats::new();
449
450        // Record some allocations with time delays
451        stats.record_allocation(100);
452        thread::sleep(Duration::from_millis(100));
453        stats.record_allocation(100);
454        thread::sleep(Duration::from_millis(100));
455        stats.record_allocation(100);
456
457        let rate = stats.allocation_rate();
458        assert!(rate > 0.0);
459        assert!(rate < 100.0); // Should be reasonable
460    }
461
462    #[test]
463    fn test_performance_metrics() {
464        let stats = GcStats::new();
465        stats.record_allocation(1000);
466        stats.record_minor_collection(5, Duration::from_millis(2));
467
468        let metrics = PerformanceMetrics::from_stats(&stats);
469        assert!(metrics.performance_score() >= 0.0);
470        assert!(metrics.performance_score() <= 100.0);
471    }
472
473    #[test]
474    fn test_stats_reset() {
475        let stats = GcStats::new();
476
477        stats.record_allocation(100);
478        stats.record_minor_collection(5, Duration::from_millis(2));
479
480        assert!(stats.total_allocations.load(Ordering::Relaxed) > 0);
481
482        stats.reset();
483
484        assert_eq!(stats.total_allocations.load(Ordering::Relaxed), 0);
485        assert_eq!(stats.minor_collections.load(Ordering::Relaxed), 0);
486    }
487}