use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
static EVENT_ID_GENERATOR: AtomicU64 = AtomicU64::new(1);
pub struct OwnershipHistoryRecorder {
ownership_events: HashMap<usize, VecDeque<OwnershipEvent>>,
ownership_summaries: HashMap<usize, OwnershipSummary>,
config: HistoryConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryConfig {
pub max_events_per_allocation: usize,
pub track_borrowing: bool,
pub track_cloning: bool,
pub track_ownership_transfers: bool,
}
impl Default for HistoryConfig {
fn default() -> Self {
Self {
max_events_per_allocation: 100,
track_borrowing: true,
track_cloning: true,
track_ownership_transfers: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum OwnershipEventType {
Allocated,
Cloned { source_ptr: usize },
Dropped,
OwnershipTransferred { target_var: String },
Borrowed { borrower_scope: String },
MutablyBorrowed { borrower_scope: String },
BorrowReleased { borrower_scope: String },
RefCountChanged { old_count: usize, new_count: usize },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnershipEvent {
pub event_id: u64,
pub timestamp: u64,
pub event_type: OwnershipEventType,
pub source_stack_id: u32,
pub details: OwnershipEventDetails,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnershipEventDetails {
pub clone_source_ptr: Option<usize>,
pub transfer_target_var: Option<String>,
pub borrower_scope: Option<String>,
pub ref_count_info: Option<RefCountInfo>,
pub context: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefCountInfo {
pub strong_count: usize,
pub weak_count: usize,
pub data_ptr: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnershipSummary {
pub allocation_ptr: usize,
pub lifetime_ms: Option<u64>,
pub borrow_info: BorrowInfo,
pub clone_info: CloneInfo,
pub ownership_history_available: bool,
pub total_events: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BorrowInfo {
pub immutable_borrows: u32,
pub mutable_borrows: u32,
pub max_concurrent_borrows: u32,
pub last_borrow_timestamp: Option<u64>,
pub active_borrows: Vec<ActiveBorrow>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActiveBorrow {
pub borrower_scope: String,
pub borrow_type: BorrowType,
pub start_timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BorrowType {
Immutable,
Mutable,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloneInfo {
pub clone_count: u32,
pub is_clone: bool,
pub original_ptr: Option<usize>,
pub cloned_ptrs: Vec<usize>,
}
impl OwnershipHistoryRecorder {
pub fn new() -> Self {
Self::with_config(HistoryConfig::default())
}
pub fn with_config(config: HistoryConfig) -> Self {
Self {
ownership_events: HashMap::new(),
ownership_summaries: HashMap::new(),
config,
}
}
pub fn record_event(
&mut self,
ptr: usize,
event_type: OwnershipEventType,
source_stack_id: u32,
) {
let event_id = EVENT_ID_GENERATOR.fetch_add(1, Ordering::Relaxed);
let timestamp = self.get_current_timestamp();
let details = self.create_event_details(&event_type);
let event = OwnershipEvent {
event_id,
timestamp,
event_type: event_type.clone(),
source_stack_id,
details,
};
let events = self.ownership_events.entry(ptr).or_default();
events.push_back(event);
if events.len() > self.config.max_events_per_allocation {
events.pop_front(); }
self.update_ownership_summary(ptr, &event_type, timestamp);
}
fn create_event_details(&self, event_type: &OwnershipEventType) -> OwnershipEventDetails {
match event_type {
OwnershipEventType::Cloned { source_ptr } => OwnershipEventDetails {
clone_source_ptr: Some(*source_ptr),
transfer_target_var: None,
borrower_scope: None,
ref_count_info: None,
context: Some("Memory cloned from another allocation".to_string()),
},
OwnershipEventType::OwnershipTransferred { target_var } => OwnershipEventDetails {
clone_source_ptr: None,
transfer_target_var: Some(target_var.clone()),
borrower_scope: None,
ref_count_info: None,
context: Some("Ownership transferred to another variable".to_string()),
},
OwnershipEventType::Borrowed { borrower_scope } => OwnershipEventDetails {
clone_source_ptr: None,
transfer_target_var: None,
borrower_scope: Some(borrower_scope.clone()),
ref_count_info: None,
context: Some("Memory immutably borrowed".to_string()),
},
OwnershipEventType::MutablyBorrowed { borrower_scope } => OwnershipEventDetails {
clone_source_ptr: None,
transfer_target_var: None,
borrower_scope: Some(borrower_scope.clone()),
ref_count_info: None,
context: Some("Memory mutably borrowed".to_string()),
},
OwnershipEventType::BorrowReleased { borrower_scope } => OwnershipEventDetails {
clone_source_ptr: None,
transfer_target_var: None,
borrower_scope: Some(borrower_scope.clone()),
ref_count_info: None,
context: Some("Borrow released".to_string()),
},
OwnershipEventType::RefCountChanged {
old_count,
new_count,
} => OwnershipEventDetails {
clone_source_ptr: None,
transfer_target_var: None,
borrower_scope: None,
ref_count_info: Some(RefCountInfo {
strong_count: *new_count,
weak_count: 0, data_ptr: 0, }),
context: Some(format!(
"Reference count changed from {old_count} to {new_count}",
)),
},
_ => OwnershipEventDetails {
clone_source_ptr: None,
transfer_target_var: None,
borrower_scope: None,
ref_count_info: None,
context: None,
},
}
}
fn update_ownership_summary(
&mut self,
ptr: usize,
event_type: &OwnershipEventType,
timestamp: u64,
) {
let summary = self
.ownership_summaries
.entry(ptr)
.or_insert_with(|| OwnershipSummary {
allocation_ptr: ptr,
lifetime_ms: None,
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: true,
total_events: 0,
});
summary.total_events += 1;
match event_type {
OwnershipEventType::Borrowed { borrower_scope } => {
summary.borrow_info.immutable_borrows += 1;
summary.borrow_info.last_borrow_timestamp = Some(timestamp);
summary.borrow_info.active_borrows.push(ActiveBorrow {
borrower_scope: borrower_scope.clone(),
borrow_type: BorrowType::Immutable,
start_timestamp: timestamp,
});
summary.borrow_info.max_concurrent_borrows = summary
.borrow_info
.max_concurrent_borrows
.max(summary.borrow_info.active_borrows.len() as u32);
}
OwnershipEventType::MutablyBorrowed { borrower_scope } => {
summary.borrow_info.mutable_borrows += 1;
summary.borrow_info.last_borrow_timestamp = Some(timestamp);
summary.borrow_info.active_borrows.push(ActiveBorrow {
borrower_scope: borrower_scope.clone(),
borrow_type: BorrowType::Mutable,
start_timestamp: timestamp,
});
summary.borrow_info.max_concurrent_borrows = summary
.borrow_info
.max_concurrent_borrows
.max(summary.borrow_info.active_borrows.len() as u32);
}
OwnershipEventType::BorrowReleased { borrower_scope } => {
summary
.borrow_info
.active_borrows
.retain(|borrow| borrow.borrower_scope != *borrower_scope);
}
OwnershipEventType::Cloned { source_ptr } => {
summary.clone_info.is_clone = true;
summary.clone_info.original_ptr = Some(*source_ptr);
if let Some(source_summary) = self.ownership_summaries.get_mut(source_ptr) {
source_summary.clone_info.clone_count += 1;
source_summary.clone_info.cloned_ptrs.push(ptr);
}
}
_ => {
}
}
}
pub fn get_events(&self, ptr: usize) -> Option<&VecDeque<OwnershipEvent>> {
self.ownership_events.get(&ptr)
}
pub fn get_summary(&self, ptr: usize) -> Option<&OwnershipSummary> {
self.ownership_summaries.get(&ptr)
}
pub fn get_all_summaries(&self) -> &HashMap<usize, OwnershipSummary> {
&self.ownership_summaries
}
pub fn export_to_json(&self) -> serde_json::Result<String> {
let export_data = OwnershipHistoryExport {
summaries: self.ownership_summaries.clone(),
detailed_events: self.ownership_events.clone(),
export_timestamp: self.get_current_timestamp(),
config: self.config.clone(),
};
serde_json::to_string_pretty(&export_data)
}
pub fn clear(&mut self) {
self.ownership_events.clear();
self.ownership_summaries.clear();
}
pub fn get_statistics(&self) -> OwnershipStatistics {
let total_allocations = self.ownership_summaries.len();
let total_events = self
.ownership_events
.values()
.map(|events| events.len())
.sum();
let mut event_type_counts = HashMap::new();
for events in self.ownership_events.values() {
for event in events {
let event_type_name = match &event.event_type {
OwnershipEventType::Allocated => "Allocated",
OwnershipEventType::Cloned { .. } => "Cloned",
OwnershipEventType::Dropped => "Dropped",
OwnershipEventType::OwnershipTransferred { .. } => "OwnershipTransferred",
OwnershipEventType::Borrowed { .. } => "Borrowed",
OwnershipEventType::MutablyBorrowed { .. } => "MutablyBorrowed",
OwnershipEventType::BorrowReleased { .. } => "BorrowReleased",
OwnershipEventType::RefCountChanged { .. } => "RefCountChanged",
};
*event_type_counts
.entry(event_type_name.to_string())
.or_insert(0) += 1;
}
}
let cloned_allocations = self
.ownership_summaries
.values()
.filter(|summary| summary.clone_info.is_clone)
.count();
let allocations_with_borrows = self
.ownership_summaries
.values()
.filter(|summary| {
summary.borrow_info.immutable_borrows > 0 || summary.borrow_info.mutable_borrows > 0
})
.count();
OwnershipStatistics {
total_allocations,
total_events,
event_type_counts,
cloned_allocations,
allocations_with_borrows,
average_events_per_allocation: if total_allocations > 0 {
total_events as f64 / total_allocations as f64
} else {
0.0
},
}
}
fn get_current_timestamp(&self) -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
}
impl Default for OwnershipHistoryRecorder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnershipHistoryExport {
pub summaries: HashMap<usize, OwnershipSummary>,
pub detailed_events: HashMap<usize, VecDeque<OwnershipEvent>>,
pub export_timestamp: u64,
pub config: HistoryConfig,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnershipStatistics {
pub total_allocations: usize,
pub total_events: usize,
pub event_type_counts: HashMap<String, usize>,
pub cloned_allocations: usize,
pub allocations_with_borrows: usize,
pub average_events_per_allocation: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ownership_history_recorder_creation() {
let recorder = OwnershipHistoryRecorder::new();
assert_eq!(recorder.ownership_events.len(), 0);
assert_eq!(recorder.ownership_summaries.len(), 0);
}
#[test]
fn test_record_allocation_event() {
let mut recorder = OwnershipHistoryRecorder::new();
let ptr = 0x1000;
recorder.record_event(ptr, OwnershipEventType::Allocated, 1024);
let events = recorder.get_events(ptr).expect("Failed to get events");
assert_eq!(events.len(), 1);
assert!(matches!(
events[0].event_type,
OwnershipEventType::Allocated
));
let summary = recorder.get_summary(ptr).expect("Failed to get summary");
assert_eq!(summary.allocation_ptr, ptr);
assert_eq!(summary.total_events, 1);
}
#[test]
fn test_clone_tracking() {
let mut recorder = OwnershipHistoryRecorder::new();
let source_ptr = 0x1000;
let clone1_ptr = 0x2000;
let clone2_ptr = 0x3000;
recorder.record_event(source_ptr, OwnershipEventType::Allocated, 512);
recorder.record_event(clone1_ptr, OwnershipEventType::Cloned { source_ptr }, 256);
recorder.record_event(clone2_ptr, OwnershipEventType::Cloned { source_ptr }, 256);
let clone1_summary = recorder
.get_summary(clone1_ptr)
.expect("Failed to get clone1 summary");
assert!(clone1_summary.clone_info.is_clone);
assert_eq!(clone1_summary.clone_info.original_ptr, Some(source_ptr));
let source_summary = recorder
.get_summary(source_ptr)
.expect("Failed to get source summary");
assert_eq!(source_summary.clone_info.clone_count, 2);
assert!(source_summary.clone_info.cloned_ptrs.contains(&clone1_ptr));
assert!(source_summary.clone_info.cloned_ptrs.contains(&clone2_ptr));
}
#[test]
fn test_borrow_tracking() {
let mut recorder = OwnershipHistoryRecorder::new();
let ptr = 0x1000;
recorder.record_event(ptr, OwnershipEventType::Allocated, 1024);
recorder.record_event(
ptr,
OwnershipEventType::Borrowed {
borrower_scope: "function_a".to_string(),
},
0,
);
recorder.record_event(
ptr,
OwnershipEventType::Borrowed {
borrower_scope: "function_b".to_string(),
},
0,
);
recorder.record_event(
ptr,
OwnershipEventType::MutablyBorrowed {
borrower_scope: "function_c".to_string(),
},
0,
);
let summary = recorder.get_summary(ptr).expect("Failed to get summary");
assert_eq!(summary.borrow_info.immutable_borrows, 2);
assert_eq!(summary.borrow_info.mutable_borrows, 1);
assert_eq!(summary.borrow_info.max_concurrent_borrows, 3);
assert_eq!(summary.borrow_info.active_borrows.len(), 3);
}
#[test]
fn test_ownership_transfer() {
let mut recorder = OwnershipHistoryRecorder::new();
let ptr = 0x1000;
recorder.record_event(ptr, OwnershipEventType::Allocated, 1024);
recorder.record_event(
ptr,
OwnershipEventType::OwnershipTransferred {
target_var: "moved_var".to_string(),
},
0,
);
let events = recorder.get_events(ptr).expect("Failed to get events");
assert!(matches!(
events[1].event_type,
OwnershipEventType::OwnershipTransferred { .. }
));
}
#[test]
fn test_reference_count_tracking() {
let mut recorder = OwnershipHistoryRecorder::new();
let ptr = 0x1000;
recorder.record_event(ptr, OwnershipEventType::Allocated, 512);
recorder.record_event(
ptr,
OwnershipEventType::RefCountChanged {
old_count: 1,
new_count: 2,
},
0,
);
recorder.record_event(
ptr,
OwnershipEventType::RefCountChanged {
old_count: 2,
new_count: 3,
},
0,
);
let summary = recorder.get_summary(ptr).expect("Failed to get summary");
assert_eq!(summary.total_events, 3);
}
#[test]
fn test_max_events_limit() {
let mut recorder = OwnershipHistoryRecorder::with_config(HistoryConfig {
max_events_per_allocation: 3,
track_borrowing: true,
track_cloning: true,
track_ownership_transfers: true,
});
let ptr = 0x1000;
recorder.record_event(ptr, OwnershipEventType::Allocated, 512);
recorder.record_event(
ptr,
OwnershipEventType::Borrowed {
borrower_scope: "scope1".to_string(),
},
0,
);
recorder.record_event(
ptr,
OwnershipEventType::Borrowed {
borrower_scope: "scope2".to_string(),
},
0,
);
recorder.record_event(
ptr,
OwnershipEventType::Borrowed {
borrower_scope: "scope3".to_string(),
},
0,
);
let events = recorder.get_events(ptr).expect("Failed to get events");
assert!(events.len() <= 3);
}
#[test]
fn test_get_all_summaries() {
let mut recorder = OwnershipHistoryRecorder::new();
let ptr1 = 0x1000;
let ptr2 = 0x2000;
recorder.record_event(ptr1, OwnershipEventType::Allocated, 512);
recorder.record_event(ptr2, OwnershipEventType::Allocated, 1024);
let summaries = recorder.get_all_summaries();
assert_eq!(summaries.len(), 2);
assert!(summaries.contains_key(&ptr1));
assert!(summaries.contains_key(&ptr2));
}
#[test]
fn test_ownership_statistics() {
let mut recorder = OwnershipHistoryRecorder::new();
let ptr1 = 0x1000;
let ptr2 = 0x2000;
let ptr3 = 0x3000;
recorder.record_event(ptr1, OwnershipEventType::Allocated, 512);
recorder.record_event(ptr2, OwnershipEventType::Cloned { source_ptr: ptr1 }, 256);
recorder.record_event(ptr3, OwnershipEventType::Allocated, 1024);
let stats = recorder.get_statistics();
assert_eq!(stats.total_allocations, 3);
assert_eq!(stats.total_events, 3);
assert_eq!(stats.cloned_allocations, 1);
}
#[test]
fn test_clear() {
let mut recorder = OwnershipHistoryRecorder::new();
recorder.record_event(0x1000, OwnershipEventType::Allocated, 1024);
recorder.record_event(0x2000, OwnershipEventType::Allocated, 512);
assert_eq!(recorder.ownership_events.len(), 2);
assert_eq!(recorder.ownership_summaries.len(), 2);
recorder.clear();
assert_eq!(recorder.ownership_events.len(), 0);
assert_eq!(recorder.ownership_summaries.len(), 0);
}
#[test]
fn test_json_export() {
let mut recorder = OwnershipHistoryRecorder::new();
recorder.record_event(0x1000, OwnershipEventType::Allocated, 1024);
let json = recorder.export_to_json().expect("Failed to export to JSON");
assert!(json.contains("summaries"));
assert!(json.contains("detailed_events"));
assert!(json.contains("export_timestamp"));
}
}