1use runmat_time::Instant;
7use std::collections::VecDeque;
8use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
9use std::time::Duration;
10
11#[derive(Debug)]
13pub struct GcStats {
14 pub total_allocations: AtomicUsize,
16
17 pub total_allocated_bytes: AtomicU64,
19
20 pub minor_collections: AtomicUsize,
22
23 pub major_collections: AtomicUsize,
25
26 pub minor_collection_time: AtomicU64,
28
29 pub major_collection_time: AtomicU64,
31
32 pub minor_objects_collected: AtomicUsize,
34
35 pub major_objects_collected: AtomicUsize,
37
38 pub objects_promoted: AtomicUsize,
40
41 pub peak_memory_usage: AtomicUsize,
43
44 pub current_memory_usage: AtomicUsize,
46
47 collection_history: parking_lot::Mutex<VecDeque<CollectionEvent>>,
49
50 allocation_timestamps: parking_lot::Mutex<VecDeque<Instant>>,
52
53 start_time: Instant,
55}
56
57#[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 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 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 let mut timestamps = self.allocation_timestamps.lock();
120 timestamps.push_back(Instant::now());
121
122 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 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, heap_size_before: 0,
144 heap_size_after: 0,
145 promotion_count: 0,
146 });
147 }
148
149 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 pub fn record_promotion(&self, count: usize) {
171 self.objects_promoted.fetch_add(count, Ordering::Relaxed);
172 }
173
174 pub fn record_deallocation(&self, size: usize) {
176 self.current_memory_usage.fetch_sub(size, Ordering::Relaxed);
177 }
178
179 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 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 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 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 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 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 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 fn record_collection_event(&self, event: CollectionEvent) {
270 let mut history = self.collection_history.lock();
271 history.push_back(event);
272
273 while history.len() > 1000 {
275 history.pop_front();
276 }
277 }
278
279 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 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#[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 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 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 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 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); }
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}