1use super::ownership_history::{BorrowInfo, CloneInfo, OwnershipHistoryRecorder, OwnershipSummary};
7use crate::capture::types::AllocationInfo;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11pub struct LifecycleSummaryGenerator {
13 config: SummaryConfig,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SummaryConfig {
20 pub include_borrow_details: bool,
22 pub include_clone_details: bool,
24 pub min_lifetime_threshold_ms: u64,
26 pub max_events_per_allocation: usize,
28}
29
30impl Default for SummaryConfig {
31 fn default() -> Self {
32 Self {
33 include_borrow_details: true,
34 include_clone_details: true,
35 min_lifetime_threshold_ms: 0,
36 max_events_per_allocation: 50,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LifecycleExportData {
44 pub lifecycle_events: Vec<LifecycleEventSummary>,
46 pub variable_groups: Vec<VariableGroup>,
48 pub user_variables_count: usize,
50 pub visualization_ready: bool,
52 pub metadata: ExportMetadata,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct LifecycleEventSummary {
59 pub allocation_ptr: usize,
61 pub var_name: Option<String>,
63 pub type_name: Option<String>,
65 pub size: usize,
67 pub lifetime_ms: Option<u64>,
69 pub events: Vec<LifecycleEvent>,
71 pub summary: AllocationLifecycleSummary,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct LifecycleEvent {
78 pub id: u64,
80 pub event_type: String,
82 pub timestamp: u64,
84 pub size: Option<usize>,
86 pub details: Option<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct AllocationLifecycleSummary {
93 pub lifetime_ms: Option<u64>,
95 pub borrow_info: BorrowInfo,
97 pub clone_info: CloneInfo,
99 pub ownership_history_available: bool,
101 pub lifecycle_pattern: LifecyclePattern,
103 pub efficiency_score: f64,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum LifecyclePattern {
110 Ephemeral,
112 ShortTerm,
114 MediumTerm,
116 LongTerm,
118 Leaked,
120 Unknown,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct VariableGroup {
127 pub name: String,
129 pub variables: Vec<String>,
131 pub total_memory: usize,
133 pub average_lifetime_ms: f64,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ExportMetadata {
140 pub export_timestamp: u64,
142 pub total_allocations: usize,
144 pub total_events: usize,
146 pub analysis_duration_ms: u64,
148}
149
150impl LifecycleSummaryGenerator {
151 pub fn new() -> Self {
153 Self::with_config(SummaryConfig::default())
154 }
155
156 pub fn with_config(config: SummaryConfig) -> Self {
158 Self { config }
159 }
160
161 pub fn generate_lifecycle_export(
163 &self,
164 ownership_history: &OwnershipHistoryRecorder,
165 allocations: &[AllocationInfo],
166 ) -> LifecycleExportData {
167 let start_time = std::time::Instant::now();
168
169 let lifecycle_events = self.generate_lifecycle_events(ownership_history, allocations);
171
172 let variable_groups = self.generate_variable_groups(&lifecycle_events);
174
175 let user_variables_count = lifecycle_events
177 .iter()
178 .filter(|event| {
179 event
180 .var_name
181 .as_ref()
182 .map(|name| self.is_user_variable(name))
183 .unwrap_or(false)
184 })
185 .count();
186
187 let analysis_duration = start_time.elapsed().as_millis() as u64;
188
189 LifecycleExportData {
190 lifecycle_events,
191 variable_groups,
192 user_variables_count,
193 visualization_ready: true,
194 metadata: ExportMetadata {
195 export_timestamp: self.get_current_timestamp(),
196 total_allocations: allocations.len(),
197 total_events: ownership_history.get_statistics().total_events,
198 analysis_duration_ms: analysis_duration,
199 },
200 }
201 }
202
203 fn generate_lifecycle_events(
205 &self,
206 ownership_history: &OwnershipHistoryRecorder,
207 allocations: &[AllocationInfo],
208 ) -> Vec<LifecycleEventSummary> {
209 let mut summaries = Vec::new();
210
211 for allocation in allocations {
212 if let Some(lifetime_ms) = allocation.lifetime_ms {
214 if lifetime_ms < self.config.min_lifetime_threshold_ms {
215 continue;
216 }
217 }
218
219 let summary = self.generate_single_lifecycle_summary(ownership_history, allocation);
220 summaries.push(summary);
221 }
222
223 summaries
224 }
225
226 fn generate_single_lifecycle_summary(
228 &self,
229 ownership_history: &OwnershipHistoryRecorder,
230 allocation: &AllocationInfo,
231 ) -> LifecycleEventSummary {
232 let ptr = allocation.ptr;
233
234 let ownership_summary = ownership_history.get_summary(ptr);
236
237 let events = if let Some(ownership_events) = ownership_history.get_events(ptr) {
239 ownership_events
240 .iter()
241 .take(self.config.max_events_per_allocation)
242 .map(|event| LifecycleEvent {
243 id: event.event_id,
244 event_type: self.format_event_type(&event.event_type),
245 timestamp: event.timestamp,
246 size: Some(allocation.size),
247 details: event.details.context.clone(),
248 })
249 .collect()
250 } else {
251 let mut basic_events = vec![LifecycleEvent {
253 id: 1,
254 event_type: "Allocation".to_string(),
255 timestamp: allocation.timestamp_alloc,
256 size: Some(allocation.size),
257 details: Some("Memory allocated".to_string()),
258 }];
259
260 if let Some(dealloc_time) = allocation.timestamp_dealloc {
261 basic_events.push(LifecycleEvent {
262 id: 2,
263 event_type: "Deallocation".to_string(),
264 timestamp: dealloc_time,
265 size: Some(allocation.size),
266 details: Some("Memory deallocated".to_string()),
267 });
268 }
269
270 basic_events
271 };
272
273 let summary = if let Some(ownership_summary) = ownership_summary {
275 AllocationLifecycleSummary {
276 lifetime_ms: allocation.lifetime_ms,
277 borrow_info: ownership_summary.borrow_info.clone(),
278 clone_info: ownership_summary.clone_info.clone(),
279 ownership_history_available: true,
280 lifecycle_pattern: self.classify_lifecycle_pattern(allocation.lifetime_ms),
281 efficiency_score: self.calculate_efficiency_score(allocation, ownership_summary),
282 }
283 } else {
284 AllocationLifecycleSummary {
285 lifetime_ms: allocation.lifetime_ms,
286 borrow_info: BorrowInfo {
287 immutable_borrows: 0,
288 mutable_borrows: 0,
289 max_concurrent_borrows: 0,
290 last_borrow_timestamp: None,
291 active_borrows: Vec::new(),
292 },
293 clone_info: CloneInfo {
294 clone_count: 0,
295 is_clone: false,
296 original_ptr: None,
297 cloned_ptrs: Vec::new(),
298 },
299 ownership_history_available: false,
300 lifecycle_pattern: self.classify_lifecycle_pattern(allocation.lifetime_ms),
301 efficiency_score: 0.5, }
303 };
304
305 LifecycleEventSummary {
306 allocation_ptr: ptr,
307 var_name: allocation.var_name.clone(),
308 type_name: allocation.type_name.clone(),
309 size: allocation.size,
310 lifetime_ms: allocation.lifetime_ms,
311 events,
312 summary,
313 }
314 }
315
316 fn format_event_type(
318 &self,
319 event_type: &super::ownership_history::OwnershipEventType,
320 ) -> String {
321 match event_type {
322 super::ownership_history::OwnershipEventType::Allocated => "Allocation".to_string(),
323 super::ownership_history::OwnershipEventType::Cloned { .. } => "Clone".to_string(),
324 super::ownership_history::OwnershipEventType::Dropped => "Deallocation".to_string(),
325 super::ownership_history::OwnershipEventType::OwnershipTransferred { .. } => {
326 "OwnershipTransfer".to_string()
327 }
328 super::ownership_history::OwnershipEventType::Borrowed { .. } => "Borrow".to_string(),
329 super::ownership_history::OwnershipEventType::MutablyBorrowed { .. } => {
330 "MutableBorrow".to_string()
331 }
332 super::ownership_history::OwnershipEventType::BorrowReleased { .. } => {
333 "BorrowRelease".to_string()
334 }
335 super::ownership_history::OwnershipEventType::RefCountChanged { .. } => {
336 "RefCountChange".to_string()
337 }
338 }
339 }
340
341 fn classify_lifecycle_pattern(&self, lifetime_ms: Option<u64>) -> LifecyclePattern {
343 match lifetime_ms {
344 None => LifecyclePattern::Leaked,
345 Some(0) => LifecyclePattern::Ephemeral,
346 Some(ms) if ms < 1 => LifecyclePattern::Ephemeral,
347 Some(ms) if ms < 100 => LifecyclePattern::ShortTerm,
348 Some(ms) if ms < 10_000 => LifecyclePattern::MediumTerm,
349 Some(_) => LifecyclePattern::LongTerm,
350 }
351 }
352
353 fn calculate_efficiency_score(
355 &self,
356 allocation: &AllocationInfo,
357 ownership_summary: &OwnershipSummary,
358 ) -> f64 {
359 let mut score: f64 = 0.5; if allocation
363 .var_name
364 .as_ref()
365 .map(|name| self.is_user_variable(name))
366 .unwrap_or(false)
367 {
368 score += 0.1;
369 }
370
371 match self.classify_lifecycle_pattern(allocation.lifetime_ms) {
373 LifecyclePattern::ShortTerm | LifecyclePattern::MediumTerm => score += 0.2,
374 LifecyclePattern::Ephemeral => score -= 0.1,
375 LifecyclePattern::Leaked => score -= 0.3,
376 _ => {}
377 }
378
379 if ownership_summary.borrow_info.max_concurrent_borrows > 5 {
381 score -= 0.1;
382 }
383
384 if ownership_summary.clone_info.clone_count > 0 || ownership_summary.clone_info.is_clone {
386 score += 0.1;
387 }
388
389 score.clamp(0.0, 1.0)
391 }
392
393 fn is_user_variable(&self, name: &str) -> bool {
395 !name.starts_with("primitive_")
397 && !name.starts_with("struct_")
398 && !name.starts_with("collection_")
399 && !name.starts_with("buffer_")
400 && !name.starts_with("system_")
401 && !name.starts_with("fast_tracked")
402 && name != "unknown"
403 }
404
405 fn generate_variable_groups(
407 &self,
408 lifecycle_events: &[LifecycleEventSummary],
409 ) -> Vec<VariableGroup> {
410 let mut groups: HashMap<String, Vec<&LifecycleEventSummary>> = HashMap::new();
411
412 for event in lifecycle_events {
414 if let Some(ref type_name) = event.type_name {
415 let group_name = self.extract_base_type_name(type_name);
416 groups.entry(group_name).or_default().push(event);
417 }
418 }
419
420 groups
422 .into_iter()
423 .map(|(name, events)| {
424 let variables: Vec<String> =
425 events.iter().filter_map(|e| e.var_name.clone()).collect();
426
427 let total_memory: usize = events.iter().map(|e| e.size).sum();
428
429 let average_lifetime_ms = if !events.is_empty() {
430 let total_lifetime: u64 = events.iter().filter_map(|e| e.lifetime_ms).sum();
431 let count = events.iter().filter(|e| e.lifetime_ms.is_some()).count();
432 if count > 0 {
433 total_lifetime as f64 / count as f64
434 } else {
435 0.0
436 }
437 } else {
438 0.0
439 };
440
441 VariableGroup {
442 name,
443 variables,
444 total_memory,
445 average_lifetime_ms,
446 }
447 })
448 .collect()
449 }
450
451 fn extract_base_type_name(&self, type_name: &str) -> String {
453 if let Some(pos) = type_name.find('<') {
455 type_name[..pos].to_string()
456 } else if let Some(pos) = type_name.rfind("::") {
457 type_name[pos + 2..].to_string()
458 } else {
459 type_name.to_string()
460 }
461 }
462
463 fn get_current_timestamp(&self) -> u64 {
465 std::time::SystemTime::now()
466 .duration_since(std::time::UNIX_EPOCH)
467 .unwrap_or_default()
468 .as_nanos() as u64
469 }
470
471 pub fn export_to_json(&self, export_data: &LifecycleExportData) -> serde_json::Result<String> {
473 serde_json::to_string_pretty(export_data)
474 }
475}
476
477impl Default for LifecycleSummaryGenerator {
478 fn default() -> Self {
479 Self::new()
480 }
481}
482
483#[cfg(test)]
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_lifecycle_summary_generator_creation() {
490 let generator = LifecycleSummaryGenerator::new();
491 assert!(generator.config.include_borrow_details);
492 assert!(generator.config.include_clone_details);
493 }
494
495 #[test]
496 fn test_lifecycle_pattern_classification() {
497 let generator = LifecycleSummaryGenerator::default();
498
499 assert!(matches!(
500 generator.classify_lifecycle_pattern(None),
501 LifecyclePattern::Leaked
502 ));
503 assert!(matches!(
504 generator.classify_lifecycle_pattern(Some(0)),
505 LifecyclePattern::Ephemeral
506 ));
507 assert!(matches!(
508 generator.classify_lifecycle_pattern(Some(50)),
509 LifecyclePattern::ShortTerm
510 ));
511 assert!(matches!(
512 generator.classify_lifecycle_pattern(Some(5000)),
513 LifecyclePattern::MediumTerm
514 ));
515 assert!(matches!(
516 generator.classify_lifecycle_pattern(Some(15000)),
517 LifecyclePattern::LongTerm
518 ));
519 }
520
521 #[test]
522 fn test_json_export_basic() {
523 let generator = LifecycleSummaryGenerator::default();
524 let export_data = LifecycleExportData {
525 lifecycle_events: vec![],
526 variable_groups: vec![],
527 user_variables_count: 0,
528 visualization_ready: true,
529 metadata: ExportMetadata {
530 export_timestamp: 0,
531 total_allocations: 0,
532 total_events: 0,
533 analysis_duration_ms: 0,
534 },
535 };
536
537 let json = generator.export_to_json(&export_data).unwrap();
538 assert!(json.contains("lifecycle_events"));
539 assert!(json.contains("variable_groups"));
540 assert!(json.contains("metadata"));
541 }
542
543 #[test]
544 fn test_json_export_with_events() {
545 let generator = LifecycleSummaryGenerator::default();
546
547 let event_summary = LifecycleEventSummary {
548 allocation_ptr: 0x1000,
549 var_name: Some("test_var".to_string()),
550 type_name: Some("String".to_string()),
551 size: 1024,
552 lifetime_ms: Some(1000),
553 events: vec![],
554 summary: AllocationLifecycleSummary {
555 lifetime_ms: Some(1000),
556 borrow_info: BorrowInfo {
557 immutable_borrows: 0,
558 mutable_borrows: 0,
559 max_concurrent_borrows: 0,
560 last_borrow_timestamp: None,
561 active_borrows: vec![],
562 },
563 clone_info: CloneInfo {
564 clone_count: 0,
565 is_clone: false,
566 original_ptr: None,
567 cloned_ptrs: vec![],
568 },
569 ownership_history_available: false,
570 lifecycle_pattern: LifecyclePattern::ShortTerm,
571 efficiency_score: 0.8,
572 },
573 };
574
575 let export_data = LifecycleExportData {
576 lifecycle_events: vec![event_summary],
577 variable_groups: vec![],
578 user_variables_count: 1,
579 visualization_ready: true,
580 metadata: ExportMetadata {
581 export_timestamp: 1000,
582 total_allocations: 1,
583 total_events: 1,
584 analysis_duration_ms: 100,
585 },
586 };
587
588 let json = generator.export_to_json(&export_data).unwrap();
589 assert!(json.contains("test_var"));
590 assert!(json.contains("String"));
591 assert!(json.contains("ShortTerm"));
592 }
593
594 #[test]
595 fn test_summary_config_default() {
596 let config = SummaryConfig::default();
597 assert!(config.include_borrow_details);
598 assert!(config.include_clone_details);
599 assert_eq!(config.min_lifetime_threshold_ms, 0);
600 assert_eq!(config.max_events_per_allocation, 50);
601 }
602
603 #[test]
604 fn test_lifecycle_export_metadata() {
605 let metadata = ExportMetadata {
606 export_timestamp: 1234567890,
607 total_allocations: 100,
608 total_events: 500,
609 analysis_duration_ms: 1000,
610 };
611
612 assert_eq!(metadata.export_timestamp, 1234567890);
613 assert_eq!(metadata.total_allocations, 100);
614 assert_eq!(metadata.total_events, 500);
615 assert_eq!(metadata.analysis_duration_ms, 1000);
616 }
617
618 #[test]
619 fn test_lifecycle_event_serialization() {
620 let event = LifecycleEvent {
621 id: 1,
622 event_type: "Allocation".to_string(),
623 timestamp: 1000,
624 size: Some(1024),
625 details: Some("Memory allocated".to_string()),
626 };
627
628 let json = serde_json::to_string(&event).unwrap();
629 assert!(json.contains("Allocation"));
630 assert!(json.contains("1024"));
631 }
632}