memscope_rs/export/binary/
writer.rs1use crate::core::types::AllocationInfo;
4use crate::export::binary::config::BinaryExportConfig;
5use crate::export::binary::error::BinaryExportError;
6use crate::export::binary::format::{
7 AdvancedMetricsHeader, BinaryExportMode, FileHeader, MetricsBitmapFlags, ALLOCATION_RECORD_TYPE,
8};
9use crate::export::binary::serializable::BinarySerializable;
10use crate::export::binary::string_table::{StringTable, StringTableBuilder};
11use std::fs::File;
12use std::io::{BufWriter, Write};
13use std::path::Path;
14
15pub struct BinaryWriter {
17 writer: BufWriter<File>,
18 config: BinaryExportConfig,
19 string_table: Option<StringTable>,
20}
21
22impl BinaryWriter {
23 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, BinaryExportError> {
25 Self::new_with_config(path, &BinaryExportConfig::default())
26 }
27
28 pub fn new_with_config<P: AsRef<Path>>(
30 path: P,
31 config: &BinaryExportConfig,
32 ) -> Result<Self, BinaryExportError> {
33 let file = File::create(path)?;
34 let writer = BufWriter::new(file);
35
36 Ok(Self {
37 writer,
38 config: config.clone(),
39 string_table: None,
40 })
41 }
42
43 pub fn build_string_table(
45 &mut self,
46 allocations: &[AllocationInfo],
47 ) -> Result<(), BinaryExportError> {
48 if !self.config.string_table_optimization {
49 return Ok(()); }
51
52 let min_frequency = if allocations.len() > 1000 { 3 } else { 2 };
54 let mut builder = StringTableBuilder::new(min_frequency);
55
56 for alloc in allocations {
58 if let Some(ref type_name) = alloc.type_name {
60 builder.record_string(type_name);
61 }
62 if let Some(ref var_name) = alloc.var_name {
63 builder.record_string(var_name);
64 }
65 if let Some(ref scope_name) = alloc.scope_name {
66 builder.record_string(scope_name);
67 }
68 builder.record_string(&alloc.thread_id);
69
70 if let Some(ref stack_trace) = alloc.stack_trace {
72 for frame in stack_trace {
73 builder.record_string(frame);
74 }
75 }
76 }
77
78 let table = builder.build()?;
79 let stats = table.compression_stats();
80
81 if stats.space_saved() > 0 && !table.is_empty() {
83 tracing::debug!(
84 "String table built: {} strings, {:.1}% space savings",
85 table.len(),
86 stats.space_saved_percent()
87 );
88 self.string_table = Some(table);
89 }
90
91 Ok(())
92 }
93
94 pub fn write_header(&mut self, count: u32) -> Result<(), BinaryExportError> {
96 let header = FileHeader::new_legacy(count);
97 let header_bytes = header.to_bytes();
98
99 self.writer.write_all(&header_bytes)?;
100
101 self.write_string_table_if_present()
102 }
103
104 pub fn write_enhanced_header(
106 &mut self,
107 total_count: u32,
108 export_mode: BinaryExportMode,
109 user_count: u16,
110 system_count: u16,
111 ) -> Result<(), BinaryExportError> {
112 let header = FileHeader::new(total_count, export_mode, user_count, system_count);
113 let header_bytes = header.to_bytes();
114
115 self.writer.write_all(&header_bytes)?;
116
117 self.write_string_table_if_present()
118 }
119
120 fn write_string_table_if_present(&mut self) -> Result<(), BinaryExportError> {
122 if let Some(ref string_table) = self.string_table {
124 self.writer.write_all(b"STBL")?; let table_size = string_table.serialized_size() as u32;
127 self.writer.write_all(&table_size.to_le_bytes())?;
128
129 string_table.write_binary(&mut self.writer)?;
131 } else {
132 self.writer.write_all(b"NONE")?; self.writer.write_all(&0u32.to_le_bytes())?; }
136
137 Ok(())
138 }
139
140 pub fn write_allocation(&mut self, alloc: &AllocationInfo) -> Result<(), BinaryExportError> {
142 let value_size = self.calculate_value_size(alloc);
144
145 self.writer.write_all(&[ALLOCATION_RECORD_TYPE])?;
147
148 self.writer.write_all(&(value_size as u32).to_le_bytes())?;
150
151 self.writer.write_all(&(alloc.ptr as u64).to_le_bytes())?;
153 self.writer.write_all(&(alloc.size as u64).to_le_bytes())?;
154 self.writer
155 .write_all(&alloc.timestamp_alloc.to_le_bytes())?;
156
157 match alloc.timestamp_dealloc {
159 Some(ts) => {
160 self.writer.write_all(&1u8.to_le_bytes())?; self.writer.write_all(&ts.to_le_bytes())?;
162 }
163 None => {
164 self.writer.write_all(&0u8.to_le_bytes())?; }
166 }
167
168 self.write_optional_string(&alloc.var_name)?;
170 self.write_optional_string(&alloc.type_name)?;
171 self.write_optional_string(&alloc.scope_name)?;
172 self.write_string(&alloc.thread_id)?;
173
174 self.write_optional_string_vec(&alloc.stack_trace)?;
176
177 self.writer
179 .write_all(&(alloc.borrow_count as u32).to_le_bytes())?;
180 self.writer
181 .write_all(&(alloc.is_leaked as u8).to_le_bytes())?;
182
183 match alloc.lifetime_ms {
185 Some(ms) => {
186 self.writer.write_all(&1u8.to_le_bytes())?; self.writer.write_all(&ms.to_le_bytes())?;
188 }
189 None => {
190 self.writer.write_all(&0u8.to_le_bytes())?; }
192 }
193
194 match &alloc.borrow_info {
196 Some(borrow_info) => {
197 self.writer.write_all(&1u8.to_le_bytes())?; self.writer
199 .write_all(&(borrow_info.immutable_borrows as u32).to_le_bytes())?;
200 self.writer
201 .write_all(&(borrow_info.mutable_borrows as u32).to_le_bytes())?;
202 self.writer
203 .write_all(&(borrow_info.max_concurrent_borrows as u32).to_le_bytes())?;
204 match borrow_info.last_borrow_timestamp {
205 Some(ts) => {
206 self.writer.write_all(&1u8.to_le_bytes())?; self.writer.write_all(&ts.to_le_bytes())?;
208 }
209 None => {
210 self.writer.write_all(&0u8.to_le_bytes())?; }
212 }
213 }
214 None => {
215 self.writer.write_all(&0u8.to_le_bytes())?; }
217 }
218
219 match &alloc.clone_info {
221 Some(clone_info) => {
222 self.writer.write_all(&1u8.to_le_bytes())?; self.writer
224 .write_all(&(clone_info.clone_count as u32).to_le_bytes())?;
225 self.writer
226 .write_all(&(clone_info.is_clone as u8).to_le_bytes())?;
227 match clone_info.original_ptr {
228 Some(ptr) => {
229 self.writer.write_all(&1u8.to_le_bytes())?; self.writer.write_all(&(ptr as u64).to_le_bytes())?;
231 }
232 None => {
233 self.writer.write_all(&0u8.to_le_bytes())?; }
235 }
236 }
237 None => {
238 self.writer.write_all(&0u8.to_le_bytes())?; }
240 }
241
242 self.writer
244 .write_all(&(alloc.ownership_history_available as u8).to_le_bytes())?;
245
246 self.write_optional_binary_field(&alloc.smart_pointer_info)?;
248 self.write_optional_binary_field(&alloc.memory_layout)?;
249 self.write_optional_json_field(&alloc.generic_info)?;
250 self.write_optional_json_field(&alloc.dynamic_type_info)?;
251 self.write_optional_json_field(&alloc.runtime_state)?;
252 self.write_optional_json_field(&alloc.stack_allocation)?;
253 self.write_optional_json_field(&alloc.temporary_object)?;
254 self.write_optional_json_field(&alloc.fragmentation_analysis)?;
255 self.write_optional_json_field(&alloc.generic_instantiation)?;
256 self.write_optional_json_field(&alloc.type_relationships)?;
257 self.write_optional_json_field(&alloc.type_usage)?;
258 self.write_optional_json_field(&alloc.function_call_tracking)?;
259 self.write_optional_json_field(&alloc.lifecycle_tracking)?;
260 self.write_optional_json_field(&alloc.access_tracking)?;
261
262 Ok(())
263 }
264
265 pub fn write_advanced_metrics_segment(
267 &mut self,
268 allocations: &[AllocationInfo],
269 ) -> Result<(), BinaryExportError> {
270 if !self.config.has_advanced_metrics() {
271 return Ok(()); }
273
274 let mut metrics_bitmap = 0u32;
276
277 if self.config.lifecycle_timeline {
278 metrics_bitmap =
279 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::LifecycleAnalysis);
280 }
281 if self.config.container_analysis {
282 metrics_bitmap =
283 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::ContainerAnalysis);
284 }
285 if self.config.source_analysis {
286 metrics_bitmap =
287 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::SourceAnalysis);
288 }
289 if self.config.fragmentation_analysis {
290 metrics_bitmap = MetricsBitmapFlags::enable(
291 metrics_bitmap,
292 MetricsBitmapFlags::FragmentationAnalysis,
293 );
294 }
295 if self.config.thread_context_tracking {
296 metrics_bitmap =
297 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::ThreadContext);
298 }
299 if self.config.drop_chain_analysis {
300 metrics_bitmap =
301 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::DropChainAnalysis);
302 }
303 if self.config.zst_analysis {
304 metrics_bitmap =
305 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::ZstAnalysis);
306 }
307 if self.config.health_scoring {
308 metrics_bitmap =
309 MetricsBitmapFlags::enable(metrics_bitmap, MetricsBitmapFlags::HealthScoring);
310 }
311 if self.config.performance_benchmarking {
312 metrics_bitmap = MetricsBitmapFlags::enable(
313 metrics_bitmap,
314 MetricsBitmapFlags::PerformanceBenchmarks,
315 );
316 }
317
318 let data_size = self.calculate_advanced_metrics_data_size(allocations, metrics_bitmap);
320 let segment_size = 16 + data_size; let header = AdvancedMetricsHeader::new(segment_size as u32, metrics_bitmap);
324 let header_bytes = header.to_bytes();
325 self.writer.write_all(&header_bytes)?;
326
327 self.write_metrics_data(allocations, metrics_bitmap)?;
329
330 Ok(())
331 }
332
333 fn calculate_advanced_metrics_data_size(
335 &self,
336 allocations: &[AllocationInfo],
337 metrics_bitmap: u32,
338 ) -> usize {
339 let mut size = 0;
340
341 if MetricsBitmapFlags::is_enabled(metrics_bitmap, MetricsBitmapFlags::LifecycleAnalysis) {
343 size += self.calculate_lifecycle_data_size(allocations);
344 }
345 if MetricsBitmapFlags::is_enabled(metrics_bitmap, MetricsBitmapFlags::ContainerAnalysis) {
346 size += self.calculate_container_data_size(allocations);
347 }
348 if MetricsBitmapFlags::is_enabled(metrics_bitmap, MetricsBitmapFlags::TypeUsageStats) {
349 size += self.calculate_type_usage_data_size(allocations);
350 }
351 size
354 }
355
356 fn write_metrics_data(
358 &mut self,
359 allocations: &[AllocationInfo],
360 metrics_bitmap: u32,
361 ) -> Result<(), BinaryExportError> {
362 if MetricsBitmapFlags::is_enabled(metrics_bitmap, MetricsBitmapFlags::LifecycleAnalysis) {
364 self.write_lifecycle_metrics(allocations)?;
365 }
366
367 if MetricsBitmapFlags::is_enabled(metrics_bitmap, MetricsBitmapFlags::ContainerAnalysis) {
369 self.write_container_metrics(allocations)?;
370 }
371
372 if MetricsBitmapFlags::is_enabled(metrics_bitmap, MetricsBitmapFlags::TypeUsageStats) {
374 self.write_type_usage_metrics(allocations)?;
375 }
376
377 Ok(())
380 }
381
382 fn write_lifecycle_metrics(
384 &mut self,
385 allocations: &[AllocationInfo],
386 ) -> Result<(), BinaryExportError> {
387 let lifecycle_count = allocations
389 .iter()
390 .filter(|a| a.lifetime_ms.is_some())
391 .count();
392
393 self.writer
394 .write_all(&(lifecycle_count as u32).to_le_bytes())?;
395
396 for alloc in allocations {
398 if let Some(lifetime) = alloc.lifetime_ms {
399 self.writer.write_all(&(alloc.ptr as u64).to_le_bytes())?; self.writer.write_all(&lifetime.to_le_bytes())?; if let Some(ref lifecycle_tracking) = alloc.lifecycle_tracking {
404 self.writer.write_all(&1u8.to_le_bytes())?; let json_str = serde_json::to_string(lifecycle_tracking).map_err(|e| {
406 BinaryExportError::CorruptedData(format!(
407 "Lifecycle JSON serialization failed: {e}"
408 ))
409 })?;
410 self.write_string(&json_str)?;
411 } else {
412 self.writer.write_all(&0u8.to_le_bytes())?; }
414 }
415 }
416
417 Ok(())
418 }
419
420 fn write_container_metrics(
422 &mut self,
423 allocations: &[AllocationInfo],
424 ) -> Result<(), BinaryExportError> {
425 let container_count = allocations
427 .iter()
428 .filter(|a| a.memory_layout.is_some())
429 .count();
430
431 self.writer
432 .write_all(&(container_count as u32).to_le_bytes())?;
433
434 for alloc in allocations {
436 if let Some(ref memory_layout) = alloc.memory_layout {
437 self.writer.write_all(&(alloc.ptr as u64).to_le_bytes())?; let json_str = serde_json::to_string(memory_layout).map_err(|e| {
441 BinaryExportError::CorruptedData(format!(
442 "Memory layout JSON serialization failed: {e}"
443 ))
444 })?;
445 self.write_string(&json_str)?;
446 }
447 }
448
449 Ok(())
450 }
451
452 fn write_type_usage_metrics(
454 &mut self,
455 allocations: &[AllocationInfo],
456 ) -> Result<(), BinaryExportError> {
457 let type_usage_count = allocations
459 .iter()
460 .filter(|a| a.type_usage.is_some())
461 .count();
462
463 self.writer
464 .write_all(&(type_usage_count as u32).to_le_bytes())?;
465
466 for alloc in allocations {
468 if let Some(ref type_usage) = alloc.type_usage {
469 self.writer.write_all(&(alloc.ptr as u64).to_le_bytes())?; let json_str = serde_json::to_string(type_usage).map_err(|e| {
473 BinaryExportError::CorruptedData(format!(
474 "Type usage JSON serialization failed: {e}"
475 ))
476 })?;
477 self.write_string(&json_str)?;
478 }
479 }
480
481 Ok(())
482 }
483
484 fn calculate_lifecycle_data_size(&self, allocations: &[AllocationInfo]) -> usize {
486 let mut size = 4; for alloc in allocations {
489 if alloc.lifetime_ms.is_some() {
490 size += 8; size += 8; size += 1; if let Some(ref lifecycle_tracking) = alloc.lifecycle_tracking {
495 if let Ok(json_str) = serde_json::to_string(lifecycle_tracking) {
496 size += 4 + json_str.len(); }
498 }
499 }
500 }
501
502 size
503 }
504
505 fn calculate_container_data_size(&self, allocations: &[AllocationInfo]) -> usize {
507 let mut size = 4; for alloc in allocations {
510 if let Some(ref memory_layout) = alloc.memory_layout {
511 size += 8; if let Ok(json_str) = serde_json::to_string(memory_layout) {
513 size += 4 + json_str.len(); }
515 }
516 }
517
518 size
519 }
520
521 fn calculate_type_usage_data_size(&self, allocations: &[AllocationInfo]) -> usize {
523 let mut size = 4; for alloc in allocations {
526 if let Some(ref type_usage) = alloc.type_usage {
527 size += 8; if let Ok(json_str) = serde_json::to_string(type_usage) {
529 size += 4 + json_str.len(); }
531 }
532 }
533
534 size
535 }
536
537 pub fn finish(mut self) -> Result<(), BinaryExportError> {
539 self.writer.flush()?;
540 Ok(())
541 }
542
543 fn calculate_value_size(&self, alloc: &AllocationInfo) -> usize {
545 let mut size = 8 + 8 + 8; size += 1;
549 if alloc.timestamp_dealloc.is_some() {
550 size += 8;
551 }
552
553 size += 4; if let Some(ref name) = alloc.var_name {
556 size += name.len();
557 }
558
559 size += 4; if let Some(ref name) = alloc.type_name {
561 size += name.len();
562 }
563
564 size += 4; if let Some(ref name) = alloc.scope_name {
566 size += name.len();
567 }
568
569 size += 4 + alloc.thread_id.len(); size += 4; if let Some(ref stack_trace) = alloc.stack_trace {
574 for frame in stack_trace {
575 size += 4 + frame.len(); }
577 }
578
579 size += 4; size += 1; size += 1;
585 if alloc.lifetime_ms.is_some() {
586 size += 8;
587 }
588
589 size += 1; if let Some(ref borrow_info) = alloc.borrow_info {
592 size += 4 + 4 + 4; size += 1; if borrow_info.last_borrow_timestamp.is_some() {
595 size += 8; }
597 }
598
599 size += 1; if let Some(ref clone_info) = alloc.clone_info {
602 size += 4 + 1; size += 1; if clone_info.original_ptr.is_some() {
605 size += 8; }
607 }
608
609 size += 1; size += self.calculate_binary_field_size(&alloc.smart_pointer_info);
614 size += self.calculate_binary_field_size(&alloc.memory_layout);
615 size += self.calculate_json_field_size(&alloc.generic_info);
616 size += self.calculate_json_field_size(&alloc.dynamic_type_info);
617 size += self.calculate_json_field_size(&alloc.runtime_state);
618 size += self.calculate_json_field_size(&alloc.stack_allocation);
619 size += self.calculate_json_field_size(&alloc.temporary_object);
620 size += self.calculate_json_field_size(&alloc.fragmentation_analysis);
621 size += self.calculate_json_field_size(&alloc.generic_instantiation);
622 size += self.calculate_json_field_size(&alloc.type_relationships);
623 size += self.calculate_json_field_size(&alloc.type_usage);
624 size += self.calculate_json_field_size(&alloc.function_call_tracking);
625 size += self.calculate_json_field_size(&alloc.lifecycle_tracking);
626 size += self.calculate_json_field_size(&alloc.access_tracking);
627
628 size
629 }
630
631 fn calculate_binary_field_size<T: BinarySerializable>(&self, field: &Option<T>) -> usize {
633 let mut size = 1; if let Some(value) = field {
635 size += value.binary_size();
636 }
637 size
638 }
639
640 fn calculate_json_field_size<T: serde::Serialize>(&self, field: &Option<T>) -> usize {
642 let mut size = 1; if let Some(value) = field {
644 if let Ok(json_str) = serde_json::to_string(value) {
646 size += 4 + json_str.len(); } else {
648 size = 1;
650 }
651 }
652 size
653 }
654
655 fn write_optional_string(&mut self, opt_str: &Option<String>) -> Result<(), BinaryExportError> {
657 match opt_str {
658 Some(s) => {
659 if let Some(ref string_table) = self.string_table {
660 if let Some(index) = self.find_string_index(s) {
662 self.writer.write_all(&0xFFFFu32.to_le_bytes())?;
664 string_table.write_compressed_index(&mut self.writer, index)?;
665 } else {
666 self.write_inline_string(s)?;
668 }
669 } else {
670 self.write_inline_string(s)?;
672 }
673 }
674 None => {
675 self.writer.write_all(&0xFFFFFFFEu32.to_le_bytes())?;
677 }
678 }
679 Ok(())
680 }
681
682 fn write_string(&mut self, s: &str) -> Result<(), BinaryExportError> {
684 if let Some(ref string_table) = self.string_table {
685 if let Some(index) = self.find_string_index(s) {
687 self.writer.write_all(&0xFFFFu32.to_le_bytes())?;
689 string_table.write_compressed_index(&mut self.writer, index)?;
690 } else {
691 self.write_inline_string(s)?;
693 }
694 } else {
695 self.write_inline_string(s)?;
697 }
698 Ok(())
699 }
700
701 fn write_inline_string(&mut self, s: &str) -> Result<(), BinaryExportError> {
703 self.writer.write_all(&(s.len() as u32).to_le_bytes())?;
704 self.writer.write_all(s.as_bytes())?;
705 Ok(())
706 }
707
708 fn find_string_index(&self, s: &str) -> Option<u16> {
710 if s.is_empty() {
712 return None;
713 }
714
715 if let Some(ref string_table) = self.string_table {
716 string_table.get_index(s)
717 } else {
718 None
719 }
720 }
721
722 fn write_optional_string_vec(
724 &mut self,
725 vec: &Option<Vec<String>>,
726 ) -> Result<(), BinaryExportError> {
727 match vec {
728 Some(strings) => {
729 self.writer
731 .write_all(&(strings.len() as u32).to_le_bytes())?;
732 for string in strings {
734 self.write_string(string)?;
735 }
736 }
737 None => {
738 self.writer.write_all(&0u32.to_le_bytes())?; }
740 }
741 Ok(())
742 }
743
744 fn write_optional_binary_field<T: BinarySerializable>(
746 &mut self,
747 field: &Option<T>,
748 ) -> Result<(), BinaryExportError> {
749 match field {
750 Some(value) => {
751 self.writer.write_all(&1u8.to_le_bytes())?; value.write_binary(&mut self.writer)?;
753 }
754 None => {
755 self.writer.write_all(&0u8.to_le_bytes())?; }
757 }
758 Ok(())
759 }
760
761 fn write_optional_json_field<T: serde::Serialize>(
763 &mut self,
764 field: &Option<T>,
765 ) -> Result<(), BinaryExportError> {
766 match field {
767 Some(value) => {
768 let json_str = serde_json::to_string(value).map_err(|e| {
769 BinaryExportError::CorruptedData(format!("JSON serialization failed: {e}"))
770 })?;
771 self.writer.write_all(&1u8.to_le_bytes())?; self.write_string(&json_str)?;
773 }
774 None => {
775 self.writer.write_all(&0u8.to_le_bytes())?; }
777 }
778 Ok(())
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785 use std::fs;
786 use tempfile::NamedTempFile;
787
788 fn create_test_allocation() -> AllocationInfo {
789 AllocationInfo {
790 ptr: 0x1000,
791 size: 1024,
792 var_name: Some("test_var".to_string()),
793 type_name: Some("i32".to_string()),
794 scope_name: None,
795 timestamp_alloc: 1234567890,
796 timestamp_dealloc: None,
797 thread_id: "main".to_string(),
798 borrow_count: 0,
799 stack_trace: None,
800 is_leaked: false,
801 lifetime_ms: None,
802 borrow_info: None,
803 clone_info: None,
804 ownership_history_available: false,
805 smart_pointer_info: None,
806 memory_layout: None,
807 generic_info: None,
808 dynamic_type_info: None,
809 runtime_state: None,
810 stack_allocation: None,
811 temporary_object: None,
812 fragmentation_analysis: None,
813 generic_instantiation: None,
814 type_relationships: None,
815 type_usage: None,
816 function_call_tracking: None,
817 lifecycle_tracking: None,
818 access_tracking: None,
819 drop_chain_analysis: None,
820 }
821 }
822
823 #[test]
824 fn test_writer_creation() {
825 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
826 let writer = BinaryWriter::new(temp_file.path());
827 assert!(writer.is_ok());
828 }
829
830 #[test]
831 fn test_header_writing() {
832 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
833 let mut writer = BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
834
835 let result = writer.write_header(42);
836 assert!(result.is_ok());
837
838 writer.finish().expect("Failed to finish writing");
839
840 let metadata = fs::metadata(temp_file.path()).expect("Failed to create temp file");
842 assert!(metadata.len() >= 16);
843 }
844
845 #[test]
846 fn test_allocation_writing() {
847 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
848 let mut writer = BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
849
850 writer.write_header(1).expect("Failed to write header");
851
852 let alloc = create_test_allocation();
853 let result = writer.write_allocation(&alloc);
854 assert!(result.is_ok());
855
856 writer.finish().expect("Failed to finish writing");
857
858 let metadata = fs::metadata(temp_file.path()).expect("Failed to create temp file");
860 assert!(metadata.len() > 16);
861 }
862
863 #[test]
864 fn test_record_size_calculation() {
865 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
866 let writer = BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
867
868 let alloc = create_test_allocation();
869 let size = writer.calculate_value_size(&alloc);
870
871 assert_eq!(size, 83);
887 }
888
889 #[test]
890 fn test_advanced_metrics_segment_writing() {
891 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
892 let config = BinaryExportConfig::debug_comprehensive();
893 let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
894 .expect("Failed to create temp file");
895
896 writer.write_header(1).expect("Failed to write header");
897
898 let mut alloc = create_test_allocation();
899 alloc.lifetime_ms = Some(1500); writer
902 .write_allocation(&alloc)
903 .expect("Failed to write allocation");
904 writer
905 .write_advanced_metrics_segment(&[alloc])
906 .expect("Failed to write metrics segment");
907 writer.finish().expect("Failed to finish writing");
908
909 let metadata = std::fs::metadata(temp_file.path()).expect("Failed to create temp file");
911 assert!(metadata.len() > 100); }
913
914 #[test]
915 fn test_advanced_metrics_disabled() {
916 let temp_file = NamedTempFile::new().expect("Failed to create temp file");
917 let config = BinaryExportConfig::minimal(); let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
919 .expect("Failed to create temp file");
920
921 writer.write_header(1).expect("Failed to write header");
922 let alloc = create_test_allocation();
923 writer
924 .write_allocation(&alloc)
925 .expect("Failed to write allocation");
926
927 writer
929 .write_advanced_metrics_segment(&[alloc])
930 .expect("Failed to write metrics segment");
931 writer.finish().expect("Failed to finish writing");
932
933 let metadata = std::fs::metadata(temp_file.path()).expect("Failed to create temp file");
935 assert!(metadata.len() < 150); }
937}