use runmat_time::Instant;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::Duration;
#[derive(Debug)]
pub struct GcStats {
pub total_allocations: AtomicUsize,
pub total_allocated_bytes: AtomicU64,
pub minor_collections: AtomicUsize,
pub major_collections: AtomicUsize,
pub minor_collection_time: AtomicU64,
pub major_collection_time: AtomicU64,
pub minor_objects_collected: AtomicUsize,
pub major_objects_collected: AtomicUsize,
pub objects_promoted: AtomicUsize,
pub peak_memory_usage: AtomicUsize,
pub current_memory_usage: AtomicUsize,
collection_history: parking_lot::Mutex<VecDeque<CollectionEvent>>,
allocation_timestamps: parking_lot::Mutex<VecDeque<Instant>>,
start_time: Instant,
}
#[derive(Debug, Clone)]
pub struct CollectionEvent {
pub timestamp: Instant,
pub collection_type: CollectionType,
pub duration: Duration,
pub objects_collected: usize,
pub bytes_collected: usize,
pub heap_size_before: usize,
pub heap_size_after: usize,
pub promotion_count: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CollectionType {
Minor,
Major,
}
impl GcStats {
pub fn new() -> Self {
Self {
total_allocations: AtomicUsize::new(0),
total_allocated_bytes: AtomicU64::new(0),
minor_collections: AtomicUsize::new(0),
major_collections: AtomicUsize::new(0),
minor_collection_time: AtomicU64::new(0),
major_collection_time: AtomicU64::new(0),
minor_objects_collected: AtomicUsize::new(0),
major_objects_collected: AtomicUsize::new(0),
objects_promoted: AtomicUsize::new(0),
peak_memory_usage: AtomicUsize::new(0),
current_memory_usage: AtomicUsize::new(0),
collection_history: parking_lot::Mutex::new(VecDeque::new()),
allocation_timestamps: parking_lot::Mutex::new(VecDeque::new()),
start_time: Instant::now(),
}
}
pub fn record_allocation(&self, size: usize) {
self.total_allocations.fetch_add(1, Ordering::Relaxed);
self.total_allocated_bytes
.fetch_add(size as u64, Ordering::Relaxed);
let new_usage = self.current_memory_usage.fetch_add(size, Ordering::Relaxed) + size;
let mut peak = self.peak_memory_usage.load(Ordering::Relaxed);
while new_usage > peak {
match self.peak_memory_usage.compare_exchange_weak(
peak,
new_usage,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(x) => peak = x,
}
}
let mut timestamps = self.allocation_timestamps.lock();
timestamps.push_back(Instant::now());
let cutoff = Instant::now() - Duration::from_secs(60);
while timestamps.front().is_some_and(|&t| t < cutoff) {
timestamps.pop_front();
}
}
pub fn record_minor_collection(&self, objects_collected: usize, duration: Duration) {
self.minor_collections.fetch_add(1, Ordering::Relaxed);
self.minor_objects_collected
.fetch_add(objects_collected, Ordering::Relaxed);
self.minor_collection_time
.fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
self.record_collection_event(CollectionEvent {
timestamp: Instant::now(),
collection_type: CollectionType::Minor,
duration,
objects_collected,
bytes_collected: objects_collected * 64, heap_size_before: 0,
heap_size_after: 0,
promotion_count: 0,
});
}
pub fn record_major_collection(&self, objects_collected: usize, duration: Duration) {
self.major_collections.fetch_add(1, Ordering::Relaxed);
self.major_objects_collected
.fetch_add(objects_collected, Ordering::Relaxed);
self.major_collection_time
.fetch_add(duration.as_nanos() as u64, Ordering::Relaxed);
self.record_collection_event(CollectionEvent {
timestamp: Instant::now(),
collection_type: CollectionType::Major,
duration,
objects_collected,
bytes_collected: 0,
heap_size_before: 0,
heap_size_after: 0,
promotion_count: 0,
});
}
pub fn record_promotion(&self, count: usize) {
self.objects_promoted.fetch_add(count, Ordering::Relaxed);
}
pub fn record_deallocation(&self, size: usize) {
self.current_memory_usage.fetch_sub(size, Ordering::Relaxed);
}
pub fn allocation_rate(&self) -> f64 {
let timestamps = self.allocation_timestamps.lock();
if timestamps.len() < 2 {
return 0.0;
}
let duration = timestamps
.back()
.unwrap()
.duration_since(*timestamps.front().unwrap());
if duration.as_secs_f64() == 0.0 {
return 0.0;
}
timestamps.len() as f64 / duration.as_secs_f64()
}
pub fn average_minor_collection_time(&self) -> Duration {
let total_time = Duration::from_nanos(self.minor_collection_time.load(Ordering::Relaxed));
let count = self.minor_collections.load(Ordering::Relaxed);
if count == 0 {
Duration::ZERO
} else {
total_time / count as u32
}
}
pub fn average_major_collection_time(&self) -> Duration {
let total_time = Duration::from_nanos(self.major_collection_time.load(Ordering::Relaxed));
let count = self.major_collections.load(Ordering::Relaxed);
if count == 0 {
Duration::ZERO
} else {
total_time / count as u32
}
}
pub fn gc_overhead_percentage(&self) -> f64 {
let total_gc_time = Duration::from_nanos(
self.minor_collection_time.load(Ordering::Relaxed)
+ self.major_collection_time.load(Ordering::Relaxed),
);
let total_runtime = self.start_time.elapsed();
if total_runtime.as_nanos() == 0 {
0.0
} else {
(total_gc_time.as_nanos() as f64 / total_runtime.as_nanos() as f64) * 100.0
}
}
pub fn memory_utilization(&self) -> f64 {
let current = self.current_memory_usage.load(Ordering::Relaxed);
let peak = self.peak_memory_usage.load(Ordering::Relaxed);
if peak == 0 {
0.0
} else {
(current as f64 / peak as f64) * 100.0
}
}
pub fn collection_frequency(&self) -> (f64, f64) {
let runtime_minutes = self.start_time.elapsed().as_secs_f64() / 60.0;
if runtime_minutes == 0.0 {
return (0.0, 0.0);
}
let minor_freq = self.minor_collections.load(Ordering::Relaxed) as f64 / runtime_minutes;
let major_freq = self.major_collections.load(Ordering::Relaxed) as f64 / runtime_minutes;
(minor_freq, major_freq)
}
pub fn recent_collections(&self, limit: usize) -> Vec<CollectionEvent> {
let history = self.collection_history.lock();
history.iter().rev().take(limit).cloned().collect()
}
fn record_collection_event(&self, event: CollectionEvent) {
let mut history = self.collection_history.lock();
history.push_back(event);
while history.len() > 1000 {
history.pop_front();
}
}
pub fn reset(&self) {
self.total_allocations.store(0, Ordering::Relaxed);
self.total_allocated_bytes.store(0, Ordering::Relaxed);
self.minor_collections.store(0, Ordering::Relaxed);
self.major_collections.store(0, Ordering::Relaxed);
self.minor_collection_time.store(0, Ordering::Relaxed);
self.major_collection_time.store(0, Ordering::Relaxed);
self.minor_objects_collected.store(0, Ordering::Relaxed);
self.major_objects_collected.store(0, Ordering::Relaxed);
self.objects_promoted.store(0, Ordering::Relaxed);
self.peak_memory_usage.store(0, Ordering::Relaxed);
self.current_memory_usage.store(0, Ordering::Relaxed);
self.collection_history.lock().clear();
self.allocation_timestamps.lock().clear();
}
pub fn summary_report(&self) -> String {
let total_allocs = self.total_allocations.load(Ordering::Relaxed);
let total_bytes = self.total_allocated_bytes.load(Ordering::Relaxed);
let minor_colls = self.minor_collections.load(Ordering::Relaxed);
let major_colls = self.major_collections.load(Ordering::Relaxed);
let current_mem = self.current_memory_usage.load(Ordering::Relaxed);
let peak_mem = self.peak_memory_usage.load(Ordering::Relaxed);
let (minor_freq, major_freq) = self.collection_frequency();
format!(
"GC Statistics Summary:\n\
Allocations: {} ({} bytes)\n\
Current Memory: {} bytes (Peak: {} bytes)\n\
Minor Collections: {} (avg {:.2}ms, {:.1}/min)\n\
Major Collections: {} (avg {:.2}ms, {:.1}/min)\n\
GC Overhead: {:.2}%\n\
Allocation Rate: {:.1} allocs/sec\n\
Memory Utilization: {:.1}%",
total_allocs,
total_bytes,
current_mem,
peak_mem,
minor_colls,
self.average_minor_collection_time().as_secs_f64() * 1000.0,
minor_freq,
major_colls,
self.average_major_collection_time().as_secs_f64() * 1000.0,
major_freq,
self.gc_overhead_percentage(),
self.allocation_rate(),
self.memory_utilization()
)
}
}
impl Clone for GcStats {
fn clone(&self) -> Self {
Self {
total_allocations: AtomicUsize::new(self.total_allocations.load(Ordering::Relaxed)),
total_allocated_bytes: AtomicU64::new(
self.total_allocated_bytes.load(Ordering::Relaxed),
),
minor_collections: AtomicUsize::new(self.minor_collections.load(Ordering::Relaxed)),
major_collections: AtomicUsize::new(self.major_collections.load(Ordering::Relaxed)),
minor_collection_time: AtomicU64::new(
self.minor_collection_time.load(Ordering::Relaxed),
),
major_collection_time: AtomicU64::new(
self.major_collection_time.load(Ordering::Relaxed),
),
minor_objects_collected: AtomicUsize::new(
self.minor_objects_collected.load(Ordering::Relaxed),
),
major_objects_collected: AtomicUsize::new(
self.major_objects_collected.load(Ordering::Relaxed),
),
objects_promoted: AtomicUsize::new(self.objects_promoted.load(Ordering::Relaxed)),
peak_memory_usage: AtomicUsize::new(self.peak_memory_usage.load(Ordering::Relaxed)),
current_memory_usage: AtomicUsize::new(
self.current_memory_usage.load(Ordering::Relaxed),
),
collection_history: parking_lot::Mutex::new(self.collection_history.lock().clone()),
allocation_timestamps: parking_lot::Mutex::new(
self.allocation_timestamps.lock().clone(),
),
start_time: self.start_time,
}
}
}
impl Default for GcStats {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub allocation_rate: f64,
pub gc_overhead: f64,
pub memory_efficiency: f64,
pub collection_latency: Duration,
pub throughput: f64,
}
impl PerformanceMetrics {
pub fn from_stats(stats: &GcStats) -> Self {
Self {
allocation_rate: stats.allocation_rate(),
gc_overhead: stats.gc_overhead_percentage(),
memory_efficiency: stats.memory_utilization(),
collection_latency: stats
.average_minor_collection_time()
.max(stats.average_major_collection_time()),
throughput: stats.total_allocations.load(Ordering::Relaxed) as f64
/ stats.start_time.elapsed().as_secs_f64(),
}
}
pub fn performance_score(&self) -> f64 {
let latency_score = if self.collection_latency.as_millis() < 10 {
100.0
} else {
(100.0 / (self.collection_latency.as_millis() as f64 / 10.0)).min(100.0)
};
let overhead_score = (100.0 - self.gc_overhead).max(0.0);
let efficiency_score = self.memory_efficiency;
(latency_score + overhead_score + efficiency_score) / 3.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_stats_basic_operations() {
let stats = GcStats::new();
stats.record_allocation(100);
assert_eq!(stats.total_allocations.load(Ordering::Relaxed), 1);
assert_eq!(stats.total_allocated_bytes.load(Ordering::Relaxed), 100);
assert_eq!(stats.current_memory_usage.load(Ordering::Relaxed), 100);
stats.record_deallocation(50);
assert_eq!(stats.current_memory_usage.load(Ordering::Relaxed), 50);
}
#[test]
fn test_collection_recording() {
let stats = GcStats::new();
stats.record_minor_collection(10, Duration::from_millis(5));
assert_eq!(stats.minor_collections.load(Ordering::Relaxed), 1);
assert_eq!(stats.minor_objects_collected.load(Ordering::Relaxed), 10);
stats.record_major_collection(50, Duration::from_millis(20));
assert_eq!(stats.major_collections.load(Ordering::Relaxed), 1);
assert_eq!(stats.major_objects_collected.load(Ordering::Relaxed), 50);
}
#[test]
fn test_allocation_rate() {
let stats = GcStats::new();
stats.record_allocation(100);
thread::sleep(Duration::from_millis(100));
stats.record_allocation(100);
thread::sleep(Duration::from_millis(100));
stats.record_allocation(100);
let rate = stats.allocation_rate();
assert!(rate > 0.0);
assert!(rate < 100.0); }
#[test]
fn test_performance_metrics() {
let stats = GcStats::new();
stats.record_allocation(1000);
stats.record_minor_collection(5, Duration::from_millis(2));
let metrics = PerformanceMetrics::from_stats(&stats);
assert!(metrics.performance_score() >= 0.0);
assert!(metrics.performance_score() <= 100.0);
}
#[test]
fn test_stats_reset() {
let stats = GcStats::new();
stats.record_allocation(100);
stats.record_minor_collection(5, Duration::from_millis(2));
assert!(stats.total_allocations.load(Ordering::Relaxed) > 0);
stats.reset();
assert_eq!(stats.total_allocations.load(Ordering::Relaxed), 0);
assert_eq!(stats.minor_collections.load(Ordering::Relaxed), 0);
}
}