memscope_rs/analyzer/
timeline.rs1use crate::event_store::{MemoryEvent, MemoryEventType};
4use crate::view::MemoryView;
5use tracing::debug;
6
7pub struct TimelineAnalysis {
11 view: MemoryView,
12}
13
14impl TimelineAnalysis {
15 pub fn from_view(view: &MemoryView) -> Self {
17 debug!(
18 "Creating TimelineAnalysis with {} events",
19 view.events().len()
20 );
21 Self { view: view.clone() }
22 }
23
24 pub fn query(&self, start: u64, end: u64) -> TimelineResult {
26 debug!("Querying events in time range [{}, {}]", start, end);
27 let events: Vec<&MemoryEvent> = self
28 .view
29 .events()
30 .iter()
31 .filter(|e| e.timestamp >= start && e.timestamp <= end)
32 .collect();
33
34 let result = TimelineResult {
35 event_count: events.len(),
36 start,
37 end,
38 };
39 debug!("Query returned {} events", result.event_count);
40 result
41 }
42
43 pub fn timeline(&self) -> Vec<TimelineEvent> {
45 let events: Vec<TimelineEvent> = self
46 .view
47 .events()
48 .iter()
49 .map(|e| TimelineEvent {
50 timestamp: e.timestamp,
51 event_type: format!("{:?}", e.event_type),
52 ptr: e.ptr,
53 size: e.size,
54 thread_id: e.thread_id,
55 })
56 .collect();
57 debug!("Timeline has {} events", events.len());
58 events
59 }
60
61 pub fn allocation_rate(&self, bucket_ms: u64) -> Vec<AllocationRate> {
66 debug!("Computing allocation rate with {}ms buckets", bucket_ms);
67 let events = self.view.events();
68 if events.is_empty() || bucket_ms == 0 {
69 debug!("Allocation rate: no events or invalid bucket size");
70 return vec![];
71 }
72
73 let min_time = events.iter().map(|e| e.timestamp).min().unwrap_or(0);
75 let max_time = events.iter().map(|e| e.timestamp).max().unwrap_or(0);
76
77 let time_range = max_time.saturating_sub(min_time);
79 let bucket_ns = bucket_ms * 1_000_000; let num_buckets = ((time_range / bucket_ns) + 1) as usize;
81
82 if num_buckets == 0 || num_buckets > 10000 {
83 debug!(
84 "Allocation rate: bucket count {} exceeds limit or is zero",
85 num_buckets
86 );
87 return vec![];
88 }
89
90 let mut buckets: Vec<AllocationRate> = (0..num_buckets)
92 .map(|i| AllocationRate {
93 start: min_time + (i as u64 * bucket_ns),
94 end: min_time + ((i + 1) as u64 * bucket_ns),
95 count: 0,
96 bytes: 0,
97 })
98 .collect();
99
100 let mut skipped_count = 0usize;
102 for event in events {
103 if event.event_type == MemoryEventType::Allocate {
104 let bucket_idx = ((event.timestamp.saturating_sub(min_time)) / bucket_ns) as usize;
105 if bucket_idx < buckets.len() {
106 buckets[bucket_idx].count += 1;
107 buckets[bucket_idx].bytes += event.size;
108 } else {
109 skipped_count += 1;
110 }
111 }
112 }
113
114 if skipped_count > 0 {
115 debug!(
116 "Allocation rate: {} events skipped due to bucket index out of range",
117 skipped_count
118 );
119 }
120
121 while buckets.last().map(|b| b.count == 0).unwrap_or(false) {
123 buckets.pop();
124 }
125
126 debug!("Allocation rate: {} buckets computed", buckets.len());
127 buckets
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct TimelineResult {
134 pub event_count: usize,
136 pub start: u64,
138 pub end: u64,
140}
141
142#[derive(Debug, Clone)]
144pub struct TimelineEvent {
145 pub timestamp: u64,
147 pub event_type: String,
149 pub ptr: usize,
151 pub size: usize,
153 pub thread_id: u64,
155}
156
157#[derive(Debug, Clone)]
159pub struct AllocationRate {
160 pub start: u64,
162 pub end: u64,
164 pub count: usize,
166 pub bytes: usize,
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::event_store::MemoryEvent;
174
175 #[test]
176 fn test_timeline_query() {
177 let events = vec![
178 MemoryEvent::allocate(0x1000, 64, 1),
179 MemoryEvent::allocate(0x2000, 128, 2),
180 ];
181 let view = MemoryView::from_events(events);
182 let analysis = TimelineAnalysis::from_view(&view);
183 let result = analysis.query(0, u64::MAX);
184 assert_eq!(result.event_count, 2);
185 }
186
187 #[test]
188 fn test_allocation_rate() {
189 let events = vec![
190 MemoryEvent::allocate(0x1000, 64, 1),
191 MemoryEvent::allocate(0x2000, 128, 1),
192 MemoryEvent::allocate(0x3000, 256, 1),
193 ];
194 let view = MemoryView::from_events(events);
195 let analysis = TimelineAnalysis::from_view(&view);
196 let rates = analysis.allocation_rate(1); assert!(!rates.is_empty());
198 let total_count: usize = rates.iter().map(|r| r.count).sum();
199 assert_eq!(total_count, 3);
200 }
201
202 #[test]
203 fn test_allocation_rate_empty() {
204 let events: Vec<MemoryEvent> = vec![];
205 let view = MemoryView::from_events(events);
206 let analysis = TimelineAnalysis::from_view(&view);
207 let rates = analysis.allocation_rate(1);
208 assert!(rates.is_empty());
209 }
210}