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