Skip to main content

memscope_rs/capture/types/
timeline.rs

1//! Timeline tracking types.
2//!
3//! This module contains types for timeline visualization,
4//! including memory snapshots, allocation events, and time ranges.
5
6use serde::{Deserialize, Serialize};
7
8use super::scope::{AllocationEventType, ScopeEventType};
9use super::stats::TypeMemoryUsage;
10
11/// Timeline data for visualization.
12#[derive(Debug, Clone, Serialize)]
13pub struct TimelineData {
14    /// Time range.
15    pub time_range: TimeRange,
16    /// Allocation events.
17    pub allocation_events: Vec<AllocationEvent>,
18    /// Scope events.
19    pub scope_events: Vec<ScopeEvent>,
20    /// Memory snapshots.
21    pub memory_snapshots: Vec<MemorySnapshot>,
22}
23
24/// Time range for timeline visualization.
25#[derive(Debug, Clone, Serialize)]
26pub struct TimeRange {
27    /// Start time.
28    pub start_time: u64,
29    /// End time.
30    pub end_time: u64,
31    /// Duration in milliseconds.
32    pub duration_ms: u64,
33}
34
35/// One of several same-named `MemorySnapshot` types in this crate: this one is the
36/// the timeline capture snapshot used for replay windows.
37///
38/// Convert at the module boundary instead of passing it across; a global rename is
39/// deliberately avoided (see DATA_ACCURACY_DEVELOPMENT_PLAN ยง14.2).
40/// Memory snapshot at a point in time.
41#[derive(Debug, Clone, Serialize)]
42pub struct MemorySnapshot {
43    /// Timestamp.
44    pub timestamp: u64,
45    /// Total memory.
46    pub total_memory: usize,
47    /// Active allocations.
48    pub active_allocations: usize,
49    /// Fragmentation ratio.
50    pub fragmentation_ratio: f64,
51    /// Top types by memory usage.
52    pub top_types: Vec<TypeMemoryUsage>,
53}
54
55/// Allocation event for timeline.
56#[derive(Debug, Clone, Serialize)]
57pub struct AllocationEvent {
58    /// Timestamp.
59    pub timestamp: u64,
60    /// Event type.
61    pub event_type: AllocationEventType,
62    /// Memory pointer address.
63    pub ptr: usize,
64    /// Size in bytes.
65    pub size: usize,
66    /// Variable name.
67    pub var_name: Option<String>,
68    /// Type name.
69    pub type_name: Option<String>,
70}
71
72/// Scope event for timeline.
73#[derive(Debug, Clone, Serialize)]
74pub struct ScopeEvent {
75    /// Timestamp.
76    pub timestamp: u64,
77    /// Event type.
78    pub event_type: ScopeEventType,
79    /// Scope name.
80    pub scope_name: String,
81    /// Memory usage.
82    pub memory_usage: usize,
83    /// Variable count.
84    pub variable_count: usize,
85}
86
87/// Stack trace data for analysis.
88#[derive(Debug, Clone, Serialize)]
89pub struct StackTraceData {
90    /// Memory allocation hotspots.
91    pub hotspots: Vec<StackTraceHotspot>,
92    /// Detected allocation patterns.
93    pub allocation_patterns: Vec<AllocationPattern>,
94    /// Total number of samples.
95    pub total_samples: usize,
96}
97
98/// Stack trace hotspot.
99#[derive(Debug, Clone, Serialize)]
100pub struct StackTraceHotspot {
101    /// Function name.
102    pub function_name: String,
103    /// Number of allocations.
104    pub allocation_count: usize,
105    /// Total bytes allocated.
106    pub total_bytes: usize,
107    /// Average allocation size.
108    pub average_size: f64,
109    /// Percentage of total allocations.
110    pub percentage: f64,
111}
112
113/// Allocation pattern analysis.
114#[derive(Debug, Clone, Serialize)]
115pub struct AllocationPattern {
116    /// Pattern type.
117    pub pattern_type: String,
118    /// Frequency of occurrence.
119    pub frequency: usize,
120    /// Total bytes allocated.
121    pub total_bytes: usize,
122    /// Description.
123    pub description: String,
124}
125
126/// Stack frame for stack traces.
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct StackFrame {
129    /// Function name.
130    pub function_name: String,
131    /// Source file name.
132    pub file_name: Option<String>,
133    /// Line number in source code.
134    pub line_number: Option<u32>,
135    /// Module path.
136    pub module_path: Option<String>,
137}
138
139/// Safety violation types.
140#[derive(Debug, Clone, Serialize)]
141pub enum SafetyViolation {
142    /// Potential memory leak detected.
143    PotentialLeak {
144        /// Memory pointer.
145        ptr: usize,
146        /// Size in bytes.
147        size: usize,
148        /// Age in milliseconds.
149        age_ms: u64,
150        /// Description.
151        description: String,
152    },
153    /// Use after free violation detected.
154    UseAfterFree {
155        /// Memory pointer.
156        ptr: usize,
157        /// Description.
158        description: String,
159    },
160    /// Double free violation detected.
161    DoubleFree {
162        /// Memory pointer.
163        ptr: usize,
164        /// Description.
165        description: String,
166    },
167    /// Buffer overflow detected.
168    BufferOverflow {
169        /// Memory pointer.
170        ptr: usize,
171        /// Size in bytes.
172        size: usize,
173        /// Description.
174        description: String,
175    },
176}
177
178/// Allocation hotspot information.
179#[derive(Debug, Clone, Serialize)]
180pub struct AllocationHotspot {
181    /// Location information.
182    pub location: HotspotLocation,
183    /// Number of allocations.
184    pub allocation_count: usize,
185    /// Total bytes allocated.
186    pub total_bytes: usize,
187    /// Average allocation size.
188    pub average_size: f64,
189    /// Frequency of occurrence.
190    pub frequency: f64,
191}
192
193/// Hotspot location information.
194#[derive(Debug, Clone, Serialize)]
195pub struct HotspotLocation {
196    /// Function name.
197    pub function_name: String,
198    /// File path.
199    pub file_path: Option<String>,
200    /// Line number.
201    pub line_number: Option<u32>,
202    /// Module path.
203    pub module_path: Option<String>,
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_timeline_data() {
212        let timeline = TimelineData {
213            time_range: TimeRange {
214                start_time: 0,
215                end_time: 1000,
216                duration_ms: 1000,
217            },
218            allocation_events: vec![],
219            scope_events: vec![],
220            memory_snapshots: vec![],
221        };
222
223        assert_eq!(timeline.time_range.duration_ms, 1000);
224    }
225
226    #[test]
227    fn test_memory_snapshot() {
228        let snapshot = MemorySnapshot {
229            timestamp: 100,
230            total_memory: 1024 * 1024,
231            active_allocations: 10,
232            fragmentation_ratio: 0.1,
233            top_types: vec![],
234        };
235
236        assert_eq!(snapshot.active_allocations, 10);
237    }
238
239    #[test]
240    fn test_safety_violation() {
241        let violation = SafetyViolation::PotentialLeak {
242            ptr: 0x1000,
243            size: 1024,
244            age_ms: 5000,
245            description: "test leak".to_string(),
246        };
247
248        assert!(matches!(violation, SafetyViolation::PotentialLeak { .. }));
249    }
250
251    #[test]
252    fn test_time_range_creation() {
253        let range = TimeRange {
254            start_time: 0,
255            end_time: 10000,
256            duration_ms: 10000,
257        };
258
259        assert_eq!(range.start_time, 0);
260        assert_eq!(range.end_time, 10000);
261        assert_eq!(range.duration_ms, 10000);
262    }
263
264    #[test]
265    fn test_allocation_event_creation() {
266        let event = AllocationEvent {
267            timestamp: 1000,
268            event_type: AllocationEventType::Allocate,
269            ptr: 0x1000,
270            size: 1024,
271            var_name: Some("buffer".to_string()),
272            type_name: Some("Vec<u8>".to_string()),
273        };
274
275        assert_eq!(event.timestamp, 1000);
276        assert_eq!(event.event_type, AllocationEventType::Allocate);
277        assert_eq!(event.size, 1024);
278    }
279
280    #[test]
281    fn test_scope_event_creation() {
282        let event = ScopeEvent {
283            timestamp: 500,
284            event_type: ScopeEventType::Enter,
285            scope_name: "main".to_string(),
286            memory_usage: 2048,
287            variable_count: 5,
288        };
289
290        assert_eq!(event.event_type, ScopeEventType::Enter);
291        assert_eq!(event.scope_name, "main");
292        assert_eq!(event.variable_count, 5);
293    }
294
295    #[test]
296    fn test_stack_trace_data_creation() {
297        let data = StackTraceData {
298            hotspots: vec![],
299            allocation_patterns: vec![],
300            total_samples: 100,
301        };
302
303        assert_eq!(data.total_samples, 100);
304        assert!(data.hotspots.is_empty());
305    }
306
307    #[test]
308    fn test_stack_trace_hotspot_creation() {
309        let hotspot = StackTraceHotspot {
310            function_name: "allocate_buffer".to_string(),
311            allocation_count: 50,
312            total_bytes: 10240,
313            average_size: 204.8,
314            percentage: 25.0,
315        };
316
317        assert_eq!(hotspot.function_name, "allocate_buffer");
318        assert_eq!(hotspot.allocation_count, 50);
319    }
320
321    #[test]
322    fn test_allocation_pattern_creation() {
323        let pattern = AllocationPattern {
324            pattern_type: "repeated".to_string(),
325            frequency: 100,
326            total_bytes: 4096,
327            description: "Repeated small allocations".to_string(),
328        };
329
330        assert_eq!(pattern.pattern_type, "repeated");
331        assert_eq!(pattern.frequency, 100);
332    }
333
334    #[test]
335    fn test_stack_frame_creation() {
336        let frame = StackFrame {
337            function_name: "main".to_string(),
338            file_name: Some("main.rs".to_string()),
339            line_number: Some(42),
340            module_path: Some("myapp".to_string()),
341        };
342
343        assert_eq!(frame.function_name, "main");
344        assert_eq!(frame.line_number, Some(42));
345    }
346
347    #[test]
348    fn test_stack_frame_minimal() {
349        let frame = StackFrame {
350            function_name: "unknown".to_string(),
351            file_name: None,
352            line_number: None,
353            module_path: None,
354        };
355
356        assert_eq!(frame.function_name, "unknown");
357        assert!(frame.file_name.is_none());
358    }
359
360    #[test]
361    fn test_safety_violation_use_after_free() {
362        let violation = SafetyViolation::UseAfterFree {
363            ptr: 0x2000,
364            description: "Access after free".to_string(),
365        };
366
367        assert!(matches!(violation, SafetyViolation::UseAfterFree { .. }));
368    }
369
370    #[test]
371    fn test_safety_violation_double_free() {
372        let violation = SafetyViolation::DoubleFree {
373            ptr: 0x3000,
374            description: "Double free detected".to_string(),
375        };
376
377        assert!(matches!(violation, SafetyViolation::DoubleFree { .. }));
378    }
379
380    #[test]
381    fn test_safety_violation_buffer_overflow() {
382        let violation = SafetyViolation::BufferOverflow {
383            ptr: 0x4000,
384            size: 1024,
385            description: "Buffer overflow".to_string(),
386        };
387
388        assert!(matches!(violation, SafetyViolation::BufferOverflow { .. }));
389    }
390
391    #[test]
392    fn test_allocation_hotspot_creation() {
393        let hotspot = AllocationHotspot {
394            location: HotspotLocation {
395                function_name: "process_data".to_string(),
396                file_path: Some("processor.rs".to_string()),
397                line_number: Some(100),
398                module_path: None,
399            },
400            allocation_count: 200,
401            total_bytes: 8192,
402            average_size: 40.96,
403            frequency: 0.5,
404        };
405
406        assert_eq!(hotspot.allocation_count, 200);
407        assert_eq!(hotspot.location.function_name, "process_data");
408    }
409
410    #[test]
411    fn test_hotspot_location_creation() {
412        let location = HotspotLocation {
413            function_name: "test_fn".to_string(),
414            file_path: Some("test.rs".to_string()),
415            line_number: Some(10),
416            module_path: Some("test_module".to_string()),
417        };
418
419        assert_eq!(location.function_name, "test_fn");
420        assert_eq!(location.line_number, Some(10));
421    }
422
423    #[test]
424    fn test_timeline_data_with_events() {
425        let alloc_event = AllocationEvent {
426            timestamp: 100,
427            event_type: AllocationEventType::Allocate,
428            ptr: 0x1000,
429            size: 512,
430            var_name: None,
431            type_name: None,
432        };
433
434        let scope_event = ScopeEvent {
435            timestamp: 50,
436            event_type: ScopeEventType::Enter,
437            scope_name: "test".to_string(),
438            memory_usage: 0,
439            variable_count: 0,
440        };
441
442        let timeline = TimelineData {
443            time_range: TimeRange {
444                start_time: 0,
445                end_time: 1000,
446                duration_ms: 1000,
447            },
448            allocation_events: vec![alloc_event],
449            scope_events: vec![scope_event],
450            memory_snapshots: vec![],
451        };
452
453        assert_eq!(timeline.allocation_events.len(), 1);
454        assert_eq!(timeline.scope_events.len(), 1);
455    }
456
457    #[test]
458    fn test_memory_snapshot_with_types() {
459        let type_usage = TypeMemoryUsage {
460            type_name: "String".to_string(),
461            total_size: 4096,
462            allocation_count: 100,
463            average_size: 40.96,
464            peak_size: 5000,
465            current_size: 4096,
466            efficiency_score: 0.9,
467        };
468
469        let snapshot = MemorySnapshot {
470            timestamp: 500,
471            total_memory: 8192,
472            active_allocations: 50,
473            fragmentation_ratio: 0.15,
474            top_types: vec![type_usage],
475        };
476
477        assert_eq!(snapshot.top_types.len(), 1);
478        assert_eq!(snapshot.top_types[0].type_name, "String");
479    }
480
481    #[test]
482    fn test_stack_trace_data_with_hotspots() {
483        let hotspot = StackTraceHotspot {
484            function_name: "hot_function".to_string(),
485            allocation_count: 500,
486            total_bytes: 20480,
487            average_size: 40.96,
488            percentage: 50.0,
489        };
490
491        let pattern = AllocationPattern {
492            pattern_type: "burst".to_string(),
493            frequency: 10,
494            total_bytes: 1024,
495            description: "Burst allocation pattern".to_string(),
496        };
497
498        let data = StackTraceData {
499            hotspots: vec![hotspot],
500            allocation_patterns: vec![pattern],
501            total_samples: 1000,
502        };
503
504        assert_eq!(data.hotspots.len(), 1);
505        assert_eq!(data.allocation_patterns.len(), 1);
506    }
507}