Skip to main content

memscope_rs/render_engine/
export.rs

1//! Export functionality for the render engine
2//!
3//! This module provides export functionality for memory tracking data,
4//! including JSON export, lifetime analysis, and variable relationships.
5
6use crate::analysis::memory_passport_tracker::MemoryPassportTracker;
7use crate::analysis::ownership_graph::{EdgeKind, ObjectId, OwnershipGraph, OwnershipOp};
8use crate::capture::platform::memory_info::PlatformMemoryInfo;
9use crate::core::{MemScopeError, MemScopeResult};
10use crate::render_engine::dashboard::DashboardRenderer;
11use crate::snapshot::{ActiveAllocation, MemorySnapshot, ThreadMemoryStats};
12use crate::tracker::Tracker;
13use rayon::prelude::*;
14use serde_json::json;
15use std::{
16    collections::HashMap,
17    fs::File,
18    io::{BufWriter, Write},
19    path::Path,
20    sync::Arc,
21};
22
23/// Optimization level for JSON export
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum OptimizationLevel {
26    /// Low optimization - maximum compatibility
27    Low,
28    /// Medium optimization - balanced
29    #[default]
30    Medium,
31    /// High optimization - maximum performance
32    High,
33    /// Maximum optimization - aggressive optimization
34    Maximum,
35}
36
37/// Schema validator for JSON export
38#[derive(Debug, Clone, Default)]
39pub struct SchemaValidator {
40    strict_mode: bool,
41}
42
43impl SchemaValidator {
44    pub fn new() -> Self {
45        Self { strict_mode: false }
46    }
47
48    pub fn with_strict_mode(mut self, strict: bool) -> Self {
49        self.strict_mode = strict;
50        self
51    }
52
53    pub fn validate(&self, data: &serde_json::Value) -> Result<(), String> {
54        if !data.is_object() {
55            return Err("Export data must be a JSON object".to_string());
56        }
57
58        let obj = data.as_object().ok_or("Invalid JSON object")?;
59
60        if self.strict_mode {
61            let required_fields = ["timestamp", "allocations", "stats"];
62            for field in &required_fields {
63                if !obj.contains_key(*field) {
64                    return Err(format!("Missing required field: {}", field));
65                }
66            }
67        }
68
69        Ok(())
70    }
71}
72
73#[derive(Debug, Clone)]
74pub struct ExportJsonOptions {
75    pub parallel_processing: bool,
76    pub buffer_size: usize,
77    pub use_compact_format: Option<bool>,
78    pub enable_type_cache: bool,
79    pub batch_size: usize,
80    pub streaming_writer: bool,
81    pub schema_validation: bool,
82    pub adaptive_optimization: bool,
83    pub max_cache_size: usize,
84    pub security_analysis: bool,
85    pub include_low_severity: bool,
86    pub integrity_hashes: bool,
87    pub fast_export_mode: bool,
88    pub auto_fast_export_threshold: Option<usize>,
89    pub thread_count: Option<usize>,
90}
91
92impl Default for ExportJsonOptions {
93    fn default() -> Self {
94        Self {
95            parallel_processing: true,
96            buffer_size: 256 * 1024,
97            use_compact_format: None,
98            enable_type_cache: true,
99            batch_size: 1000,
100            streaming_writer: true,
101            schema_validation: false,
102            adaptive_optimization: true,
103            max_cache_size: 10_000,
104            security_analysis: false,
105            include_low_severity: false,
106            integrity_hashes: false,
107            fast_export_mode: false,
108            auto_fast_export_threshold: Some(10_000),
109            thread_count: None,
110        }
111    }
112}
113
114impl ExportJsonOptions {
115    pub fn fast_export_mode(mut self, enabled: bool) -> Self {
116        self.fast_export_mode = enabled;
117        self
118    }
119
120    pub fn security_analysis(mut self, enabled: bool) -> Self {
121        self.security_analysis = enabled;
122        self
123    }
124
125    pub fn streaming_writer(mut self, enabled: bool) -> Self {
126        self.streaming_writer = enabled;
127        self
128    }
129
130    pub fn schema_validation(mut self, enabled: bool) -> Self {
131        self.schema_validation = enabled;
132        self
133    }
134
135    pub fn integrity_hashes(mut self, enabled: bool) -> Self {
136        self.integrity_hashes = enabled;
137        self
138    }
139
140    pub fn batch_size(mut self, size: usize) -> Self {
141        self.batch_size = size;
142        self
143    }
144
145    pub fn adaptive_optimization(mut self, enabled: bool) -> Self {
146        self.adaptive_optimization = enabled;
147        self
148    }
149
150    pub fn max_cache_size(mut self, size: usize) -> Self {
151        self.max_cache_size = size;
152        self
153    }
154
155    pub fn include_low_severity(mut self, include: bool) -> Self {
156        self.include_low_severity = include;
157        self
158    }
159
160    pub fn thread_count(mut self, count: Option<usize>) -> Self {
161        self.thread_count = count;
162        self
163    }
164}
165
166pub fn export_snapshot_to_json(
167    snapshot: &MemorySnapshot,
168    output_path: &Path,
169    options: &ExportJsonOptions,
170) -> Result<(), Box<dyn std::error::Error>> {
171    // Create parent directory if it doesn't exist
172    if let Some(parent) = output_path.parent() {
173        if !parent.as_os_str().is_empty() {
174            std::fs::create_dir_all(parent)?;
175        }
176    }
177
178    let allocations: Vec<&ActiveAllocation> = snapshot.active_allocations.values().collect();
179    let processed = process_allocations(&allocations, options)?;
180
181    // Use output_path as the base directory for generated files
182    let output_dir = if output_path.extension().is_some() {
183        // If output_path has an extension, treat it as a file and use its parent
184        output_path.parent().unwrap_or(Path::new("."))
185    } else {
186        output_path
187    };
188
189    generate_memory_analysis_json(output_dir, &processed, options)?;
190    generate_lifetime_json(output_dir, &processed, options)?;
191    generate_thread_analysis_json(output_dir, &snapshot.thread_stats, options)?;
192
193    Ok(())
194}
195
196fn process_allocations(
197    allocations: &[&ActiveAllocation],
198    options: &ExportJsonOptions,
199) -> Result<Vec<serde_json::Value>, Box<dyn std::error::Error>> {
200    if options.parallel_processing && allocations.len() > options.batch_size {
201        let chunk_size = (allocations.len() / num_cpus::get()).max(1);
202        Ok(allocations
203            .par_chunks(chunk_size)
204            .flat_map(process_allocation_batch)
205            .collect())
206    } else {
207        Ok(process_allocation_batch(allocations))
208    }
209}
210
211fn process_allocation_batch(allocations: &[&ActiveAllocation]) -> Vec<serde_json::Value> {
212    let current_time = std::time::SystemTime::now()
213        .duration_since(std::time::UNIX_EPOCH)
214        .map(|d| d.as_nanos() as u64)
215        .unwrap_or(0);
216
217    allocations
218        .iter()
219        .map(|alloc| {
220            let type_info = get_or_compute_type_info(
221                alloc.type_name.as_deref().unwrap_or("unknown"),
222                alloc.size,
223            );
224
225            let lifetime_ms = if alloc.allocated_at > 0 {
226                (current_time.saturating_sub(alloc.allocated_at)) / 1_000_000
227            } else {
228                0
229            };
230
231            let mut entry = json!({
232                "address": format!("0x{:x}", alloc.ptr),
233                "size": alloc.size,
234                "type": type_info,
235                "timestamp": alloc.allocated_at,
236                "thread_id": alloc.thread_id,
237                "lifetime_ms": lifetime_ms,
238            });
239
240            if let Some(ref var_name) = alloc.var_name {
241                entry["var_name"] = serde_json::json!(var_name);
242            }
243
244            if let Some(ref type_name) = alloc.type_name {
245                entry["type_name"] = serde_json::json!(type_name);
246            }
247
248            entry
249        })
250        .collect()
251}
252
253fn get_or_compute_type_info(type_name: &str, size: usize) -> String {
254    // Check for Vec but not VecDeque
255    if (type_name.contains("Vec<") || type_name.contains("vec::Vec<"))
256        && !type_name.contains("VecDeque")
257    {
258        "dynamic_array".to_string()
259    } else if type_name == "str"
260        || type_name == "String"
261        || type_name.contains("&str")
262        || type_name.contains("alloc::string::String")
263    {
264        "string".to_string()
265    } else if type_name.contains("Box") || type_name.contains("Rc") || type_name.contains("Arc") {
266        "smart_pointer".to_string()
267    } else if type_name.contains("[") && type_name.contains("u8") {
268        "byte_array".to_string()
269    } else if size > 1024 * 1024 {
270        "large_buffer".to_string()
271    } else {
272        "custom".to_string()
273    }
274}
275
276fn generate_memory_analysis_json<P: AsRef<Path>>(
277    output_path: P,
278    allocations: &[serde_json::Value],
279    options: &ExportJsonOptions,
280) -> Result<(), Box<dyn std::error::Error>> {
281    let total_size: usize = allocations
282        .iter()
283        .filter_map(|a| a.get("size").and_then(|s| s.as_u64()))
284        .map(|s| s as usize)
285        .sum();
286
287    let type_distribution: HashMap<String, usize> = {
288        let mut dist = HashMap::new();
289        for alloc in allocations {
290            if let Some(t) = alloc.get("type").and_then(|t| t.as_str()) {
291                *dist.entry(t.to_string()).or_insert(0) += 1;
292            }
293        }
294        dist
295    };
296
297    let data = json!({
298        "metadata": {
299            "export_version": "2.0",
300            "export_timestamp": chrono::Utc::now().to_rfc3339(),
301            "specification": "memscope-rs memory analysis",
302            "total_allocations": allocations.len(),
303            "total_size_bytes": total_size
304        },
305        "allocations": allocations,
306        "statistics": {
307            "total_allocations": allocations.len(),
308            "total_size_bytes": total_size,
309            "average_size_bytes": if allocations.is_empty() { 0 } else { total_size / allocations.len() }
310        },
311        "type_distribution": type_distribution
312    });
313
314    let path = output_path.as_ref().join("memory_analysis.json");
315    write_json_optimized(path, &data, options)?;
316    Ok(())
317}
318
319fn generate_lifetime_json<P: AsRef<Path>>(
320    output_path: P,
321    allocations: &[serde_json::Value],
322    options: &ExportJsonOptions,
323) -> Result<(), Box<dyn std::error::Error>> {
324    let ownership_histories: Vec<serde_json::Value> = allocations
325        .iter()
326        .map(|alloc| {
327            json!({
328                "address": alloc.get("address"),
329                "var_name": alloc.get("var_name"),
330                "type_name": alloc.get("type_name"),
331                "size": alloc.get("size"),
332                "timestamp_alloc": alloc.get("timestamp"),
333                "timestamp_dealloc": null,
334                "lifetime_ms": alloc.get("lifetime_ms"),
335                "events": [
336                    {
337                        "event_type": "Created",
338                        "timestamp": alloc.get("timestamp"),
339                        "context": "initial_allocation"
340                    }
341                ]
342            })
343        })
344        .collect();
345
346    let lifetime_data = json!({
347        "metadata": {
348            "export_version": "2.0",
349            "export_timestamp": chrono::Utc::now().to_rfc3339(),
350            "specification": "memscope-rs lifetime tracking",
351            "total_tracked_allocations": ownership_histories.len()
352        },
353        "ownership_histories": ownership_histories
354    });
355
356    let lifetime_path = output_path.as_ref().join("lifetime.json");
357    write_json_optimized(lifetime_path, &lifetime_data, options)?;
358    Ok(())
359}
360
361fn generate_thread_analysis_json<P: AsRef<Path>>(
362    output_path: P,
363    thread_stats: &HashMap<u64, ThreadMemoryStats>,
364    options: &ExportJsonOptions,
365) -> Result<(), Box<dyn std::error::Error>> {
366    let thread_analysis: Vec<serde_json::Value> = thread_stats
367        .values()
368        .map(|stats| {
369            json!({
370                "thread_id": stats.thread_id,
371                "allocation_count": stats.allocation_count,
372                "total_allocated": stats.total_allocated,
373                "current_memory": stats.current_memory,
374                "peak_memory": stats.peak_memory,
375            })
376        })
377        .collect();
378
379    let data = json!({
380        "metadata": {
381            "export_version": "2.0",
382            "export_timestamp": chrono::Utc::now().to_rfc3339(),
383            "specification": "thread analysis",
384            "total_threads": thread_analysis.len()
385        },
386        "thread_analysis": thread_analysis
387    });
388
389    let path = output_path.as_ref().join("thread_analysis.json");
390    write_json_optimized(path, &data, options)?;
391    Ok(())
392}
393
394fn write_json_optimized<P: AsRef<Path>>(
395    path: P,
396    data: &serde_json::Value,
397    options: &ExportJsonOptions,
398) -> Result<(), Box<dyn std::error::Error>> {
399    let path = path.as_ref();
400
401    let estimated_size = estimate_json_size(data);
402    let use_compact = options
403        .use_compact_format
404        .unwrap_or(estimated_size > 1_000_000);
405
406    if options.streaming_writer && estimated_size > 500_000 {
407        let file = File::create(path)?;
408        let mut writer = BufWriter::with_capacity(options.buffer_size, file);
409
410        if use_compact {
411            serde_json::to_writer(&mut writer, data)?;
412        } else {
413            serde_json::to_writer_pretty(&mut writer, data)?;
414        }
415
416        writer.flush()?;
417    } else {
418        let json_string = if use_compact {
419            serde_json::to_string(data)?
420        } else {
421            serde_json::to_string_pretty(data)?
422        };
423        std::fs::write(path, json_string)?;
424    }
425
426    Ok(())
427}
428
429fn estimate_json_size(data: &serde_json::Value) -> usize {
430    match data {
431        serde_json::Value::Object(map) => {
432            map.values().map(estimate_json_size).sum::<usize>() + map.len() * 20
433        }
434        serde_json::Value::Array(arr) => {
435            arr.iter().map(estimate_json_size).sum::<usize>() + arr.len() * 10
436        }
437        serde_json::Value::String(s) => s.len(),
438        serde_json::Value::Number(n) => n.to_string().len(),
439        _ => 10,
440    }
441}
442
443#[derive(Debug, thiserror::Error)]
444pub enum ExportError {
445    #[error("IO error: {0}")]
446    Io(#[from] std::io::Error),
447
448    #[error("JSON error: {0}")]
449    Json(#[from] serde_json::Error),
450
451    #[error("Export failed: {0}")]
452    ExportFailed(String),
453}
454
455pub fn export_all_json<P: AsRef<Path>>(
456    path: P,
457    tracker: &Tracker,
458    passport_tracker: &Arc<MemoryPassportTracker>,
459    async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
460) -> MemScopeResult<()> {
461    let path_ref = path.as_ref();
462
463    let allocations = tracker.inner().get_active_allocations().unwrap_or_default();
464    let snapshot = MemorySnapshot::from_allocation_infos(allocations.clone());
465    let options = ExportJsonOptions::default();
466
467    std::fs::create_dir_all(path_ref)
468        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
469
470    export_snapshot_to_json(&snapshot, path_ref, &options)
471        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
472
473    export_memory_passports_json(path_ref, passport_tracker)
474        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
475    export_leak_detection_json(path_ref, passport_tracker)
476        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
477    export_unsafe_ffi_json(path_ref, passport_tracker)
478        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
479    export_system_resources_json(path_ref)
480        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
481    export_async_analysis_json(path_ref, async_tracker)
482        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
483    // Convert core_types::AllocationInfo to types::AllocationInfo for ownership graph export
484    let typed_allocations: Vec<crate::capture::types::AllocationInfo> =
485        allocations.clone().into_iter().map(|a| a.into()).collect();
486    export_ownership_graph_json(path_ref, &typed_allocations)
487        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
488
489    Ok(())
490}
491
492/// Export async task analysis to JSON
493pub fn export_async_analysis_json<P: AsRef<Path>>(
494    path: P,
495    async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
496) -> MemScopeResult<()> {
497    let path_ref = path.as_ref();
498    let stats = async_tracker.get_stats();
499    let profiles = async_tracker.get_all_profiles();
500    let snapshot = async_tracker.snapshot();
501
502    let async_data = json!({
503        "summary": {
504            "total_tasks": stats.total_tasks,
505            "active_tasks": stats.active_tasks,
506            "total_allocations": stats.total_allocations,
507            "total_memory_bytes": stats.total_memory,
508            "active_memory_bytes": stats.active_memory,
509            "peak_memory_bytes": stats.peak_memory,
510        },
511        "task_profiles": profiles.iter().map(|p| json!({
512            "task_id": p.task_id,
513            "task_name": p.task_name,
514            "task_type": format!("{:?}", p.task_type),
515            "created_at_ms": p.created_at_ms,
516            "completed_at_ms": p.completed_at_ms,
517            "total_bytes": p.total_bytes,
518            "current_memory": p.current_memory,
519            "peak_memory": p.peak_memory,
520            "total_allocations": p.total_allocations,
521            "total_deallocations": p.total_deallocations,
522            "duration_ns": p.duration_ns,
523            "allocation_rate": p.allocation_rate,
524            "efficiency_score": p.efficiency_score,
525            "average_allocation_size": p.average_allocation_size,
526            "is_completed": p.is_completed(),
527            "has_potential_leak": p.has_potential_leak(),
528        })).collect::<Vec<_>>(),
529        "allocations": snapshot.allocations.iter().map(|a| json!({
530            "ptr": format!("0x{:x}", a.ptr),
531            "size": a.size,
532            "timestamp": a.timestamp,
533            "task_id": a.task_id,
534            "var_name": a.var_name,
535            "type_name": a.type_name,
536        })).collect::<Vec<_>>(),
537    });
538
539    let async_path = path_ref.join("async_analysis.json");
540    let file = File::create(async_path)
541        .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
542    let mut writer = BufWriter::new(file);
543    serde_json::to_writer_pretty(&mut writer, &async_data)
544        .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
545    writer
546        .flush()
547        .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
548
549    Ok(())
550}
551
552/// Dashboard template type
553#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
554pub enum DashboardTemplate {
555    /// Unified dashboard (multi-mode in single HTML)
556    #[default]
557    Unified,
558    /// Final dashboard (new investigation console)
559    // #[default]
560    Final,
561}
562
563impl std::fmt::Display for DashboardTemplate {
564    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
565        match self {
566            DashboardTemplate::Unified => write!(f, "dashboard_unified"),
567            DashboardTemplate::Final => write!(f, "dashboard_final"),
568        }
569    }
570}
571
572/// Export HTML dashboard from tracker data
573///
574/// This function generates a complete HTML dashboard from the tracker data,
575/// including memory analysis, variable relationships, unsafe/FFI tracking,
576/// and system resources. The dashboard is rendered using Handlebars templates.
577pub fn export_dashboard_html<P: AsRef<Path>>(
578    path: P,
579    tracker: &Tracker,
580    passport_tracker: &Arc<MemoryPassportTracker>,
581) -> MemScopeResult<()> {
582    export_dashboard_html_with_template(
583        path,
584        tracker,
585        passport_tracker,
586        DashboardTemplate::default(),
587        None,
588    )
589}
590
591/// Export HTML dashboard with async tracker support
592pub fn export_dashboard_html_with_async<P: AsRef<Path>>(
593    path: P,
594    tracker: &Tracker,
595    passport_tracker: &Arc<MemoryPassportTracker>,
596    async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
597) -> MemScopeResult<()> {
598    export_dashboard_html_with_template(
599        path,
600        tracker,
601        passport_tracker,
602        DashboardTemplate::default(),
603        Some(async_tracker),
604    )
605}
606
607/// Export HTML dashboard with specific template
608///
609/// This function generates a complete HTML dashboard from the tracker data
610/// using the specified template type.
611pub fn export_dashboard_html_with_template<P: AsRef<Path>>(
612    path: P,
613    tracker: &Tracker,
614    passport_tracker: &Arc<MemoryPassportTracker>,
615    template: DashboardTemplate,
616    async_tracker: Option<&Arc<crate::capture::backends::async_tracker::AsyncTracker>>,
617) -> MemScopeResult<()> {
618    let path_ref = path.as_ref();
619
620    // Create output directory if it doesn't exist
621    std::fs::create_dir_all(path_ref).map_err(|e| {
622        MemScopeError::error(
623            "export",
624            "export_dashboard_html_with_template",
625            format!("Failed to create output directory: {}", e),
626        )
627    })?;
628
629    // Create dashboard renderer
630    let renderer = DashboardRenderer::new().map_err(|e| {
631        MemScopeError::error(
632            "export",
633            "export_dashboard_html_with_template",
634            format!("Failed to create dashboard renderer: {}", e),
635        )
636    })?;
637
638    // Render HTML from tracker data using selected template
639    let context = renderer
640        .build_context_from_tracker_with_async(tracker, passport_tracker, async_tracker)
641        .map_err(|e| {
642            MemScopeError::error(
643                "export",
644                "export_dashboard_html_with_template",
645                format!("Failed to build context: {}", e),
646            )
647        })?;
648
649    let html_content = match template {
650        DashboardTemplate::Final => renderer.render_final_dashboard(&context).map_err(|e| {
651            MemScopeError::error(
652                "export",
653                "export_dashboard_html_with_template",
654                format!("Failed to render final dashboard: {}", e),
655            )
656        })?,
657        DashboardTemplate::Unified => renderer.render_unified_dashboard(&context).map_err(|e| {
658            MemScopeError::error(
659                "export",
660                "export_dashboard_html_with_template",
661                format!("Failed to render dashboard: {}", e),
662            )
663        })?,
664    };
665
666    // Write HTML to file
667    let output_file = path_ref.join(format!("{}_dashboard.html", template));
668    std::fs::write(&output_file, html_content).map_err(|e| {
669        MemScopeError::error(
670            "export",
671            "export_dashboard_html_with_template",
672            format!("Failed to write HTML file: {}", e),
673        )
674    })?;
675
676    tracing::info!("✅ Dashboard HTML exported to: {:?}", output_file);
677
678    Ok(())
679}
680
681pub fn export_memory_passports_json<P: AsRef<Path>>(
682    base_path: P,
683    passport_tracker: &Arc<MemoryPassportTracker>,
684) -> MemScopeResult<()> {
685    let base_path = base_path.as_ref();
686    let passports = passport_tracker.get_all_passports();
687
688    let passport_data: Vec<_> = passports
689        .values()
690        .map(|p| {
691            serde_json::json!({
692                "passport_id": p.passport_id,
693                "allocation_ptr": format!("0x{:x}", p.allocation_ptr),
694                "size_bytes": p.size_bytes,
695                "created_at": p.created_at,
696                "lifecycle_events": p.lifecycle_events.len(),
697                "status": format!("{:?}", p.status_at_shutdown),
698            })
699        })
700        .collect();
701
702    let json_data = serde_json::json!({
703        "metadata": {
704            "export_version": "2.0",
705            "specification": "memory passport tracking",
706            "total_passports": passports.len()
707        },
708        "memory_passports": passport_data,
709    });
710
711    let file_path = base_path.join("memory_passports.json");
712    let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
713        MemScopeError::error("export", "export_memory_passports_json", e.to_string())
714    })?;
715    std::fs::write(&file_path, json_string).map_err(|e| {
716        MemScopeError::error("export", "export_memory_passports_json", e.to_string())
717    })?;
718
719    Ok(())
720}
721
722pub fn export_leak_detection_json<P: AsRef<Path>>(
723    base_path: P,
724    passport_tracker: &Arc<MemoryPassportTracker>,
725) -> MemScopeResult<()> {
726    let base_path = base_path.as_ref();
727    let leak_result = passport_tracker.detect_leaks_at_shutdown();
728
729    let leak_details: Vec<_> = leak_result
730        .leak_details
731        .iter()
732        .map(|detail| {
733            serde_json::json!({
734                "passport_id": detail.passport_id,
735                "memory_address": format!("0x{:x}", detail.memory_address),
736                "size_bytes": detail.size_bytes,
737                "lifecycle_summary": detail.lifecycle_summary,
738            })
739        })
740        .collect();
741
742    let json_data = serde_json::json!({
743        "metadata": {
744            "export_version": "2.0",
745            "specification": "leak detection",
746            "leaks_detected": leak_result.total_leaks
747        },
748        "leak_detection": {
749            "total_leaks": leak_result.total_leaks,
750            "leak_details": leak_details
751        }
752    });
753
754    let file_path = base_path.join("leak_detection.json");
755    let json_string = serde_json::to_string_pretty(&json_data)
756        .map_err(|e| MemScopeError::error("export", "export_leak_detection_json", e.to_string()))?;
757    std::fs::write(&file_path, json_string)
758        .map_err(|e| MemScopeError::error("export", "export_leak_detection_json", e.to_string()))?;
759
760    Ok(())
761}
762
763pub fn export_unsafe_ffi_json<P: AsRef<Path>>(
764    base_path: P,
765    passport_tracker: &Arc<MemoryPassportTracker>,
766) -> MemScopeResult<()> {
767    use crate::analysis::memory_passport_tracker::PassportStatus;
768
769    let base_path = base_path.as_ref();
770    let passports = passport_tracker.get_all_passports();
771
772    let ffi_reports: Vec<_> = passports
773        .values()
774        .filter(|p| {
775            matches!(
776                p.status_at_shutdown,
777                PassportStatus::HandoverToFfi
778                    | PassportStatus::InForeignCustody
779                    | PassportStatus::FreedByForeign
780            )
781        })
782        .map(|p| {
783            serde_json::json!({
784                "passport_id": p.passport_id,
785                "allocation_ptr": format!("0x{:x}", p.allocation_ptr),
786                "size_bytes": p.size_bytes,
787                "status": format!("{:?}", p.status_at_shutdown),
788                "created_at": p.created_at,
789                "boundary_events": p.lifecycle_events.iter().map(|e| {
790                    serde_json::json!({
791                        "timestamp": e.timestamp,
792                        "event_type": format!("{:?}", e.event_type),
793                        "context": e.context,
794                    })
795                }).collect::<Vec<_>>(),
796            })
797        })
798        .collect();
799
800    let json_data = serde_json::json!({
801        "metadata": {
802            "export_version": "2.0",
803            "specification": "unsafe FFI tracking",
804            "total_ffi_reports": ffi_reports.len(),
805            "total_memory_passports": passports.len()
806        },
807        "unsafe_reports": ffi_reports,
808        "memory_passports": passports.len()
809    });
810
811    let file_path = base_path.join("unsafe_ffi.json");
812    let json_string = serde_json::to_string_pretty(&json_data)
813        .map_err(|e| MemScopeError::error("export", "export_unsafe_ffi_json", e.to_string()))?;
814    std::fs::write(&file_path, json_string)
815        .map_err(|e| MemScopeError::error("export", "export_unsafe_ffi_json", e.to_string()))?;
816
817    Ok(())
818}
819
820/// Export system resource monitoring data to JSON
821///
822/// This function exports comprehensive system resource statistics including:
823/// - Memory statistics (virtual, physical, process, system)
824/// - CPU information (cores, cache, system info)
825/// - Memory pressure indicators
826pub fn export_system_resources_json<P: AsRef<Path>>(base_path: P) -> MemScopeResult<()> {
827    let base_path = base_path.as_ref();
828
829    // Collect memory statistics
830    let mut memory_info = PlatformMemoryInfo::new();
831    let _ = memory_info.initialize();
832
833    let memory_stats = match memory_info.collect_stats() {
834        Ok(stats) => stats,
835        Err(e) => {
836            eprintln!("Warning: Failed to collect memory stats: {}", e);
837            return Err(MemScopeError::error(
838                "export",
839                "export_system_resources_json",
840                e.to_string(),
841            ));
842        }
843    };
844
845    // Collect system information
846    let system_info = match memory_info.get_system_info() {
847        Ok(info) => info,
848        Err(e) => {
849            eprintln!("Warning: Failed to collect system info: {}", e);
850            return Err(MemScopeError::error(
851                "export",
852                "export_system_resources_json",
853                e.to_string(),
854            ));
855        }
856    };
857
858    // Build JSON structure
859    let json_data = serde_json::json!({
860        "metadata": {
861            "export_version": "2.0",
862            "specification": "system resource monitoring",
863            "timestamp": std::time::SystemTime::now()
864                .duration_since(std::time::UNIX_EPOCH)
865                .unwrap_or_default()
866                .as_secs()
867        },
868        "system_info": {
869            "os_name": system_info.os_name,
870            "os_version": system_info.os_version,
871            "architecture": system_info.architecture,
872            "cpu_cores": system_info.cpu_cores,
873            "page_size": system_info.page_size,
874            "large_page_size": system_info.large_page_size,
875            "cpu_cache": {
876                "l1_cache_size": system_info.cpu_cache.l1_cache_size,
877                "l2_cache_size": system_info.cpu_cache.l2_cache_size,
878                "l3_cache_size": system_info.cpu_cache.l3_cache_size,
879                "cache_line_size": system_info.cpu_cache.cache_line_size
880            },
881            "mmu_info": {
882                "virtual_address_bits": system_info.mmu_info.virtual_address_bits,
883                "physical_address_bits": system_info.mmu_info.physical_address_bits,
884                "aslr_enabled": system_info.mmu_info.aslr_enabled,
885                "nx_bit_supported": system_info.mmu_info.nx_bit_supported
886            }
887        },
888        "memory_stats": {
889            "virtual_memory": {
890                "total_virtual": memory_stats.virtual_memory.total_virtual,
891                "available_virtual": memory_stats.virtual_memory.available_virtual,
892                "used_virtual": memory_stats.virtual_memory.used_virtual,
893                "reserved": memory_stats.virtual_memory.reserved,
894                "committed": memory_stats.virtual_memory.committed
895            },
896            "physical_memory": {
897                "total_physical": memory_stats.physical_memory.total_physical,
898                "available_physical": memory_stats.physical_memory.available_physical,
899                "used_physical": memory_stats.physical_memory.used_physical,
900                "cached": memory_stats.physical_memory.cached,
901                "buffers": memory_stats.physical_memory.buffers,
902                "swap": {
903                    "total_swap": memory_stats.physical_memory.swap.total_swap,
904                    "used_swap": memory_stats.physical_memory.swap.used_swap,
905                    "available_swap": memory_stats.physical_memory.swap.available_swap,
906                    "swap_in_rate": memory_stats.physical_memory.swap.swap_in_rate,
907                    "swap_out_rate": memory_stats.physical_memory.swap.swap_out_rate
908                }
909            },
910            "process_memory": {
911                "virtual_size": memory_stats.process_memory.virtual_size,
912                "resident_size": memory_stats.process_memory.resident_size,
913                "shared_size": memory_stats.process_memory.shared_size,
914                "private_size": memory_stats.process_memory.private_size,
915                "heap_size": memory_stats.process_memory.heap_size,
916                "stack_size": memory_stats.process_memory.stack_size,
917                "mapped_files": memory_stats.process_memory.mapped_files,
918                "peak_usage": memory_stats.process_memory.peak_usage
919            },
920            "system_memory": {
921                "allocation_count": memory_stats.system_memory.allocation_count,
922                "deallocation_count": memory_stats.system_memory.deallocation_count,
923                "active_allocations": memory_stats.system_memory.active_allocations,
924                "total_allocated": memory_stats.system_memory.total_allocated,
925                "total_deallocated": memory_stats.system_memory.total_deallocated,
926                "fragmentation_level": memory_stats.system_memory.fragmentation_level,
927                "large_pages": {
928                    "supported": memory_stats.system_memory.large_pages.supported,
929                    "total_large_pages": memory_stats.system_memory.large_pages.total_large_pages,
930                    "used_large_pages": memory_stats.system_memory.large_pages.used_large_pages,
931                    "page_size": memory_stats.system_memory.large_pages.page_size
932                }
933            },
934            "pressure_indicators": {
935                "pressure_level": format!("{:?}", memory_stats.pressure_indicators.pressure_level),
936                "low_memory": memory_stats.pressure_indicators.low_memory,
937                "swapping_active": memory_stats.pressure_indicators.swapping_active,
938                "allocation_failure_rate": memory_stats.pressure_indicators.allocation_failure_rate,
939                "gc_pressure": memory_stats.pressure_indicators.gc_pressure
940            }
941        }
942    });
943
944    let file_path = base_path.join("system_resources.json");
945    let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
946        MemScopeError::error("export", "export_system_resources_json", e.to_string())
947    })?;
948    std::fs::write(&file_path, json_string).map_err(|e| {
949        MemScopeError::error("export", "export_system_resources_json", e.to_string())
950    })?;
951
952    Ok(())
953}
954
955/// Export ownership graph analysis to JSON
956///
957/// This function exports the ownership graph including:
958/// - Node information (objects with their types and sizes)
959/// - Edge information (clone relationships)
960/// - Detected cycles (Rc/Arc retain cycles)
961/// - Diagnostics (clone storms, cycle warnings)
962pub fn export_ownership_graph_json<P: AsRef<Path>>(
963    base_path: P,
964    allocations: &[crate::capture::types::AllocationInfo],
965) -> MemScopeResult<()> {
966    let base_path = base_path.as_ref();
967
968    // Build ownership graph from allocations
969    let graph = build_ownership_graph_from_allocations(allocations);
970
971    // Get diagnostics
972    let diagnostics = graph.diagnostics(50);
973
974    // Convert nodes to JSON
975    let nodes_json: Vec<_> = graph
976        .nodes
977        .iter()
978        .map(|node| {
979            json!({
980                "id": format!("0x{:x}", node.id.0),
981                "type_name": node.type_name,
982                "size": node.size,
983            })
984        })
985        .collect();
986
987    // Convert edges to JSON
988    let edges_json: Vec<_> = graph
989        .edges
990        .iter()
991        .map(|edge| {
992            json!({
993                "from": format!("0x{:x}", edge.from.0),
994                "to": format!("0x{:x}", edge.to.0),
995                "kind": match edge.op {
996                    EdgeKind::Owns => "Owns",
997                    EdgeKind::Borrows => "Borrows",
998                    EdgeKind::RcClone => "RcClone",
999                    EdgeKind::ArcClone => "ArcClone",
1000                },
1001            })
1002        })
1003        .collect();
1004
1005    // Convert cycles to JSON
1006    let cycles_json: Vec<_> = graph
1007        .cycles
1008        .iter()
1009        .map(|cycle| {
1010            let nodes: Vec<_> = cycle.iter().map(|id| format!("0x{:x}", id.0)).collect();
1011            json!({
1012                "nodes": nodes,
1013            })
1014        })
1015        .collect();
1016
1017    // Convert issues to JSON
1018    let issues_json: Vec<_> = diagnostics
1019        .issues
1020        .iter()
1021        .map(|issue| match issue {
1022            crate::analysis::ownership_graph::DiagnosticIssue::RcCycle { nodes, cycle_type } => {
1023                json!({
1024                    "type": "RcCycle",
1025                    "cycle_type": format!("{:?}", cycle_type),
1026                    "nodes": nodes.iter().map(|id| format!("0x{:x}", id.0)).collect::<Vec<_>>(),
1027                    "severity": "error",
1028                })
1029            }
1030            crate::analysis::ownership_graph::DiagnosticIssue::ArcCloneStorm {
1031                clone_count,
1032                threshold,
1033            } => {
1034                json!({
1035                    "type": "ArcCloneStorm",
1036                    "clone_count": clone_count,
1037                    "threshold": threshold,
1038                    "severity": "warning",
1039                })
1040            }
1041        })
1042        .collect();
1043
1044    // Build root cause info
1045    let root_cause_json = graph.find_root_cause().map(|rc| {
1046        json!({
1047            "cause": match rc.root_cause {
1048                crate::analysis::ownership_graph::RootCause::ArcCloneStorm => "ArcCloneStorm",
1049                crate::analysis::ownership_graph::RootCause::RcCycle => "RcCycle",
1050            },
1051            "description": rc.description,
1052            "impact": rc.impact,
1053        })
1054    });
1055
1056    let json_data = json!({
1057        "metadata": {
1058            "export_version": "2.0",
1059            "specification": "ownership graph analysis",
1060            "timestamp": std::time::SystemTime::now()
1061                .duration_since(std::time::UNIX_EPOCH)
1062                .unwrap_or_default()
1063                .as_secs()
1064        },
1065        "summary": {
1066            "total_nodes": graph.nodes.len(),
1067            "total_edges": graph.edges.len(),
1068            "total_cycles": graph.cycles.len(),
1069            "rc_clone_count": diagnostics.rc_clone_count,
1070            "arc_clone_count": diagnostics.arc_clone_count,
1071            "has_issues": diagnostics.has_issues(),
1072        },
1073        "nodes": nodes_json,
1074        "edges": edges_json,
1075        "cycles": cycles_json,
1076        "diagnostics": {
1077            "issues": issues_json,
1078            "root_cause": root_cause_json,
1079        },
1080    });
1081
1082    let file_path = base_path.join("ownership_graph.json");
1083    let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
1084        MemScopeError::error("export", "export_ownership_graph_json", e.to_string())
1085    })?;
1086    std::fs::write(&file_path, json_string).map_err(|e| {
1087        MemScopeError::error("export", "export_ownership_graph_json", e.to_string())
1088    })?;
1089
1090    Ok(())
1091}
1092
1093/// Build ownership graph from allocation data
1094fn build_ownership_graph_from_allocations(
1095    allocations: &[crate::capture::types::AllocationInfo],
1096) -> OwnershipGraph {
1097    use crate::analysis::relation_inference::{Relation, RelationGraphBuilder};
1098
1099    // Convert allocations to passport format for graph building
1100    let passports: Vec<(
1101        ObjectId,
1102        String,
1103        usize,
1104        Vec<crate::analysis::ownership_graph::OwnershipEvent>,
1105    )> = allocations
1106        .iter()
1107        .map(|alloc| {
1108            let id = ObjectId::from_ptr(alloc.ptr);
1109            let type_name = alloc
1110                .type_name
1111                .clone()
1112                .unwrap_or_else(|| "unknown".to_string());
1113            let size = alloc.size;
1114
1115            // Generate ownership events from allocation info
1116            let events = vec![crate::analysis::ownership_graph::OwnershipEvent::new(
1117                alloc.timestamp_alloc,
1118                OwnershipOp::Create,
1119                id,
1120                None,
1121            )];
1122
1123            // Detect smart pointer clones from type name
1124            if type_name.contains("Arc<") || type_name.contains("Rc<") {
1125                // Check for clone patterns
1126                // Note: In real tracking, clone events would be recorded at runtime
1127                // Here we infer from type and allocation patterns
1128            }
1129
1130            (id, type_name, size, events)
1131        })
1132        .collect();
1133
1134    let mut graph = OwnershipGraph::build(&passports);
1135
1136    // Use RelationGraphBuilder to detect relationships and add edges
1137    let active_allocations: Vec<ActiveAllocation> = allocations
1138        .iter()
1139        .filter(|a| a.timestamp_dealloc.is_none())
1140        .map(|a| ActiveAllocation {
1141            ptr: a.ptr,
1142            size: a.size,
1143            allocated_at: a.timestamp_alloc,
1144            var_name: a.var_name.clone(),
1145            type_name: a.type_name.clone(),
1146            thread_id: 0,
1147            call_stack_hash: None,
1148        })
1149        .collect();
1150
1151    let relation_graph = RelationGraphBuilder::build(&active_allocations, None);
1152
1153    // Add edges from relation inference
1154    for edge in &relation_graph.edges {
1155        let from_id = ObjectId(edge.from as u64);
1156        let to_id = ObjectId(edge.to as u64);
1157
1158        let edge_kind = match edge.relation {
1159            Relation::Owner => EdgeKind::Owns,
1160            Relation::Slice => EdgeKind::Borrows,
1161            Relation::Clone => EdgeKind::RcClone,
1162            Relation::Shared => EdgeKind::ArcClone,
1163        };
1164
1165        graph.edges.push(crate::analysis::ownership_graph::Edge {
1166            from: from_id,
1167            to: to_id,
1168            op: edge_kind,
1169        });
1170    }
1171
1172    graph
1173}