1use crate::event_store::{MemoryEvent, MemoryEventType, SharedEventStore};
7use crate::snapshot::types::{ActiveAllocation, MemorySnapshot, ThreadMemoryStats};
8use std::collections::HashMap;
9
10pub struct SnapshotEngine {
20 event_store: SharedEventStore,
22}
23
24impl SnapshotEngine {
25 pub fn new(event_store: SharedEventStore) -> Self {
27 Self { event_store }
28 }
29
30 pub fn build_snapshot(&self) -> MemorySnapshot {
35 let events = self.event_store.snapshot();
36 self.build_snapshot_from_events(events)
37 }
38
39 pub fn build_snapshot_from_events(&self, events: Vec<MemoryEvent>) -> MemorySnapshot {
44 let mut snapshot = MemorySnapshot::new();
45 let mut ptr_to_allocation: HashMap<usize, ActiveAllocation> = HashMap::new();
46 let mut thread_stats: HashMap<u64, ThreadMemoryStats> = HashMap::new();
47 let mut peak_memory: usize = 0;
48 let mut current_memory: usize = 0;
49
50 for event in events {
51 match event.event_type {
52 MemoryEventType::Allocate => {
53 let allocation = ActiveAllocation {
54 ptr: event.ptr,
55 size: event.size,
56 allocated_at: event.timestamp,
57 var_name: event.var_name,
58 type_name: event.type_name,
59 thread_id: event.thread_id,
60 call_stack_hash: event.call_stack_hash,
61 };
62
63 ptr_to_allocation.insert(event.ptr, allocation);
64
65 snapshot.stats.total_allocations += 1;
66 snapshot.stats.total_allocated += event.size;
67 current_memory += event.size;
68
69 let thread_stat =
70 thread_stats
71 .entry(event.thread_id)
72 .or_insert_with(|| ThreadMemoryStats {
73 thread_id: event.thread_id,
74 allocation_count: 0,
75 total_allocated: 0,
76 total_deallocated: 0,
77 current_memory: 0,
78 peak_memory: 0,
79 });
80 thread_stat.allocation_count += 1;
81 thread_stat.total_allocated += event.size;
82 thread_stat.current_memory += event.size;
83 if thread_stat.current_memory > thread_stat.peak_memory {
84 thread_stat.peak_memory = thread_stat.current_memory;
85 }
86 }
87 MemoryEventType::Reallocate => {
88 let old_allocation = ptr_to_allocation.get(&event.ptr).cloned();
89 let old_size = event.old_size.unwrap_or_else(|| {
90 old_allocation.as_ref().map(|a| a.size).unwrap_or_else(|| {
91 tracing::warn!(
92 "Reallocation without old_size or previous allocation: ptr={:#x}, new_size={}",
93 event.ptr,
94 event.size
95 );
96 0
97 })
98 });
99
100 let allocation = ActiveAllocation {
101 ptr: event.ptr,
102 size: event.size,
103 allocated_at: old_allocation
104 .map(|a| a.allocated_at)
105 .unwrap_or(event.timestamp),
106 var_name: event.var_name,
107 type_name: event.type_name,
108 thread_id: event.thread_id,
109 call_stack_hash: event.call_stack_hash,
110 };
111
112 ptr_to_allocation.insert(event.ptr, allocation);
113
114 snapshot.stats.total_reallocations += 1;
115 snapshot.stats.total_allocated += event.size;
116 snapshot.stats.total_deallocated += old_size;
117
118 if old_size > current_memory {
120 tracing::warn!(
121 "Potential memory tracking bug detected: deallocating {} bytes but only {} bytes are currently tracked. ptr=0x{:x}",
122 old_size, current_memory, event.ptr
123 );
124 current_memory = current_memory
127 .saturating_sub(old_size)
128 .saturating_add(event.size);
129 } else {
130 current_memory =
132 current_memory
133 .checked_sub(old_size)
134 .and_then(|v| v.checked_add(event.size))
135 .unwrap_or_else(|| {
136 tracing::error!(
137 "Integer overflow detected in memory calculation: {} - {} + {}",
138 current_memory, old_size, event.size
139 );
140 current_memory
142 .saturating_sub(old_size)
143 .saturating_add(event.size)
144 });
145 }
146
147 let thread_stat =
148 thread_stats
149 .entry(event.thread_id)
150 .or_insert_with(|| ThreadMemoryStats {
151 thread_id: event.thread_id,
152 allocation_count: 0,
153 total_allocated: 0,
154 total_deallocated: 0,
155 current_memory: 0,
156 peak_memory: 0,
157 });
158 thread_stat.total_allocated += event.size;
159 thread_stat.total_deallocated += old_size;
160 thread_stat.current_memory = thread_stat
161 .current_memory
162 .saturating_sub(old_size)
163 .saturating_add(event.size);
164 if thread_stat.current_memory > thread_stat.peak_memory {
165 thread_stat.peak_memory = thread_stat.current_memory;
166 }
167 }
168 MemoryEventType::Deallocate => {
169 if let Some(allocation) = ptr_to_allocation.remove(&event.ptr) {
170 snapshot.stats.total_deallocations += 1;
171 snapshot.stats.total_deallocated += allocation.size;
172 current_memory = current_memory.saturating_sub(allocation.size);
173
174 if let Some(thread_stat) = thread_stats.get_mut(&event.thread_id) {
175 thread_stat.total_deallocated += allocation.size;
176 thread_stat.current_memory =
177 thread_stat.current_memory.saturating_sub(allocation.size);
178 }
179 } else {
180 snapshot.stats.unmatched_deallocations += 1;
181 tracing::debug!(
182 "Unmatched deallocation: ptr={:#x}, thread_id={}",
183 event.ptr,
184 event.thread_id
185 );
186 }
187 }
188 MemoryEventType::Move | MemoryEventType::Borrow | MemoryEventType::Return => {
189 }
192 }
193
194 if current_memory > peak_memory {
196 peak_memory = current_memory;
197 }
198 }
199
200 snapshot.active_allocations = ptr_to_allocation;
202 snapshot.thread_stats = thread_stats;
203 snapshot.stats.active_allocations = snapshot.active_allocations.len();
204 snapshot.stats.current_memory = current_memory;
205 snapshot.stats.peak_memory = peak_memory;
206
207 snapshot
208 }
209
210 pub fn event_store(&self) -> &SharedEventStore {
212 &self.event_store
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::event_store::EventStore;
220 use std::sync::Arc;
221
222 #[test]
223 fn test_snapshot_engine_creation() {
224 let event_store = Arc::new(EventStore::new());
225 let engine = SnapshotEngine::new(event_store);
226 let snapshot = engine.build_snapshot();
227 assert_eq!(snapshot.active_count(), 0);
228 }
229
230 #[test]
231 fn test_snapshot_with_allocations() {
232 let event_store = Arc::new(EventStore::new());
233 event_store.record(MemoryEvent::allocate(0x1000, 1024, 1));
234 event_store.record(MemoryEvent::allocate(0x2000, 2048, 1));
235
236 let engine = SnapshotEngine::new(event_store);
237 let snapshot = engine.build_snapshot();
238
239 assert_eq!(snapshot.active_count(), 2);
240 assert_eq!(snapshot.current_memory(), 3072);
241 }
242
243 #[test]
244 fn test_snapshot_with_deallocations() {
245 let event_store = Arc::new(EventStore::new());
246 event_store.record(MemoryEvent::allocate(0x1000, 1024, 1));
247 event_store.record(MemoryEvent::deallocate(0x1000, 1024, 1));
248
249 let engine = SnapshotEngine::new(event_store);
250 let snapshot = engine.build_snapshot();
251
252 assert_eq!(snapshot.active_count(), 0);
253 assert_eq!(snapshot.current_memory(), 0);
254 assert_eq!(snapshot.stats.total_allocations, 1);
255 assert_eq!(snapshot.stats.total_deallocations, 1);
256 }
257
258 #[test]
259 fn test_snapshot_peak_memory() {
260 let event_store = Arc::new(EventStore::new());
261 event_store.record(MemoryEvent::allocate(0x1000, 1024, 1));
262 event_store.record(MemoryEvent::allocate(0x2000, 2048, 1));
263 event_store.record(MemoryEvent::deallocate(0x2000, 2048, 1));
264
265 let engine = SnapshotEngine::new(event_store);
266 let snapshot = engine.build_snapshot();
267
268 assert_eq!(snapshot.peak_memory(), 3072);
269 assert_eq!(snapshot.current_memory(), 1024);
270 }
271
272 #[test]
273 fn test_snapshot_thread_stats() {
274 let event_store = Arc::new(EventStore::new());
275 event_store.record(MemoryEvent::allocate(0x1000, 1024, 1));
276 event_store.record(MemoryEvent::allocate(0x2000, 2048, 2));
277
278 let engine = SnapshotEngine::new(event_store);
279 let snapshot = engine.build_snapshot();
280
281 assert_eq!(snapshot.thread_stats.len(), 2);
282
283 let thread1 = snapshot.thread_stats.get(&1).unwrap();
284 assert_eq!(thread1.allocation_count, 1);
285 assert_eq!(thread1.total_allocated, 1024);
286
287 let thread2 = snapshot.thread_stats.get(&2).unwrap();
288 assert_eq!(thread2.allocation_count, 1);
289 assert_eq!(thread2.total_allocated, 2048);
290 }
291
292 #[test]
293 fn test_deallocation_underflow_protection() {
294 let event_store = Arc::new(EventStore::new());
295 event_store.record(MemoryEvent::allocate(0x1000, 1024, 1));
296 event_store.record(MemoryEvent::deallocate(0x1000, 2048, 1));
297
298 let engine = SnapshotEngine::new(event_store);
299 let snapshot = engine.build_snapshot();
300
301 assert_eq!(snapshot.current_memory(), 0);
302 let thread1 = snapshot.thread_stats.get(&1).unwrap();
303 assert_eq!(thread1.current_memory, 0);
304 }
305}