use crate::analyzer::report::{MetricsReport, TypeMetric};
use crate::view::MemoryView;
use std::collections::HashMap;
use tracing::debug;
pub struct MetricsAnalysis {
view: MemoryView,
}
impl MetricsAnalysis {
pub fn from_view(view: &MemoryView) -> Self {
debug!("Creating MetricsAnalysis with {} allocations", view.len());
Self { view: view.clone() }
}
pub fn summary(&self) -> MetricsReport {
debug!("Computing metrics summary");
let stats = self.view.snapshot();
let report = MetricsReport {
allocation_count: stats.stats.active_allocations,
total_bytes: stats.stats.current_memory,
peak_bytes: stats.stats.peak_memory,
thread_count: stats.thread_stats.len(),
by_type: self.by_type(),
};
debug!(
"Metrics summary: {} allocations, {} bytes, {} types, {} threads",
report.allocation_count,
report.total_bytes,
report.by_type.len(),
report.thread_count
);
report
}
pub fn top_by_size(&self, n: usize) -> Vec<AllocationMetric> {
debug!("Getting top {} allocations by size", n);
let mut allocs: Vec<_> = self.view.allocations();
allocs.sort_by_key(|a| std::cmp::Reverse(a.size));
let result: Vec<AllocationMetric> = allocs
.into_iter()
.take(n)
.map(AllocationMetric::from_allocation)
.collect();
debug!("Returning {} top allocations", result.len());
result
}
pub fn by_type(&self) -> HashMap<String, TypeMetric> {
let mut types: HashMap<String, TypeMetric> = HashMap::new();
for a in self.view.allocations() {
let type_name = a.type_name.clone().unwrap_or_else(|| "unknown".to_string());
let entry = types.entry(type_name).or_default();
entry.count += 1;
entry.total_bytes += a.size;
}
debug!("Computed metrics for {} types", types.len());
types
}
pub fn by_thread(&self) -> HashMap<u64, ThreadMetric> {
let mut threads: HashMap<u64, ThreadMetric> = HashMap::new();
for a in self.view.allocations() {
let entry = threads.entry(a.thread_id).or_default();
entry.thread_id = a.thread_id;
entry.allocation_count += 1;
entry.total_bytes += a.size;
}
debug!("Computed metrics for {} threads", threads.len());
threads
}
pub fn size_distribution(&self) -> SizeDistribution {
let allocations = self.view.allocations();
let sizes: Vec<usize> = allocations.iter().map(|a| a.size).collect();
if sizes.is_empty() {
debug!("Size distribution: no allocations");
return SizeDistribution::default();
}
let min = *sizes.iter().min().unwrap_or(&0);
let max = *sizes.iter().max().unwrap_or(&0);
let avg = sizes.iter().sum::<usize>() / sizes.len();
debug!("Size distribution: min={}, max={}, avg={}", min, max, avg);
SizeDistribution { min, max, avg }
}
}
#[derive(Debug, Clone)]
pub struct AllocationMetric {
pub ptr: usize,
pub size: usize,
pub type_name: Option<String>,
pub var_name: Option<String>,
pub thread_id: u64,
}
impl AllocationMetric {
fn from_allocation(alloc: &crate::snapshot::ActiveAllocation) -> Self {
Self {
ptr: alloc.ptr.unwrap_or(0),
size: alloc.size,
type_name: alloc.type_name.clone(),
var_name: alloc.var_name.clone(),
thread_id: alloc.thread_id,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ThreadMetric {
pub thread_id: u64,
pub allocation_count: usize,
pub total_bytes: usize,
}
#[derive(Debug, Clone, Default)]
pub struct SizeDistribution {
pub min: usize,
pub max: usize,
pub avg: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event_store::MemoryEvent;
#[test]
fn test_metrics_summary() {
let events = vec![
MemoryEvent::allocate(0x1000, 64, 1),
MemoryEvent::allocate(0x2000, 128, 2),
];
let view = MemoryView::from_events(events);
let analysis = MetricsAnalysis::from_view(&view);
let summary = analysis.summary();
assert_eq!(summary.allocation_count, 2);
assert_eq!(summary.total_bytes, 192);
}
#[test]
fn test_top_by_size() {
let events = vec![
MemoryEvent::allocate(0x1000, 64, 1),
MemoryEvent::allocate(0x2000, 256, 1),
MemoryEvent::allocate(0x3000, 128, 1),
];
let view = MemoryView::from_events(events);
let analysis = MetricsAnalysis::from_view(&view);
let top = analysis.top_by_size(2);
assert_eq!(top.len(), 2);
assert_eq!(top[0].size, 256);
assert_eq!(top[1].size, 128);
}
}