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