memscope_rs/export/binary/
mod.rs

1//! Binary export module for high-performance memory tracking data export.
2//!
3//! Features:
4//! - 3x faster than JSON export with 60%+ smaller file size
5//! - Lock-free, single-threaded design for simplicity
6//! - Compatible with existing JSON/HTML export APIs
7//! - Modular architecture for easy testing and maintenance
8
9mod batch_processor;
10mod binary_html_export;
11mod binary_html_writer;
12mod binary_template_engine;
13mod cache;
14mod complex_type_analyzer;
15mod config;
16mod error;
17mod error_recovery;
18mod ffi_safety_analyzer;
19mod field_parser;
20mod filter_engine;
21pub mod format;
22pub mod html_converter;
23pub mod html_export;
24pub mod html_template;
25mod index;
26mod index_builder;
27#[cfg(test)]
28mod integration_test_complex_types;
29#[cfg(test)]
30mod integration_test_ffi_safety;
31#[cfg(test)]
32mod integration_test_template_resources;
33#[cfg(test)]
34mod integration_test_variable_relationships;
35mod memory_layout_serialization;
36mod parser;
37pub mod reader;
38mod selective_json_exporter;
39mod selective_reader;
40mod serializable;
41mod smart_pointer_serialization;
42mod streaming_field_processor;
43mod streaming_json_writer;
44mod string_table;
45mod template_resource_manager;
46mod variable_relationship_analyzer;
47pub mod writer;
48
49pub use batch_processor::{
50    BatchProcessor, BatchProcessorBuilder, BatchProcessorConfig, BatchProcessorStats, RecordBatch,
51};
52pub use binary_html_export::{
53    get_recommended_config, parse_binary_to_html_auto, parse_binary_to_html_direct,
54    parse_binary_to_html_with_config, BinaryHtmlExportConfig, BinaryHtmlExportStats,
55    ProcessingStrategy,
56};
57pub use binary_html_writer::{
58    BinaryAllocationData, BinaryHtmlStats, BinaryHtmlWriter, BinaryHtmlWriterConfig,
59    BinaryTemplateData,
60};
61pub use binary_template_engine::{
62    BinaryTemplateEngine, BinaryTemplateEngineConfig, BinaryTemplateEngineStats,
63};
64pub use cache::{CacheEntry, CacheStats, IndexCache, IndexCacheConfig};
65pub use complex_type_analyzer::{
66    CategorizedTypes, ComplexTypeAnalysis, ComplexTypeAnalyzer, ComplexTypeSummary,
67    GenericInstantiation, GenericTypeAnalysis, TypeCategory, TypeInfo,
68};
69pub use ffi_safety_analyzer::{
70    FfiCallGraph, FfiHotspot, FfiSafetyAnalysis, FfiSafetyAnalyzer, FfiSafetySummary,
71    RiskAssessment, RiskLevel, UnsafeOperation, UnsafeOperationType,
72};
73pub use html_converter::*;
74pub use variable_relationship_analyzer::{
75    GraphEdge, GraphNode, NodeCategory, OwnershipStatus, RelationshipGraph, RelationshipType,
76    VariableRelationshipAnalysis, VariableRelationshipAnalyzer,
77};
78
79pub use config::{
80    AdvancedMetricsLevel, AnalysisType, BinaryExportConfig, BinaryExportConfigBuilder,
81    DashboardExportStats, DashboardFormat, DashboardOptions, DataScope, PerformanceMode,
82};
83pub use error::BinaryExportError;
84pub use error_recovery::{
85    ErrorRecoveryManager, ErrorReport, ErrorStatistics, ErrorTrend, RecoveryConfig, RecoveryResult,
86    RecoveryStrategy,
87};
88pub use field_parser::{FieldParser, FieldParserConfig, FieldParserStats, PartialAllocationInfo};
89pub use filter_engine::{FilterEngine, FilterEngineBuilder, FilterOptimizer, FilterStats};
90pub use format::{BinaryExportMode, FileHeader, FORMAT_VERSION, MAGIC_BYTES};
91pub use html_export::{
92    export_binary,
93    export_binary_optimized,
94    export_binary_to_both,
95    export_binary_to_dashboard, // New unified API
96    export_binary_to_html,
97    export_binary_to_html_both,
98    export_binary_to_html_system,
99    export_binary_to_json,
100    export_binary_with_config,
101    export_binary_with_format,
102    show_export_options,
103    BinaryOutputFormat,
104};
105pub use index::{BinaryIndex, CompactAllocationIndex, QuickFilterData, RecordMetadata};
106pub use index_builder::BinaryIndexBuilder;
107pub use parser::BinaryParser;
108pub use reader::BinaryReader;
109pub use selective_json_exporter::{
110    OptimizationLevel, SelectiveJsonExportConfig, SelectiveJsonExportConfigBuilder,
111    SelectiveJsonExportStats, SelectiveJsonExporter,
112};
113pub use selective_reader::{
114    AllocationField, AllocationFilter, SelectiveReadOptions, SelectiveReadOptionsBuilder,
115    SortField, SortOrder,
116};
117pub use streaming_field_processor::{
118    OptimizedRecord, StreamingFieldProcessor, StreamingFieldProcessorConfig,
119    StreamingFieldProcessorConfigBuilder, StreamingFieldProcessorStats,
120};
121pub use streaming_json_writer::{
122    SelectiveSerializationOptions, StreamingJsonStats, StreamingJsonWriter,
123    StreamingJsonWriterConfig, StreamingJsonWriterConfigBuilder,
124};
125pub use template_resource_manager::{
126    create_template_data, PlaceholderProcessor, ResourceConfig, TemplateData,
127    TemplateResourceManager,
128};
129pub use writer::BinaryWriter;
130
131// Auto-detection functions are defined below and exported automatically
132
133use crate::core::types::AllocationInfo;
134use std::path::Path;
135
136/// Export allocation information to binary format with default configuration
137pub fn export_to_binary<P: AsRef<Path>>(
138    allocations: &[AllocationInfo],
139    path: P,
140) -> Result<(), BinaryExportError> {
141    export_to_binary_with_config(allocations, path, &BinaryExportConfig::default())
142}
143
144/// Export allocation information to binary format with enhanced header
145///
146/// This function properly filters allocations based on the export mode:
147/// - UserOnly: Only exports allocations with var_name (user allocations)
148/// - Full: Exports all allocations (user + system)
149///
150/// # Arguments
151/// * `allocations` - Vector of allocation information to export
152/// * `path` - Path where the binary file will be written
153/// * `export_mode` - Binary export mode that controls which allocations are written
154/// * `config` - Export configuration settings
155///
156/// # Returns
157/// * `Result<(), BinaryExportError>` - Success or detailed export error
158pub fn export_to_binary_with_mode<P: AsRef<Path>>(
159    allocations: &[AllocationInfo],
160    path: P,
161    export_mode: BinaryExportMode,
162    config: &BinaryExportConfig,
163) -> Result<(), BinaryExportError> {
164    let mut writer = BinaryWriter::new_with_config(path, config)?;
165
166    // Filter allocations based on export mode
167    let filtered_allocations: Vec<&AllocationInfo> = match export_mode {
168        BinaryExportMode::UserOnly => {
169            // Only include allocations with var_name (user allocations)
170            allocations
171                .iter()
172                .filter(|a| a.var_name.is_some())
173                .collect()
174        }
175        BinaryExportMode::Full => {
176            // Include all allocations
177            allocations.iter().collect()
178        }
179    };
180
181    // Build string table for optimization if enabled
182    // Note: We use the original allocations for string table optimization
183    // as it doesn't affect the actual data written, only internal optimization
184    writer.build_string_table(allocations)?;
185
186    // Calculate accurate allocation counts based on filtered data
187    let user_count = filtered_allocations
188        .iter()
189        .filter(|a| a.var_name.is_some())
190        .count() as u16;
191    let system_count = (filtered_allocations.len() - user_count as usize) as u16;
192    let total_count = filtered_allocations.len() as u32;
193
194    // Validate count consistency
195    debug_assert_eq!(
196        user_count as usize + system_count as usize,
197        total_count as usize,
198        "Count validation failed: user({user_count}) + system({system_count}) != total({total_count})"
199    );
200
201    tracing::info!(
202        "Binary export starting: {total_count} allocations ({user_count} user, {system_count} system) in {export_mode:?} mode"
203    );
204    tracing::info!("Filtered from {} original allocations", allocations.len());
205
206    // Write enhanced header with mode and accurate counts
207    writer.write_enhanced_header(total_count, export_mode, user_count, system_count)?;
208
209    // Write only the filtered allocation records
210    for allocation in filtered_allocations {
211        writer.write_allocation(allocation)?;
212    }
213
214    writer.finish()?;
215
216    tracing::info!("Binary export completed: {total_count} allocations written");
217
218    Ok(())
219}
220
221/// Export allocation information to binary format with custom configuration
222pub fn export_to_binary_with_config<P: AsRef<Path>>(
223    allocations: &[AllocationInfo],
224    path: P,
225    config: &BinaryExportConfig,
226) -> Result<(), BinaryExportError> {
227    let mut writer = BinaryWriter::new_with_config(path, config)?;
228
229    // Build string table for optimization if enabled
230    writer.build_string_table(allocations)?;
231
232    writer.write_header(allocations.len() as u32)?;
233
234    // Write basic allocation records
235    for allocation in allocations {
236        writer.write_allocation(allocation)?;
237    }
238
239    // Write advanced metrics segment if enabled
240    writer.write_advanced_metrics_segment(allocations)?;
241
242    writer.finish()?;
243
244    // Log configuration info if advanced metrics are enabled
245    if config.has_advanced_metrics() {
246        tracing::info!(
247            "Binary export completed with advanced metrics (impact: {:.1}%)",
248            config.estimated_performance_impact() * 100.0
249        );
250    }
251
252    Ok(())
253}
254
255/// Convert binary file to JSON format
256pub fn parse_binary_to_json<P: AsRef<Path>>(
257    binary_path: P,
258    json_path: P,
259) -> Result<(), BinaryExportError> {
260    BinaryParser::to_json(binary_path, json_path)
261}
262
263/// Convert binary file to HTML format
264pub fn parse_binary_to_html<P: AsRef<Path>>(
265    binary_path: P,
266    html_path: P,
267) -> Result<(), BinaryExportError> {
268    BinaryParser::to_html(binary_path, html_path)
269}
270
271/// Binary file type information
272#[derive(Debug, Clone, PartialEq)]
273pub struct BinaryFileInfo {
274    /// Export mode (user-only vs full)
275    pub export_mode: BinaryExportMode,
276    /// Total allocation count
277    pub total_count: u32,
278    /// User allocation count (var_name.is_some())
279    pub user_count: u16,
280    /// System allocation count (var_name.is_none())
281    pub system_count: u16,
282    /// Binary format version
283    pub version: u32,
284    /// Whether counts are consistent
285    pub is_count_consistent: bool,
286    /// File size in bytes
287    pub file_size: u64,
288}
289
290impl BinaryFileInfo {
291    /// Check if this is a user-only binary
292    pub fn is_user_only(&self) -> bool {
293        self.export_mode == BinaryExportMode::UserOnly
294    }
295
296    /// Check if this is a full binary
297    pub fn is_full_binary(&self) -> bool {
298        self.export_mode == BinaryExportMode::Full
299    }
300
301    /// Get a human-readable description of the binary type
302    pub fn type_description(&self) -> String {
303        match self.export_mode {
304            BinaryExportMode::UserOnly => format!(
305                "User-only binary ({} user allocations, {} KB)",
306                self.user_count,
307                self.file_size / 1024
308            ),
309            BinaryExportMode::Full => format!(
310                "Full binary ({} total allocations: {} user + {} system, {} KB)",
311                self.total_count,
312                self.user_count,
313                self.system_count,
314                self.file_size / 1024
315            ),
316        }
317    }
318
319    /// Get recommended processing strategy
320    pub fn recommended_strategy(&self) -> &'static str {
321        match self.export_mode {
322            BinaryExportMode::UserOnly => "Simple processing (small file, user data only)",
323            BinaryExportMode::Full => "Optimized processing (large file, comprehensive data)",
324        }
325    }
326}
327
328/// Automatically detect binary file type and characteristics
329///
330/// This function reads the binary file header to determine:
331/// - Export mode (user-only vs full)
332/// - Allocation counts (total, user, system)
333/// - Format version and compatibility
334/// - File size and consistency checks
335///
336/// # Arguments
337/// * `path` - Path to the binary file
338///
339/// # Returns
340/// * `Ok(BinaryFileInfo)` - File information and characteristics
341/// * `Err(BinaryExportError)` - If file cannot be read or is invalid
342///
343/// # Example
344/// ```no_run
345/// use memscope_rs::export::binary::detect_binary_type;
346///
347/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
348/// let info = detect_binary_type("MemoryAnalysis/my_program.memscope")?;
349/// println!("Binary type: {}", info.type_description());
350/// println!("Strategy: {}", info.recommended_strategy());
351///
352/// if info.is_full_binary() {
353///     // Use optimized processing for large files
354///     // parse_full_binary_to_json(path, base_name)?;
355/// } else {
356///     // Use simple processing for small files
357///     // parse_user_binary_to_json(path, base_name)?;
358/// }
359/// # Ok(())
360/// # }
361/// ```
362pub fn detect_binary_type<P: AsRef<Path>>(path: P) -> Result<BinaryFileInfo, BinaryExportError> {
363    use std::fs::File;
364    use std::io::Read;
365
366    let path = path.as_ref();
367
368    // Get file size
369    let metadata = std::fs::metadata(path).map_err(BinaryExportError::Io)?;
370    let file_size = metadata.len();
371
372    // Open file and read header
373    let mut file = File::open(path).map_err(BinaryExportError::Io)?;
374
375    let mut header_bytes = [0u8; format::HEADER_SIZE];
376    file.read_exact(&mut header_bytes)
377        .map_err(BinaryExportError::Io)?;
378
379    // Parse header
380    let header = FileHeader::from_bytes(&header_bytes);
381
382    // Validate magic bytes
383    if !header.is_valid_magic() {
384        return Err(BinaryExportError::InvalidFormat);
385    }
386
387    // Check version compatibility
388    if !header.is_compatible_version() {
389        return Err(BinaryExportError::CorruptedData(format!(
390            "Unsupported format version: {}",
391            header.version
392        )));
393    }
394
395    // Extract information
396    let export_mode = header.get_export_mode();
397    let (total_count, user_count, system_count) = header.get_allocation_counts();
398    let is_count_consistent = header.is_count_consistent();
399
400    Ok(BinaryFileInfo {
401        export_mode,
402        total_count,
403        user_count,
404        system_count,
405        version: header.version,
406        is_count_consistent,
407        file_size,
408    })
409}
410
411/// Automatically choose the optimal parsing strategy based on binary type
412///
413/// This function detects the binary type and automatically selects the most
414/// appropriate parsing method:
415/// - User-only binaries: Simple, fast processing
416/// - Full binaries: Optimized processing with advanced features
417///
418/// # Arguments
419/// * `binary_path` - Path to the binary file
420/// * `base_name` - Base name for output JSON files
421///
422/// # Returns
423/// * `Ok(())` - Parsing completed successfully
424/// * `Err(BinaryExportError)` - If parsing fails
425///
426/// Export binary data directly to interactive HTML dashboard
427///
428/// This function creates a complete HTML dashboard with interactive visualizations
429/// using the templates in ./templates/dashboard.html
430///
431/// # Example
432/// ```no_run
433/// use memscope_rs::export::binary::export_binary_to_html_dashboard;
434///
435/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
436/// // Create interactive HTML dashboard
437/// export_binary_to_html_dashboard(
438///     "MemoryAnalysis/my_program.memscope",
439///     "MemoryAnalysis/my_program/dashboard.html",
440///     "my_program"
441/// )?;
442/// # Ok(())
443/// # }
444/// ```
445pub fn export_binary_to_html_dashboard<P: AsRef<Path>>(
446    binary_path: P,
447    _output_path: P,
448    project_name: &str,
449) -> Result<(), BinaryExportError> {
450    // Use the new unified API - the output path is determined automatically
451    export_binary_to_html(binary_path, project_name)
452}
453
454/// # Example
455/// ```no_run
456/// use memscope_rs::export::binary::parse_binary_auto;
457///
458/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
459/// // Automatically detects type and uses optimal strategy
460/// parse_binary_auto("MemoryAnalysis/my_program.memscope", "my_program")?;
461/// # Ok(())
462/// # }
463/// ```
464pub fn parse_binary_auto<P: AsRef<Path>>(
465    binary_path: P,
466    base_name: &str,
467) -> Result<(), BinaryExportError> {
468    let binary_path = binary_path.as_ref();
469
470    // Detect binary type
471    let info = detect_binary_type(binary_path)?;
472
473    tracing::info!(
474        "Auto-detected binary type: {} (version {})",
475        info.type_description(),
476        info.version
477    );
478
479    tracing::info!("Using strategy: {}", info.recommended_strategy());
480
481    // Choose optimal parsing strategy
482    match info.export_mode {
483        BinaryExportMode::UserOnly => {
484            tracing::debug!("Using simple parsing for user-only binary");
485            BinaryParser::parse_user_binary_to_json(binary_path, base_name)
486        }
487        BinaryExportMode::Full => {
488            tracing::debug!("Using optimized parsing for full binary");
489            BinaryParser::parse_full_binary_to_json(binary_path, base_name)
490        }
491    }
492}
493
494#[cfg(test)]
495mod tests {
496    use super::*;
497    use crate::core::types::AllocationInfo;
498    use std::fs;
499    use tempfile::TempDir;
500
501    /// Create test allocation data
502    fn create_test_allocations() -> Vec<AllocationInfo> {
503        let mut alloc1 = AllocationInfo::new(0x1000, 64);
504        alloc1.var_name = Some("user_var1".to_string());
505        alloc1.type_name = Some("String".to_string());
506        alloc1.timestamp_alloc = 1000;
507        alloc1.stack_trace = Some(vec!["main".to_string(), "allocate".to_string()]);
508        alloc1.thread_id = "1".to_string();
509
510        let mut alloc2 = AllocationInfo::new(0x2000, 128);
511        alloc2.var_name = Some("user_var2".to_string());
512        alloc2.type_name = Some("Vec<i32>".to_string());
513        alloc2.timestamp_alloc = 2000;
514        alloc2.stack_trace = Some(vec!["main".to_string(), "create_vec".to_string()]);
515        alloc2.thread_id = "1".to_string();
516
517        let mut alloc3 = AllocationInfo::new(0x3000, 32);
518        alloc3.var_name = None; // System allocation
519        alloc3.type_name = None;
520        alloc3.timestamp_alloc = 3000;
521        alloc3.stack_trace = Some(vec!["system".to_string()]);
522        alloc3.thread_id = "2".to_string();
523
524        vec![alloc1, alloc2, alloc3]
525    }
526
527    #[test]
528    fn test_export_to_binary_default() {
529        let temp_dir = TempDir::new().unwrap();
530        let binary_path = temp_dir.path().join("test.memscope");
531        let allocations = create_test_allocations();
532
533        let result = export_to_binary(&allocations, &binary_path);
534        assert!(result.is_ok());
535        assert!(binary_path.exists());
536        assert!(binary_path.metadata().unwrap().len() > 0);
537    }
538
539    #[test]
540    fn test_export_to_binary_with_config() {
541        let temp_dir = TempDir::new().unwrap();
542        let binary_path = temp_dir.path().join("test_config.memscope");
543        let allocations = create_test_allocations();
544
545        let config = BinaryExportConfigBuilder::new()
546            .advanced_metrics_level(AdvancedMetricsLevel::Essential)
547            .build();
548
549        let result = export_to_binary_with_config(&allocations, &binary_path, &config);
550        assert!(result.is_ok());
551        assert!(binary_path.exists());
552        assert!(binary_path.metadata().unwrap().len() > 0);
553    }
554
555    #[test]
556    fn test_export_to_binary_with_mode_user_only() {
557        let temp_dir = TempDir::new().unwrap();
558        let binary_path = temp_dir.path().join("test_user_only.memscope");
559        let allocations = create_test_allocations();
560
561        let config = BinaryExportConfig::minimal();
562
563        let result = export_to_binary_with_mode(
564            &allocations,
565            &binary_path,
566            BinaryExportMode::UserOnly,
567            &config,
568        );
569        assert!(result.is_ok());
570        assert!(binary_path.exists());
571
572        // Verify the file was created with user-only mode by reading it back
573        let info = detect_binary_type(&binary_path).unwrap();
574        assert!(info.is_user_only());
575
576        // Count actual user allocations in our test data
577        let expected_user_count = allocations.iter().filter(|a| a.var_name.is_some()).count();
578
579        // The binary should reflect the user-only mode
580        assert_eq!(info.export_mode, BinaryExportMode::UserOnly);
581
582        // In UserOnly mode, only user allocations should be written
583        assert_eq!(info.user_count, expected_user_count as u16);
584        assert_eq!(info.system_count, 0); // No system allocations in UserOnly mode
585        assert_eq!(info.total_count, expected_user_count as u32);
586
587        // Verify we can parse it back to JSON to check the data integrity
588        let json_path = temp_dir.path().join("test_user_only.json");
589        let parse_result = parse_binary_to_json(&binary_path, &json_path);
590        assert!(parse_result.is_ok());
591        assert!(json_path.exists());
592
593        // Verify JSON content contains only user allocations
594        let json_content = fs::read_to_string(&json_path).unwrap();
595        let json_data: serde_json::Value = serde_json::from_str(&json_content).unwrap();
596
597        // Check that the JSON contains only user allocations
598        if json_data.is_array() {
599            // JSON is directly an array of allocations
600            let json_allocations = json_data.as_array().unwrap();
601            assert_eq!(json_allocations.len(), expected_user_count); // Only user allocations
602
603            // All allocations in the JSON should have var_name
604            for alloc in json_allocations {
605                let var_name = alloc.get("var_name");
606                assert!(
607                    var_name.is_some() && !var_name.unwrap().is_null(),
608                    "All allocations in UserOnly mode should have var_name"
609                );
610            }
611        } else if json_data.is_object() {
612            // JSON is an object containing allocations array
613            if let Some(allocations_array) = json_data.get("allocations") {
614                assert!(allocations_array.is_array());
615                let json_allocations = allocations_array.as_array().unwrap();
616                assert_eq!(json_allocations.len(), expected_user_count); // Only user allocations
617
618                // All allocations should have var_name
619                for alloc in json_allocations {
620                    let var_name = alloc.get("var_name");
621                    assert!(
622                        var_name.is_some() && !var_name.unwrap().is_null(),
623                        "All allocations in UserOnly mode should have var_name"
624                    );
625                }
626            }
627        }
628    }
629
630    #[test]
631    fn test_export_to_binary_with_mode_full() {
632        let temp_dir = TempDir::new().unwrap();
633        let binary_path = temp_dir.path().join("test_full.memscope");
634        let allocations = create_test_allocations();
635
636        let config = BinaryExportConfig::debug_comprehensive();
637
638        let result =
639            export_to_binary_with_mode(&allocations, &binary_path, BinaryExportMode::Full, &config);
640        assert!(result.is_ok());
641        assert!(binary_path.exists());
642
643        // Verify the file was created with full mode
644        let info = detect_binary_type(&binary_path).unwrap();
645        assert!(info.is_full_binary());
646        assert_eq!(info.user_count, 2); // User allocations
647        assert_eq!(info.system_count, 1); // System allocations
648        assert_eq!(info.total_count, 3); // Total allocations
649    }
650
651    #[test]
652    fn test_detect_binary_type_user_only() {
653        let temp_dir = TempDir::new().unwrap();
654        let binary_path = temp_dir.path().join("test_detect_user.memscope");
655        let allocations = create_test_allocations();
656
657        let config = BinaryExportConfig::minimal();
658
659        export_to_binary_with_mode(
660            &allocations,
661            &binary_path,
662            BinaryExportMode::UserOnly,
663            &config,
664        )
665        .unwrap();
666
667        let info = detect_binary_type(&binary_path).unwrap();
668        assert!(info.is_user_only());
669        assert!(!info.is_full_binary());
670        assert_eq!(info.export_mode, BinaryExportMode::UserOnly);
671        assert!(info.is_count_consistent);
672        assert!(info.file_size > 0);
673
674        let description = info.type_description();
675        assert!(description.contains("User-only binary"));
676
677        let strategy = info.recommended_strategy();
678        assert!(strategy.contains("Simple processing"));
679
680        // Verify we can actually read the binary back and get meaningful data
681        let json_path = temp_dir.path().join("verify_user_only.json");
682        let parse_result = parse_binary_to_json(&binary_path, &json_path);
683        assert!(parse_result.is_ok());
684
685        // Read and verify the JSON content
686        let json_content = fs::read_to_string(&json_path).unwrap();
687
688        tracing::info!(
689            "JSON content preview: {}",
690            &json_content[..json_content.len().min(500)]
691        );
692
693        let json_data: serde_json::Value = serde_json::from_str(&json_content).unwrap();
694
695        // Verify the JSON structure is valid and matches our expectations
696        match json_data {
697            serde_json::Value::Array(ref json_allocations) => {
698                tracing::info!("JSON is an array with {} elements", json_allocations.len());
699
700                // Count user vs system allocations in JSON
701                let json_user_count = json_allocations
702                    .iter()
703                    .filter(|alloc| alloc.get("var_name").is_some_and(|v| !v.is_null()))
704                    .count();
705                let json_system_count = json_allocations.len() - json_user_count;
706
707                tracing::info!(
708                    "JSON contains: {} user, {} system allocations",
709                    json_user_count,
710                    json_system_count
711                );
712
713                // Verify the JSON data matches the binary header
714                assert_eq!(json_user_count, info.user_count as usize);
715                assert_eq!(json_system_count, info.system_count as usize);
716                assert_eq!(json_allocations.len(), info.total_count as usize);
717            }
718            serde_json::Value::Object(ref obj) => {
719                tracing::info!(
720                    "JSON is an object with keys: {:?}",
721                    obj.keys().collect::<Vec<_>>()
722                );
723            }
724            _ => {
725                tracing::info!("JSON is neither array nor object: {:?}", json_data);
726            }
727        }
728
729        // Count user allocations in original data
730        let original_user_count = allocations.iter().filter(|a| a.var_name.is_some()).count();
731        tracing::info!("Original user allocations: {}", original_user_count);
732
733        // The binary detection should reflect actual data
734        tracing::info!(
735            "Detected user count: {}, system count: {}",
736            info.user_count,
737            info.system_count
738        );
739    }
740
741    #[test]
742    fn test_detect_binary_type_full() {
743        let temp_dir = TempDir::new().unwrap();
744        let binary_path = temp_dir.path().join("test_detect_full.memscope");
745        let allocations = create_test_allocations();
746
747        let config = BinaryExportConfig::debug_comprehensive();
748
749        export_to_binary_with_mode(&allocations, &binary_path, BinaryExportMode::Full, &config)
750            .unwrap();
751
752        let info = detect_binary_type(&binary_path).unwrap();
753        assert!(!info.is_user_only());
754        assert!(info.is_full_binary());
755        assert_eq!(info.export_mode, BinaryExportMode::Full);
756        assert_eq!(info.user_count, 2);
757        assert_eq!(info.system_count, 1);
758        assert_eq!(info.total_count, 3);
759        assert!(info.is_count_consistent);
760        assert!(info.file_size > 0);
761
762        let description = info.type_description();
763        assert!(description.contains("Full binary"));
764        assert!(description.contains("3 total allocations"));
765        assert!(description.contains("2 user + 1 system"));
766
767        let strategy = info.recommended_strategy();
768        assert!(strategy.contains("Optimized processing"));
769    }
770
771    #[test]
772    fn test_detect_binary_type_invalid_file() {
773        let temp_dir = TempDir::new().unwrap();
774        let invalid_path = temp_dir.path().join("nonexistent.memscope");
775
776        let result = detect_binary_type(&invalid_path);
777        assert!(result.is_err());
778        if let Err(BinaryExportError::Io(_)) = result {
779            // Expected error type
780        } else {
781            panic!("Expected IoError for nonexistent file");
782        }
783    }
784
785    #[test]
786    fn test_detect_binary_type_invalid_format() {
787        let temp_dir = TempDir::new().unwrap();
788        let invalid_path = temp_dir.path().join("invalid.memscope");
789
790        // Create a file with invalid content
791        fs::write(&invalid_path, b"invalid binary data").unwrap();
792
793        let result = detect_binary_type(&invalid_path);
794        assert!(result.is_err());
795
796        // Verify error classification is appropriate for invalid binary format
797        match result {
798            Err(BinaryExportError::InvalidFormat) => {
799                // Correct error type for invalid magic bytes or format
800            }
801            Err(BinaryExportError::Io(_)) => {
802                // Acceptable if file is too small to contain valid header
803            }
804            Err(BinaryExportError::CorruptedData(msg)) => {
805                // Acceptable for malformed header data
806                assert!(!msg.is_empty(), "Error message should be descriptive");
807            }
808            Err(other) => {
809                panic!("Unexpected error type for invalid format: {:?}", other);
810            }
811            Ok(_) => {
812                panic!("Invalid binary file should not be detected as valid");
813            }
814        }
815    }
816
817    #[test]
818    fn test_parse_binary_to_json() {
819        let temp_dir = TempDir::new().unwrap();
820        let binary_path = temp_dir.path().join("test_parse.memscope");
821        let json_path = temp_dir.path().join("test_parse.json");
822        let allocations = create_test_allocations();
823
824        // First create a binary file
825        export_to_binary(&allocations, &binary_path).unwrap();
826
827        // Then parse it to JSON
828        let result = parse_binary_to_json(&binary_path, &json_path);
829        assert!(result.is_ok());
830        assert!(json_path.exists());
831        assert!(json_path.metadata().unwrap().len() > 0);
832
833        // Verify JSON content is valid
834        let json_content = fs::read_to_string(&json_path).unwrap();
835        assert!(!json_content.is_empty());
836        // Should be valid JSON
837        let _: serde_json::Value = serde_json::from_str(&json_content).unwrap();
838    }
839
840    #[test]
841    fn test_parse_binary_to_html() {
842        let temp_dir = TempDir::new().unwrap();
843        let binary_path = temp_dir.path().join("test_parse_html.memscope");
844        let html_path = temp_dir.path().join("test_parse.html");
845        let allocations = create_test_allocations();
846
847        // First create a binary file
848        export_to_binary(&allocations, &binary_path).unwrap();
849
850        // Then parse it to HTML
851        let result = parse_binary_to_html(&binary_path, &html_path);
852        assert!(result.is_ok());
853        assert!(html_path.exists());
854        assert!(html_path.metadata().unwrap().len() > 0);
855
856        // Verify HTML content
857        let html_content = fs::read_to_string(&html_path).unwrap();
858        assert!(!html_content.is_empty());
859        assert!(html_content.contains("<!DOCTYPE html") || html_content.contains("<html"));
860    }
861
862    #[test]
863    fn test_empty_allocations() {
864        let temp_dir = TempDir::new().unwrap();
865        let binary_path = temp_dir.path().join("test_empty.memscope");
866        let allocations: Vec<AllocationInfo> = vec![];
867
868        let result = export_to_binary(&allocations, &binary_path);
869        assert!(result.is_ok());
870        assert!(binary_path.exists());
871
872        // Verify the file was created with zero allocations
873        let info = detect_binary_type(&binary_path).unwrap();
874        assert_eq!(info.total_count, 0);
875        assert_eq!(info.user_count, 0);
876        assert_eq!(info.system_count, 0);
877    }
878
879    #[test]
880    fn test_large_allocation_count() {
881        let temp_dir = TempDir::new().unwrap();
882        let binary_path = temp_dir.path().join("test_large.memscope");
883
884        // Create a larger number of allocations with known user/system split
885        let mut allocations = Vec::new();
886        let mut _expected_user_count = 0;
887        let mut _expected_system_count = 0;
888
889        for i in 0..100 {
890            let mut alloc = AllocationInfo::new(0x1000 + (i * 0x100), 64 + i);
891            if i % 2 == 0 {
892                alloc.var_name = Some(format!("var_{i}"));
893                _expected_user_count += 1;
894            } else {
895                alloc.var_name = None;
896                _expected_system_count += 1;
897            }
898            alloc.type_name = Some(format!("Type_{i}"));
899            alloc.timestamp_alloc = 1000 + i as u64;
900            alloc.stack_trace = Some(vec![format!("func_{i}")]);
901            alloc.thread_id = ((i % 4) + 1).to_string();
902            allocations.push(alloc);
903        }
904
905        tracing::info!(
906            "Expected: {} user, {} system, {} total",
907            _expected_user_count,
908            _expected_system_count,
909            allocations.len()
910        );
911
912        let result = export_to_binary(&allocations, &binary_path);
913        assert!(result.is_ok());
914        assert!(binary_path.exists());
915
916        // Verify the counts are correct by reading the binary
917        let info = detect_binary_type(&binary_path).unwrap();
918        tracing::info!(
919            "Detected: {} user, {} system, {} total",
920            info.user_count,
921            info.system_count,
922            info.total_count
923        );
924
925        assert_eq!(info.total_count, 100);
926        assert!(info.is_count_consistent);
927
928        // Verify by parsing back to JSON and counting
929        let json_path = temp_dir.path().join("test_large.json");
930        let parse_result = parse_binary_to_json(&binary_path, &json_path);
931        assert!(parse_result.is_ok());
932
933        let json_content = fs::read_to_string(&json_path).unwrap();
934        let json_data: serde_json::Value = serde_json::from_str(&json_content).unwrap();
935
936        if let Some(allocations_array) = json_data.get("allocations") {
937            let json_allocations = allocations_array.as_array().unwrap();
938            let json_user_count = json_allocations
939                .iter()
940                .filter(|alloc| alloc.get("var_name").is_some_and(|v| !v.is_null()))
941                .count();
942            let json_system_count = json_allocations.len() - json_user_count;
943
944            tracing::info!(
945                "JSON parsed: {} user, {} system, {} total",
946                json_user_count,
947                json_system_count,
948                json_allocations.len()
949            );
950
951            // Verify the JSON data matches our expectations
952            assert_eq!(json_allocations.len(), 100);
953            assert_eq!(json_user_count, _expected_user_count);
954            assert_eq!(json_system_count, _expected_system_count);
955        }
956    }
957}