1use std::collections::VecDeque;
7use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
8use std::time::{Duration, Instant};
9
10#[derive(Debug)]
12pub struct GcStats {
13 pub total_allocations: AtomicUsize,
15
16 pub total_allocated_bytes: AtomicU64,
18
19 pub minor_collections: AtomicUsize,
21
22 pub major_collections: AtomicUsize,
24
25 pub minor_collection_time: AtomicU64,
27
28 pub major_collection_time: AtomicU64,
30
31 pub minor_objects_collected: AtomicUsize,
33
34 pub major_objects_collected: AtomicUsize,
36
37 pub objects_promoted: AtomicUsize,
39
40 pub peak_memory_usage: AtomicUsize,
42
43 pub current_memory_usage: AtomicUsize,
45
46 collection_history: parking_lot::Mutex<VecDeque<CollectionEvent>>,
48
49 allocation_timestamps: parking_lot::Mutex<VecDeque<Instant>>,
51
52 start_time: Instant,
54}
55
56#[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 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 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 let mut timestamps = self.allocation_timestamps.lock();
119 timestamps.push_back(Instant::now());
120
121 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 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, heap_size_before: 0,
143 heap_size_after: 0,
144 promotion_count: 0,
145 });
146 }
147
148 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 pub fn record_promotion(&self, count: usize) {
170 self.objects_promoted.fetch_add(count, Ordering::Relaxed);
171 }
172
173 pub fn record_deallocation(&self, size: usize) {
175 self.current_memory_usage.fetch_sub(size, Ordering::Relaxed);
176 }
177
178 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 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 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 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 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 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 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 fn record_collection_event(&self, event: CollectionEvent) {
269 let mut history = self.collection_history.lock();
270 history.push_back(event);
271
272 while history.len() > 1000 {
274 history.pop_front();
275 }
276 }
277
278 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 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#[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 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 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 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 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); }
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}