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 tracing::{debug, warn};
7
8use crate::analysis::memory_passport_tracker::MemoryPassportTracker;
9use crate::capture::platform::memory_info::PlatformMemoryInfo;
10use crate::core::{MemScopeError, MemScopeResult};
11use crate::render_engine::dashboard::{rebuild_allocations_from_events, DashboardRenderer};
12use crate::tracker::Tracker;
13use serde_json::json;
14use std::{
15    fs::File,
16    io::{BufWriter, Write},
17    path::Path,
18    sync::Arc,
19};
20
21mod ownership;
22mod snapshot_json;
23mod task_graph;
24pub use ownership::{export_ownership_graph_json, export_ownership_graph_snapshot};
25pub use snapshot_json::{
26    export_snapshot_to_json, export_snapshot_with_containers, ExportError, ExportJsonOptions,
27    OptimizationLevel, SchemaValidator,
28};
29pub use task_graph::{
30    export_task_graph_json, export_task_graph_json_with_accounting, export_task_graph_snapshot,
31};
32
33/// Nanosecond wall-clock stamp for the immutable event projections.
34fn capture_cut_ns() -> u64 {
35    std::time::SystemTime::now()
36        .duration_since(std::time::UNIX_EPOCH)
37        .map(|elapsed| elapsed.as_nanos().min(u128::from(u64::MAX)) as u64)
38        .unwrap_or(0)
39}
40
41pub fn export_all_json<P: AsRef<Path>>(
42    path: P,
43    tracker: &Tracker,
44    passport_tracker: &Arc<MemoryPassportTracker>,
45    async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
46) -> MemScopeResult<()> {
47    // Suppress allocator tracking during the entire export. The export path
48    // performs large amounts of allocation (event replay, JSON serialisation,
49    // HTML rendering) that are not user allocations. Without suppression,
50    // these self-allocations enter the event stream, creating a feedback loop
51    // that can inflate memory usage to many GB and run for minutes.
52    //
53    // `TrackingGuard::acquire` sets a thread-local flag that the custom
54    // allocator checks before recording. The guard restores the flag on drop,
55    // so tracking resumes immediately after the export completes.
56    let _suppression = crate::core::allocator::TrackingGuard::acquire();
57
58    let path_ref = path.as_ref();
59
60    // Use event_store as unified data source (includes both HeapOwner and Container allocations)
61    let events = tracker.event_store().snapshot();
62    // Auxiliary metadata sources have independent locks. Freeze each once before I/O;
63    // event-derived counters always use `events`, not these independently sampled caches.
64    let passports = passport_tracker.snapshot_passports();
65    let mut task_graph = crate::task_registry::global_registry().export_graph();
66    let async_metadata = crate::capture::backends::profile_replay::ProfileMetadata::capture(
67        &task_graph,
68        Some(async_tracker),
69    );
70    async_metadata.reconcile_graph(&mut task_graph);
71    let captured_at = capture_cut_ns();
72    let async_rebuilt = crate::capture::backends::profile_replay::rebuild_at(
73        &events,
74        &task_graph,
75        &async_metadata,
76        captured_at,
77    );
78    let borrow_history =
79        crate::analysis::borrow_analysis::get_global_borrow_analyzer().get_borrow_history();
80
81    // Replay once and project that single cut: the ledger keeps the transition-based
82    // totals and peaks that the live list can no longer reconstruct, and the projection
83    // keeps every identity (including overlapping generations) so the rows and the
84    // counters describe the same thing.
85    let mut ledger = crate::snapshot::BindingLedger::new();
86    for event in &events {
87        ledger.apply(event);
88    }
89    let snapshot = crate::snapshot::builder::project_snapshot(&ledger, captured_at);
90    let options = ExportJsonOptions::default();
91
92    std::fs::create_dir_all(path_ref)
93        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
94
95    debug!("Starting export_snapshot_to_json");
96
97    // Container (stack-side) metadata from the same event slice; it is exported next to
98    // the heap rows but never counted in the statistics.
99    let containers: Vec<crate::capture::types::AllocationInfo> =
100        rebuild_allocations_from_events(&events)
101            .into_iter()
102            .filter(|allocation| allocation.ptr >= crate::analysis::VIRTUAL_PTR_BASE)
103            .collect();
104
105    export_snapshot_with_containers(&snapshot, &containers, path_ref, &options)
106        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
107
108    debug!("Completed export_snapshot_to_json");
109
110    debug!("Starting export_memory_passports_json");
111
112    write_memory_passports_json(path_ref, passport_tracker, &passports)
113        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
114
115    debug!("Completed export_memory_passports_json");
116
117    debug!("Starting export_leak_detection_json");
118
119    write_leak_detection_json(path_ref, &passport_tracker.detect_leaks_in(&passports))
120        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
121
122    debug!("Completed export_leak_detection_json");
123
124    debug!("Starting export_unsafe_ffi_json");
125
126    write_unsafe_ffi_json(path_ref, passport_tracker, &passports)
127        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
128
129    debug!("Completed export_unsafe_ffi_json");
130
131    debug!("Starting export_system_resources_json");
132
133    export_system_resources_json(path_ref)
134        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
135
136    debug!("Completed export_system_resources_json");
137
138    debug!("Starting export_async_analysis_json");
139
140    write_async_analysis_json(path_ref, async_rebuilt)
141        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
142
143    debug!("Completed export_async_analysis_json");
144
145    debug!("Starting export_ownership_graph_json");
146
147    // Same event slice as the snapshot above, so still one cut. Container rows keep the
148    // virtual ids that `rebuild_allocations_from_events` assigns them; the graph builder
149    // recognises those ids and never dereferences them.
150    let typed_allocations: Vec<crate::capture::types::AllocationInfo> =
151        rebuild_allocations_from_events(&events);
152
153    debug!(
154        allocations = typed_allocations.len(),
155        "Converted allocations to typed format"
156    );
157
158    export_ownership_graph_snapshot(
159        path_ref,
160        &typed_allocations,
161        &events,
162        &borrow_history,
163        captured_at,
164    )
165    .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
166
167    debug!("Completed export_ownership_graph_json");
168
169    // Export task graph JSON
170    export_task_graph_snapshot(path_ref, &events, Some(&task_graph))
171        .map_err(|e| MemScopeError::error("export", "export_all_json", e.to_string()))?;
172
173    debug!("Completed export_task_graph_json");
174
175    debug!("All exports completed successfully");
176
177    Ok(())
178}
179
180/// Export async task analysis to JSON
181///
182/// This function rebuilds task profiles from the event stream rather than
183/// reading live `AsyncTracker` state. This ensures the export always sees
184/// a consistent, complete snapshot regardless of concurrent tracking
185/// activity (no lock contention, no data loss from `try_lock` failures).
186pub fn export_async_analysis_json<P: AsRef<Path>>(
187    path: P,
188    events: &[crate::event_store::MemoryEvent],
189    task_graph: &crate::task_registry::TaskGraph,
190    async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
191) -> MemScopeResult<()> {
192    // Suppress allocator tracking during the entire export. The JSON
193    // serialisation and event replay perform many internal allocations
194    // that are not user allocations. Without suppression, these
195    // self-allocations enter the event stream, creating a feedback loop
196    // that inflates memory usage and corrupts the exported statistics.
197    let _suppression = crate::core::allocator::TrackingGuard::acquire();
198
199    let path_ref = path.as_ref();
200
201    // Rebuild profiles from the event stream, merging live tracker metadata
202    // (task names, completion times, creation timestamps) in one pass.
203    let rebuilt =
204        crate::capture::backends::rebuild_with_live_tracker(events, task_graph, async_tracker);
205    write_async_analysis_json(path_ref, rebuilt)
206}
207
208fn write_async_analysis_json(
209    path_ref: &Path,
210    rebuilt: crate::capture::backends::profile_replay::RebuiltProfiles,
211) -> MemScopeResult<()> {
212    let profiles = rebuilt.profiles;
213    let live_allocations = rebuilt.live_allocations;
214
215    let total_tasks = profiles.len() as u64;
216    let active_tasks = profiles.iter().filter(|p| !p.is_completed()).count() as u64;
217
218    let async_data = json!({
219        "summary": {
220            "total_tasks": total_tasks,
221            "active_tasks": active_tasks,
222            "total_allocations": rebuilt.total_allocations,
223            "total_memory_bytes": rebuilt.total_memory,
224            "active_memory_bytes": rebuilt.current_memory,
225            "peak_memory_bytes": rebuilt.global_peak_memory,
226        },
227        "task_profiles": profiles.iter().map(|p| json!({
228            "task_id": p.task_id,
229            "task_name": p.task_name,
230            "task_type": format!("{:?}", p.task_type),
231            "created_at_ms": p.created_at_ms,
232            "completed_at_ms": p.completed_at_ms,
233            "total_bytes": p.total_bytes,
234            "current_memory": p.current_memory,
235            "peak_memory": p.peak_memory,
236            "total_allocations": p.total_allocations,
237            "total_deallocations": p.total_deallocations,
238            "duration_ns": p.duration_ns,
239            "allocation_rate": p.allocation_rate,
240            "efficiency_score": p.efficiency_score,
241            "average_allocation_size": p.average_allocation_size,
242            "is_completed": p.is_completed(),
243            "has_potential_leak": p.has_potential_leak(),
244        })).collect::<Vec<_>>(),
245        "allocations": live_allocations.iter().map(|a| json!({
246            "ptr": format!("0x{:x}", a.ptr),
247            "size": a.size,
248            "timestamp": a.timestamp,
249            "task_id": a.task_id,
250            "var_name": a.var_name,
251            "type_name": a.type_name,
252        })).collect::<Vec<_>>(),
253    });
254
255    let async_path = path_ref.join("async_analysis.json");
256    let file = File::create(async_path)
257        .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
258    let mut writer = BufWriter::new(file);
259    serde_json::to_writer_pretty(&mut writer, &async_data)
260        .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
261    writer
262        .flush()
263        .map_err(|e| MemScopeError::error("export", "export_async_analysis_json", e.to_string()))?;
264
265    Ok(())
266}
267
268/// Dashboard template type — the single merged template id, plus a Custom variant
269/// for registry-based rendering of external templates.
270#[derive(Debug, Clone, PartialEq, Eq, Default)]
271pub enum DashboardTemplate {
272    /// Unified merged dashboard (default) — bundles all 8 modes in one HTML file
273    #[default]
274    Unified,
275    /// Custom template by ID (for registry-based rendering of external templates)
276    Custom(String),
277}
278
279impl std::fmt::Display for DashboardTemplate {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        match self {
282            DashboardTemplate::Unified => write!(f, "dashboard_unified"),
283            DashboardTemplate::Custom(id) => write!(f, "{}", id),
284        }
285    }
286}
287
288impl DashboardTemplate {
289    /// Get the template ID used by the Handlebars registry
290    pub fn template_id(&self) -> &str {
291        match self {
292            DashboardTemplate::Unified => "dashboard_unified",
293            DashboardTemplate::Custom(id) => id.as_str(),
294        }
295    }
296
297    /// Get a human-readable name for this template
298    pub fn name(&self) -> &str {
299        match self {
300            DashboardTemplate::Unified => "Unified Dashboard",
301            DashboardTemplate::Custom(_) => "Custom Template",
302        }
303    }
304}
305
306/// Export HTML dashboard from tracker data
307///
308/// This function generates a complete HTML dashboard from the tracker data,
309/// including memory analysis, variable relationships, unsafe/FFI tracking,
310/// and system resources. The dashboard is rendered using Handlebars templates.
311pub fn export_dashboard_html<P: AsRef<Path>>(
312    path: P,
313    tracker: &Tracker,
314    passport_tracker: &Arc<MemoryPassportTracker>,
315) -> MemScopeResult<()> {
316    export_dashboard_html_with_template(
317        path,
318        tracker,
319        passport_tracker,
320        DashboardTemplate::default(),
321        None,
322    )
323}
324
325/// Export HTML dashboard with async tracker support
326pub fn export_dashboard_html_with_async<P: AsRef<Path>>(
327    path: P,
328    tracker: &Tracker,
329    passport_tracker: &Arc<MemoryPassportTracker>,
330    async_tracker: &Arc<crate::capture::backends::async_tracker::AsyncTracker>,
331) -> MemScopeResult<()> {
332    export_dashboard_html_with_template(
333        path,
334        tracker,
335        passport_tracker,
336        DashboardTemplate::default(),
337        Some(async_tracker),
338    )
339}
340
341/// Export HTML dashboard with specific template
342///
343/// This function generates a complete HTML dashboard from the tracker data
344/// using the specified template type.
345pub fn export_dashboard_html_with_template<P: AsRef<Path>>(
346    path: P,
347    tracker: &Tracker,
348    passport_tracker: &Arc<MemoryPassportTracker>,
349    template: DashboardTemplate,
350    async_tracker: Option<&Arc<crate::capture::backends::async_tracker::AsyncTracker>>,
351) -> MemScopeResult<()> {
352    // Suppress allocator tracking during HTML rendering for the same reason
353    // as export_all_json: the renderer allocates heavily (template parsing,
354    // context building, HTML generation) and those allocations must not
355    // pollute the event stream.
356    let _suppression = crate::core::allocator::TrackingGuard::acquire();
357
358    let path_ref = path.as_ref();
359
360    // Create output directory if it doesn't exist
361    std::fs::create_dir_all(path_ref).map_err(|e| {
362        MemScopeError::error(
363            "export",
364            "export_dashboard_html_with_template",
365            format!("Failed to create output directory: {}", e),
366        )
367    })?;
368
369    // Create dashboard renderer
370    let renderer = DashboardRenderer::new().map_err(|e| {
371        MemScopeError::error(
372            "export",
373            "export_dashboard_html_with_template",
374            format!("Failed to create dashboard renderer: {}", e),
375        )
376    })?;
377
378    // Render HTML from tracker data using selected template
379    let context = renderer
380        .build_context_from_tracker_with_async(tracker, passport_tracker, async_tracker)
381        .map_err(|e| {
382            MemScopeError::error(
383                "export",
384                "export_dashboard_html_with_template",
385                format!("Failed to build context: {}", e),
386            )
387        })?;
388
389    let html_content = match template {
390        // The unified template and any custom external template are rendered via the registry
391        DashboardTemplate::Unified | DashboardTemplate::Custom(_) => {
392            let template_id = template.template_id();
393            renderer
394                .render_with_template(template_id, &context)
395                .map_err(|e| {
396                    MemScopeError::error(
397                        "export",
398                        "export_dashboard_html_with_template",
399                        format!("Failed to render {} template: {}", template.name(), e),
400                    )
401                })?
402        }
403    };
404
405    // Write HTML to file
406    let output_file = path_ref.join(format!("{}_dashboard.html", template));
407    std::fs::write(&output_file, html_content).map_err(|e| {
408        MemScopeError::error(
409            "export",
410            "export_dashboard_html_with_template",
411            format!("Failed to write HTML file: {}", e),
412        )
413    })?;
414
415    tracing::info!("✅ Dashboard HTML exported to: {:?}", output_file);
416
417    Ok(())
418}
419
420pub fn export_memory_passports_json<P: AsRef<Path>>(
421    base_path: P,
422    passport_tracker: &Arc<MemoryPassportTracker>,
423) -> MemScopeResult<()> {
424    write_memory_passports_json(
425        base_path.as_ref(),
426        passport_tracker,
427        &passport_tracker.snapshot_passports(),
428    )
429}
430
431fn write_memory_passports_json(
432    base_path: &Path,
433    passport_tracker: &MemoryPassportTracker,
434    passports: &[crate::analysis::memory_passport_tracker::MemoryPassport],
435) -> MemScopeResult<()> {
436    let passport_data: Vec<_> = passports
437        .iter()
438        .map(|p| {
439            serde_json::json!({
440                "passport_id": p.passport_id,
441                "allocation_ptr": format!("0x{:x}", p.allocation_ptr),
442                "generation": p.generation,
443                "superseded": p.superseded,
444                "size_bytes": p.size_bytes,
445                "created_at": p.created_at,
446                "lifecycle_events": p.lifecycle_events.len(),
447                "history_truncated": p.history_truncated,
448                "status": format!("{:?}", passport_tracker.assess_status(p)),
449                "status_source": "assessed_at_export",
450            })
451        })
452        .collect();
453
454    let json_data = serde_json::json!({
455        "metadata": {
456            "export_version": "2.0",
457            "specification": "memory passport tracking",
458            "total_passports": passports.len()
459        },
460        "memory_passports": passport_data,
461    });
462
463    let file_path = base_path.join("memory_passports.json");
464    let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
465        MemScopeError::error("export", "export_memory_passports_json", e.to_string())
466    })?;
467    std::fs::write(&file_path, json_string).map_err(|e| {
468        MemScopeError::error("export", "export_memory_passports_json", e.to_string())
469    })?;
470
471    Ok(())
472}
473
474pub fn export_leak_detection_json<P: AsRef<Path>>(
475    base_path: P,
476    passport_tracker: &Arc<MemoryPassportTracker>,
477) -> MemScopeResult<()> {
478    write_leak_detection_json(
479        base_path.as_ref(),
480        &passport_tracker.detect_leaks_at_shutdown(),
481    )
482}
483
484fn write_leak_detection_json(
485    base_path: &Path,
486    leak_result: &crate::analysis::memory_passport_tracker::LeakDetectionResult,
487) -> MemScopeResult<()> {
488    let leak_details: Vec<_> = leak_result
489        .leak_details
490        .iter()
491        .map(|detail| {
492            serde_json::json!({
493                "passport_id": detail.passport_id,
494                "memory_address": format!("0x{:x}", detail.memory_address),
495                "size_bytes": detail.size_bytes,
496                "lifecycle_summary": detail.lifecycle_summary,
497            })
498        })
499        .collect();
500
501    let json_data = serde_json::json!({
502        "metadata": {
503            "export_version": "2.0",
504            "specification": "leak detection",
505            "leaks_detected": leak_result.total_leaks
506        },
507        "leak_detection": {
508            "total_leaks": leak_result.total_leaks,
509            "leak_details": leak_details
510        }
511    });
512
513    let file_path = base_path.join("leak_detection.json");
514    let json_string = serde_json::to_string_pretty(&json_data)
515        .map_err(|e| MemScopeError::error("export", "export_leak_detection_json", e.to_string()))?;
516    std::fs::write(&file_path, json_string)
517        .map_err(|e| MemScopeError::error("export", "export_leak_detection_json", e.to_string()))?;
518
519    Ok(())
520}
521
522pub fn export_unsafe_ffi_json<P: AsRef<Path>>(
523    base_path: P,
524    passport_tracker: &Arc<MemoryPassportTracker>,
525) -> MemScopeResult<()> {
526    write_unsafe_ffi_json(
527        base_path.as_ref(),
528        passport_tracker,
529        &passport_tracker.snapshot_passports(),
530    )
531}
532
533fn write_unsafe_ffi_json(
534    base_path: &Path,
535    passport_tracker: &MemoryPassportTracker,
536    passports: &[crate::analysis::memory_passport_tracker::MemoryPassport],
537) -> MemScopeResult<()> {
538    use crate::analysis::memory_passport_tracker::PassportStatus;
539
540    let ffi_reports: Vec<_> = passports
541        .iter()
542        .filter(|p| {
543            matches!(
544                passport_tracker.assess_status(p),
545                PassportStatus::HandoverToFfi
546                    | PassportStatus::InForeignCustody
547                    | PassportStatus::FreedByForeign
548            )
549        })
550        .map(|p| {
551            serde_json::json!({
552                "passport_id": p.passport_id,
553                "allocation_ptr": format!("0x{:x}", p.allocation_ptr),
554                "generation": p.generation,
555                "superseded": p.superseded,
556                "size_bytes": p.size_bytes,
557                "history_truncated": p.history_truncated,
558                "status": format!("{:?}", passport_tracker.assess_status(p)),
559                "created_at": p.created_at,
560                "boundary_events": p.lifecycle_events.iter().map(|e| {
561                    serde_json::json!({
562                        "timestamp": e.timestamp,
563                        "event_type": format!("{:?}", e.event_type),
564                        "context": e.context,
565                    })
566                }).collect::<Vec<_>>(),
567            })
568        })
569        .collect();
570
571    let json_data = serde_json::json!({
572        "metadata": {
573            "export_version": "2.0",
574            "specification": "unsafe FFI tracking",
575            "total_ffi_reports": ffi_reports.len(),
576            "total_memory_passports": passports.len()
577        },
578        "unsafe_reports": ffi_reports,
579        "memory_passports": passports.len()
580    });
581
582    let file_path = base_path.join("unsafe_ffi.json");
583    let json_string = serde_json::to_string_pretty(&json_data)
584        .map_err(|e| MemScopeError::error("export", "export_unsafe_ffi_json", e.to_string()))?;
585    std::fs::write(&file_path, json_string)
586        .map_err(|e| MemScopeError::error("export", "export_unsafe_ffi_json", e.to_string()))?;
587
588    Ok(())
589}
590
591/// Export system resource monitoring data to JSON
592///
593/// This function exports comprehensive system resource statistics including:
594/// - Memory statistics (virtual, physical, process, system)
595/// - CPU information (cores, cache, system info)
596/// - Memory pressure indicators
597pub fn export_system_resources_json<P: AsRef<Path>>(base_path: P) -> MemScopeResult<()> {
598    let base_path = base_path.as_ref();
599
600    // Collect memory statistics
601    let mut memory_info = PlatformMemoryInfo::new();
602    let _ = memory_info.initialize();
603
604    let memory_stats = match memory_info.collect_stats() {
605        Ok(stats) => stats,
606        Err(e) => {
607            warn!(error = %e, "Failed to collect memory stats");
608            return Err(MemScopeError::error(
609                "export",
610                "export_system_resources_json",
611                e.to_string(),
612            ));
613        }
614    };
615
616    // Collect system information
617    let system_info = match memory_info.get_system_info() {
618        Ok(info) => info,
619        Err(e) => {
620            warn!(error = %e, "Failed to collect system info");
621            return Err(MemScopeError::error(
622                "export",
623                "export_system_resources_json",
624                e.to_string(),
625            ));
626        }
627    };
628
629    // Build JSON structure
630    let json_data = serde_json::json!({
631        "metadata": {
632            "export_version": "2.0",
633            "specification": "system resource monitoring",
634            "timestamp": std::time::SystemTime::now()
635                .duration_since(std::time::UNIX_EPOCH)
636                .unwrap_or_default()
637                .as_secs()
638        },
639        "system_info": {
640            "os_name": system_info.os_name,
641            "os_version": system_info.os_version,
642            "architecture": system_info.architecture,
643            "cpu_cores": system_info.cpu_cores,
644            "page_size": system_info.page_size,
645            "large_page_size": system_info.large_page_size,
646            "cpu_cache": {
647                "l1_cache_size": system_info.cpu_cache.l1_cache_size,
648                "l2_cache_size": system_info.cpu_cache.l2_cache_size,
649                "l3_cache_size": system_info.cpu_cache.l3_cache_size,
650                "cache_line_size": system_info.cpu_cache.cache_line_size
651            },
652            "mmu_info": {
653                "virtual_address_bits": system_info.mmu_info.virtual_address_bits,
654                "physical_address_bits": system_info.mmu_info.physical_address_bits,
655                "aslr_enabled": system_info.mmu_info.aslr_enabled,
656                "nx_bit_supported": system_info.mmu_info.nx_bit_supported
657            }
658        },
659        "memory_stats": {
660            "virtual_memory": {
661                "total_virtual": memory_stats.virtual_memory.total_virtual,
662                "available_virtual": memory_stats.virtual_memory.available_virtual,
663                "used_virtual": memory_stats.virtual_memory.used_virtual,
664                "reserved": memory_stats.virtual_memory.reserved,
665                "committed": memory_stats.virtual_memory.committed
666            },
667            "physical_memory": {
668                "total_physical": memory_stats.physical_memory.total_physical,
669                "available_physical": memory_stats.physical_memory.available_physical,
670                "used_physical": memory_stats.physical_memory.used_physical,
671                "cached": memory_stats.physical_memory.cached,
672                "buffers": memory_stats.physical_memory.buffers,
673                "swap": {
674                    "total_swap": memory_stats.physical_memory.swap.total_swap,
675                    "used_swap": memory_stats.physical_memory.swap.used_swap,
676                    "available_swap": memory_stats.physical_memory.swap.available_swap,
677                    "swap_in_rate": memory_stats.physical_memory.swap.swap_in_rate,
678                    "swap_out_rate": memory_stats.physical_memory.swap.swap_out_rate
679                }
680            },
681            "process_memory": {
682                "virtual_size": memory_stats.process_memory.virtual_size,
683                "resident_size": memory_stats.process_memory.resident_size,
684                "shared_size": memory_stats.process_memory.shared_size,
685                "private_size": memory_stats.process_memory.private_size,
686                "heap_size": memory_stats.process_memory.heap_size,
687                "stack_size": memory_stats.process_memory.stack_size,
688                "mapped_files": memory_stats.process_memory.mapped_files,
689                "peak_usage": memory_stats.process_memory.peak_usage
690            },
691            "system_memory": {
692                "allocation_count": memory_stats.system_memory.allocation_count,
693                "deallocation_count": memory_stats.system_memory.deallocation_count,
694                "active_allocations": memory_stats.system_memory.active_allocations,
695                "total_allocated": memory_stats.system_memory.total_allocated,
696                "total_deallocated": memory_stats.system_memory.total_deallocated,
697                "fragmentation_level": memory_stats.system_memory.fragmentation_level,
698                "large_pages": {
699                    "supported": memory_stats.system_memory.large_pages.supported,
700                    "total_large_pages": memory_stats.system_memory.large_pages.total_large_pages,
701                    "used_large_pages": memory_stats.system_memory.large_pages.used_large_pages,
702                    "page_size": memory_stats.system_memory.large_pages.page_size
703                }
704            },
705            "pressure_indicators": {
706                "pressure_level": format!("{:?}", memory_stats.pressure_indicators.pressure_level),
707                "low_memory": memory_stats.pressure_indicators.low_memory,
708                "swapping_active": memory_stats.pressure_indicators.swapping_active,
709                "allocation_failure_rate": memory_stats.pressure_indicators.allocation_failure_rate,
710                "gc_pressure": memory_stats.pressure_indicators.gc_pressure
711            }
712        }
713    });
714
715    let file_path = base_path.join("system_resources.json");
716    let json_string = serde_json::to_string_pretty(&json_data).map_err(|e| {
717        MemScopeError::error("export", "export_system_resources_json", e.to_string())
718    })?;
719    std::fs::write(&file_path, json_string).map_err(|e| {
720        MemScopeError::error("export", "export_system_resources_json", e.to_string())
721    })?;
722
723    Ok(())
724}
725
726#[cfg(test)]
727mod tests {
728    use super::snapshot_json::{estimate_json_size, get_or_compute_type_info};
729    use super::*;
730
731    /// Objective: Verify that OptimizationLevel enum variants exist.
732    /// Invariants: All variants should be accessible.
733    #[test]
734    fn test_optimization_level_variants() {
735        let _low = OptimizationLevel::Low;
736        let _medium = OptimizationLevel::Medium;
737        let _high = OptimizationLevel::High;
738        let _maximum = OptimizationLevel::Maximum;
739    }
740
741    /// Objective: Verify that OptimizationLevel default is Medium.
742    /// Invariants: Default should return Medium variant.
743    #[test]
744    fn test_optimization_level_default() {
745        let level = OptimizationLevel::default();
746        assert!(
747            matches!(level, OptimizationLevel::Medium),
748            "Default should be Medium"
749        );
750    }
751
752    /// Objective: Verify that SchemaValidator creates with default settings.
753    /// Invariants: New validator should have strict_mode disabled.
754    #[test]
755    fn test_schema_validator_new() {
756        let validator = SchemaValidator::new();
757        let data = serde_json::json!({"test": "value"});
758        let result = validator.validate(&data);
759        assert!(result.is_ok(), "Validation should pass for any object");
760    }
761
762    /// Objective: Verify that SchemaValidator with strict mode validates required fields.
763    /// Invariants: Strict mode should require timestamp, allocations, stats fields.
764    #[test]
765    fn test_schema_validator_strict_mode() {
766        let validator = SchemaValidator::new().with_strict_mode(true);
767
768        let missing_fields = serde_json::json!({"other": "data"});
769        let result = validator.validate(&missing_fields);
770        assert!(result.is_err(), "Should fail with missing required fields");
771
772        let valid_data = serde_json::json!({
773            "timestamp": 123,
774            "allocations": [],
775            "stats": {}
776        });
777        let result = validator.validate(&valid_data);
778        assert!(result.is_ok(), "Should pass with all required fields");
779    }
780
781    /// Objective: Verify that SchemaValidator rejects non-object data.
782    /// Invariants: Non-object JSON should be rejected.
783    #[test]
784    fn test_schema_validator_non_object() {
785        let validator = SchemaValidator::new();
786        let data = serde_json::json!("not an object");
787        let result = validator.validate(&data);
788        assert!(result.is_err(), "Should reject non-object data");
789    }
790
791    /// Objective: Verify that ExportJsonOptions has correct defaults.
792    /// Invariants: Default options should enable parallel processing and streaming.
793    #[test]
794    fn test_export_json_options_default() {
795        let options = ExportJsonOptions::default();
796        assert!(
797            options.parallel_processing,
798            "parallel_processing should be true by default"
799        );
800        assert!(
801            options.streaming_writer,
802            "streaming_writer should be true by default"
803        );
804        assert!(
805            options.enable_type_cache,
806            "enable_type_cache should be true by default"
807        );
808        assert!(
809            options.adaptive_optimization,
810            "adaptive_optimization should be true by default"
811        );
812        assert!(
813            !options.schema_validation,
814            "schema_validation should be false by default"
815        );
816        assert!(
817            !options.security_analysis,
818            "security_analysis should be false by default"
819        );
820    }
821
822    /// Objective: Verify that ExportJsonOptions builder methods work.
823    /// Invariants: Builder methods should set corresponding fields.
824    #[test]
825    fn test_export_json_options_builders() {
826        let options = ExportJsonOptions::default()
827            .fast_export_mode(true)
828            .security_analysis(true)
829            .streaming_writer(false)
830            .schema_validation(true)
831            .integrity_hashes(true)
832            .batch_size(500)
833            .adaptive_optimization(false)
834            .max_cache_size(5000)
835            .include_low_severity(true)
836            .thread_count(Some(4));
837
838        assert!(options.fast_export_mode, "fast_export_mode should be true");
839        assert!(
840            options.security_analysis,
841            "security_analysis should be true"
842        );
843        assert!(
844            !options.streaming_writer,
845            "streaming_writer should be false"
846        );
847        assert!(
848            options.schema_validation,
849            "schema_validation should be true"
850        );
851        assert!(options.integrity_hashes, "integrity_hashes should be true");
852        assert_eq!(options.batch_size, 500, "batch_size should be 500");
853        assert!(
854            !options.adaptive_optimization,
855            "adaptive_optimization should be false"
856        );
857        assert_eq!(
858            options.max_cache_size, 5000,
859            "max_cache_size should be 5000"
860        );
861        assert!(
862            options.include_low_severity,
863            "include_low_severity should be true"
864        );
865        assert_eq!(
866            options.thread_count,
867            Some(4),
868            "thread_count should be Some(4)"
869        );
870    }
871
872    /// Objective: Verify that ExportError variants exist and display correctly.
873    /// Invariants: Error variants should be displayable.
874    #[test]
875    fn test_export_error_variants() {
876        let io_err = ExportError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
877        let json_err = ExportError::Json(serde_json::from_str::<i32>("invalid").unwrap_err());
878        let export_err = ExportError::ExportFailed("test error".to_string());
879
880        assert!(
881            format!("{}", io_err).contains("IO error"),
882            "Should contain IO error"
883        );
884        assert!(
885            format!("{}", json_err).contains("JSON error"),
886            "Should contain JSON error"
887        );
888        assert!(
889            format!("{}", export_err).contains("Export failed"),
890            "Should contain Export failed"
891        );
892    }
893
894    /// Objective: Verify that estimate_json_size works for different JSON types.
895    /// Invariants: Size estimation should be reasonable.
896    #[test]
897    fn test_estimate_json_size() {
898        let string_val = serde_json::json!("hello world");
899        let num_val = serde_json::json!(42);
900        let array_val = serde_json::json!([1, 2, 3]);
901        let object_val = serde_json::json!({"key": "value"});
902
903        let string_size = estimate_json_size(&string_val);
904        let num_size = estimate_json_size(&num_val);
905        let array_size = estimate_json_size(&array_val);
906        let object_size = estimate_json_size(&object_val);
907
908        assert!(string_size > 0, "String size should be positive");
909        assert!(num_size > 0, "Number size should be positive");
910        assert!(array_size > 0, "Array size should be positive");
911        assert!(object_size > 0, "Object size should be positive");
912    }
913
914    /// Objective: Verify that get_or_compute_type_info categorizes types correctly.
915    /// Invariants: Type categorization should match expected patterns.
916    #[test]
917    fn test_get_or_compute_type_info() {
918        assert_eq!(
919            get_or_compute_type_info("Vec<i32>", 100),
920            "dynamic_array",
921            "Vec should be dynamic_array"
922        );
923        assert_eq!(
924            get_or_compute_type_info("String", 24),
925            "string",
926            "String should be string"
927        );
928        assert_eq!(
929            get_or_compute_type_info("Box<i32>", 8),
930            "smart_pointer",
931            "Box should be smart_pointer"
932        );
933        assert_eq!(
934            get_or_compute_type_info("Rc<String>", 8),
935            "smart_pointer",
936            "Rc should be smart_pointer"
937        );
938        assert_eq!(
939            get_or_compute_type_info("Arc<i32>", 16),
940            "smart_pointer",
941            "Arc should be smart_pointer"
942        );
943        assert_eq!(
944            get_or_compute_type_info("[u8; 100]", 100),
945            "byte_array",
946            "u8 array should be byte_array"
947        );
948        assert_eq!(
949            get_or_compute_type_info("CustomType", 1024 * 1024 * 2),
950            "large_buffer",
951            "Large allocation should be large_buffer"
952        );
953        assert_eq!(
954            get_or_compute_type_info("MyType", 100),
955            "custom",
956            "Unknown type should be custom"
957        );
958    }
959}