1use crate::core::types::AllocationInfo;
4use crate::export::binary::error::BinaryExportError;
5use crate::export::binary::format::{
6 AdvancedMetricsHeader, FileHeader, MetricsBitmapFlags, ALLOCATION_RECORD_TYPE, HEADER_SIZE,
7};
8use crate::export::binary::serializable::{primitives, BinarySerializable};
9use crate::export::binary::string_table::StringTable;
10use std::collections::HashMap;
11use std::fs::File;
12#[cfg(test)]
13use std::io::Write;
14use std::io::{BufReader, Read, Seek, SeekFrom};
15use std::path::Path;
16
17pub struct BinaryReader {
19 reader: BufReader<File>,
20 advanced_metrics: Option<AdvancedMetricsData>,
21 string_table: Option<StringTable>,
22 file_version: Option<u32>,
23}
24
25#[derive(Debug, Clone)]
27pub struct AdvancedMetricsData {
28 pub lifecycle_metrics: HashMap<u64, LifecycleMetric>,
29 pub container_metrics: HashMap<u64, String>, pub type_usage_metrics: HashMap<u64, String>, }
32
33#[derive(Debug, Clone)]
35pub struct LifecycleMetric {
36 pub lifetime_ms: u64,
37 pub lifecycle_tracking: Option<String>, }
39
40impl BinaryReader {
41 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, BinaryExportError> {
43 let file = File::open(path)?;
44 let reader = BufReader::new(file);
45
46 Ok(Self {
47 reader,
48 advanced_metrics: None,
49 string_table: None,
50 file_version: None,
51 })
52 }
53
54 pub fn read_header(&mut self) -> Result<FileHeader, BinaryExportError> {
56 let mut header_bytes = [0u8; HEADER_SIZE];
57 self.reader.read_exact(&mut header_bytes)?;
58
59 let header = FileHeader::from_bytes(&header_bytes);
60
61 if !header.is_valid_magic() {
63 let expected = String::from_utf8_lossy(&header.magic);
64 let actual = String::from_utf8_lossy(b"MEMSCOPE");
65 return Err(BinaryExportError::InvalidMagic {
66 expected: expected.to_string(),
67 actual: actual.to_string(),
68 });
69 }
70
71 if !header.is_compatible_version() {
73 return Err(BinaryExportError::UnsupportedVersion(header.version));
74 }
75
76 self.file_version = Some(header.get_version());
78
79 self.read_string_table()?;
81
82 Ok(header)
83 }
84
85 fn read_string_table(&mut self) -> Result<(), BinaryExportError> {
87 let mut marker = [0u8; 4];
89 self.reader.read_exact(&mut marker)?;
90
91 let table_size = primitives::read_u32(&mut self.reader)?;
93
94 if &marker == b"STBL" && table_size > 0 {
95 let use_compressed_indices = primitives::read_u8(&mut self.reader)? != 0;
97
98 let string_count = primitives::read_u32(&mut self.reader)? as usize;
100 let mut strings = Vec::with_capacity(string_count);
101
102 for _ in 0..string_count {
103 let string = primitives::read_string(&mut self.reader)?;
104 strings.push(string);
105 }
106
107 let mut string_table = StringTable::with_compression(use_compressed_indices);
109 for string in strings {
110 string_table.add_string(&string)?;
111 }
112 self.string_table = Some(string_table);
113 } else if &marker == b"NONE" {
114 self.string_table = None;
116 } else {
117 return Err(BinaryExportError::CorruptedData(
118 "Invalid string table marker".to_string(),
119 ));
120 }
121
122 Ok(())
123 }
124
125 pub fn read_allocation(&mut self) -> Result<AllocationInfo, BinaryExportError> {
127 let file_version = self.file_version.unwrap_or(1);
128
129 match file_version {
130 1 => self.read_allocation_v1(),
131 2 | 3 => self.read_allocation_v2(), _ => Err(BinaryExportError::UnsupportedVersion(file_version)),
133 }
134 }
135
136 fn read_allocation_v1(&mut self) -> Result<AllocationInfo, BinaryExportError> {
138 let mut type_byte = [0u8; 1];
140 self.reader.read_exact(&mut type_byte)?;
141
142 if type_byte[0] != ALLOCATION_RECORD_TYPE {
143 return Err(BinaryExportError::CorruptedData(format!(
144 "Invalid record type: {}",
145 type_byte[0]
146 )));
147 }
148
149 let mut length_bytes = [0u8; 4];
151 self.reader.read_exact(&mut length_bytes)?;
152 let _record_length = u32::from_le_bytes(length_bytes);
153
154 let ptr = self.read_u64()? as usize;
156 let size = self.read_u64()? as usize;
157 let timestamp_alloc = self.read_u64()?;
158
159 let timestamp_dealloc = if self.read_u8()? == 1 {
161 Some(self.read_u64()?)
162 } else {
163 None
164 };
165
166 let var_name = self.read_optional_string()?;
168 let type_name = self.read_optional_string()?;
169 let scope_name = self.read_optional_string()?;
170 let thread_id = self.read_string()?;
171
172 let stack_trace = self.read_optional_string_vec()?;
174
175 let borrow_count = self.read_u32()? as usize;
177 let is_leaked_byte = self.read_u8()?;
178 let is_leaked = is_leaked_byte != 0;
179
180 let lifetime_flag = self.read_u8()?;
182 let lifetime_ms = if lifetime_flag == 1 {
183 Some(self.read_u64()?)
184 } else {
185 None
186 };
187
188 Ok(AllocationInfo {
190 ptr,
191 size,
192 var_name,
193 type_name,
194 scope_name,
195 timestamp_alloc,
196 timestamp_dealloc,
197 thread_id,
198 borrow_count,
199 stack_trace,
200 is_leaked,
201 lifetime_ms,
202 borrow_info: None,
203 clone_info: None,
204 ownership_history_available: false,
205 smart_pointer_info: None,
206 memory_layout: None,
207 generic_info: None,
208 dynamic_type_info: None,
209 runtime_state: None,
210 stack_allocation: None,
211 temporary_object: None,
212 fragmentation_analysis: None,
213 generic_instantiation: None,
214 type_relationships: None,
215 type_usage: None,
216 function_call_tracking: None,
217 lifecycle_tracking: None,
218 access_tracking: None,
219 drop_chain_analysis: None,
220 })
221 }
222
223 fn read_allocation_v2(&mut self) -> Result<AllocationInfo, BinaryExportError> {
225 let mut type_byte = [0u8; 1];
227 self.reader.read_exact(&mut type_byte)?;
228
229 if type_byte[0] != ALLOCATION_RECORD_TYPE {
230 return Err(BinaryExportError::CorruptedData(format!(
231 "Invalid record type: {}",
232 type_byte[0]
233 )));
234 }
235
236 let mut length_bytes = [0u8; 4];
238 self.reader.read_exact(&mut length_bytes)?;
239 let _record_length = u32::from_le_bytes(length_bytes);
240
241 let ptr = self.read_u64()? as usize;
243 let size = self.read_u64()? as usize;
244 let timestamp_alloc = self.read_u64()?;
245
246 let timestamp_dealloc = if self.read_u8()? == 1 {
248 Some(self.read_u64()?)
249 } else {
250 None
251 };
252
253 let var_name = self.read_optional_string()?;
255 let type_name = self.read_optional_string()?;
256 let scope_name = self.read_optional_string()?;
257 let thread_id = self.read_string()?;
258
259 let stack_trace = self.read_optional_string_vec()?;
261
262 let borrow_count = self.read_u32()? as usize;
264 let is_leaked_byte = self.read_u8()?;
265 let is_leaked = is_leaked_byte != 0;
266
267 let lifetime_flag = self.read_u8()?;
269 let lifetime_ms = if lifetime_flag == 1 {
270 Some(self.read_u64()?)
271 } else {
272 None
273 };
274
275 let borrow_info = if self.read_u8()? == 1 {
277 let immutable_borrows = self.read_u32()? as usize;
278 let mutable_borrows = self.read_u32()? as usize;
279 let max_concurrent_borrows = self.read_u32()? as usize;
280 let last_borrow_timestamp = if self.read_u8()? == 1 {
281 Some(self.read_u64()?)
282 } else {
283 None
284 };
285 Some(crate::core::types::BorrowInfo {
286 immutable_borrows,
287 mutable_borrows,
288 max_concurrent_borrows,
289 last_borrow_timestamp,
290 })
291 } else {
292 None
293 };
294
295 let clone_info = if self.read_u8()? == 1 {
297 let clone_count = self.read_u32()? as usize;
298 let is_clone = self.read_u8()? != 0;
299 let original_ptr = if self.read_u8()? == 1 {
300 Some(self.read_u64()? as usize)
301 } else {
302 None
303 };
304 Some(crate::core::types::CloneInfo {
305 clone_count,
306 is_clone,
307 original_ptr,
308 })
309 } else {
310 None
311 };
312
313 let ownership_history_available = self.read_u8()? != 0;
315
316 let smart_pointer_info = self.read_optional_binary_field()?;
318 let memory_layout = self.read_optional_binary_field()?;
319
320 let generic_info = self.read_optional_json_field()?;
321 let dynamic_type_info = self.read_optional_json_field()?;
322 let runtime_state = self.read_optional_json_field()?;
323 let stack_allocation = self.read_optional_json_field()?;
324 let temporary_object = self.read_optional_json_field()?;
325 let fragmentation_analysis = self.read_optional_json_field()?;
326 let generic_instantiation = self.read_optional_json_field()?;
327 let type_relationships = self.read_optional_json_field()?;
328 let type_usage = self.read_optional_json_field()?;
329 let function_call_tracking = self.read_optional_json_field()?;
330 let lifecycle_tracking = self.read_optional_json_field()?;
331 let access_tracking = self.read_optional_json_field()?;
332
333 Ok(AllocationInfo {
334 ptr,
335 size,
336 var_name,
337 type_name,
338 scope_name,
339 timestamp_alloc,
340 timestamp_dealloc,
341 thread_id,
342 borrow_count,
343 stack_trace,
344 is_leaked,
345 lifetime_ms,
346 borrow_info,
347 clone_info,
348 ownership_history_available,
349 smart_pointer_info,
350 memory_layout,
351 generic_info,
352 dynamic_type_info,
353 runtime_state,
354 stack_allocation,
355 temporary_object,
356 fragmentation_analysis,
357 generic_instantiation,
358 type_relationships,
359 type_usage,
360 function_call_tracking,
361 lifecycle_tracking,
362 access_tracking,
363 drop_chain_analysis: None,
364 })
365 }
366
367 pub fn read_all(&mut self) -> Result<Vec<AllocationInfo>, BinaryExportError> {
369 let header = self.read_header()?;
370 let mut allocations = Vec::with_capacity(header.total_count as usize);
371
372 for i in 0..header.total_count {
374 match self.read_allocation() {
375 Ok(allocation) => allocations.push(allocation),
376 Err(BinaryExportError::Io(ref e))
377 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
378 {
379 tracing::warn!(
381 "Reached end of file after reading {} of {} allocations",
382 i,
383 header.total_count
384 );
385 break;
386 }
387 Err(e) => return Err(e),
388 }
389 }
390
391 if self.try_read_advanced_metrics_segment().is_err() {
393 tracing::debug!("No advanced metrics segment found or failed to read");
395 }
396
397 tracing::info!("Successfully read {} allocation records", allocations.len());
398 Ok(allocations)
399 }
400
401 fn try_read_advanced_metrics_segment(&mut self) -> Result<(), BinaryExportError> {
403 let mut header_bytes = [0u8; 16];
405 let mut bytes_read = 0;
406
407 while bytes_read < 16 {
409 match self.reader.read(&mut header_bytes[bytes_read..]) {
410 Ok(0) => {
411 if bytes_read == 0 {
413 return Err(BinaryExportError::CorruptedData(
415 "No advanced metrics segment".to_string(),
416 ));
417 } else {
418 tracing::warn!(
420 "File appears to be truncated, only read {} of 16 header bytes",
421 bytes_read
422 );
423 return Err(BinaryExportError::CorruptedData(
424 "Truncated advanced metrics header".to_string(),
425 ));
426 }
427 }
428 Ok(n) => bytes_read += n,
429 Err(e) => {
430 tracing::debug!("Failed to read advanced metrics header: {}", e);
431 return Err(BinaryExportError::Io(e));
432 }
433 }
434 }
435
436 let header = AdvancedMetricsHeader::from_bytes(&header_bytes);
437
438 if header.is_valid_magic() {
439 self.read_advanced_metrics_data(header)?;
441 } else {
442 if let Err(e) = self.reader.seek(SeekFrom::Current(-16)) {
444 tracing::debug!("Failed to seek back: {}", e);
445 }
446 return Err(BinaryExportError::CorruptedData(
447 "Invalid advanced metrics magic".to_string(),
448 ));
449 }
450
451 Ok(())
452 }
453
454 fn read_advanced_metrics_data(
456 &mut self,
457 header: AdvancedMetricsHeader,
458 ) -> Result<(), BinaryExportError> {
459 let mut lifecycle_metrics = HashMap::new();
460 let mut container_metrics = HashMap::new();
461 let mut type_usage_metrics = HashMap::new();
462
463 if MetricsBitmapFlags::is_enabled(
465 header.metrics_bitmap,
466 MetricsBitmapFlags::LifecycleAnalysis,
467 ) {
468 lifecycle_metrics = self.read_lifecycle_metrics()?;
469 }
470
471 if MetricsBitmapFlags::is_enabled(
473 header.metrics_bitmap,
474 MetricsBitmapFlags::ContainerAnalysis,
475 ) {
476 container_metrics = self.read_container_metrics()?;
477 }
478
479 if MetricsBitmapFlags::is_enabled(header.metrics_bitmap, MetricsBitmapFlags::TypeUsageStats)
481 {
482 type_usage_metrics = self.read_type_usage_metrics()?;
483 }
484
485 self.advanced_metrics = Some(AdvancedMetricsData {
487 lifecycle_metrics,
488 container_metrics,
489 type_usage_metrics,
490 });
491
492 Ok(())
493 }
494
495 fn read_lifecycle_metrics(
497 &mut self,
498 ) -> Result<HashMap<u64, LifecycleMetric>, BinaryExportError> {
499 let count = self.read_u32()? as usize;
500 let mut metrics = HashMap::with_capacity(count);
501
502 for _ in 0..count {
503 let ptr = self.read_u64()?;
504 let lifetime_ms = self.read_u64()?;
505
506 let lifecycle_tracking = if self.read_u8()? == 1 {
507 Some(self.read_string()?)
508 } else {
509 None
510 };
511
512 metrics.insert(
513 ptr,
514 LifecycleMetric {
515 lifetime_ms,
516 lifecycle_tracking,
517 },
518 );
519 }
520
521 Ok(metrics)
522 }
523
524 fn read_container_metrics(&mut self) -> Result<HashMap<u64, String>, BinaryExportError> {
526 let count = self.read_u32()? as usize;
527 let mut metrics = HashMap::with_capacity(count);
528
529 for _ in 0..count {
530 let ptr = self.read_u64()?;
531 let json_data = self.read_string()?;
532 metrics.insert(ptr, json_data);
533 }
534
535 Ok(metrics)
536 }
537
538 fn read_type_usage_metrics(&mut self) -> Result<HashMap<u64, String>, BinaryExportError> {
540 let count = self.read_u32()? as usize;
541 let mut metrics = HashMap::with_capacity(count);
542
543 for _ in 0..count {
544 let ptr = self.read_u64()?;
545 let json_data = self.read_string()?;
546 metrics.insert(ptr, json_data);
547 }
548
549 Ok(metrics)
550 }
551
552 pub fn get_advanced_metrics(&self) -> Option<&AdvancedMetricsData> {
554 self.advanced_metrics.as_ref()
555 }
556
557 fn read_u64(&mut self) -> Result<u64, BinaryExportError> {
559 let mut bytes = [0u8; 8];
560 self.reader.read_exact(&mut bytes)?;
561 Ok(u64::from_le_bytes(bytes))
562 }
563
564 fn read_u32(&mut self) -> Result<u32, BinaryExportError> {
566 let mut bytes = [0u8; 4];
567 self.reader.read_exact(&mut bytes)?;
568 Ok(u32::from_le_bytes(bytes))
569 }
570
571 fn read_optional_string(&mut self) -> Result<Option<String>, BinaryExportError> {
573 let mut length_bytes = [0u8; 4];
574 self.reader.read_exact(&mut length_bytes)?;
575 let length = u32::from_le_bytes(length_bytes);
576
577 if length == 0xFFFFFFFE {
578 Ok(None)
580 } else if length == 0xFFFF {
581 let index = primitives::read_u16(&mut self.reader)?;
583 if let Some(ref string_table) = self.string_table {
584 if let Some(string) = string_table.get_string(index) {
585 Ok(Some(string.to_string()))
586 } else {
587 Err(BinaryExportError::CorruptedData(format!(
588 "Invalid string table index: {index}",
589 )))
590 }
591 } else {
592 Err(BinaryExportError::CorruptedData(
593 "String table reference found but no string table loaded".to_string(),
594 ))
595 }
596 } else {
597 let mut string_bytes = vec![0u8; length as usize];
599 self.reader.read_exact(&mut string_bytes)?;
600
601 let string = String::from_utf8(string_bytes).map_err(|_| {
602 BinaryExportError::CorruptedData("Invalid UTF-8 string".to_string())
603 })?;
604
605 Ok(Some(string))
606 }
607 }
608
609 fn read_string(&mut self) -> Result<String, BinaryExportError> {
611 let mut length_bytes = [0u8; 4];
612 self.reader.read_exact(&mut length_bytes)?;
613 let length = u32::from_le_bytes(length_bytes);
614
615 if length == 0xFFFF {
616 let index = primitives::read_u16(&mut self.reader)?;
618 if let Some(ref string_table) = self.string_table {
619 if let Some(string) = string_table.get_string(index) {
620 Ok(string.to_string())
621 } else {
622 Err(BinaryExportError::CorruptedData(format!(
623 "Invalid string table index: {index}",
624 )))
625 }
626 } else {
627 Err(BinaryExportError::CorruptedData(
628 "String table reference found but no string table loaded".to_string(),
629 ))
630 }
631 } else {
632 let mut string_bytes = vec![0u8; length as usize];
634 self.reader.read_exact(&mut string_bytes)?;
635
636 String::from_utf8(string_bytes)
637 .map_err(|_| BinaryExportError::CorruptedData("Invalid UTF-8 string".to_string()))
638 }
639 }
640
641 fn read_optional_string_vec(&mut self) -> Result<Option<Vec<String>>, BinaryExportError> {
643 let mut count_bytes = [0u8; 4];
644 self.reader.read_exact(&mut count_bytes)?;
645 let count = u32::from_le_bytes(count_bytes) as usize;
646
647 if count == 0 {
648 Ok(None)
649 } else {
650 let mut strings = Vec::with_capacity(count);
651 for _ in 0..count {
652 strings.push(self.read_string()?);
653 }
654 Ok(Some(strings))
655 }
656 }
657
658 fn read_u8(&mut self) -> Result<u8, BinaryExportError> {
660 let mut buffer = [0u8; 1];
661 self.reader.read_exact(&mut buffer)?;
662 Ok(buffer[0])
663 }
664
665 fn read_optional_binary_field<T: BinarySerializable>(
667 &mut self,
668 ) -> Result<Option<T>, BinaryExportError> {
669 if self.read_u8()? == 1 {
670 Ok(Some(T::read_binary(&mut self.reader)?))
671 } else {
672 Ok(None)
673 }
674 }
675
676 fn read_optional_json_field<T: serde::de::DeserializeOwned>(
678 &mut self,
679 ) -> Result<Option<T>, BinaryExportError> {
680 if self.read_u8()? == 1 {
681 let json_str = self.read_string()?;
682 let value = serde_json::from_str(&json_str).map_err(|e| {
683 BinaryExportError::CorruptedData(format!("JSON deserialization failed: {e}"))
684 })?;
685 Ok(Some(value))
686 } else {
687 Ok(None)
688 }
689 }
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use crate::export::binary::writer::BinaryWriter;
696 use tempfile::NamedTempFile;
697
698 fn create_test_allocation() -> AllocationInfo {
699 AllocationInfo {
700 ptr: 0x1000,
701 size: 1024,
702 var_name: Some("test_var".to_string()),
703 type_name: Some("i32".to_string()),
704 scope_name: None,
705 timestamp_alloc: 1234567890,
706 timestamp_dealloc: None,
707 thread_id: "main".to_string(),
708 borrow_count: 0,
709 stack_trace: None,
710 is_leaked: false,
711 lifetime_ms: None,
712 borrow_info: None,
713 clone_info: None,
714 ownership_history_available: false,
715 smart_pointer_info: None,
716 memory_layout: None,
717 generic_info: None,
718 dynamic_type_info: None,
719 runtime_state: None,
720 stack_allocation: None,
721 temporary_object: None,
722 fragmentation_analysis: None,
723 generic_instantiation: None,
724 type_relationships: None,
725 type_usage: None,
726 function_call_tracking: None,
727 lifecycle_tracking: None,
728 access_tracking: None,
729 drop_chain_analysis: None,
730 }
731 }
732
733 #[test]
734 fn test_reader_creation() {
735 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
736
737 {
739 let mut writer =
740 BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
741 writer.write_header(0).expect("Failed to write header");
742 writer.finish().expect("Failed to finish writing");
743 }
744
745 let reader = BinaryReader::new(temp_file.path());
746 assert!(reader.is_ok());
747 }
748
749 #[test]
750 fn test_header_reading() {
751 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
752
753 {
755 let mut writer =
756 BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
757 writer.write_header(42).expect("Failed to write header");
758 writer.finish().expect("Failed to finish writing");
759 }
760
761 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
763 let header = reader
764 .read_header()
765 .expect("Failed to read from binary file");
766
767 assert_eq!(header.total_count, 42);
768 assert!(header.is_valid_magic());
769 assert!(header.is_compatible_version());
770 }
771
772 #[test]
773 fn test_allocation_round_trip() {
774 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
775 let original_alloc = create_test_allocation();
776
777 {
779 let mut writer =
780 BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
781 writer.write_header(1).expect("Failed to write header");
782 writer
783 .write_allocation(&original_alloc)
784 .expect("Failed to write allocation");
785 writer.finish().expect("Failed to finish writing");
786 }
787
788 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
790 let allocations = reader.read_all().expect("Failed to read from binary file");
791
792 assert_eq!(allocations.len(), 1);
793 let read_alloc = &allocations[0];
794
795 assert_eq!(read_alloc.ptr, original_alloc.ptr);
796 assert_eq!(read_alloc.size, original_alloc.size);
797 assert_eq!(read_alloc.timestamp_alloc, original_alloc.timestamp_alloc);
798 assert_eq!(read_alloc.var_name, original_alloc.var_name);
799 assert_eq!(read_alloc.type_name, original_alloc.type_name);
800 assert_eq!(read_alloc.thread_id, original_alloc.thread_id);
801 }
802
803 #[test]
804 fn test_advanced_metrics_round_trip() {
805 use crate::export::binary::config::BinaryExportConfig;
806
807 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
808 let mut original_alloc = create_test_allocation();
809 original_alloc.lifetime_ms = Some(2500); {
813 let config = BinaryExportConfig::debug_comprehensive();
814 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
815 .expect("Failed to create temp file");
816 writer.write_header(1).expect("Failed to write header");
817 writer
818 .write_allocation(&original_alloc)
819 .expect("Failed to write allocation");
820 writer
821 .write_advanced_metrics_segment(&[original_alloc.clone()])
822 .expect("Test operation failed");
823 writer.finish().expect("Failed to finish writing");
824 }
825
826 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
828 let allocations = reader.read_all().expect("Failed to read from binary file");
829
830 assert_eq!(allocations.len(), 1);
831 let read_alloc = &allocations[0];
832
833 assert_eq!(read_alloc.ptr, original_alloc.ptr);
835 assert_eq!(read_alloc.size, original_alloc.size);
836 assert_eq!(read_alloc.lifetime_ms, original_alloc.lifetime_ms);
837
838 let advanced_metrics = reader.get_advanced_metrics();
840 assert!(advanced_metrics.is_some());
841
842 let metrics = advanced_metrics.expect("Failed to get test value");
843 assert!(metrics
844 .lifecycle_metrics
845 .contains_key(&(original_alloc.ptr as u64)));
846
847 let lifecycle_metric = &metrics.lifecycle_metrics[&(original_alloc.ptr as u64)];
848 assert_eq!(lifecycle_metric.lifetime_ms, 2500);
849 }
850
851 #[test]
852 fn test_backward_compatibility() {
853 use crate::export::binary::config::BinaryExportConfig;
854
855 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
856 let original_alloc = create_test_allocation();
857
858 {
860 let config = BinaryExportConfig::minimal();
861 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
862 .expect("Failed to create temp file");
863 writer.write_header(1).expect("Failed to write header");
864 writer
865 .write_allocation(&original_alloc)
866 .expect("Failed to write allocation");
867 writer.finish().expect("Failed to finish writing");
868 }
869
870 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
872 let allocations = reader.read_all().expect("Failed to read from binary file");
873
874 assert_eq!(allocations.len(), 1);
875 let read_alloc = &allocations[0];
876
877 assert_eq!(read_alloc.ptr, original_alloc.ptr);
879 assert_eq!(read_alloc.size, original_alloc.size);
880
881 let advanced_metrics = reader.get_advanced_metrics();
883 assert!(advanced_metrics.is_none());
884 }
885
886 #[test]
887 fn test_binary_reader_error_handling() {
888 let result = BinaryReader::new("non_existent_file.bin");
890 assert!(result.is_err());
891
892 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
894 let result = BinaryReader::new(temp_file.path());
895 if let Ok(mut reader) = result {
898 let read_result = reader.read_all();
899 assert!(read_result.is_err()); } else {
901 assert!(result.is_err()); }
903
904 {
906 let mut file = File::create(temp_file.path()).expect("Failed to create file");
907 file.write_all(b"INVALID_HEADER")
908 .expect("Failed to write invalid header");
909 }
910 let result = BinaryReader::new(temp_file.path());
911 if let Ok(mut reader) = result {
913 let read_result = reader.read_all();
914 assert!(
915 read_result.is_err(),
916 "Reading with invalid header should fail"
917 );
918 } else {
919 assert!(result.is_err(), "Creation with invalid header should fail");
920 }
921 }
922
923 #[test]
924 fn test_binary_reader_large_dataset() {
925 use crate::export::binary::config::BinaryExportConfig;
926
927 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
928 let mut allocations = Vec::new();
929
930 for i in 0..1000 {
932 let mut alloc = create_test_allocation();
933 alloc.ptr = 0x1000 + i * 0x100;
934 alloc.size = 64 + (i % 100);
935 alloc.timestamp_alloc = 1000000 + i as u64;
936 allocations.push(alloc);
937 }
938
939 {
941 let config = BinaryExportConfig::default();
942 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
943 .expect("Failed to create writer");
944 writer
945 .write_header(allocations.len() as u32)
946 .expect("Failed to write header");
947
948 for alloc in &allocations {
949 writer
950 .write_allocation(alloc)
951 .expect("Failed to write allocation");
952 }
953 writer.finish().expect("Failed to finish writing");
954 }
955
956 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
958 let read_allocations = reader.read_all().expect("Failed to read allocations");
959
960 assert_eq!(read_allocations.len(), 1000);
961
962 assert_eq!(read_allocations[0].ptr, 0x1000);
964 assert_eq!(read_allocations[999].ptr, 0x1000 + 999 * 0x100);
965 assert_eq!(read_allocations[0].timestamp_alloc, 1000000);
966 assert_eq!(read_allocations[999].timestamp_alloc, 1000000 + 999);
967 }
968
969 #[test]
970 fn test_binary_reader_chunked_reading() {
971 use crate::export::binary::config::BinaryExportConfig;
972
973 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
974 let mut allocations = Vec::new();
975
976 for i in 0..100 {
978 let mut alloc = create_test_allocation();
979 alloc.ptr = 0x2000 + i * 0x50;
980 alloc.size = 32 + i;
981 allocations.push(alloc);
982 }
983
984 {
986 let config = BinaryExportConfig::default();
987 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
988 .expect("Failed to create writer");
989 writer
990 .write_header(allocations.len() as u32)
991 .expect("Failed to write header");
992
993 for alloc in &allocations {
994 writer
995 .write_allocation(alloc)
996 .expect("Failed to write allocation");
997 }
998 writer.finish().expect("Failed to finish writing");
999 }
1000
1001 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1003
1004 let all_read = reader.read_all().expect("Failed to read allocations");
1006
1007 assert_eq!(all_read.len(), 100);
1008 assert_eq!(all_read[0].ptr, 0x2000);
1009 assert_eq!(all_read[99].ptr, 0x2000 + 99 * 0x50);
1010 }
1011
1012 #[test]
1013 fn test_binary_reader_metadata() {
1014 use crate::export::binary::config::BinaryExportConfig;
1015
1016 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1017 let alloc = create_test_allocation();
1018
1019 {
1021 let config = BinaryExportConfig::debug_comprehensive();
1022 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1023 .expect("Failed to create writer");
1024 writer.write_header(1).expect("Failed to write header");
1025 writer
1026 .write_allocation(&alloc)
1027 .expect("Failed to write allocation");
1028 writer.finish().expect("Failed to finish writing");
1029 }
1030
1031 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1033
1034 let allocations = reader.read_all().expect("Failed to read allocations");
1036 assert_eq!(allocations.len(), 1);
1037 assert_eq!(allocations[0].ptr, alloc.ptr);
1038 assert_eq!(allocations[0].size, alloc.size);
1039 }
1040
1041 #[test]
1042 fn test_binary_reader_corrupted_data() {
1043 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1044
1045 {
1047 let mut file = File::create(temp_file.path()).expect("Failed to create file");
1048
1049 file.write_all(b"MEMSCOPE").expect("Failed to write magic");
1051 file.write_all(&1u32.to_le_bytes())
1052 .expect("Failed to write version");
1053 file.write_all(&1u32.to_le_bytes())
1054 .expect("Failed to write count");
1055
1056 file.write_all(&[0xFF; 100])
1058 .expect("Failed to write corrupted data");
1059 }
1060
1061 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1062 let result = reader.read_all();
1063
1064 assert!(result.is_err() || result.unwrap().is_empty());
1066 }
1067
1068 #[test]
1069 fn test_binary_reader_empty_dataset() {
1070 use crate::export::binary::config::BinaryExportConfig;
1071
1072 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1073
1074 {
1076 let config = BinaryExportConfig::minimal();
1077 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1078 .expect("Failed to create writer");
1079 writer.write_header(0).expect("Failed to write header");
1080 writer.finish().expect("Failed to finish writing");
1081 }
1082
1083 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1085 let allocations = reader.read_all().expect("Failed to read allocations");
1086
1087 assert_eq!(allocations.len(), 0);
1088 assert_eq!(allocations.len(), 0);
1090 }
1091
1092 #[test]
1093 fn test_binary_reader_seek_operations() {
1094 use crate::export::binary::config::BinaryExportConfig;
1095
1096 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1097 let mut allocations = Vec::new();
1098
1099 for i in 0..50 {
1101 let mut alloc = create_test_allocation();
1102 alloc.ptr = 0x3000 + i * 0x100;
1103 alloc.timestamp_alloc = 2000000 + i as u64;
1104 allocations.push(alloc);
1105 }
1106
1107 {
1109 let config = BinaryExportConfig::default();
1110 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1111 .expect("Failed to create writer");
1112 writer
1113 .write_header(allocations.len() as u32)
1114 .expect("Failed to write header");
1115
1116 for alloc in &allocations {
1117 writer
1118 .write_allocation(alloc)
1119 .expect("Failed to write allocation");
1120 }
1121 writer.finish().expect("Failed to finish writing");
1122 }
1123
1124 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1126
1127 let all_allocations = reader.read_all().expect("Failed to read allocations");
1128 assert_eq!(all_allocations.len(), 50);
1129 assert_eq!(all_allocations[0].ptr, 0x3000);
1130 assert_eq!(all_allocations[49].ptr, 0x3000 + 49 * 0x100);
1131 }
1132
1133 #[test]
1134 fn test_binary_reader_statistics() {
1135 use crate::export::binary::config::BinaryExportConfig;
1136
1137 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1138 let mut allocations = Vec::new();
1139
1140 for i in 0..20 {
1142 let mut alloc = create_test_allocation();
1143 alloc.ptr = 0x4000 + i * 0x200;
1144 alloc.size = if i % 2 == 0 { 64 } else { 128 };
1145 alloc.timestamp_alloc = 3000000 + (i * 1000) as u64;
1146 alloc.borrow_count = i % 5;
1147 allocations.push(alloc);
1148 }
1149
1150 {
1152 let config = BinaryExportConfig::debug_comprehensive();
1153 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1154 .expect("Failed to create writer");
1155 writer
1156 .write_header(allocations.len() as u32)
1157 .expect("Failed to write header");
1158
1159 for alloc in &allocations {
1160 writer
1161 .write_allocation(alloc)
1162 .expect("Failed to write allocation");
1163 }
1164 writer.finish().expect("Failed to finish writing");
1165 }
1166
1167 let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1169 let read_allocations = reader.read_all().expect("Failed to read allocations");
1170
1171 assert_eq!(read_allocations.len(), 20);
1173
1174 let total_size: usize = read_allocations.iter().map(|a| a.size).sum();
1175 let expected_total: usize = (0..20).map(|i| if i % 2 == 0 { 64 } else { 128 }).sum();
1176 assert_eq!(total_size, expected_total);
1177
1178 let max_borrow_count = read_allocations
1179 .iter()
1180 .map(|a| a.borrow_count)
1181 .max()
1182 .unwrap();
1183 assert_eq!(max_borrow_count, 4); let min_timestamp = read_allocations
1186 .iter()
1187 .map(|a| a.timestamp_alloc)
1188 .min()
1189 .unwrap();
1190 assert_eq!(min_timestamp, 3000000);
1191 }
1192
1193 #[test]
1194 fn test_binary_reader_concurrent_access() {
1195 use crate::export::binary::config::BinaryExportConfig;
1196 use std::sync::Arc;
1197 use std::thread;
1198
1199 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1200 let mut allocations = Vec::new();
1201
1202 for i in 0..100 {
1204 let mut alloc = create_test_allocation();
1205 alloc.ptr = 0x5000 + i * 0x50;
1206 alloc.size = 32 + (i % 50);
1207 allocations.push(alloc);
1208 }
1209
1210 {
1212 let config = BinaryExportConfig::default();
1213 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1214 .expect("Failed to create writer");
1215 writer
1216 .write_header(allocations.len() as u32)
1217 .expect("Failed to write header");
1218
1219 for alloc in &allocations {
1220 writer
1221 .write_allocation(alloc)
1222 .expect("Failed to write allocation");
1223 }
1224 writer.finish().expect("Failed to finish writing");
1225 }
1226
1227 let file_path = Arc::new(temp_file.path().to_path_buf());
1229 let mut handles = vec![];
1230
1231 for thread_id in 0..5 {
1232 let path_clone = file_path.clone();
1233 let handle = thread::spawn(move || {
1234 let mut reader =
1235 BinaryReader::new(path_clone.as_ref()).expect("Failed to create reader");
1236 let chunk_size = 20;
1237 let _start_index = thread_id * chunk_size;
1238
1239 let all_allocations = reader.read_all().expect("Failed to read allocations");
1241 (thread_id, all_allocations.len())
1242 });
1243 handles.push(handle);
1244 }
1245
1246 for handle in handles {
1248 let (thread_id, allocation_count) = handle.join().expect("Thread should complete");
1249 assert_eq!(
1250 allocation_count, 100,
1251 "Thread {} should read all allocations",
1252 thread_id
1253 );
1254 }
1255 }
1256}