use super::ownership_history::{BorrowInfo, CloneInfo, OwnershipHistoryRecorder, OwnershipSummary};
use crate::capture::types::AllocationInfo;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub struct LifecycleSummaryGenerator {
config: SummaryConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SummaryConfig {
pub include_borrow_details: bool,
pub include_clone_details: bool,
pub min_lifetime_threshold_ms: u64,
pub max_events_per_allocation: usize,
}
impl Default for SummaryConfig {
fn default() -> Self {
Self {
include_borrow_details: true,
include_clone_details: true,
min_lifetime_threshold_ms: 0,
max_events_per_allocation: 50,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LifecycleExportData {
pub lifecycle_events: Vec<LifecycleEventSummary>,
pub variable_groups: Vec<VariableGroup>,
pub user_variables_count: usize,
pub visualization_ready: bool,
pub metadata: ExportMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LifecycleEventSummary {
pub allocation_ptr: usize,
pub var_name: Option<String>,
pub type_name: Option<String>,
pub size: usize,
pub lifetime_ms: Option<u64>,
pub events: Vec<LifecycleEvent>,
pub summary: AllocationLifecycleSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LifecycleEvent {
pub id: u64,
pub event_type: String,
pub timestamp: u64,
pub size: Option<usize>,
pub details: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AllocationLifecycleSummary {
pub lifetime_ms: Option<u64>,
pub borrow_info: BorrowInfo,
pub clone_info: CloneInfo,
pub ownership_history_available: bool,
pub lifecycle_pattern: LifecyclePattern,
pub efficiency_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LifecyclePattern {
Ephemeral,
ShortTerm,
MediumTerm,
LongTerm,
Leaked,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableGroup {
pub name: String,
pub variables: Vec<String>,
pub total_memory: usize,
pub average_lifetime_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportMetadata {
pub export_timestamp: u64,
pub total_allocations: usize,
pub total_events: usize,
pub analysis_duration_ms: u64,
}
impl LifecycleSummaryGenerator {
pub fn new() -> Self {
Self::with_config(SummaryConfig::default())
}
pub fn with_config(config: SummaryConfig) -> Self {
Self { config }
}
pub fn generate_lifecycle_export(
&self,
ownership_history: &OwnershipHistoryRecorder,
allocations: &[AllocationInfo],
) -> LifecycleExportData {
let start_time = std::time::Instant::now();
let lifecycle_events = self.generate_lifecycle_events(ownership_history, allocations);
let variable_groups = self.generate_variable_groups(&lifecycle_events);
let user_variables_count = lifecycle_events
.iter()
.filter(|event| {
event
.var_name
.as_ref()
.map(|name| self.is_user_variable(name))
.unwrap_or(false)
})
.count();
let analysis_duration = start_time.elapsed().as_millis() as u64;
LifecycleExportData {
lifecycle_events,
variable_groups,
user_variables_count,
visualization_ready: true,
metadata: ExportMetadata {
export_timestamp: self.get_current_timestamp(),
total_allocations: allocations.len(),
total_events: ownership_history.get_statistics().total_events,
analysis_duration_ms: analysis_duration,
},
}
}
fn generate_lifecycle_events(
&self,
ownership_history: &OwnershipHistoryRecorder,
allocations: &[AllocationInfo],
) -> Vec<LifecycleEventSummary> {
let mut summaries = Vec::new();
for allocation in allocations {
if let Some(lifetime_ms) = allocation.lifetime_ms {
if lifetime_ms < self.config.min_lifetime_threshold_ms {
continue;
}
}
let summary = self.generate_single_lifecycle_summary(ownership_history, allocation);
summaries.push(summary);
}
summaries
}
fn generate_single_lifecycle_summary(
&self,
ownership_history: &OwnershipHistoryRecorder,
allocation: &AllocationInfo,
) -> LifecycleEventSummary {
let ptr = allocation.ptr;
let ownership_summary = ownership_history.get_summary(ptr);
let events = if let Some(ownership_events) = ownership_history.get_events(ptr) {
ownership_events
.iter()
.take(self.config.max_events_per_allocation)
.map(|event| LifecycleEvent {
id: event.event_id,
event_type: self.format_event_type(&event.event_type),
timestamp: event.timestamp,
size: Some(allocation.size),
details: event.details.context.clone(),
})
.collect()
} else {
let mut basic_events = vec![LifecycleEvent {
id: 1,
event_type: "Allocation".to_string(),
timestamp: allocation.timestamp_alloc,
size: Some(allocation.size),
details: Some("Memory allocated".to_string()),
}];
if let Some(dealloc_time) = allocation.timestamp_dealloc {
basic_events.push(LifecycleEvent {
id: 2,
event_type: "Deallocation".to_string(),
timestamp: dealloc_time,
size: Some(allocation.size),
details: Some("Memory deallocated".to_string()),
});
}
basic_events
};
let summary = if let Some(ownership_summary) = ownership_summary {
AllocationLifecycleSummary {
lifetime_ms: allocation.lifetime_ms,
borrow_info: ownership_summary.borrow_info.clone(),
clone_info: ownership_summary.clone_info.clone(),
ownership_history_available: true,
lifecycle_pattern: self.classify_lifecycle_pattern(allocation.lifetime_ms),
efficiency_score: self.calculate_efficiency_score(allocation, ownership_summary),
}
} else {
AllocationLifecycleSummary {
lifetime_ms: allocation.lifetime_ms,
borrow_info: BorrowInfo {
immutable_borrows: 0,
mutable_borrows: 0,
max_concurrent_borrows: 0,
last_borrow_timestamp: None,
active_borrows: Vec::new(),
},
clone_info: CloneInfo {
clone_count: 0,
is_clone: false,
original_ptr: None,
cloned_ptrs: Vec::new(),
},
ownership_history_available: false,
lifecycle_pattern: self.classify_lifecycle_pattern(allocation.lifetime_ms),
efficiency_score: 0.5, }
};
LifecycleEventSummary {
allocation_ptr: ptr,
var_name: allocation.var_name.clone(),
type_name: allocation.type_name.clone(),
size: allocation.size,
lifetime_ms: allocation.lifetime_ms,
events,
summary,
}
}
fn format_event_type(
&self,
event_type: &super::ownership_history::OwnershipEventType,
) -> String {
match event_type {
super::ownership_history::OwnershipEventType::Allocated => "Allocation".to_string(),
super::ownership_history::OwnershipEventType::Cloned { .. } => "Clone".to_string(),
super::ownership_history::OwnershipEventType::Dropped => "Deallocation".to_string(),
super::ownership_history::OwnershipEventType::OwnershipTransferred { .. } => {
"OwnershipTransfer".to_string()
}
super::ownership_history::OwnershipEventType::Borrowed { .. } => "Borrow".to_string(),
super::ownership_history::OwnershipEventType::MutablyBorrowed { .. } => {
"MutableBorrow".to_string()
}
super::ownership_history::OwnershipEventType::BorrowReleased { .. } => {
"BorrowRelease".to_string()
}
super::ownership_history::OwnershipEventType::RefCountChanged { .. } => {
"RefCountChange".to_string()
}
}
}
fn classify_lifecycle_pattern(&self, lifetime_ms: Option<u64>) -> LifecyclePattern {
match lifetime_ms {
None => LifecyclePattern::Leaked,
Some(0) => LifecyclePattern::Ephemeral,
Some(ms) if ms < 1 => LifecyclePattern::Ephemeral,
Some(ms) if ms < 100 => LifecyclePattern::ShortTerm,
Some(ms) if ms < 10_000 => LifecyclePattern::MediumTerm,
Some(_) => LifecyclePattern::LongTerm,
}
}
fn calculate_efficiency_score(
&self,
allocation: &AllocationInfo,
ownership_summary: &OwnershipSummary,
) -> f64 {
let mut score: f64 = 0.5;
if allocation
.var_name
.as_ref()
.map(|name| self.is_user_variable(name))
.unwrap_or(false)
{
score += 0.1;
}
match self.classify_lifecycle_pattern(allocation.lifetime_ms) {
LifecyclePattern::ShortTerm | LifecyclePattern::MediumTerm => score += 0.2,
LifecyclePattern::Ephemeral => score -= 0.1,
LifecyclePattern::Leaked => score -= 0.3,
_ => {}
}
if ownership_summary.borrow_info.max_concurrent_borrows > 5 {
score -= 0.1;
}
if ownership_summary.clone_info.clone_count > 0 || ownership_summary.clone_info.is_clone {
score += 0.1;
}
score.clamp(0.0, 1.0)
}
fn is_user_variable(&self, name: &str) -> bool {
!name.starts_with("primitive_")
&& !name.starts_with("struct_")
&& !name.starts_with("collection_")
&& !name.starts_with("buffer_")
&& !name.starts_with("system_")
&& !name.starts_with("fast_tracked")
&& name != "unknown"
}
fn generate_variable_groups(
&self,
lifecycle_events: &[LifecycleEventSummary],
) -> Vec<VariableGroup> {
let mut groups: HashMap<String, Vec<&LifecycleEventSummary>> = HashMap::new();
for event in lifecycle_events {
if let Some(ref type_name) = event.type_name {
let group_name = self.extract_base_type_name(type_name);
groups.entry(group_name).or_default().push(event);
}
}
groups
.into_iter()
.map(|(name, events)| {
let variables: Vec<String> =
events.iter().filter_map(|e| e.var_name.clone()).collect();
let total_memory: usize = events.iter().map(|e| e.size).sum();
let average_lifetime_ms = if !events.is_empty() {
let total_lifetime: u64 = events.iter().filter_map(|e| e.lifetime_ms).sum();
let count = events.iter().filter(|e| e.lifetime_ms.is_some()).count();
if count > 0 {
total_lifetime as f64 / count as f64
} else {
0.0
}
} else {
0.0
};
VariableGroup {
name,
variables,
total_memory,
average_lifetime_ms,
}
})
.collect()
}
fn extract_base_type_name(&self, type_name: &str) -> String {
if let Some(pos) = type_name.find('<') {
type_name[..pos].to_string()
} else if let Some(pos) = type_name.rfind("::") {
type_name[pos + 2..].to_string()
} else {
type_name.to_string()
}
}
fn get_current_timestamp(&self) -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
pub fn export_to_json(&self, export_data: &LifecycleExportData) -> serde_json::Result<String> {
serde_json::to_string_pretty(export_data)
}
}
impl Default for LifecycleSummaryGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lifecycle_summary_generator_creation() {
let generator = LifecycleSummaryGenerator::new();
assert!(generator.config.include_borrow_details);
assert!(generator.config.include_clone_details);
}
#[test]
fn test_lifecycle_pattern_classification() {
let generator = LifecycleSummaryGenerator::default();
assert!(matches!(
generator.classify_lifecycle_pattern(None),
LifecyclePattern::Leaked
));
assert!(matches!(
generator.classify_lifecycle_pattern(Some(0)),
LifecyclePattern::Ephemeral
));
assert!(matches!(
generator.classify_lifecycle_pattern(Some(50)),
LifecyclePattern::ShortTerm
));
assert!(matches!(
generator.classify_lifecycle_pattern(Some(5000)),
LifecyclePattern::MediumTerm
));
assert!(matches!(
generator.classify_lifecycle_pattern(Some(15000)),
LifecyclePattern::LongTerm
));
}
#[test]
fn test_json_export_basic() {
let generator = LifecycleSummaryGenerator::default();
let export_data = LifecycleExportData {
lifecycle_events: vec![],
variable_groups: vec![],
user_variables_count: 0,
visualization_ready: true,
metadata: ExportMetadata {
export_timestamp: 0,
total_allocations: 0,
total_events: 0,
analysis_duration_ms: 0,
},
};
let json = generator.export_to_json(&export_data).unwrap();
assert!(json.contains("lifecycle_events"));
assert!(json.contains("variable_groups"));
assert!(json.contains("metadata"));
}
#[test]
fn test_json_export_with_events() {
let generator = LifecycleSummaryGenerator::default();
let event_summary = LifecycleEventSummary {
allocation_ptr: 0x1000,
var_name: Some("test_var".to_string()),
type_name: Some("String".to_string()),
size: 1024,
lifetime_ms: Some(1000),
events: vec![],
summary: AllocationLifecycleSummary {
lifetime_ms: Some(1000),
borrow_info: BorrowInfo {
immutable_borrows: 0,
mutable_borrows: 0,
max_concurrent_borrows: 0,
last_borrow_timestamp: None,
active_borrows: vec![],
},
clone_info: CloneInfo {
clone_count: 0,
is_clone: false,
original_ptr: None,
cloned_ptrs: vec![],
},
ownership_history_available: false,
lifecycle_pattern: LifecyclePattern::ShortTerm,
efficiency_score: 0.8,
},
};
let export_data = LifecycleExportData {
lifecycle_events: vec![event_summary],
variable_groups: vec![],
user_variables_count: 1,
visualization_ready: true,
metadata: ExportMetadata {
export_timestamp: 1000,
total_allocations: 1,
total_events: 1,
analysis_duration_ms: 100,
},
};
let json = generator.export_to_json(&export_data).unwrap();
assert!(json.contains("test_var"));
assert!(json.contains("String"));
assert!(json.contains("ShortTerm"));
}
#[test]
fn test_summary_config_default() {
let config = SummaryConfig::default();
assert!(config.include_borrow_details);
assert!(config.include_clone_details);
assert_eq!(config.min_lifetime_threshold_ms, 0);
assert_eq!(config.max_events_per_allocation, 50);
}
#[test]
fn test_lifecycle_export_metadata() {
let metadata = ExportMetadata {
export_timestamp: 1234567890,
total_allocations: 100,
total_events: 500,
analysis_duration_ms: 1000,
};
assert_eq!(metadata.export_timestamp, 1234567890);
assert_eq!(metadata.total_allocations, 100);
assert_eq!(metadata.total_events, 500);
assert_eq!(metadata.analysis_duration_ms, 1000);
}
#[test]
fn test_lifecycle_event_serialization() {
let event = LifecycleEvent {
id: 1,
event_type: "Allocation".to_string(),
timestamp: 1000,
size: Some(1024),
details: Some("Memory allocated".to_string()),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("Allocation"));
assert!(json.contains("1024"));
}
}