1use crate::core::types::TrackKind;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ActiveAllocation {
13 pub ptr: Option<usize>,
15 pub size: usize,
17 pub kind: TrackKind,
19 pub allocated_at: u64,
21 pub var_name: Option<String>,
23 pub type_name: Option<String>,
25 pub thread_id: u64,
27 pub call_stack_hash: Option<u64>,
29 pub module_path: Option<String>,
31 pub stack_ptr: Option<usize>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, Default)]
37pub struct MemoryStats {
38 pub total_allocations: usize,
40 pub total_reallocations: usize,
42 pub total_deallocations: usize,
44 pub unmatched_deallocations: usize,
46 #[serde(default)]
50 pub degraded_releases: usize,
51 pub active_allocations: usize,
53 pub total_allocated: usize,
55 pub total_deallocated: usize,
57 pub current_memory: usize,
59 pub peak_memory: usize,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, Default)]
65pub struct ThreadMemoryStats {
66 pub thread_id: u64,
68 pub allocation_count: usize,
70 pub total_allocated: usize,
72 pub total_deallocated: usize,
74 pub current_memory: usize,
76 pub peak_memory: usize,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, Default)]
87pub struct MemorySnapshot {
88 pub timestamp: u64,
90 pub stats: MemoryStats,
92 pub active_allocations: HashMap<usize, ActiveAllocation>,
94 #[serde(default)]
96 pub active_bindings: Option<Vec<SnapshotBinding>>,
97 pub thread_stats: HashMap<u64, ThreadMemoryStats>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct SnapshotBinding {
104 pub generation_id: u64,
105 pub binding_serial: Option<u64>,
106 pub allocation: ActiveAllocation,
107}
108
109impl MemorySnapshot {
110 pub fn new() -> Self {
112 Self {
113 timestamp: std::time::SystemTime::now()
114 .duration_since(std::time::UNIX_EPOCH)
115 .unwrap_or_default()
116 .as_nanos() as u64,
117 stats: MemoryStats::default(),
118 active_allocations: HashMap::new(),
119 active_bindings: None,
120 thread_stats: HashMap::new(),
121 }
122 }
123
124 pub fn from_allocation_infos(allocations: Vec<crate::capture::types::AllocationInfo>) -> Self {
126 let mut snapshot = Self::new();
127 let mut thread_stats: HashMap<u64, ThreadMemoryStats> = HashMap::new();
128 let mut current_memory: usize = 0;
129
130 for alloc in allocations {
131 let thread_id = alloc.thread_id_u64;
132
133 let active_alloc = ActiveAllocation {
134 ptr: Some(alloc.ptr),
135 size: alloc.size,
136 kind: TrackKind::HeapOwner {
137 ptr: alloc.ptr,
138 size: alloc.size,
139 },
140 allocated_at: alloc.timestamp_alloc,
141 var_name: alloc.var_name,
142 type_name: alloc.type_name,
143 thread_id,
144 call_stack_hash: None,
145 module_path: alloc.module_path,
146 stack_ptr: None,
147 };
148
149 current_memory += alloc.size;
150
151 snapshot.stats.total_allocations += 1;
152 snapshot.stats.total_allocated += alloc.size;
153
154 let thread_stat = thread_stats
155 .entry(thread_id)
156 .or_insert_with(|| ThreadMemoryStats {
157 thread_id,
158 allocation_count: 0,
159 total_allocated: 0,
160 total_deallocated: 0,
161 current_memory: 0,
162 peak_memory: 0,
163 });
164
165 thread_stat.allocation_count += 1;
166 thread_stat.total_allocated += alloc.size;
167 thread_stat.current_memory += alloc.size;
168 if thread_stat.current_memory > thread_stat.peak_memory {
169 thread_stat.peak_memory = thread_stat.current_memory;
170 }
171
172 snapshot.active_allocations.insert(alloc.ptr, active_alloc);
173 }
174
175 snapshot.stats.current_memory = current_memory;
176 snapshot.stats.peak_memory = 0; snapshot.stats.active_allocations = snapshot.active_allocations.len();
178 snapshot.thread_stats = thread_stats;
179
180 snapshot
181 }
182
183 pub fn active_count(&self) -> usize {
185 self.active_bindings
186 .as_ref()
187 .map_or(self.active_allocations.len(), Vec::len)
188 }
189
190 pub fn allocations(&self) -> impl Iterator<Item = &ActiveAllocation> {
193 self.active_bindings
194 .iter()
195 .flatten()
196 .map(|binding| &binding.allocation)
197 .chain(
198 self.active_allocations
199 .values()
200 .filter(|_| self.active_bindings.is_none()),
201 )
202 }
203
204 pub fn current_memory(&self) -> usize {
206 self.stats.current_memory
207 }
208
209 pub fn peak_memory(&self) -> usize {
211 self.stats.peak_memory
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_memory_snapshot_new() {
221 let snapshot = MemorySnapshot::new();
222 assert!(snapshot.timestamp > 0);
223 assert_eq!(snapshot.stats.total_allocations, 0);
224 assert!(snapshot.active_allocations.is_empty());
225 }
226
227 #[test]
228 fn test_memory_snapshot_default() {
229 let snapshot = MemorySnapshot::default();
230 assert_eq!(snapshot.timestamp, 0);
231 assert_eq!(snapshot.stats.total_allocations, 0);
232 }
233
234 #[test]
235 fn test_active_allocation_creation() {
236 let alloc = ActiveAllocation {
237 ptr: Some(0x1000),
238 size: 1024,
239 kind: TrackKind::HeapOwner {
240 ptr: 0x1000,
241 size: 1024,
242 },
243 allocated_at: 1000,
244 var_name: Some("test".to_string()),
245 type_name: Some("Vec<u8>".to_string()),
246 thread_id: 1,
247 call_stack_hash: None,
248 module_path: None,
249 stack_ptr: None,
250 };
251
252 assert_eq!(alloc.ptr, Some(0x1000));
253 assert_eq!(alloc.size, 1024);
254 assert_eq!(alloc.var_name, Some("test".to_string()));
255 }
256
257 #[test]
258 fn test_active_allocation_clone() {
259 let alloc = ActiveAllocation {
260 ptr: Some(0x1000),
261 size: 1024,
262 kind: TrackKind::HeapOwner {
263 ptr: 0x1000,
264 size: 1024,
265 },
266 allocated_at: 1000,
267 var_name: None,
268 type_name: None,
269 thread_id: 1,
270 call_stack_hash: None,
271 module_path: None,
272 stack_ptr: None,
273 };
274
275 let cloned = alloc.clone();
276 assert_eq!(cloned.size, alloc.size);
277 }
278
279 #[test]
280 fn test_active_allocation_debug() {
281 let alloc = ActiveAllocation {
282 ptr: Some(0x1000),
283 size: 1024,
284 kind: TrackKind::HeapOwner {
285 ptr: 0x1000,
286 size: 1024,
287 },
288 allocated_at: 1000,
289 var_name: None,
290 type_name: None,
291 thread_id: 1,
292 call_stack_hash: None,
293 module_path: None,
294 stack_ptr: None,
295 };
296
297 let debug_str = format!("{:?}", alloc);
298 assert!(debug_str.contains("ActiveAllocation"));
299 }
300
301 #[test]
302 fn test_memory_stats_default() {
303 let stats = MemoryStats::default();
304 assert_eq!(stats.total_allocations, 0);
305 assert_eq!(stats.total_reallocations, 0);
306 assert_eq!(stats.total_deallocations, 0);
307 assert_eq!(stats.active_allocations, 0);
308 assert_eq!(stats.current_memory, 0);
309 assert_eq!(stats.peak_memory, 0);
310 }
311
312 #[test]
313 fn test_memory_stats_clone() {
314 let stats = MemoryStats {
315 total_allocations: 100,
316 total_reallocations: 10,
317 total_deallocations: 50,
318 unmatched_deallocations: 2,
319 degraded_releases: 0,
320 active_allocations: 50,
321 total_allocated: 1024 * 1024,
322 total_deallocated: 512 * 1024,
323 current_memory: 512 * 1024,
324 peak_memory: 1024 * 1024,
325 };
326
327 let cloned = stats.clone();
328 assert_eq!(cloned.total_allocations, 100);
329 assert_eq!(cloned.peak_memory, 1024 * 1024);
330 }
331
332 #[test]
333 fn test_thread_memory_stats_default() {
334 let stats = ThreadMemoryStats::default();
335 assert_eq!(stats.thread_id, 0);
336 assert_eq!(stats.allocation_count, 0);
337 assert_eq!(stats.current_memory, 0);
338 }
339
340 #[test]
341 fn test_thread_memory_stats_clone() {
342 let stats = ThreadMemoryStats {
343 thread_id: 1,
344 allocation_count: 50,
345 total_allocated: 4096,
346 total_deallocated: 2048,
347 current_memory: 2048,
348 peak_memory: 4096,
349 };
350
351 let cloned = stats.clone();
352 assert_eq!(cloned.thread_id, 1);
353 assert_eq!(cloned.allocation_count, 50);
354 }
355
356 #[test]
357 fn test_memory_snapshot_active_count() {
358 let mut snapshot = MemorySnapshot::new();
359 assert_eq!(snapshot.active_count(), 0);
360
361 snapshot.active_allocations.insert(
362 0x1000,
363 ActiveAllocation {
364 ptr: Some(0x1000),
365 size: 1024,
366 kind: TrackKind::HeapOwner {
367 ptr: 0x1000,
368 size: 1024,
369 },
370 allocated_at: 1000,
371 var_name: None,
372 type_name: None,
373 thread_id: 1,
374 call_stack_hash: None,
375 module_path: None,
376 stack_ptr: Some(0x2000), },
378 );
379
380 assert_eq!(snapshot.active_count(), 1);
381 }
382
383 #[test]
384 fn test_memory_snapshot_current_memory() {
385 let mut snapshot = MemorySnapshot::new();
386 assert_eq!(snapshot.current_memory(), 0);
387
388 snapshot.stats.current_memory = 4096;
389 assert_eq!(snapshot.current_memory(), 4096);
390 }
391
392 #[test]
393 fn test_memory_snapshot_peak_memory() {
394 let mut snapshot = MemorySnapshot::new();
395 assert_eq!(snapshot.peak_memory(), 0);
396
397 snapshot.stats.peak_memory = 8192;
398 assert_eq!(snapshot.peak_memory(), 8192);
399 }
400
401 #[test]
402 fn test_memory_snapshot_serialization() {
403 let snapshot = MemorySnapshot::new();
404
405 let json = serde_json::to_string(&snapshot);
406 assert!(json.is_ok());
407
408 let deserialized: Result<MemorySnapshot, _> = serde_json::from_str(&json.unwrap());
409 assert!(deserialized.is_ok());
410 }
411
412 #[test]
413 fn test_active_allocation_serialization() {
414 let alloc = ActiveAllocation {
415 ptr: Some(0x1000),
416 size: 1024,
417 kind: TrackKind::HeapOwner {
418 ptr: 0x1000,
419 size: 1024,
420 },
421 allocated_at: 1000,
422 var_name: Some("test".to_string()),
423 type_name: Some("i32".to_string()),
424 thread_id: 1,
425 call_stack_hash: Some(12345),
426 module_path: Some("test_module".to_string()),
427 stack_ptr: None,
428 };
429
430 let json = serde_json::to_string(&alloc);
431 assert!(json.is_ok());
432
433 let deserialized: Result<ActiveAllocation, _> = serde_json::from_str(&json.unwrap());
434 assert!(deserialized.is_ok());
435 }
436
437 #[test]
438 fn test_memory_stats_serialization() {
439 let stats = MemoryStats {
440 total_allocations: 100,
441 total_reallocations: 10,
442 total_deallocations: 50,
443 unmatched_deallocations: 2,
444 degraded_releases: 0,
445 active_allocations: 50,
446 total_allocated: 1024,
447 total_deallocated: 512,
448 current_memory: 512,
449 peak_memory: 1024,
450 };
451
452 let json = serde_json::to_string(&stats);
453 assert!(json.is_ok());
454
455 let deserialized: Result<MemoryStats, _> = serde_json::from_str(&json.unwrap());
456 assert!(deserialized.is_ok());
457 }
458}