Skip to main content

memscope_rs/analysis/lifecycle/
lifecycle_summary.rs

1//! Lifecycle summary generation for enhanced memory analysis
2//!
3//! This module provides functionality to generate high-level lifecycle summaries
4//! from detailed ownership history, creating the data needed for lifetime.json export.
5
6use super::ownership_history::{BorrowInfo, CloneInfo, OwnershipHistoryRecorder, OwnershipSummary};
7use crate::capture::types::AllocationInfo;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Generator for lifecycle summaries and lifetime.json export
12pub struct LifecycleSummaryGenerator {
13    /// Configuration for summary generation
14    config: SummaryConfig,
15}
16
17/// Configuration for lifecycle summary generation
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SummaryConfig {
20    /// Include detailed borrow information
21    pub include_borrow_details: bool,
22    /// Include clone relationship information
23    pub include_clone_details: bool,
24    /// Minimum lifetime threshold for inclusion (in milliseconds)
25    pub min_lifetime_threshold_ms: u64,
26    /// Maximum number of lifecycle events to include per allocation
27    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/// Complete lifecycle data for export to lifetime.json
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LifecycleExportData {
44    /// Lifecycle events for each allocation
45    pub lifecycle_events: Vec<LifecycleEventSummary>,
46    /// Variable groups for organization
47    pub variable_groups: Vec<VariableGroup>,
48    /// Count of user variables (those with meaningful names)
49    pub user_variables_count: usize,
50    /// Whether visualization data is ready
51    pub visualization_ready: bool,
52    /// Export metadata
53    pub metadata: ExportMetadata,
54}
55
56/// Summary of lifecycle events for a single allocation
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct LifecycleEventSummary {
59    /// Allocation pointer
60    pub allocation_ptr: usize,
61    /// Variable name (if available)
62    pub var_name: Option<String>,
63    /// Type name
64    pub type_name: Option<String>,
65    /// Size of the allocation
66    pub size: usize,
67    /// Total lifetime in milliseconds
68    pub lifetime_ms: Option<u64>,
69    /// Lifecycle events
70    pub events: Vec<LifecycleEvent>,
71    /// High-level summary
72    pub summary: AllocationLifecycleSummary,
73}
74
75/// Individual lifecycle event
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct LifecycleEvent {
78    /// Event ID
79    pub id: u64,
80    /// Event type
81    pub event_type: String,
82    /// Timestamp when the event occurred
83    pub timestamp: u64,
84    /// Size involved in the event (if applicable)
85    pub size: Option<usize>,
86    /// Additional event details
87    pub details: Option<String>,
88}
89
90/// High-level summary of an allocation's lifecycle
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct AllocationLifecycleSummary {
93    /// Total lifetime in milliseconds
94    pub lifetime_ms: Option<u64>,
95    /// Borrowing information
96    pub borrow_info: BorrowInfo,
97    /// Cloning information
98    pub clone_info: CloneInfo,
99    /// Whether detailed ownership history is available
100    pub ownership_history_available: bool,
101    /// Lifecycle pattern classification
102    pub lifecycle_pattern: LifecyclePattern,
103    /// Memory efficiency score (0.0 to 1.0)
104    pub efficiency_score: f64,
105}
106
107/// Classification of lifecycle patterns
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum LifecyclePattern {
110    /// Short-lived allocation (< 1ms)
111    Ephemeral,
112    /// Short-term allocation (1ms - 100ms)
113    ShortTerm,
114    /// Medium-term allocation (100ms - 10s)
115    MediumTerm,
116    /// Long-term allocation (> 10s)
117    LongTerm,
118    /// Leaked allocation (never deallocated)
119    Leaked,
120    /// Unknown pattern
121    Unknown,
122}
123
124/// Group of related variables
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct VariableGroup {
127    /// Group name
128    pub name: String,
129    /// Variables in this group
130    pub variables: Vec<String>,
131    /// Total memory used by this group
132    pub total_memory: usize,
133    /// Average lifetime of variables in this group
134    pub average_lifetime_ms: f64,
135}
136
137/// Export metadata
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ExportMetadata {
140    /// Export timestamp
141    pub export_timestamp: u64,
142    /// Total allocations analyzed
143    pub total_allocations: usize,
144    /// Total events processed
145    pub total_events: usize,
146    /// Analysis duration in milliseconds
147    pub analysis_duration_ms: u64,
148}
149
150impl LifecycleSummaryGenerator {
151    /// Create a new lifecycle summary generator
152    pub fn new() -> Self {
153        Self::with_config(SummaryConfig::default())
154    }
155
156    /// Create a new lifecycle summary generator with custom configuration
157    pub fn with_config(config: SummaryConfig) -> Self {
158        Self { config }
159    }
160
161    /// Generate complete lifecycle export data
162    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        // Generate lifecycle event summaries
170        let lifecycle_events = self.generate_lifecycle_events(ownership_history, allocations);
171
172        // Generate variable groups
173        let variable_groups = self.generate_variable_groups(&lifecycle_events);
174
175        // Count user variables (those with meaningful names)
176        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    /// Generate lifecycle event summaries for all allocations
204    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            // Skip allocations below the minimum lifetime threshold
213            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    /// Generate lifecycle summary for a single allocation
227    fn generate_single_lifecycle_summary(
228        &self,
229        ownership_history: &OwnershipHistoryRecorder,
230        allocation: &AllocationInfo,
231    ) -> LifecycleEventSummary {
232        let ptr = allocation.ptr;
233
234        // Get ownership summary if available
235        let ownership_summary = ownership_history.get_summary(ptr);
236
237        // Generate lifecycle events
238        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            // Create basic events from allocation info
252            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        // Create allocation lifecycle summary
274        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, // Default score
302            }
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    /// Format ownership event type for display
317    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    /// Classify the lifecycle pattern based on lifetime
342    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    /// Calculate efficiency score for an allocation
354    fn calculate_efficiency_score(
355        &self,
356        allocation: &AllocationInfo,
357        ownership_summary: &OwnershipSummary,
358    ) -> f64 {
359        let mut score: f64 = 0.5; // Base score
360
361        // Bonus for having a meaningful variable name
362        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        // Bonus for appropriate lifetime (not too short, not leaked)
372        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        // Penalty for excessive borrowing
380        if ownership_summary.borrow_info.max_concurrent_borrows > 5 {
381            score -= 0.1;
382        }
383
384        // Bonus for being part of a clone chain (indicates reuse)
385        if ownership_summary.clone_info.clone_count > 0 || ownership_summary.clone_info.is_clone {
386            score += 0.1;
387        }
388
389        // Clamp score between 0.0 and 1.0
390        score.clamp(0.0, 1.0)
391    }
392
393    /// Check if a variable name indicates a user-defined variable
394    fn is_user_variable(&self, name: &str) -> bool {
395        // Filter out system-generated names
396        !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    /// Generate variable groups for organization
406    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        // Group by type name
413        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        // Convert to VariableGroup structs
421        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    /// Extract base type name for grouping
452    fn extract_base_type_name(&self, type_name: &str) -> String {
453        // Extract the base type from complex type names
454        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    /// Get current timestamp in nanoseconds
464    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    /// Export lifecycle data to JSON string
472    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}