Skip to main content

memscope_rs/render_engine/dashboard/
renderer.rs

1//! Dashboard renderer using Handlebars templates
2
3use crate::analysis::memory_passport_tracker::MemoryPassportTracker;
4use crate::tracker::Tracker;
5use handlebars::Handlebars;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8
9/// Risk score penalty per high-risk operation.
10/// Each high-risk operation reduces the health score by this amount.
11const HIGH_RISK_PENALTY: f64 = 10.0;
12
13/// Dashboard context for template rendering
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct DashboardContext {
16    /// Page title
17    pub title: String,
18    /// Export timestamp
19    pub export_timestamp: String,
20    /// Total memory allocated (formatted)
21    pub total_memory: String,
22    /// Total number of allocations
23    pub total_allocations: usize,
24    /// Number of active allocations
25    pub active_allocations: usize,
26    /// Peak memory usage (formatted)
27    pub peak_memory: String,
28    /// Number of threads
29    pub thread_count: usize,
30    /// Number of memory passports
31    pub passport_count: usize,
32    /// Number of memory leaks detected
33    pub leak_count: usize,
34    /// Number of unsafe operations
35    pub unsafe_count: usize,
36    /// Number of FFI operations
37    pub ffi_count: usize,
38    /// Allocation information
39    pub allocations: Vec<AllocationInfo>,
40    /// Variable relationships
41    pub relationships: Vec<RelationshipInfo>,
42    /// Unsafe/FFI reports
43    pub unsafe_reports: Vec<UnsafeReport>,
44    /// Detailed passport information
45    pub passport_details: Vec<PassportDetail>,
46    /// Count helper for template
47    pub allocations_count: usize,
48    /// Count helper for template
49    pub relationships_count: usize,
50    /// Count helper for template
51    pub unsafe_reports_count: usize,
52    /// JSON data string for injection (performance optimization)
53    pub json_data: String,
54    /// OS name
55    pub os_name: String,
56    /// Architecture
57    pub architecture: String,
58    /// CPU cores
59    pub cpu_cores: usize,
60    /// System resources
61    pub system_resources: SystemResources,
62    /// Thread analysis data
63    pub threads: Vec<ThreadInfo>,
64    /// Async task analysis data
65    pub async_tasks: Vec<AsyncTaskInfo>,
66    /// Async summary
67    pub async_summary: AsyncSummary,
68    /// Health score (0-100)
69    pub health_score: u32,
70    /// Health status text
71    pub health_status: String,
72    /// Safe operations count
73    pub safe_ops_count: usize,
74    /// High risk issues count
75    pub high_risk_count: usize,
76    /// Clean passports count
77    pub clean_passport_count: usize,
78    /// Active passports count
79    pub active_passport_count: usize,
80    /// Leaked passports count
81    pub leaked_passport_count: usize,
82    /// FFI tracked passports count
83    pub ffi_tracked_count: usize,
84    /// Safe code percentage
85    pub safe_code_percent: u32,
86    /// Ownership graph information
87    pub ownership_graph: OwnershipGraphInfo,
88}
89
90/// Ownership graph information for dashboard
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct OwnershipGraphInfo {
93    /// Total number of nodes
94    pub total_nodes: usize,
95    /// Total number of edges
96    pub total_edges: usize,
97    /// Number of detected cycles
98    pub total_cycles: usize,
99    /// Rc clone count
100    pub rc_clone_count: usize,
101    /// Arc clone count
102    pub arc_clone_count: usize,
103    /// Whether there are issues
104    pub has_issues: bool,
105    /// Detected issues
106    pub issues: Vec<OwnershipIssue>,
107    /// Root cause if any
108    pub root_cause: Option<RootCauseInfo>,
109}
110
111/// Ownership issue for dashboard
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct OwnershipIssue {
114    /// Issue type
115    pub issue_type: String,
116    /// Severity (error, warning)
117    pub severity: String,
118    /// Description
119    pub description: String,
120}
121
122/// Root cause information
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct RootCauseInfo {
125    /// Cause type
126    pub cause: String,
127    /// Description
128    pub description: String,
129    /// Impact
130    pub impact: String,
131}
132
133/// Async task information for dashboard
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct AsyncTaskInfo {
136    /// Task ID
137    pub task_id: u64,
138    /// Task name
139    pub task_name: String,
140    /// Task type
141    pub task_type: String,
142    /// Total bytes allocated
143    pub total_bytes: u64,
144    /// Current memory usage
145    pub current_memory: u64,
146    /// Peak memory usage
147    pub peak_memory: u64,
148    /// Number of allocations
149    pub total_allocations: u64,
150    /// Duration in milliseconds
151    pub duration_ms: f64,
152    /// Efficiency score (0.0 - 1.0)
153    pub efficiency_score: f64,
154    /// Whether task is completed
155    pub is_completed: bool,
156    /// Whether task has potential leak
157    pub has_potential_leak: bool,
158}
159
160/// Async summary for dashboard
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct AsyncSummary {
163    /// Total number of async tasks
164    pub total_tasks: usize,
165    /// Number of active tasks
166    pub active_tasks: usize,
167    /// Total allocations across all tasks
168    pub total_allocations: usize,
169    /// Total memory bytes
170    pub total_memory_bytes: usize,
171    /// Peak memory bytes
172    pub peak_memory_bytes: usize,
173}
174
175/// Allocation information for dashboard
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct AllocationInfo {
178    /// Memory address
179    pub address: String,
180    /// Type name
181    pub type_name: String,
182    /// Allocation size in bytes
183    pub size: usize,
184    /// Variable name
185    pub var_name: String,
186    /// Timestamp
187    pub timestamp: String,
188    /// Thread ID
189    pub thread_id: String,
190    /// Borrow information
191    pub immutable_borrows: usize,
192    pub mutable_borrows: usize,
193    /// Clone information
194    pub is_clone: bool,
195    pub clone_count: usize,
196    /// Allocation timestamp (nanoseconds)
197    pub timestamp_alloc: u64,
198    /// Deallocation timestamp (nanoseconds, 0 if not freed)
199    pub timestamp_dealloc: u64,
200    /// Lifetime in milliseconds
201    pub lifetime_ms: f64,
202    /// Whether memory is leaked
203    pub is_leaked: bool,
204    /// Allocation type (stack, heap, etc.)
205    pub allocation_type: String,
206    /// Whether this is a smart pointer
207    pub is_smart_pointer: bool,
208    /// Smart pointer type (Arc, Rc, Box, etc.)
209    pub smart_pointer_type: String,
210    /// Source file where allocation occurred
211    pub source_file: Option<String>,
212    /// Source line where allocation occurred
213    pub source_line: Option<u32>,
214}
215
216/// Thread statistics for multithread dashboard
217#[derive(Debug, Clone, Serialize, Deserialize)]
218struct ThreadStats {
219    /// Thread ID
220    id: u64,
221    /// Number of allocations
222    allocations: usize,
223    /// Total memory used
224    memory: usize,
225    /// Peak memory usage
226    peak: usize,
227    /// Thread status
228    status: String,
229}
230
231/// Timeline allocation for multithread dashboard
232#[derive(Debug, Clone, Serialize, Deserialize)]
233struct TimelineAllocation {
234    /// Timestamp
235    timestamp: u64,
236    /// Thread ID
237    thread_id: u64,
238    /// Allocation size
239    size: usize,
240    /// Variable name
241    var_name: Option<String>,
242}
243
244/// Thread conflict information
245#[derive(Debug, Clone, Serialize, Deserialize)]
246struct ThreadConflict {
247    /// Description of the conflict
248    description: String,
249    /// Threads involved
250    threads: String,
251    /// Conflict type
252    #[serde(rename = "type")]
253    conflict_type: String,
254}
255
256/// Variable relationship information
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct RelationshipInfo {
259    /// Source pointer
260    pub source_ptr: String,
261    /// Source variable name
262    pub source_var_name: String,
263    /// Target pointer
264    pub target_ptr: String,
265    /// Target variable name
266    pub target_var_name: String,
267    /// Relationship type (reference, borrow, clone, copy, move, ownership_transfer)
268    pub relationship_type: String,
269    /// Relationship strength (0.0 to 1.0)
270    pub strength: f64,
271    /// Type name
272    pub type_name: String,
273    /// Color for visualization
274    pub color: String,
275    /// Whether this relationship is part of a detected cycle (true) or not (false)
276    pub is_part_of_cycle: bool,
277}
278
279/// Unsafe/FFI report
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct UnsafeReport {
282    /// Passport ID
283    pub passport_id: String,
284    /// Allocation pointer
285    pub allocation_ptr: String,
286    /// Variable name
287    pub var_name: String,
288    /// Type name
289    pub type_name: String,
290    /// Size in bytes
291    pub size_bytes: usize,
292    /// Created at timestamp
293    pub created_at: u64,
294    /// Last update timestamp
295    pub updated_at: u64,
296    /// Status at shutdown
297    pub status: String,
298    /// Lifecycle events
299    pub lifecycle_events: Vec<LifecycleEventInfo>,
300    /// Cross-boundary events
301    pub cross_boundary_events: Vec<BoundaryEventInfo>,
302    /// Whether this is a memory leak
303    pub is_leaked: bool,
304    /// Risk level (low, medium, high)
305    pub risk_level: String,
306    /// Risk factors
307    pub risk_factors: Vec<String>,
308}
309
310/// Lifecycle event information
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct LifecycleEventInfo {
313    /// Event type
314    pub event_type: String,
315    /// Timestamp
316    pub timestamp: u64,
317    /// Context
318    pub context: String,
319    /// Event icon
320    pub icon: String,
321    /// Event color
322    pub color: String,
323}
324
325/// Boundary event information (FFI/Rust crossings)
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct BoundaryEventInfo {
328    /// Event type (RustToFfi, FfiToRust, etc.)
329    pub event_type: String,
330    /// Source context
331    pub from_context: String,
332    /// Target context
333    pub to_context: String,
334    /// Timestamp
335    pub timestamp: u64,
336    /// Direction icon
337    pub icon: String,
338    /// Direction color
339    pub color: String,
340}
341
342/// Detailed passport information
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct PassportDetail {
345    /// Passport ID
346    pub passport_id: String,
347    /// Allocation pointer
348    pub allocation_ptr: String,
349    /// Variable name
350    pub var_name: String,
351    /// Type name
352    pub type_name: String,
353    /// Size in bytes
354    pub size_bytes: usize,
355    /// Status at shutdown
356    pub status: String,
357    /// Created at
358    pub created_at: u64,
359    /// Updated at
360    pub updated_at: u64,
361    /// Whether leaked
362    pub is_leaked: bool,
363    /// Whether FFI tracked
364    pub ffi_tracked: bool,
365    /// Lifecycle events
366    pub lifecycle_events: Vec<LifecycleEventInfo>,
367    /// Cross-boundary events
368    pub cross_boundary_events: Vec<BoundaryEventInfo>,
369    /// Risk level
370    pub risk_level: String,
371    /// Risk confidence
372    pub risk_confidence: f64,
373}
374
375/// System resources information
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct SystemResources {
378    /// OS name
379    pub os_name: String,
380    /// OS version
381    pub os_version: String,
382    /// CPU architecture
383    pub architecture: String,
384    /// Number of CPU cores
385    pub cpu_cores: u32,
386    /// Total physical memory (formatted)
387    pub total_physical: String,
388    /// Available physical memory (formatted)
389    pub available_physical: String,
390    /// Used physical memory (formatted)
391    pub used_physical: String,
392    /// Page size
393    pub page_size: u64,
394}
395
396/// Thread information
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct ThreadInfo {
399    /// Thread ID (formatted as "Thread-N" instead of "ThreadId(N)")
400    pub thread_id: String,
401    /// Thread summary (e.g., "5 allocs, 1.2KB")
402    pub thread_summary: String,
403    /// Number of allocations
404    pub allocation_count: usize,
405    /// Current memory usage
406    pub current_memory: String,
407    /// Peak memory usage
408    pub peak_memory: String,
409    /// Total allocated
410    pub total_allocated: String,
411    /// Raw current memory in bytes for sorting
412    pub current_memory_bytes: usize,
413    /// Raw peak memory in bytes for sorting
414    pub peak_memory_bytes: usize,
415    /// Raw total allocated in bytes for sorting
416    pub total_allocated_bytes: usize,
417}
418
419struct ThreadAggregator {
420    allocation_count: usize,
421    current_memory: usize,
422    peak_memory: usize,
423    total_allocated: usize,
424}
425
426/// Dashboard renderer
427pub struct DashboardRenderer {
428    handlebars: Handlebars<'static>,
429}
430
431impl DashboardRenderer {
432    /// Create a new dashboard renderer
433    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
434        let mut handlebars = Handlebars::new();
435
436        let template_path = format!(
437            "{}/src/render_engine/dashboard/templates/dashboard_unified.html",
438            env!("CARGO_MANIFEST_DIR")
439        );
440        handlebars.register_template_file("dashboard_unified", &template_path)?;
441
442        let final_path = format!(
443            "{}/src/render_engine/dashboard/templates/dashboard_final.html",
444            env!("CARGO_MANIFEST_DIR")
445        );
446        handlebars.register_template_file("dashboard_final", &final_path)?;
447
448        handlebars.register_helper("format_bytes", Box::new(format_bytes_helper));
449        handlebars.register_helper("gt", Box::new(greater_than_helper));
450        handlebars.register_helper("contains", Box::new(contains_helper));
451        handlebars.register_helper("json", Box::new(json_helper));
452
453        Ok(Self { handlebars })
454    }
455
456    /// Extract user source file from stack trace (filter out Rust internals)
457    fn extract_user_source_file(stack_trace: &Option<Vec<String>>) -> Option<String> {
458        if let Some(ref frames) = stack_trace {
459            for frame in frames {
460                let frame_lower = frame.to_lowercase();
461                if !frame_lower.contains("/rustc/")
462                    && !frame_lower.contains("/library/")
463                    && !frame_lower.contains("memscope")
464                    && !frame_lower.contains(".cargo/registry")
465                    && !frame_lower.contains("/src/core/")
466                    && !frame_lower.contains("/src/capture/")
467                    && !frame_lower.contains("/src/unified/")
468                    && !frame_lower.contains("/src/tracker")
469                {
470                    if let Some(file_part) = frame.split(':').next() {
471                        let file_name = file_part.split('/').next_back().unwrap_or(file_part);
472                        if !file_name.starts_with('<') && file_name.contains(".rs") {
473                            return Some(file_part.to_string());
474                        }
475                    }
476                }
477            }
478        }
479        None
480    }
481
482    /// Extract user source line from stack trace (filter out Rust internals)
483    fn extract_user_source_line(stack_trace: &Option<Vec<String>>) -> Option<u32> {
484        if let Some(ref frames) = stack_trace {
485            for frame in frames {
486                let frame_lower = frame.to_lowercase();
487                if !frame_lower.contains("/rustc/")
488                    && !frame_lower.contains("/library/")
489                    && !frame_lower.contains("memscope")
490                    && !frame_lower.contains(".cargo/registry")
491                    && !frame_lower.contains("/src/core/")
492                    && !frame_lower.contains("/src/capture/")
493                    && !frame_lower.contains("/src/unified/")
494                    && !frame_lower.contains("/src/tracker")
495                {
496                    if let Some(line_part) = frame.rsplit(':').next() {
497                        if let Ok(line) = line_part.parse::<u32>() {
498                            return Some(line);
499                        }
500                    }
501                }
502            }
503        }
504        None
505    }
506
507    /// Build relationships using the relation inference system
508    fn build_relationships_from_inference(
509        allocations: &[crate::capture::backends::core_types::AllocationInfo],
510        alloc_info: &[AllocationInfo],
511    ) -> Vec<RelationshipInfo> {
512        use crate::analysis::relation_inference::{Relation, RelationGraphBuilder};
513        use crate::snapshot::types::ActiveAllocation;
514
515        // Convert AllocationInfo to ActiveAllocation
516        let active_allocations: Vec<ActiveAllocation> = allocations
517            .iter()
518            .map(|a| ActiveAllocation {
519                ptr: a.ptr,
520                size: a.size,
521                allocated_at: a.allocated_at_ns,
522                var_name: a.var_name.clone(),
523                type_name: a.type_name.clone(),
524                thread_id: a.thread_id,
525                call_stack_hash: None,
526            })
527            .collect();
528
529        let graph = RelationGraphBuilder::build(&active_allocations, None);
530
531        let mut relationships = Vec::new();
532
533        for edge in &graph.edges {
534            let from_alloc = alloc_info.get(edge.from);
535            let to_alloc = alloc_info.get(edge.to);
536
537            let (rel_type, color, strength) = match edge.relation {
538                Relation::Owner => ("ownership_transfer", "#dc2626", 1.0),
539                Relation::Slice => ("immutable_borrow", "#3b82f6", 0.8),
540                Relation::Clone => ("clone", "#10b981", 0.9),
541                Relation::Shared => ("Arc", "#8b5cf6", 0.7),
542            };
543
544            let source_addr = from_alloc
545                .and_then(|a| {
546                    if a.address.starts_with("0x") {
547                        usize::from_str_radix(&a.address[2..], 16).ok()
548                    } else {
549                        None
550                    }
551                })
552                .unwrap_or(edge.from);
553
554            let target_addr = to_alloc
555                .and_then(|a| {
556                    if a.address.starts_with("0x") {
557                        usize::from_str_radix(&a.address[2..], 16).ok()
558                    } else {
559                        None
560                    }
561                })
562                .unwrap_or(edge.to);
563
564            relationships.push(RelationshipInfo {
565                source_ptr: format!("0x{:x}", source_addr),
566                source_var_name: from_alloc
567                    .map(|a| a.var_name.clone())
568                    .unwrap_or_else(|| format!("alloc_{}", edge.from)),
569                target_ptr: format!("0x{:x}", target_addr),
570                target_var_name: to_alloc
571                    .map(|a| a.var_name.clone())
572                    .unwrap_or_else(|| format!("alloc_{}", edge.to)),
573                relationship_type: rel_type.to_string(),
574                strength,
575                type_name: from_alloc
576                    .map(|a| a.type_name.clone())
577                    .unwrap_or_else(|| "unknown".to_string()),
578                color: color.to_string(),
579                is_part_of_cycle: false,
580            });
581        }
582
583        // Limit relationships to avoid performance issues
584        if relationships.len() > 500 {
585            relationships.truncate(500);
586        }
587
588        relationships
589    }
590
591    /// Infer type from size-based heuristics when type_name is unknown
592    fn infer_type_from_size(size: usize) -> String {
593        match size {
594            8 => "*mut c_void (30%)".to_string(),
595            16 => "&[T] (25%)".to_string(),
596            24 => "Vec<_>/String (15%)".to_string(),
597            32 | 48 | 64 => "CStruct (10%)".to_string(),
598            n if n.is_power_of_two() && n >= 64 => {
599                format!("Vec<_>/[u8] ({}%)", 10 + n.trailing_zeros() as u8)
600            }
601            n if (32..=256).contains(&n) => "[u8] (10%)".to_string(),
602            _ => "unknown".to_string(),
603        }
604    }
605
606    /// Get inferred type name with confidence
607    fn get_inferred_type_name(type_name: &str, size: usize) -> String {
608        if type_name != "unknown" && type_name != "-" && !type_name.is_empty() {
609            return type_name.to_string();
610        }
611        Self::infer_type_from_size(size)
612    }
613
614    /// Build dashboard context from tracker data
615    pub fn build_context_from_tracker(
616        &self,
617        tracker: &Tracker,
618        passport_tracker: &Arc<MemoryPassportTracker>,
619    ) -> Result<DashboardContext, Box<dyn std::error::Error>> {
620        self.build_context_from_tracker_with_async(tracker, passport_tracker, None)
621    }
622
623    /// Build dashboard context from tracker data with async support
624    pub fn build_context_from_tracker_with_async(
625        &self,
626        tracker: &Tracker,
627        passport_tracker: &Arc<MemoryPassportTracker>,
628        async_tracker: Option<&Arc<crate::capture::backends::async_tracker::AsyncTracker>>,
629    ) -> Result<DashboardContext, Box<dyn std::error::Error>> {
630        let allocations = tracker.inner().get_active_allocations().unwrap_or_default();
631        let passports = passport_tracker.get_all_passports();
632        let analysis = tracker.analyze();
633
634        let total_memory: usize = allocations.iter().map(|a| a.size).sum();
635
636        // Build allocation info
637        let alloc_info: Vec<AllocationInfo> = allocations
638            .iter()
639            .map(|a| {
640                let original_type_name =
641                    a.type_name.clone().unwrap_or_else(|| "unknown".to_string());
642                let type_name = Self::get_inferred_type_name(&original_type_name, a.size);
643                let timestamp_alloc = a.allocated_at_ns;
644                let timestamp_dealloc = 0u64;
645                let lifetime_ms = 0.0;
646
647                // Determine if smart pointer
648                let is_smart_pointer = type_name.contains("Arc")
649                    || type_name.contains("Rc")
650                    || type_name.contains("Box");
651                let smart_pointer_type = if type_name.contains("Arc") {
652                    "Arc".to_string()
653                } else if type_name.contains("Rc") {
654                    "Rc".to_string()
655                } else if type_name.contains("Box") {
656                    "Box".to_string()
657                } else {
658                    String::new()
659                };
660
661                AllocationInfo {
662                    address: format!("0x{:x}", a.ptr),
663                    type_name: type_name.clone(),
664                    size: a.size,
665                    var_name: a.var_name.clone().unwrap_or_else(|| "unknown".to_string()),
666                    timestamp: format!("{:?}", a.allocated_at_ns),
667                    thread_id: format!("{}", a.thread_id),
668                    immutable_borrows: 0,
669                    mutable_borrows: 0,
670                    is_clone: false,
671                    clone_count: 0,
672                    timestamp_alloc,
673                    timestamp_dealloc,
674                    lifetime_ms,
675                    is_leaked: true,
676                    allocation_type: "heap".to_string(),
677                    is_smart_pointer,
678                    smart_pointer_type,
679                    source_file: Self::extract_user_source_file(&a.stack_trace),
680                    source_line: Self::extract_user_source_line(&a.stack_trace),
681                }
682            })
683            .collect();
684
685        // Build variable relationships using RelationGraphBuilder
686        let mut relationships = Self::build_relationships_from_inference(&allocations, &alloc_info);
687
688        // Detect cycles in relationships and mark cycle edges
689        let cycle_edges: std::collections::HashSet<(String, String)> = {
690            let rel_tuples: Vec<(String, String, String)> = relationships
691                .iter()
692                .map(|r| {
693                    (
694                        r.source_ptr.clone(),
695                        r.target_ptr.clone(),
696                        r.type_name.clone(),
697                    )
698                })
699                .collect();
700            let result = crate::analysis::detect_cycles_in_relationships(&rel_tuples);
701            result.cycle_edges
702        };
703
704        for rel in &mut relationships {
705            if cycle_edges.contains(&(rel.source_ptr.clone(), rel.target_ptr.clone())) {
706                rel.is_part_of_cycle = true;
707                rel.color = "#ef4444".to_string();
708            }
709        }
710
711        // Build unsafe reports from passports
712        let unsafe_reports: Vec<UnsafeReport> = passports.values()
713            .filter(|p| !p.lifecycle_events.is_empty())
714            .map(|p| {
715                // Build lifecycle events
716                let lifecycle_events: Vec<LifecycleEventInfo> = p.lifecycle_events.iter()
717                    .map(|event| {
718                        let (icon, color, context) = match &event.event_type {
719                            crate::analysis::memory_passport_tracker::PassportEventType::AllocatedInRust => {
720                                ("🟢".to_string(), "#10b981".to_string(), "Rust Allocation".to_string())
721                            }
722                            crate::analysis::memory_passport_tracker::PassportEventType::HandoverToFfi => {
723                                ("⬇️".to_string(), "#f59e0b".to_string(), "Handover to FFI".to_string())
724                            }
725                            crate::analysis::memory_passport_tracker::PassportEventType::FreedByForeign => {
726                                ("🔵".to_string(), "#3b82f6".to_string(), "Freed by Foreign".to_string())
727                            }
728                            crate::analysis::memory_passport_tracker::PassportEventType::ReclaimedByRust => {
729                                ("⬆️".to_string(), "#10b981".to_string(), "Reclaimed by Rust".to_string())
730                            }
731                            crate::analysis::memory_passport_tracker::PassportEventType::BoundaryAccess => {
732                                ("🔄".to_string(), "#8b5cf6".to_string(), "Boundary Access".to_string())
733                            }
734                            crate::analysis::memory_passport_tracker::PassportEventType::OwnershipTransfer => {
735                                ("↔️".to_string(), "#dc2626".to_string(), "Ownership Transfer".to_string())
736                            }
737                            crate::analysis::memory_passport_tracker::PassportEventType::ValidationCheck => {
738                                ("✅".to_string(), "#10b981".to_string(), "Validation Check".to_string())
739                            }
740                            crate::analysis::memory_passport_tracker::PassportEventType::CorruptionDetected => {
741                                ("🚨".to_string(), "#dc2626".to_string(), "Corruption Detected".to_string())
742                            }
743                        };
744
745                        LifecycleEventInfo {
746                            event_type: format!("{:?}", event.event_type),
747                            timestamp: event.timestamp,
748                            context,
749                            icon,
750                            color,
751                        }
752                    })
753                    .collect();
754
755                // Build cross-boundary events
756                let cross_boundary_events: Vec<BoundaryEventInfo> = lifecycle_events.iter()
757                    .filter(|e| e.event_type.contains("Handover") || e.event_type.contains("Reclaimed"))
758                    .map(|e| {
759                        let (event_type, from, to, icon, color) = if e.event_type.contains("HandoverToFfi") {
760                            ("RustToFfi".to_string(), "Rust".to_string(), "FFI".to_string(), "⬇️".to_string(), "#f59e0b".to_string())
761                        } else if e.event_type.contains("ReclaimedByRust") {
762                            ("FfiToRust".to_string(), "FFI".to_string(), "Rust".to_string(), "⬆️".to_string(), "#10b981".to_string())
763                        } else {
764                            (e.event_type.clone(), "Unknown".to_string(), "Unknown".to_string(), "❓".to_string(), "#6b7280".to_string())
765                        };
766
767                        BoundaryEventInfo {
768                            event_type,
769                            from_context: from,
770                            to_context: to,
771                            timestamp: e.timestamp,
772                            icon,
773                            color,
774                        }
775                    })
776                    .collect();
777
778                // Determine risk level
779                let is_leaked = p.status_at_shutdown == crate::analysis::memory_passport_tracker::PassportStatus::InForeignCustody ||
780                               p.status_at_shutdown == crate::analysis::memory_passport_tracker::PassportStatus::HandoverToFfi;
781                let risk_level = if is_leaked {
782                    "high".to_string()
783                } else if !cross_boundary_events.is_empty() {
784                    "medium".to_string()
785                } else {
786                    "low".to_string()
787                };
788
789                // Use passport's stored type and variable name, fallback to allocations if needed
790                let var_name = if p.var_name != "-" {
791                    p.var_name.clone()
792                } else {
793                    allocations.iter()
794                        .find(|a| a.ptr == p.allocation_ptr)
795                        .and_then(|a| a.var_name.clone())
796                        .unwrap_or_else(|| "-".to_string())
797                };
798
799                let type_name = if p.type_name != "-" {
800                    p.type_name.clone()
801                } else {
802                    let from_alloc = allocations.iter()
803                        .find(|a| a.ptr == p.allocation_ptr)
804                        .and_then(|a| a.type_name.clone())
805                        .unwrap_or_else(|| "-".to_string());
806
807                    if from_alloc != "-" {
808                        from_alloc
809                    } else {
810                        Self::infer_type_from_size(p.size_bytes)
811                    }
812                };
813
814                // Risk factors
815                let mut risk_factors = Vec::new();
816                if is_leaked {
817                    risk_factors.push("Memory leaked at shutdown".to_string());
818                }
819                if !cross_boundary_events.is_empty() {
820                    risk_factors.push(format!("Crosses FFI boundary {} times", cross_boundary_events.len()));
821                }
822                if cross_boundary_events.len() > 3 {
823                    risk_factors.push("Frequent boundary crossings".to_string());
824                }
825
826                UnsafeReport {
827                    passport_id: p.passport_id.clone(),
828                    allocation_ptr: format!("0x{:x}", p.allocation_ptr),
829                    var_name,
830                    type_name,
831                    size_bytes: p.size_bytes,
832                    created_at: p.created_at,
833                    updated_at: p.updated_at,
834                    status: format!("{:?}", p.status_at_shutdown),
835                    lifecycle_events,
836                    cross_boundary_events,
837                    is_leaked,
838                    risk_level,
839                    risk_factors,
840                }
841            })
842            .collect();
843
844        // Build passport details
845        let passport_details: Vec<PassportDetail> = passports.values()
846            .map(|p| {
847                // Build lifecycle events
848                let lifecycle_events: Vec<LifecycleEventInfo> = p.lifecycle_events.iter()
849                    .map(|event| {
850                        let (icon, color, context) = match &event.event_type {
851                            crate::analysis::memory_passport_tracker::PassportEventType::AllocatedInRust => {
852                                ("🟢".to_string(), "#10b981".to_string(), "Rust Allocation".to_string())
853                            }
854                            crate::analysis::memory_passport_tracker::PassportEventType::HandoverToFfi => {
855                                ("⬇️".to_string(), "#f59e0b".to_string(), "Handover to FFI".to_string())
856                            }
857                            crate::analysis::memory_passport_tracker::PassportEventType::FreedByForeign => {
858                                ("🔵".to_string(), "#3b82f6".to_string(), "Freed by Foreign".to_string())
859                            }
860                            crate::analysis::memory_passport_tracker::PassportEventType::ReclaimedByRust => {
861                                ("⬆️".to_string(), "#10b981".to_string(), "Reclaimed by Rust".to_string())
862                            }
863                            crate::analysis::memory_passport_tracker::PassportEventType::BoundaryAccess => {
864                                ("🔄".to_string(), "#8b5cf6".to_string(), "Boundary Access".to_string())
865                            }
866                            crate::analysis::memory_passport_tracker::PassportEventType::OwnershipTransfer => {
867                                ("↔️".to_string(), "#dc2626".to_string(), "Ownership Transfer".to_string())
868                            }
869                            crate::analysis::memory_passport_tracker::PassportEventType::ValidationCheck => {
870                                ("✅".to_string(), "#10b981".to_string(), "Validation Check".to_string())
871                            }
872                            crate::analysis::memory_passport_tracker::PassportEventType::CorruptionDetected => {
873                                ("🚨".to_string(), "#dc2626".to_string(), "Corruption Detected".to_string())
874                            }
875                        };
876
877                        LifecycleEventInfo {
878                            event_type: format!("{:?}", event.event_type),
879                            timestamp: event.timestamp,
880                            context,
881                            icon: icon.to_string(),
882                            color,
883                        }
884                    })
885                    .collect();
886
887                // Build cross-boundary events
888                let cross_boundary_events: Vec<BoundaryEventInfo> = lifecycle_events.iter()
889                    .filter(|e| e.event_type.contains("Handover") || e.event_type.contains("Reclaimed"))
890                    .map(|e| {
891                        let (event_type, from, to, icon, color) = if e.event_type.contains("HandoverToFfi") {
892                            ("RustToFfi".to_string(), "Rust".to_string(), "FFI".to_string(), "⬇️".to_string(), "#f59e0b".to_string())
893                        } else if e.event_type.contains("ReclaimedByRust") {
894                            ("FfiToRust".to_string(), "FFI".to_string(), "Rust".to_string(), "⬆️".to_string(), "#10b981".to_string())
895                        } else {
896                            (e.event_type.clone(), "Unknown".to_string(), "Unknown".to_string(), "❓".to_string(), "#6b7280".to_string())
897                        };
898
899                        BoundaryEventInfo {
900                            event_type,
901                            from_context: from,
902                            to_context: to,
903                            timestamp: e.timestamp,
904                            icon,
905                            color,
906                        }
907                    })
908                    .collect();
909
910                // Use passport's stored type and variable name, fallback to allocations if needed
911                let var_name = if p.var_name != "-" {
912                    p.var_name.clone()
913                } else {
914                    allocations.iter()
915                        .find(|a| a.ptr == p.allocation_ptr)
916                        .and_then(|a| a.var_name.clone())
917                        .unwrap_or_else(|| "-".to_string())
918                };
919
920                let type_name = if p.type_name != "-" {
921                    p.type_name.clone()
922                } else {
923                    let from_alloc = allocations.iter()
924                        .find(|a| a.ptr == p.allocation_ptr)
925                        .and_then(|a| a.type_name.clone())
926                        .unwrap_or_else(|| "-".to_string());
927
928                    if from_alloc != "-" {
929                        from_alloc
930                    } else {
931                        Self::infer_type_from_size(p.size_bytes)
932                    }
933                };
934
935                // Determine risk level
936                let is_leaked = p.status_at_shutdown == crate::analysis::memory_passport_tracker::PassportStatus::InForeignCustody ||
937                               p.status_at_shutdown == crate::analysis::memory_passport_tracker::PassportStatus::HandoverToFfi;
938                let risk_level = if is_leaked {
939                    "high".to_string()
940                } else if !cross_boundary_events.is_empty() {
941                    "medium".to_string()
942                } else {
943                    "low".to_string()
944                };
945
946                PassportDetail {
947                    passport_id: p.passport_id.clone(),
948                    allocation_ptr: format!("0x{:x}", p.allocation_ptr),
949                    var_name,
950                    type_name,
951                    size_bytes: p.size_bytes,
952                    status: format!("{:?}", p.status_at_shutdown),
953                    created_at: p.created_at,
954                    updated_at: p.updated_at,
955                    is_leaked,
956                    ffi_tracked: !cross_boundary_events.is_empty(),
957                    lifecycle_events,
958                    cross_boundary_events,
959                    risk_level,
960                    risk_confidence: 0.85, // Default confidence
961                }
962            })
963            .collect();
964
965        // Perform leak detection
966        let leak_result = passport_tracker.detect_leaks_at_shutdown();
967        let leak_count = leak_result.leaked_passports.len();
968
969        // Build thread data from allocations
970        let thread_data = Self::aggregate_thread_data(&alloc_info);
971
972        // Prepare JSON data for direct injection (performance optimization)
973
974        #[derive(serde::Serialize)]
975        struct DashboardData<'a> {
976            allocations: &'a [AllocationInfo],
977            relationships: &'a [RelationshipInfo],
978            unsafe_reports: &'a [UnsafeReport],
979            threads: &'a [ThreadInfo],
980            passport_details: &'a [PassportDetail],
981            active_allocations: usize,
982            total_allocations: usize,
983            leak_count: usize,
984            async_tasks: &'a [AsyncTaskInfo],
985            async_summary: &'a AsyncSummary,
986            ownership_graph: &'a OwnershipGraphInfo,
987        }
988
989        let async_tasks = Self::build_async_tasks(async_tracker);
990        let async_summary = Self::build_async_summary(async_tracker);
991        let ownership_graph = Self::build_ownership_graph_info(&allocations);
992        let data = DashboardData {
993            allocations: &alloc_info,
994            relationships: &relationships,
995            unsafe_reports: &unsafe_reports,
996            threads: &thread_data,
997            passport_details: &passport_details,
998            active_allocations: analysis.active_allocations,
999            total_allocations: analysis.total_allocations,
1000            leak_count,
1001            async_tasks: &async_tasks,
1002            async_summary: &async_summary,
1003            ownership_graph: &ownership_graph,
1004        };
1005
1006        let json_data: String = serde_json::to_string(&data)
1007            .map_err(|e| format!("Failed to serialize dashboard data: {}", e))?;
1008
1009        // Get system information directly using platform-specific functions
1010        let (
1011            os_name,
1012            os_version,
1013            architecture,
1014            cpu_cores,
1015            page_size,
1016            total_physical,
1017            available_physical,
1018            used_physical,
1019        ) = {
1020            #[cfg(target_os = "macos")]
1021            {
1022                // Get OS version
1023                let os_version = unsafe {
1024                    let mut size: libc::size_t = 256;
1025                    let mut buf = [0u8; 256];
1026                    if libc::sysctlbyname(
1027                        c"kern.osrelease".as_ptr(),
1028                        buf.as_mut_ptr() as *mut libc::c_void,
1029                        &mut size,
1030                        std::ptr::null_mut(),
1031                        0,
1032                    ) == 0
1033                    {
1034                        String::from_utf8_lossy(&buf[..size - 1]).to_string()
1035                    } else {
1036                        "Unknown".to_string()
1037                    }
1038                };
1039
1040                // Get architecture
1041                let architecture = unsafe {
1042                    let mut size: libc::size_t = 256;
1043                    let mut buf = [0u8; 256];
1044                    if libc::sysctlbyname(
1045                        c"hw.machine".as_ptr(),
1046                        buf.as_mut_ptr() as *mut libc::c_void,
1047                        &mut size,
1048                        std::ptr::null_mut(),
1049                        0,
1050                    ) == 0
1051                    {
1052                        let arch_str = String::from_utf8_lossy(&buf[..size - 1]).to_string();
1053                        if arch_str.contains("arm64") || arch_str.contains("arm") {
1054                            "arm64".to_string()
1055                        } else {
1056                            arch_str
1057                        }
1058                    } else {
1059                        "unknown".to_string()
1060                    }
1061                };
1062
1063                // Get CPU cores
1064                let mut size = std::mem::size_of::<u32>();
1065                let mut cpu_cores: u32 = 1;
1066                unsafe {
1067                    let mut mib: [libc::c_int; 2] = [libc::CTL_HW, libc::HW_NCPU];
1068                    if libc::sysctl(
1069                        mib.as_mut_ptr(),
1070                        mib.len() as libc::c_uint,
1071                        &mut cpu_cores as *mut u32 as *mut libc::c_void,
1072                        &mut size,
1073                        std::ptr::null_mut(),
1074                        0,
1075                    ) == 0
1076                    {
1077                        // Successfully got CPU cores
1078                    }
1079                }
1080
1081                // Get page size
1082                let mut page_size: u64 = 4096;
1083                unsafe {
1084                    size = std::mem::size_of::<u64>();
1085                    if libc::sysctlbyname(
1086                        c"hw.pagesize".as_ptr(),
1087                        &mut page_size as *mut u64 as *mut libc::c_void,
1088                        &mut size,
1089                        std::ptr::null_mut(),
1090                        0,
1091                    ) != 0
1092                    {
1093                        page_size = 4096;
1094                    }
1095                }
1096
1097                // Get total physical memory
1098                let mut total: u64 = 0;
1099                let mut size = std::mem::size_of::<u64>();
1100                unsafe {
1101                    let mut mib: [libc::c_int; 2] = [libc::CTL_HW, libc::HW_MEMSIZE];
1102                    if libc::sysctl(
1103                        mib.as_mut_ptr(),
1104                        mib.len() as libc::c_uint,
1105                        &mut total as *mut u64 as *mut libc::c_void,
1106                        &mut size,
1107                        std::ptr::null_mut(),
1108                        0,
1109                    ) != 0
1110                    {
1111                        total = 16 * 1024 * 1024 * 1024; // 16GB default
1112                    }
1113                }
1114
1115                // Get available memory
1116                let mut vm_stats: libc::vm_statistics64 = unsafe { std::mem::zeroed() };
1117                let mut count = libc::HOST_VM_INFO64_COUNT;
1118                let (available_physical, used_physical) = unsafe {
1119                    if libc::host_statistics64(
1120                        mach2::mach_init::mach_host_self(),
1121                        libc::HOST_VM_INFO64,
1122                        &mut vm_stats as *mut _ as libc::host_info64_t,
1123                        &mut count,
1124                    ) != 0
1125                    {
1126                        // Fall back to simple calculation
1127                        (total / 2, total / 2)
1128                    } else {
1129                        let free = vm_stats.free_count as u64 * page_size;
1130                        let active = vm_stats.active_count as u64 * page_size;
1131                        let inactive = vm_stats.inactive_count as u64 * page_size;
1132                        let wired = vm_stats.wire_count as u64 * page_size;
1133                        let used = active + wired;
1134                        let available = free + inactive;
1135
1136                        (available, used)
1137                    }
1138                };
1139
1140                (
1141                    "macOS".to_string(),
1142                    os_version,
1143                    architecture,
1144                    cpu_cores,
1145                    page_size,
1146                    total,
1147                    available_physical,
1148                    used_physical,
1149                )
1150            }
1151
1152            #[cfg(not(target_os = "macos"))]
1153            {
1154                (
1155                    "Unknown".to_string(),
1156                    "Unknown".to_string(),
1157                    "unknown".to_string(),
1158                    1,
1159                    4096,
1160                    16_u64 * 1024 * 1024 * 1024,
1161                    8_u64 * 1024 * 1024 * 1024,
1162                    8_u64 * 1024 * 1024 * 1024,
1163                )
1164            }
1165        };
1166
1167        let high_risk_count = unsafe_reports
1168            .iter()
1169            .filter(|r| r.risk_level == "high")
1170            .count();
1171        let clean_passport_count = passport_details.iter().filter(|p| !p.is_leaked).count();
1172        let active_passport_count = passport_details
1173            .iter()
1174            .filter(|p| p.status == "active")
1175            .count();
1176        let leaked_passport_count = passport_details.iter().filter(|p| p.is_leaked).count();
1177        let ffi_tracked_count = passport_details.iter().filter(|p| p.ffi_tracked).count();
1178        let total_allocs = alloc_info.len().max(1);
1179        let unsafe_count = unsafe_reports.len();
1180        let leak_score = (100.0 - (leak_count as f64 / total_allocs as f64) * 100.0).max(0.0);
1181        let unsafe_score = (100.0 - (unsafe_count as f64 / total_allocs as f64) * 50.0).max(0.0);
1182        let risk_score = (100.0 - high_risk_count as f64 * HIGH_RISK_PENALTY).max(0.0);
1183        let health_score = ((leak_score + unsafe_score + risk_score) / 3.0).round() as u32;
1184        let health_status = if health_score >= 80 {
1185            "✅ Excellent"
1186        } else if health_score >= 60 {
1187            "⚠️ Good"
1188        } else {
1189            "🚨 Needs Attention"
1190        };
1191        let safe_ops_count = total_allocs.saturating_sub(unsafe_count);
1192        let safe_code_percent =
1193            ((safe_ops_count as f64 / total_allocs as f64) * 100.0).round() as u32;
1194
1195        let context = DashboardContext {
1196            title: "MemScope Dashboard".to_string(),
1197            export_timestamp: chrono::Utc::now()
1198                .format("%Y-%m-%d %H:%M:%S UTC")
1199                .to_string(),
1200            total_memory: format_bytes(total_memory),
1201            total_allocations: analysis.total_allocations,
1202            active_allocations: analysis.active_allocations,
1203            peak_memory: format_bytes(analysis.peak_memory_bytes as usize),
1204            thread_count: 1,
1205            passport_count: passports.len(),
1206            leak_count,
1207            unsafe_count: unsafe_reports.len(),
1208            ffi_count: unsafe_reports.len(),
1209            allocations: alloc_info.clone(),
1210            relationships: relationships.clone(),
1211            unsafe_reports: unsafe_reports.clone(),
1212            passport_details: passport_details.clone(),
1213            allocations_count: alloc_info.len(),
1214            relationships_count: relationships.len(),
1215            unsafe_reports_count: unsafe_reports.len(),
1216            json_data,
1217            os_name: os_name.clone(),
1218            architecture: architecture.clone(),
1219            cpu_cores: cpu_cores as usize,
1220            system_resources: SystemResources {
1221                os_name: os_name.clone(),
1222                os_version: os_version.clone(),
1223                architecture: architecture.clone(),
1224                cpu_cores,
1225                total_physical: format_bytes(total_physical as usize),
1226                available_physical: format_bytes(available_physical as usize),
1227                used_physical: format_bytes(used_physical as usize),
1228                page_size,
1229            },
1230            threads: Self::aggregate_thread_data(&alloc_info),
1231            async_tasks: Self::build_async_tasks(async_tracker),
1232            async_summary: Self::build_async_summary(async_tracker),
1233            health_score,
1234            health_status: health_status.to_string(),
1235            safe_ops_count,
1236            high_risk_count,
1237            clean_passport_count,
1238            active_passport_count,
1239            leaked_passport_count,
1240            ffi_tracked_count,
1241            safe_code_percent,
1242            ownership_graph: Self::build_ownership_graph_info(&allocations),
1243        };
1244
1245        Ok(context)
1246    }
1247
1248    /// Build ownership graph info from allocations
1249    fn build_ownership_graph_info(
1250        allocations: &[crate::capture::backends::core_types::AllocationInfo],
1251    ) -> OwnershipGraphInfo {
1252        use crate::analysis::ownership_graph::{
1253            DiagnosticIssue, ObjectId, OwnershipGraph, OwnershipOp,
1254        };
1255
1256        // Convert allocations to passport format for graph building
1257        let passports: Vec<(
1258            ObjectId,
1259            String,
1260            usize,
1261            Vec<crate::analysis::ownership_graph::OwnershipEvent>,
1262        )> = allocations
1263            .iter()
1264            .map(|alloc| {
1265                let id = ObjectId::from_ptr(alloc.ptr);
1266                let type_name = alloc
1267                    .type_name
1268                    .clone()
1269                    .unwrap_or_else(|| "unknown".to_string());
1270                let size = alloc.size;
1271
1272                // Generate ownership events from allocation info
1273                let events = vec![crate::analysis::ownership_graph::OwnershipEvent::new(
1274                    alloc.allocated_at_ns,
1275                    OwnershipOp::Create,
1276                    id,
1277                    None,
1278                )];
1279
1280                (id, type_name, size, events)
1281            })
1282            .collect();
1283
1284        let graph = OwnershipGraph::build(&passports);
1285        let diagnostics = graph.diagnostics(50);
1286
1287        // Build issues list
1288        let issues = diagnostics
1289            .issues
1290            .iter()
1291            .map(|issue| match issue {
1292                DiagnosticIssue::RcCycle { cycle_type, .. } => OwnershipIssue {
1293                    issue_type: "RcCycle".to_string(),
1294                    severity: "error".to_string(),
1295                    description: format!("{:?} retain cycle detected", cycle_type),
1296                },
1297                DiagnosticIssue::ArcCloneStorm {
1298                    clone_count,
1299                    threshold,
1300                } => OwnershipIssue {
1301                    issue_type: "ArcCloneStorm".to_string(),
1302                    severity: "warning".to_string(),
1303                    description: format!(
1304                        "Arc clone storm: {} clones (threshold: {})",
1305                        clone_count, threshold
1306                    ),
1307                },
1308            })
1309            .collect();
1310
1311        // Build root cause
1312        let root_cause = graph.find_root_cause().map(|rc| RootCauseInfo {
1313            cause: format!("{:?}", rc.root_cause),
1314            description: rc.description,
1315            impact: rc.impact,
1316        });
1317
1318        OwnershipGraphInfo {
1319            total_nodes: graph.nodes.len(),
1320            total_edges: graph.edges.len(),
1321            total_cycles: graph.cycles.len(),
1322            rc_clone_count: diagnostics.rc_clone_count,
1323            arc_clone_count: diagnostics.arc_clone_count,
1324            has_issues: diagnostics.has_issues(),
1325            issues,
1326            root_cause,
1327        }
1328    }
1329
1330    fn build_async_tasks(
1331        async_tracker: Option<&Arc<crate::capture::backends::async_tracker::AsyncTracker>>,
1332    ) -> Vec<AsyncTaskInfo> {
1333        if let Some(tracker) = async_tracker {
1334            let profiles = tracker.get_all_profiles();
1335            profiles
1336                .into_iter()
1337                .map(|p| {
1338                    let is_completed = p.is_completed();
1339                    let has_potential_leak = p.has_potential_leak();
1340                    let task_type_str = format!("{:?}", p.task_type);
1341                    AsyncTaskInfo {
1342                        task_id: p.task_id,
1343                        task_name: p.task_name,
1344                        task_type: task_type_str,
1345                        total_bytes: p.total_bytes,
1346                        current_memory: p.current_memory,
1347                        peak_memory: p.peak_memory,
1348                        total_allocations: p.total_allocations,
1349                        duration_ms: p.duration_ns as f64 / 1_000_000.0,
1350                        efficiency_score: p.efficiency_score,
1351                        is_completed,
1352                        has_potential_leak,
1353                    }
1354                })
1355                .collect()
1356        } else {
1357            Vec::new()
1358        }
1359    }
1360
1361    fn build_async_summary(
1362        async_tracker: Option<&Arc<crate::capture::backends::async_tracker::AsyncTracker>>,
1363    ) -> AsyncSummary {
1364        if let Some(tracker) = async_tracker {
1365            let stats = tracker.get_stats();
1366            AsyncSummary {
1367                total_tasks: stats.total_tasks,
1368                active_tasks: stats.active_tasks,
1369                total_allocations: stats.total_allocations,
1370                total_memory_bytes: stats.total_memory,
1371                peak_memory_bytes: stats.peak_memory,
1372            }
1373        } else {
1374            AsyncSummary {
1375                total_tasks: 0,
1376                active_tasks: 0,
1377                total_allocations: 0,
1378                total_memory_bytes: 0,
1379                peak_memory_bytes: 0,
1380            }
1381        }
1382    }
1383
1384    fn aggregate_thread_data(allocations: &[AllocationInfo]) -> Vec<ThreadInfo> {
1385        use std::collections::HashMap;
1386        let mut thread_map: HashMap<String, ThreadAggregator> = HashMap::new();
1387
1388        for alloc in allocations {
1389            let entry = thread_map
1390                .entry(alloc.thread_id.clone())
1391                .or_insert_with(|| ThreadAggregator {
1392                    allocation_count: 0,
1393                    current_memory: 0,
1394                    peak_memory: 0,
1395                    total_allocated: 0,
1396                });
1397            entry.allocation_count += 1;
1398            entry.current_memory += alloc.size;
1399            entry.total_allocated += alloc.size;
1400            if alloc.size > entry.peak_memory {
1401                entry.peak_memory = alloc.size;
1402            }
1403        }
1404
1405        thread_map
1406            .into_iter()
1407            .map(|(raw_tid, agg)| {
1408                let summary = format!(
1409                    "{} allocs, {}",
1410                    agg.allocation_count,
1411                    format_bytes(agg.current_memory)
1412                );
1413                let thread_id = format_thread_id(&raw_tid);
1414                ThreadInfo {
1415                    thread_id,
1416                    thread_summary: summary,
1417                    allocation_count: agg.allocation_count,
1418                    current_memory: format_bytes(agg.current_memory),
1419                    peak_memory: format_bytes(agg.peak_memory),
1420                    total_allocated: format_bytes(agg.total_allocated),
1421                    current_memory_bytes: agg.current_memory,
1422                    peak_memory_bytes: agg.peak_memory,
1423                    total_allocated_bytes: agg.total_allocated,
1424                }
1425            })
1426            .collect()
1427    }
1428
1429    /// Render dashboard from tracker data (for standalone template)
1430    pub fn render_from_tracker(
1431        &self,
1432        tracker: &Tracker,
1433        passport_tracker: &Arc<MemoryPassportTracker>,
1434    ) -> Result<String, Box<dyn std::error::Error>> {
1435        let context = self.build_context_from_tracker(tracker, passport_tracker)?;
1436        self.render_dashboard(&context)
1437    }
1438
1439    /// Render dashboard from context
1440    pub fn render_dashboard(
1441        &self,
1442        context: &DashboardContext,
1443    ) -> Result<String, Box<dyn std::error::Error>> {
1444        self.render_unified_dashboard(context)
1445    }
1446
1447    /// Render standalone dashboard (no external dependencies, works with file:// protocol)
1448    pub fn render_standalone_dashboard(
1449        &self,
1450        context: &DashboardContext,
1451    ) -> Result<String, Box<dyn std::error::Error>> {
1452        self.render_unified_dashboard(context)
1453    }
1454
1455    /// Render unified dashboard (multi-mode in single HTML)
1456    pub fn render_unified_dashboard(
1457        &self,
1458        context: &DashboardContext,
1459    ) -> Result<String, Box<dyn std::error::Error>> {
1460        let mut template_data = std::collections::BTreeMap::new();
1461        template_data.insert(
1462            "title".to_string(),
1463            serde_json::Value::String(context.title.clone()),
1464        );
1465        template_data.insert(
1466            "export_timestamp".to_string(),
1467            serde_json::Value::String(context.export_timestamp.clone()),
1468        );
1469        template_data.insert(
1470            "total_memory".to_string(),
1471            serde_json::Value::String(context.total_memory.clone()),
1472        );
1473        template_data.insert(
1474            "total_allocations".to_string(),
1475            serde_json::Value::Number(context.total_allocations.into()),
1476        );
1477        template_data.insert(
1478            "active_allocations".to_string(),
1479            serde_json::Value::Number(context.active_allocations.into()),
1480        );
1481        template_data.insert(
1482            "peak_memory".to_string(),
1483            serde_json::Value::String(context.peak_memory.clone()),
1484        );
1485        template_data.insert(
1486            "thread_count".to_string(),
1487            serde_json::Value::Number(context.thread_count.into()),
1488        );
1489        template_data.insert(
1490            "passport_count".to_string(),
1491            serde_json::Value::Number(context.passport_count.into()),
1492        );
1493        template_data.insert(
1494            "leak_count".to_string(),
1495            serde_json::Value::Number(context.leak_count.into()),
1496        );
1497        template_data.insert(
1498            "unsafe_count".to_string(),
1499            serde_json::Value::Number(context.unsafe_count.into()),
1500        );
1501        template_data.insert(
1502            "ffi_count".to_string(),
1503            serde_json::Value::Number(context.ffi_count.into()),
1504        );
1505        template_data.insert(
1506            "allocations_count".to_string(),
1507            serde_json::Value::Number(context.allocations_count.into()),
1508        );
1509        template_data.insert(
1510            "relationships_count".to_string(),
1511            serde_json::Value::Number(context.relationships_count.into()),
1512        );
1513        template_data.insert(
1514            "unsafe_reports_count".to_string(),
1515            serde_json::Value::Number(context.unsafe_reports_count.into()),
1516        );
1517        template_data.insert(
1518            "os_name".to_string(),
1519            serde_json::Value::String(context.os_name.clone()),
1520        );
1521        template_data.insert(
1522            "architecture".to_string(),
1523            serde_json::Value::String(context.architecture.clone()),
1524        );
1525        template_data.insert(
1526            "cpu_cores".to_string(),
1527            serde_json::Value::Number(context.cpu_cores.into()),
1528        );
1529        template_data.insert(
1530            "json_data".to_string(),
1531            serde_json::Value::String(context.json_data.clone()),
1532        );
1533        template_data.insert(
1534            "health_score".to_string(),
1535            serde_json::Value::Number(context.health_score.into()),
1536        );
1537        template_data.insert(
1538            "health_status".to_string(),
1539            serde_json::Value::String(context.health_status.clone()),
1540        );
1541        template_data.insert(
1542            "safe_ops_count".to_string(),
1543            serde_json::Value::Number(context.safe_ops_count.into()),
1544        );
1545        template_data.insert(
1546            "high_risk_count".to_string(),
1547            serde_json::Value::Number(context.high_risk_count.into()),
1548        );
1549        template_data.insert(
1550            "clean_passport_count".to_string(),
1551            serde_json::Value::Number(context.clean_passport_count.into()),
1552        );
1553        template_data.insert(
1554            "active_passport_count".to_string(),
1555            serde_json::Value::Number(context.active_passport_count.into()),
1556        );
1557        template_data.insert(
1558            "leaked_passport_count".to_string(),
1559            serde_json::Value::Number(context.leaked_passport_count.into()),
1560        );
1561        template_data.insert(
1562            "ffi_tracked_count".to_string(),
1563            serde_json::Value::Number(context.ffi_tracked_count.into()),
1564        );
1565        template_data.insert(
1566            "safe_code_percent".to_string(),
1567            serde_json::Value::Number(context.safe_code_percent.into()),
1568        );
1569
1570        template_data.insert(
1571            "allocations".to_string(),
1572            serde_json::to_value(&context.allocations)?,
1573        );
1574        template_data.insert(
1575            "passport_details".to_string(),
1576            serde_json::to_value(&context.passport_details)?,
1577        );
1578        template_data.insert(
1579            "relationships".to_string(),
1580            serde_json::to_value(&context.relationships)?,
1581        );
1582        template_data.insert(
1583            "unsafe_reports".to_string(),
1584            serde_json::to_value(&context.unsafe_reports)?,
1585        );
1586        template_data.insert(
1587            "threads".to_string(),
1588            serde_json::to_value(&context.threads)?,
1589        );
1590        template_data.insert(
1591            "ownership_graph".to_string(),
1592            serde_json::to_value(&context.ownership_graph)?,
1593        );
1594
1595        self.handlebars
1596            .render("dashboard_unified", &template_data)
1597            .map_err(|e| format!("Template rendering error: {}", e).into())
1598    }
1599
1600    /// Render final dashboard (new investigation console template)
1601    pub fn render_final_dashboard(
1602        &self,
1603        context: &DashboardContext,
1604    ) -> Result<String, Box<dyn std::error::Error>> {
1605        let mut template_data = std::collections::BTreeMap::new();
1606        template_data.insert(
1607            "title".to_string(),
1608            serde_json::Value::String(context.title.clone()),
1609        );
1610        template_data.insert(
1611            "export_timestamp".to_string(),
1612            serde_json::Value::String(context.export_timestamp.clone()),
1613        );
1614        template_data.insert(
1615            "total_memory".to_string(),
1616            serde_json::Value::String(context.total_memory.clone()),
1617        );
1618        template_data.insert(
1619            "total_allocations".to_string(),
1620            serde_json::Value::Number(context.total_allocations.into()),
1621        );
1622        template_data.insert(
1623            "active_allocations".to_string(),
1624            serde_json::Value::Number(context.active_allocations.into()),
1625        );
1626        template_data.insert(
1627            "peak_memory".to_string(),
1628            serde_json::Value::String(context.peak_memory.clone()),
1629        );
1630        template_data.insert(
1631            "thread_count".to_string(),
1632            serde_json::Value::Number(context.thread_count.into()),
1633        );
1634        template_data.insert(
1635            "passport_count".to_string(),
1636            serde_json::Value::Number(context.passport_count.into()),
1637        );
1638        template_data.insert(
1639            "leak_count".to_string(),
1640            serde_json::Value::Number(context.leak_count.into()),
1641        );
1642        template_data.insert(
1643            "unsafe_count".to_string(),
1644            serde_json::Value::Number(context.unsafe_count.into()),
1645        );
1646        template_data.insert(
1647            "ffi_count".to_string(),
1648            serde_json::Value::Number(context.ffi_count.into()),
1649        );
1650        template_data.insert(
1651            "health_score".to_string(),
1652            serde_json::Value::Number(context.health_score.into()),
1653        );
1654        template_data.insert(
1655            "health_status".to_string(),
1656            serde_json::Value::String(context.health_status.clone()),
1657        );
1658        template_data.insert(
1659            "safe_ops_count".to_string(),
1660            serde_json::Value::Number(context.safe_ops_count.into()),
1661        );
1662        template_data.insert(
1663            "high_risk_count".to_string(),
1664            serde_json::Value::Number(context.high_risk_count.into()),
1665        );
1666        template_data.insert(
1667            "clean_passport_count".to_string(),
1668            serde_json::Value::Number(context.clean_passport_count.into()),
1669        );
1670        template_data.insert(
1671            "active_passport_count".to_string(),
1672            serde_json::Value::Number(context.active_passport_count.into()),
1673        );
1674        template_data.insert(
1675            "leaked_passport_count".to_string(),
1676            serde_json::Value::Number(context.leaked_passport_count.into()),
1677        );
1678        template_data.insert(
1679            "ffi_tracked_count".to_string(),
1680            serde_json::Value::Number(context.ffi_tracked_count.into()),
1681        );
1682        template_data.insert(
1683            "safe_code_percent".to_string(),
1684            serde_json::Value::Number(context.safe_code_percent.into()),
1685        );
1686        template_data.insert(
1687            "os_name".to_string(),
1688            serde_json::Value::String(context.os_name.clone()),
1689        );
1690        template_data.insert(
1691            "architecture".to_string(),
1692            serde_json::Value::String(context.architecture.clone()),
1693        );
1694        template_data.insert(
1695            "cpu_cores".to_string(),
1696            serde_json::Value::Number(context.cpu_cores.into()),
1697        );
1698        template_data.insert(
1699            "json_data".to_string(),
1700            serde_json::Value::String(context.json_data.clone()),
1701        );
1702        template_data.insert(
1703            "allocations".to_string(),
1704            serde_json::to_value(&context.allocations)?,
1705        );
1706        template_data.insert(
1707            "passport_details".to_string(),
1708            serde_json::to_value(&context.passport_details)?,
1709        );
1710        template_data.insert(
1711            "relationships".to_string(),
1712            serde_json::to_value(&context.relationships)?,
1713        );
1714        template_data.insert(
1715            "unsafe_reports".to_string(),
1716            serde_json::to_value(&context.unsafe_reports)?,
1717        );
1718        template_data.insert(
1719            "threads".to_string(),
1720            serde_json::to_value(&context.threads)?,
1721        );
1722        template_data.insert(
1723            "async_tasks".to_string(),
1724            serde_json::to_value(&context.async_tasks)?,
1725        );
1726        template_data.insert(
1727            "ownership_graph".to_string(),
1728            serde_json::to_value(&context.ownership_graph)?,
1729        );
1730
1731        self.handlebars
1732            .render("dashboard_final", &template_data)
1733            .map_err(|e| format!("Template rendering error: {}", e).into())
1734    }
1735
1736    /// Render binary dashboard (legacy template)
1737    pub fn render_binary_dashboard(
1738        &self,
1739        context: &DashboardContext,
1740    ) -> Result<String, Box<dyn std::error::Error>> {
1741        let legacy_data = self.to_legacy_binary_data(context);
1742        let mut template_data = std::collections::BTreeMap::new();
1743        template_data.insert("BINARY_DATA".to_string(), legacy_data);
1744        template_data.insert(
1745            "PROJECT_NAME".to_string(),
1746            serde_json::Value::String("MemScope Memory Analysis".to_string()),
1747        );
1748
1749        self.handlebars
1750            .render("binary_dashboard", &template_data)
1751            .map_err(|e| format!("Template rendering error: {}", e).into())
1752    }
1753
1754    /// Render clean dashboard (legacy template)
1755    pub fn render_clean_dashboard(
1756        &self,
1757        context: &DashboardContext,
1758    ) -> Result<String, Box<dyn std::error::Error>> {
1759        let legacy_data = self.to_legacy_binary_data(context);
1760        let mut template_data = std::collections::BTreeMap::new();
1761        template_data.insert("BINARY_DATA".to_string(), legacy_data.clone());
1762        template_data.insert("json_data".to_string(), legacy_data);
1763        template_data.insert(
1764            "PROJECT_NAME".to_string(),
1765            serde_json::Value::String("MemScope Memory Analysis".to_string()),
1766        );
1767
1768        self.handlebars
1769            .render("clean_dashboard", &template_data)
1770            .map_err(|e| format!("Template rendering error: {}", e).into())
1771    }
1772
1773    /// Render hybrid dashboard (legacy template)
1774    pub fn render_hybrid_dashboard(
1775        &self,
1776        context: &DashboardContext,
1777    ) -> Result<String, Box<dyn std::error::Error>> {
1778        let variables_data = serde_json::Value::Array(
1779            context
1780                .allocations
1781                .iter()
1782                .map(|a| {
1783                    let mut map = serde_json::Map::new();
1784                    map.insert(
1785                        "var_name".to_string(),
1786                        serde_json::Value::String(a.var_name.clone()),
1787                    );
1788                    map.insert(
1789                        "type_name".to_string(),
1790                        serde_json::Value::String(a.type_name.clone()),
1791                    );
1792                    map.insert("size".to_string(), serde_json::Value::Number(a.size.into()));
1793                    map.insert(
1794                        "address".to_string(),
1795                        serde_json::Value::String(a.address.clone()),
1796                    );
1797                    map.insert(
1798                        "is_leaked".to_string(),
1799                        serde_json::Value::Bool(a.is_leaked),
1800                    );
1801                    map.insert(
1802                        "timestamp_alloc".to_string(),
1803                        serde_json::Value::Number(a.timestamp_alloc.into()),
1804                    );
1805                    map.insert(
1806                        "timestamp_dealloc".to_string(),
1807                        serde_json::Value::Number(a.timestamp_dealloc.into()),
1808                    );
1809                    map.insert(
1810                        "thread_id".to_string(),
1811                        serde_json::Value::String(a.thread_id.clone()),
1812                    );
1813                    serde_json::Value::Object(map)
1814                })
1815                .collect(),
1816        );
1817
1818        let threads_data = serde_json::Value::Array(
1819            context
1820                .threads
1821                .iter()
1822                .map(|t| {
1823                    let mut map = serde_json::Map::new();
1824                    map.insert(
1825                        "thread_id".to_string(),
1826                        serde_json::Value::String(t.thread_id.clone()),
1827                    );
1828                    map.insert(
1829                        "allocation_count".to_string(),
1830                        serde_json::Value::String(t.allocation_count.to_string()),
1831                    );
1832                    map.insert(
1833                        "current_memory".to_string(),
1834                        serde_json::Value::String(t.current_memory.clone()),
1835                    );
1836                    map.insert(
1837                        "peak_memory".to_string(),
1838                        serde_json::Value::String(t.peak_memory.clone()),
1839                    );
1840                    map.insert(
1841                        "total_allocated".to_string(),
1842                        serde_json::Value::String(t.total_allocated.clone()),
1843                    );
1844                    serde_json::Value::Object(map)
1845                })
1846                .collect(),
1847        );
1848
1849        let tasks_data = serde_json::Value::Array(Vec::new());
1850
1851        let total_memory: usize = context.allocations.iter().map(|a| a.size).sum();
1852        let efficiency = if context.total_allocations > 0 {
1853            (context.active_allocations as f64 / context.total_allocations as f64 * 100.0) as usize
1854        } else {
1855            100
1856        };
1857
1858        let mut template_data = std::collections::BTreeMap::new();
1859        template_data.insert("VARIABLES_DATA".to_string(), variables_data);
1860        template_data.insert("THREADS_DATA".to_string(), threads_data);
1861        template_data.insert("TASKS_DATA".to_string(), tasks_data);
1862        template_data.insert(
1863            "PROJECT_NAME".to_string(),
1864            serde_json::Value::String("MemScope Memory Analysis".to_string()),
1865        );
1866        template_data.insert(
1867            "TOTAL_MEMORY".to_string(),
1868            serde_json::Value::String(format_bytes(total_memory)),
1869        );
1870        template_data.insert(
1871            "TOTAL_VARIABLES".to_string(),
1872            serde_json::Value::Number(context.allocations.len().into()),
1873        );
1874        template_data.insert(
1875            "THREAD_COUNT".to_string(),
1876            serde_json::Value::Number(context.thread_count.into()),
1877        );
1878        template_data.insert(
1879            "EFFICIENCY".to_string(),
1880            serde_json::Value::String(format!("{}%", efficiency)),
1881        );
1882
1883        self.handlebars
1884            .render("hybrid_dashboard", &template_data)
1885            .map_err(|e| format!("Template rendering error: {}", e).into())
1886    }
1887
1888    /// Render performance dashboard (legacy template)
1889    pub fn render_performance_dashboard(
1890        &self,
1891        context: &DashboardContext,
1892    ) -> Result<String, Box<dyn std::error::Error>> {
1893        // Prepare performance data in expected format
1894        let performance_data = serde_json::json!({
1895            "allocations": context.allocations.iter().map(|a| {
1896                serde_json::json!({
1897                    "timestamp": a.timestamp_alloc,
1898                    "memory": a.size,
1899                    "var_name": a.var_name,
1900                    "type_name": a.type_name
1901                })
1902            }).collect::<Vec<_>>(),
1903            "total_memory": context.total_memory,
1904            "peak_memory": context.peak_memory,
1905            "allocations_count": context.total_allocations,
1906            "thread_count": context.thread_count
1907        });
1908
1909        // Prepare efficiency data
1910        // Calculate fragmentation: ratio of active allocations to total allocations
1911        // Higher ratio = less fragmentation (more allocations still in use)
1912        let fragmentation = if context.total_allocations > 0 {
1913            let deallocated = context
1914                .total_allocations
1915                .saturating_sub(context.active_allocations);
1916            deallocated as f64 / context.total_allocations as f64 * 100.0
1917        } else {
1918            0.0
1919        };
1920
1921        // Calculate reclamation rate: percentage of memory that was deallocated
1922        // This estimates how well the program is cleaning up memory
1923        let reclamation_rate = if context.total_allocations > 0 {
1924            let active_ratio = context.active_allocations as f64 / context.total_allocations as f64;
1925            (1.0 - active_ratio) * 100.0
1926        } else {
1927            100.0
1928        };
1929
1930        let efficiency_data = serde_json::json!({
1931            "memory_efficiency": if context.total_allocations > 0 {
1932                context.active_allocations as f64 / context.total_allocations as f64 * 100.0
1933            } else { 100.0 },
1934            "fragmentation": format!("{:.1}", fragmentation),
1935            "reclamation_rate": format!("{:.1}", reclamation_rate),
1936            "average_size": if context.allocations.is_empty() {
1937                0
1938            } else {
1939                context.allocations.iter().map(|a| a.size).sum::<usize>() / context.allocations.len()
1940            }
1941        });
1942
1943        let mut template_data = std::collections::BTreeMap::new();
1944        template_data.insert(
1945            "PERFORMANCE_DATA",
1946            serde_json::to_string(&performance_data)?,
1947        );
1948        template_data.insert("EFFICIENCY_DATA", serde_json::to_string(&efficiency_data)?);
1949        template_data.insert("PROJECT_NAME", "MemScope Memory Analysis".to_string());
1950
1951        self.handlebars
1952            .render("performance_dashboard", &template_data)
1953            .map_err(|e| format!("Template rendering error: {}", e).into())
1954    }
1955
1956    /// Render multithread dashboard (new template for thread analysis)
1957    pub fn render_multithread_dashboard(
1958        &self,
1959        context: &DashboardContext,
1960    ) -> Result<String, Box<dyn std::error::Error>> {
1961        let threads_data = self.prepare_thread_data(context)?;
1962        let allocation_data = self.prepare_allocation_timeline_data(context)?;
1963        let conflict_data = self.prepare_conflict_data(context)?;
1964
1965        let conflict_count = conflict_data.as_array().map(|a| a.len()).unwrap_or(0);
1966        let mut template_data = std::collections::BTreeMap::new();
1967        template_data.insert("THREADS_DATA".to_string(), threads_data);
1968        template_data.insert("ALLOCATION_DATA".to_string(), allocation_data);
1969        template_data.insert("CONFLICT_DATA".to_string(), conflict_data);
1970        template_data.insert(
1971            "PROJECT_NAME".to_string(),
1972            serde_json::Value::String("MemScope Memory Analysis".to_string()),
1973        );
1974        template_data.insert(
1975            "THREAD_COUNT".to_string(),
1976            serde_json::Value::Number(context.thread_count.into()),
1977        );
1978        template_data.insert(
1979            "TOTAL_MEMORY".to_string(),
1980            serde_json::Value::String(context.total_memory.clone()),
1981        );
1982        template_data.insert(
1983            "TOTAL_ALLOCATIONS".to_string(),
1984            serde_json::Value::Number(context.total_allocations.into()),
1985        );
1986        template_data.insert(
1987            "CONFLICT_COUNT".to_string(),
1988            serde_json::Value::Number(conflict_count.into()),
1989        );
1990
1991        self.handlebars
1992            .render("multithread_template", &template_data)
1993            .map_err(|e| format!("Template rendering error: {}", e).into())
1994    }
1995
1996    /// Prepare thread data for multithread dashboard
1997    fn prepare_thread_data(
1998        &self,
1999        context: &DashboardContext,
2000    ) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
2001        let mut thread_map: std::collections::HashMap<String, ThreadStats> =
2002            std::collections::HashMap::new();
2003
2004        for allocation in &context.allocations {
2005            let thread_id = allocation.thread_id.clone();
2006            let stats = thread_map
2007                .entry(thread_id.clone())
2008                .or_insert_with(|| ThreadStats {
2009                    id: thread_id.parse::<u64>().unwrap_or(0),
2010                    allocations: 0,
2011                    memory: 0,
2012                    peak: 0,
2013                    status: "active".to_string(),
2014                });
2015
2016            stats.allocations += 1;
2017            stats.memory += allocation.size;
2018            if allocation.size > stats.peak {
2019                stats.peak = allocation.size;
2020            }
2021        }
2022
2023        let threads: Vec<ThreadStats> = thread_map.into_values().collect();
2024        serde_json::to_value(&threads).map_err(|e| e.into())
2025    }
2026
2027    /// Prepare allocation timeline data for multithread dashboard
2028    fn prepare_allocation_timeline_data(
2029        &self,
2030        context: &DashboardContext,
2031    ) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
2032        let timeline: Vec<TimelineAllocation> = context
2033            .allocations
2034            .iter()
2035            .map(|a| TimelineAllocation {
2036                timestamp: a.timestamp_alloc,
2037                thread_id: a.thread_id.parse::<u64>().unwrap_or(0),
2038                size: a.size,
2039                var_name: Some(a.var_name.clone()),
2040            })
2041            .collect();
2042
2043        serde_json::to_value(&timeline).map_err(|e| e.into())
2044    }
2045
2046    /// Prepare conflict data for multithread dashboard
2047    fn prepare_conflict_data(
2048        &self,
2049        context: &DashboardContext,
2050    ) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
2051        let mut conflicts: Vec<ThreadConflict> = Vec::new();
2052
2053        let mut address_map: std::collections::HashMap<String, Vec<&AllocationInfo>> =
2054            std::collections::HashMap::new();
2055
2056        for allocation in &context.allocations {
2057            address_map
2058                .entry(allocation.address.clone())
2059                .or_default()
2060                .push(allocation);
2061        }
2062
2063        for (address, allocations) in &address_map {
2064            if allocations.len() > 1 {
2065                let thread_ids: Vec<u64> = allocations
2066                    .iter()
2067                    .map(|a| a.thread_id.parse::<u64>().unwrap_or(0))
2068                    .collect();
2069                let unique_threads: std::collections::HashSet<u64> =
2070                    thread_ids.iter().cloned().collect();
2071
2072                if unique_threads.len() > 1 {
2073                    conflicts.push(ThreadConflict {
2074                        description: format!(
2075                            "Address {} accessed by {} threads",
2076                            address,
2077                            unique_threads.len()
2078                        ),
2079                        threads: thread_ids
2080                            .iter()
2081                            .map(|t| t.to_string())
2082                            .collect::<Vec<_>>()
2083                            .join(", "),
2084                        conflict_type: "Data Race".to_string(),
2085                    });
2086                }
2087            }
2088        }
2089
2090        serde_json::to_value(&conflicts).map_err(|e| e.into())
2091    }
2092
2093    /// Build base async data map with common fields
2094    fn build_async_base_map(
2095        context: &DashboardContext,
2096        subtitle: &str,
2097    ) -> serde_json::Map<String, serde_json::Value> {
2098        let mut map = serde_json::Map::new();
2099        map.insert(
2100            "title".to_string(),
2101            serde_json::Value::String("Async Performance Dashboard".to_string()),
2102        );
2103        map.insert(
2104            "subtitle".to_string(),
2105            serde_json::Value::String(subtitle.to_string()),
2106        );
2107        map.insert(
2108            "total_tasks".to_string(),
2109            serde_json::Value::Number(context.allocations.len().into()),
2110        );
2111        map.insert(
2112            "active_tasks".to_string(),
2113            serde_json::Value::Number(context.active_allocations.into()),
2114        );
2115        map.insert(
2116            "completed_tasks".to_string(),
2117            serde_json::Value::Number(
2118                context
2119                    .allocations
2120                    .iter()
2121                    .filter(|a| a.timestamp_dealloc > 0)
2122                    .count()
2123                    .into(),
2124            ),
2125        );
2126        map.insert(
2127            "failed_tasks".to_string(),
2128            serde_json::Value::Number(0.into()),
2129        );
2130        map.insert(
2131            "cpu_usage_avg".to_string(),
2132            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2133        );
2134        map.insert(
2135            "cpu_usage_peak".to_string(),
2136            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2137        );
2138        map.insert(
2139            "cpu_cores".to_string(),
2140            serde_json::Value::Number(context.cpu_cores.into()),
2141        );
2142        map.insert(
2143            "context_switches".to_string(),
2144            serde_json::Value::Number(0.into()),
2145        );
2146        map.insert(
2147            "total_memory_mb".to_string(),
2148            serde_json::Value::Number(
2149                serde_json::Number::from_f64(Self::parse_bytes_to_mb(&context.total_memory))
2150                    .unwrap(),
2151            ),
2152        );
2153        map.insert(
2154            "peak_memory_mb".to_string(),
2155            serde_json::Value::Number(
2156                serde_json::Number::from_f64(Self::parse_bytes_to_mb(&context.peak_memory))
2157                    .unwrap(),
2158            ),
2159        );
2160        map.insert(
2161            "total_allocations".to_string(),
2162            serde_json::Value::Number(context.total_allocations.into()),
2163        );
2164        map.insert(
2165            "memory_efficiency".to_string(),
2166            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2167        );
2168        map.insert(
2169            "io_throughput".to_string(),
2170            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2171        );
2172        map.insert(
2173            "total_read_mb".to_string(),
2174            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2175        );
2176        map.insert(
2177            "total_write_mb".to_string(),
2178            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2179        );
2180        map.insert(
2181            "total_io_ops".to_string(),
2182            serde_json::Value::Number(0.into()),
2183        );
2184        map.insert(
2185            "network_throughput".to_string(),
2186            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2187        );
2188        map.insert(
2189            "total_sent_mb".to_string(),
2190            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2191        );
2192        map.insert(
2193            "total_received_mb".to_string(),
2194            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2195        );
2196        map.insert(
2197            "avg_latency".to_string(),
2198            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2199        );
2200        map.insert(
2201            "efficiency_score".to_string(),
2202            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2203        );
2204        map.insert(
2205            "resource_balance".to_string(),
2206            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2207        );
2208        map.insert(
2209            "bottleneck_count".to_string(),
2210            serde_json::Value::Number(0.into()),
2211        );
2212        map.insert(
2213            "optimization_potential".to_string(),
2214            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2215        );
2216        map.insert(
2217            "futures_count".to_string(),
2218            serde_json::Value::Number(0.into()),
2219        );
2220        map.insert(
2221            "total_polls".to_string(),
2222            serde_json::Value::Number(0.into()),
2223        );
2224        map.insert(
2225            "avg_poll_time".to_string(),
2226            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2227        );
2228        map.insert(
2229            "ready_rate".to_string(),
2230            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2231        );
2232        map.insert(
2233            "cpu_intensive_count".to_string(),
2234            serde_json::Value::Number(0.into()),
2235        );
2236        map.insert(
2237            "cpu_avg_efficiency".to_string(),
2238            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2239        );
2240        map.insert(
2241            "cpu_intensive_tasks".to_string(),
2242            serde_json::Value::Array(vec![]),
2243        );
2244        map.insert(
2245            "memory_intensive_count".to_string(),
2246            serde_json::Value::Number(0.into()),
2247        );
2248        map.insert(
2249            "memory_avg_efficiency".to_string(),
2250            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2251        );
2252        map.insert(
2253            "memory_intensive_tasks".to_string(),
2254            serde_json::Value::Array(vec![]),
2255        );
2256        map.insert(
2257            "io_intensive_count".to_string(),
2258            serde_json::Value::Number(0.into()),
2259        );
2260        map.insert(
2261            "io_avg_efficiency".to_string(),
2262            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2263        );
2264        map.insert(
2265            "io_intensive_tasks".to_string(),
2266            serde_json::Value::Array(vec![]),
2267        );
2268        map.insert(
2269            "network_intensive_count".to_string(),
2270            serde_json::Value::Number(0.into()),
2271        );
2272        map.insert(
2273            "network_avg_efficiency".to_string(),
2274            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2275        );
2276        map.insert(
2277            "network_intensive_tasks".to_string(),
2278            serde_json::Value::Array(vec![]),
2279        );
2280        map.insert(
2281            "executor_utilization".to_string(),
2282            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2283        );
2284        map.insert(
2285            "avg_queue_length".to_string(),
2286            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2287        );
2288        map.insert(
2289            "blocking_tasks_count".to_string(),
2290            serde_json::Value::Number(0.into()),
2291        );
2292        map.insert(
2293            "deadlock_risk".to_string(),
2294            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2295        );
2296        map.insert(
2297            "gc_pressure".to_string(),
2298            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2299        );
2300        map.insert(
2301            "avg_fragmentation".to_string(),
2302            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2303        );
2304        map.insert(
2305            "peak_alloc_rate".to_string(),
2306            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2307        );
2308        map.insert(
2309            "waker_efficiency".to_string(),
2310            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2311        );
2312        map.insert(
2313            "immediate_ready_percent".to_string(),
2314            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2315        );
2316        map
2317    }
2318
2319    /// Parse bytes string to MB (helper function)
2320    fn parse_bytes_to_mb(bytes_str: &str) -> f64 {
2321        let num_str: String = bytes_str
2322            .chars()
2323            .filter(|c| c.is_ascii_digit() || *c == '.')
2324            .collect();
2325        let num: f64 = num_str.parse().unwrap_or(0.0);
2326        if bytes_str.contains("GB") {
2327            num * 1024.0
2328        } else if bytes_str.contains("MB") {
2329            num
2330        } else if bytes_str.contains("KB") {
2331            num / 1024.0
2332        } else {
2333            num / 1024.0 / 1024.0
2334        }
2335    }
2336
2337    /// Render async template (legacy template)
2338    pub fn render_async_template(
2339        &self,
2340        context: &DashboardContext,
2341    ) -> Result<String, Box<dyn std::error::Error>> {
2342        let has_async_tasks = context.allocations.iter().any(|a| {
2343            a.type_name.contains("Future")
2344                || a.type_name.contains("Task")
2345                || a.type_name.contains("async")
2346                || a.type_name.contains("Waker")
2347        });
2348
2349        let async_data = if has_async_tasks {
2350            self.prepare_async_data(context)?
2351        } else {
2352            serde_json::Value::Object(Self::build_async_base_map(
2353                context,
2354                "No async tasks detected",
2355            ))
2356        };
2357
2358        let mut template_data = std::collections::BTreeMap::new();
2359        if let serde_json::Value::Object(map) = &async_data {
2360            for (key, value) in map {
2361                template_data.insert(key.clone(), value.clone());
2362            }
2363        }
2364        template_data.insert(
2365            "PROJECT_NAME".to_string(),
2366            serde_json::Value::String("MemScope Async Performance Analysis".to_string()),
2367        );
2368
2369        self.handlebars
2370            .render("async_template", &template_data)
2371            .map_err(|e| format!("Template rendering error: {}", e).into())
2372    }
2373
2374    /// Prepare async-specific data from context
2375    fn prepare_async_data(
2376        &self,
2377        context: &DashboardContext,
2378    ) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
2379        let mut cpu_intensive_tasks: Vec<serde_json::Value> = Vec::new();
2380        let mut memory_intensive_tasks: Vec<serde_json::Value> = Vec::new();
2381        let mut io_intensive_tasks: Vec<serde_json::Value> = Vec::new();
2382        let mut network_intensive_tasks: Vec<serde_json::Value> = Vec::new();
2383
2384        for (idx, alloc) in context.allocations.iter().enumerate() {
2385            let task_type = if alloc.type_name.contains("Future") {
2386                "future"
2387            } else if alloc.type_name.contains("Task") {
2388                "task"
2389            } else if alloc.type_name.contains("Channel") {
2390                "channel"
2391            } else {
2392                "async_op"
2393            };
2394
2395            let status = if alloc.is_leaked {
2396                "leaked"
2397            } else if alloc.timestamp_dealloc > 0 {
2398                "completed"
2399            } else {
2400                "active"
2401            };
2402            let mut task_map = serde_json::Map::new();
2403            task_map.insert("task_id".to_string(), serde_json::Value::Number(idx.into()));
2404            task_map.insert(
2405                "task_name".to_string(),
2406                serde_json::Value::String(if alloc.var_name.is_empty() {
2407                    format!("async_{}", idx)
2408                } else {
2409                    alloc.var_name.clone()
2410                }),
2411            );
2412            task_map.insert(
2413                "source_file".to_string(),
2414                serde_json::Value::String("unknown".to_string()),
2415            );
2416            task_map.insert(
2417                "source_line".to_string(),
2418                serde_json::Value::Number(0.into()),
2419            );
2420            task_map.insert(
2421                "task_type".to_string(),
2422                serde_json::Value::String(task_type.to_string()),
2423            );
2424            task_map.insert(
2425                "cpu_usage".to_string(),
2426                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2427            );
2428            task_map.insert(
2429                "cpu_cycles".to_string(),
2430                serde_json::Value::Number(0.into()),
2431            );
2432            task_map.insert(
2433                "instructions".to_string(),
2434                serde_json::Value::Number(0.into()),
2435            );
2436            task_map.insert(
2437                "cache_misses".to_string(),
2438                serde_json::Value::Number(0.into()),
2439            );
2440            task_map.insert(
2441                "allocated_mb".to_string(),
2442                serde_json::Value::Number(
2443                    serde_json::Number::from_f64(alloc.size as f64 / 1024.0 / 1024.0).unwrap(),
2444                ),
2445            );
2446            task_map.insert(
2447                "memory_usage_percent".to_string(),
2448                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2449            );
2450            task_map.insert(
2451                "peak_memory_mb".to_string(),
2452                serde_json::Value::Number(
2453                    serde_json::Number::from_f64(alloc.size as f64 / 1024.0 / 1024.0).unwrap(),
2454                ),
2455            );
2456            task_map.insert(
2457                "allocation_count".to_string(),
2458                serde_json::Value::Number(1.into()),
2459            );
2460            task_map.insert(
2461                "heap_fragmentation".to_string(),
2462                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2463            );
2464            task_map.insert(
2465                "bytes_read_mb".to_string(),
2466                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2467            );
2468            task_map.insert(
2469                "bytes_written_mb".to_string(),
2470                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2471            );
2472            task_map.insert(
2473                "avg_latency_us".to_string(),
2474                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2475            );
2476            task_map.insert(
2477                "queue_depth".to_string(),
2478                serde_json::Value::Number(0.into()),
2479            );
2480            task_map.insert(
2481                "bytes_sent_mb".to_string(),
2482                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2483            );
2484            task_map.insert(
2485                "bytes_received_mb".to_string(),
2486                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2487            );
2488            task_map.insert(
2489                "active_connections".to_string(),
2490                serde_json::Value::Number(0.into()),
2491            );
2492            task_map.insert(
2493                "avg_latency_ms".to_string(),
2494                serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2495            );
2496            task_map.insert(
2497                "status".to_string(),
2498                serde_json::Value::String(status.to_string()),
2499            );
2500            task_map.insert(
2501                "duration_ms".to_string(),
2502                serde_json::Value::Number(serde_json::Number::from_f64(alloc.lifetime_ms).unwrap()),
2503            );
2504            let task_data = serde_json::Value::Object(task_map);
2505
2506            if alloc.type_name.contains("Future") || alloc.type_name.contains("Stream") {
2507                cpu_intensive_tasks.push(task_data);
2508            } else if alloc.type_name.contains("Channel") || alloc.type_name.contains("Mutex") {
2509                memory_intensive_tasks.push(task_data);
2510            } else if alloc.type_name.contains("Tcp") || alloc.type_name.contains("Udp") {
2511                network_intensive_tasks.push(task_data);
2512            } else {
2513                io_intensive_tasks.push(task_data);
2514            }
2515        }
2516
2517        let memory_efficiency = if context.total_allocations > 0 {
2518            context.active_allocations as f64 / context.total_allocations as f64 * 100.0
2519        } else {
2520            100.0
2521        };
2522
2523        let mut map = serde_json::Map::new();
2524        map.insert(
2525            "title".to_string(),
2526            serde_json::Value::String("Async Performance Dashboard".to_string()),
2527        );
2528        map.insert(
2529            "subtitle".to_string(),
2530            serde_json::Value::String("Rust Async Runtime Analysis".to_string()),
2531        );
2532        map.insert(
2533            "total_tasks".to_string(),
2534            serde_json::Value::Number(context.allocations.len().into()),
2535        );
2536        map.insert(
2537            "active_tasks".to_string(),
2538            serde_json::Value::Number(context.active_allocations.into()),
2539        );
2540        map.insert(
2541            "completed_tasks".to_string(),
2542            serde_json::Value::Number(
2543                context
2544                    .allocations
2545                    .iter()
2546                    .filter(|a| a.timestamp_dealloc > 0)
2547                    .count()
2548                    .into(),
2549            ),
2550        );
2551        map.insert(
2552            "failed_tasks".to_string(),
2553            serde_json::Value::Number(context.leak_count.into()),
2554        );
2555        map.insert(
2556            "cpu_usage_avg".to_string(),
2557            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2558        );
2559        map.insert(
2560            "cpu_usage_peak".to_string(),
2561            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2562        );
2563        map.insert(
2564            "cpu_cores".to_string(),
2565            serde_json::Value::Number(context.cpu_cores.into()),
2566        );
2567        map.insert(
2568            "context_switches".to_string(),
2569            serde_json::Value::Number(0.into()),
2570        );
2571        map.insert(
2572            "total_memory_mb".to_string(),
2573            serde_json::Value::Number(
2574                serde_json::Number::from_f64(Self::parse_bytes_to_mb(&context.total_memory))
2575                    .unwrap(),
2576            ),
2577        );
2578        map.insert(
2579            "peak_memory_mb".to_string(),
2580            serde_json::Value::Number(
2581                serde_json::Number::from_f64(Self::parse_bytes_to_mb(&context.peak_memory))
2582                    .unwrap(),
2583            ),
2584        );
2585        map.insert(
2586            "total_allocations".to_string(),
2587            serde_json::Value::Number(context.total_allocations.into()),
2588        );
2589        map.insert(
2590            "memory_efficiency".to_string(),
2591            serde_json::Value::Number(serde_json::Number::from_f64(memory_efficiency).unwrap()),
2592        );
2593        map.insert(
2594            "io_throughput".to_string(),
2595            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2596        );
2597        map.insert(
2598            "total_read_mb".to_string(),
2599            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2600        );
2601        map.insert(
2602            "total_write_mb".to_string(),
2603            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2604        );
2605        map.insert(
2606            "total_io_ops".to_string(),
2607            serde_json::Value::Number(0.into()),
2608        );
2609        map.insert(
2610            "network_throughput".to_string(),
2611            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2612        );
2613        map.insert(
2614            "total_sent_mb".to_string(),
2615            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2616        );
2617        map.insert(
2618            "total_received_mb".to_string(),
2619            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2620        );
2621        map.insert(
2622            "avg_latency".to_string(),
2623            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2624        );
2625        map.insert(
2626            "efficiency_score".to_string(),
2627            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2628        );
2629        map.insert(
2630            "resource_balance".to_string(),
2631            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2632        );
2633        map.insert(
2634            "bottleneck_count".to_string(),
2635            serde_json::Value::Number(0.into()),
2636        );
2637        map.insert(
2638            "optimization_potential".to_string(),
2639            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2640        );
2641        map.insert(
2642            "futures_count".to_string(),
2643            serde_json::Value::Number(cpu_intensive_tasks.len().into()),
2644        );
2645        map.insert(
2646            "total_polls".to_string(),
2647            serde_json::Value::Number(0.into()),
2648        );
2649        map.insert(
2650            "avg_poll_time".to_string(),
2651            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2652        );
2653        map.insert(
2654            "ready_rate".to_string(),
2655            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2656        );
2657        map.insert(
2658            "cpu_intensive_count".to_string(),
2659            serde_json::Value::Number(cpu_intensive_tasks.len().into()),
2660        );
2661        map.insert(
2662            "cpu_avg_efficiency".to_string(),
2663            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2664        );
2665        map.insert(
2666            "cpu_intensive_tasks".to_string(),
2667            serde_json::Value::Array(cpu_intensive_tasks),
2668        );
2669        map.insert(
2670            "memory_intensive_count".to_string(),
2671            serde_json::Value::Number(memory_intensive_tasks.len().into()),
2672        );
2673        map.insert(
2674            "memory_avg_efficiency".to_string(),
2675            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2676        );
2677        map.insert(
2678            "memory_intensive_tasks".to_string(),
2679            serde_json::Value::Array(memory_intensive_tasks),
2680        );
2681        map.insert(
2682            "io_intensive_count".to_string(),
2683            serde_json::Value::Number(io_intensive_tasks.len().into()),
2684        );
2685        map.insert(
2686            "io_avg_efficiency".to_string(),
2687            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2688        );
2689        map.insert(
2690            "io_intensive_tasks".to_string(),
2691            serde_json::Value::Array(io_intensive_tasks),
2692        );
2693        map.insert(
2694            "network_intensive_count".to_string(),
2695            serde_json::Value::Number(network_intensive_tasks.len().into()),
2696        );
2697        map.insert(
2698            "network_avg_efficiency".to_string(),
2699            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2700        );
2701        map.insert(
2702            "network_intensive_tasks".to_string(),
2703            serde_json::Value::Array(network_intensive_tasks),
2704        );
2705        map.insert(
2706            "executor_utilization".to_string(),
2707            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2708        );
2709        map.insert(
2710            "avg_queue_length".to_string(),
2711            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2712        );
2713        map.insert(
2714            "blocking_tasks_count".to_string(),
2715            serde_json::Value::Number(0.into()),
2716        );
2717        map.insert(
2718            "deadlock_risk".to_string(),
2719            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2720        );
2721        map.insert(
2722            "gc_pressure".to_string(),
2723            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2724        );
2725        map.insert(
2726            "avg_fragmentation".to_string(),
2727            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2728        );
2729        map.insert(
2730            "peak_alloc_rate".to_string(),
2731            serde_json::Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
2732        );
2733        map.insert(
2734            "waker_efficiency".to_string(),
2735            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2736        );
2737        map.insert(
2738            "immediate_ready_percent".to_string(),
2739            serde_json::Value::Number(serde_json::Number::from_f64(100.0).unwrap()),
2740        );
2741        Ok(serde_json::Value::Object(map))
2742    }
2743
2744    /// Convert new DashboardContext to legacy binary data format
2745    fn to_legacy_binary_data(&self, context: &DashboardContext) -> serde_json::Value {
2746        // Calculate type distribution
2747        let mut type_counts: std::collections::HashMap<String, usize> =
2748            std::collections::HashMap::new();
2749        let mut total_size: usize = 0;
2750
2751        for alloc in &context.allocations {
2752            let type_name =
2753                if alloc.type_name.contains("Vec") || alloc.type_name.contains("vec::Vec") {
2754                    "dynamic_array"
2755                } else if alloc.type_name.contains("String") || alloc.type_name.contains("str") {
2756                    "string"
2757                } else if alloc.type_name.contains("Box")
2758                    || alloc.type_name.contains("Rc")
2759                    || alloc.type_name.contains("Arc")
2760                {
2761                    "smart_pointer"
2762                } else if alloc.type_name.contains("[") && alloc.type_name.contains("u8") {
2763                    "byte_array"
2764                } else if alloc.size > 1024 * 1024 {
2765                    "large_buffer"
2766                } else {
2767                    "custom"
2768                }
2769                .to_string();
2770
2771            *type_counts.entry(type_name).or_insert(0) += 1;
2772            total_size += alloc.size;
2773        }
2774
2775        // Calculate statistics
2776        let average_size = if context.allocations.is_empty() {
2777            0
2778        } else {
2779            total_size / context.allocations.len()
2780        };
2781
2782        // Build lifetime events from allocations
2783        let lifetime_events: Vec<serde_json::Value> = context.allocations.iter().map(|a| {
2784            serde_json::json!({
2785                "address": a.address,
2786                "events": [{
2787                    "context": "initial_allocation",
2788                    "event_type": "Created",
2789                    "timestamp": a.timestamp_alloc
2790                }],
2791                "lifetime_ms": a.lifetime_ms,
2792                "size": a.size,
2793                "timestamp_alloc": a.timestamp_alloc,
2794                "timestamp_dealloc": if a.timestamp_dealloc > 0 { Some(a.timestamp_dealloc) } else { None },
2795                "type_name": a.type_name,
2796                "var_name": a.var_name
2797            })
2798        }).collect();
2799
2800        serde_json::json!({
2801            "memory_analysis": {
2802                "allocations": context.allocations.iter().map(|a| {
2803                    serde_json::json!({
2804                        "var_name": a.var_name,
2805                        "type_name": a.type_name,
2806                        "size": a.size,
2807                        "address": a.address,
2808                        "timestamp": a.timestamp,
2809                        "timestamp_alloc": a.timestamp_alloc,
2810                        "timestamp_dealloc": if a.timestamp_dealloc > 0 { Some(a.timestamp_dealloc) } else { None },
2811                        "lifetime_ms": a.lifetime_ms,
2812                        "is_leaked": a.is_leaked,
2813                        "thread_id": a.thread_id,
2814                        "immutable_borrows": a.immutable_borrows,
2815                        "mutable_borrows": a.mutable_borrows,
2816                        "is_clone": a.is_clone,
2817                        "clone_count": a.clone_count,
2818                        "allocation_type": a.allocation_type,
2819                        "is_smart_pointer": a.is_smart_pointer,
2820                        "smart_pointer_type": a.smart_pointer_type,
2821                        "borrow_info": {
2822                            "immutable_borrows": a.immutable_borrows,
2823                            "max_concurrent_borrows": a.immutable_borrows + a.mutable_borrows,
2824                            "mutable_borrows": a.mutable_borrows
2825                        },
2826                        "clone_info": {
2827                            "clone_count": a.clone_count,
2828                            "is_clone": a.is_clone,
2829                            "original_ptr": null
2830                        },
2831                        "ownership_history_available": false,
2832                        "type": if a.type_name.contains("Vec") || a.type_name.contains("vec::Vec") {
2833                            "dynamic_array"
2834                        } else if a.type_name.contains("String") || a.type_name.contains("str") {
2835                            "string"
2836                        } else if a.type_name.contains("Box") || a.type_name.contains("Rc") || a.type_name.contains("Arc") {
2837                            "smart_pointer"
2838                        } else {
2839                            "custom"
2840                        }
2841                    })
2842                }).collect::<Vec<_>>(),
2843                "metadata": {
2844                    "export_timestamp": context.export_timestamp,
2845                    "export_version": "2.0",
2846                    "specification": "memscope-rs memory analysis",
2847                    "total_allocations": context.allocations.len(),
2848                    "total_size_bytes": total_size
2849                },
2850                "statistics": {
2851                    "average_size_bytes": average_size,
2852                    "total_allocations": context.allocations.len(),
2853                    "total_size_bytes": total_size
2854                },
2855                "type_distribution": type_counts
2856            },
2857            "lifetime": {
2858                "metadata": {
2859                    "export_timestamp": context.export_timestamp,
2860                    "export_version": "2.0",
2861                    "specification": "memscope-rs lifetime tracking",
2862                    "total_tracked_allocations": context.allocations.len()
2863                },
2864                "ownership_histories": lifetime_events
2865            },
2866            "complex_types": {
2867                "smart_pointers": context.allocations.iter().filter(|a| a.is_smart_pointer).count(),
2868                "collections": context.allocations.iter().filter(|a| {
2869                    a.type_name.contains("Vec") || a.type_name.contains("HashMap") || a.type_name.contains("BTreeMap")
2870                }).count()
2871            },
2872            "unsafe_ffi": {
2873                "passports": context.passport_details,
2874                "reports": context.unsafe_reports,
2875                "cross_boundary_events": context.unsafe_reports.iter()
2876                    .flat_map(|r| r.cross_boundary_events.iter())
2877                    .count()
2878            },
2879            "performance": {
2880                "total_memory": context.total_memory,
2881                "peak_memory": context.peak_memory,
2882                "total_allocations": context.total_allocations,
2883                "active_allocations": context.active_allocations,
2884                "thread_count": context.thread_count,
2885                "passport_count": context.passport_count,
2886                "leak_count": context.leak_count,
2887                "unsafe_count": context.unsafe_count,
2888                "ffi_count": context.ffi_count
2889            },
2890            "system_resources": {
2891                "os_name": context.os_name,
2892                "architecture": context.architecture,
2893                "cpu_cores": context.cpu_cores,
2894                "system_info": context.system_resources
2895            },
2896            "threads": context.threads
2897        })
2898    }
2899}
2900
2901/// Format thread_id from "ThreadId(5)" to "Thread-5"
2902fn format_thread_id(raw: &str) -> String {
2903    if raw.starts_with("ThreadId(") && raw.ends_with(')') {
2904        let num = &raw[9..raw.len() - 1];
2905        format!("Thread-{}", num)
2906    } else {
2907        raw.to_string()
2908    }
2909}
2910
2911/// Format bytes to human-readable string
2912fn format_bytes(bytes: usize) -> String {
2913    const KB: usize = 1024;
2914    const MB: usize = KB * 1024;
2915    const GB: usize = MB * 1024;
2916    const TB: usize = GB * 1024;
2917    const PB: usize = TB * 1024;
2918
2919    if bytes >= PB {
2920        format!("{:.2} PB", bytes as f64 / PB as f64)
2921    } else if bytes >= TB {
2922        format!("{:.2} TB", bytes as f64 / TB as f64)
2923    } else if bytes >= GB {
2924        format!("{:.2} GB", bytes as f64 / GB as f64)
2925    } else if bytes >= MB {
2926        format!("{:.2} MB", bytes as f64 / MB as f64)
2927    } else if bytes >= KB {
2928        format!("{:.2} KB", bytes as f64 / KB as f64)
2929    } else {
2930        format!("{} bytes", bytes)
2931    }
2932}
2933
2934// Custom Handlebars helpers
2935fn format_bytes_helper(
2936    h: &handlebars::Helper,
2937    _: &handlebars::Handlebars,
2938    _: &handlebars::Context,
2939    _: &mut handlebars::RenderContext,
2940    out: &mut dyn handlebars::Output,
2941) -> handlebars::HelperResult {
2942    let param = h.param(0).unwrap().value();
2943    if let Some(bytes) = param.as_u64() {
2944        let formatted = format_bytes(bytes as usize);
2945        out.write(&formatted)?;
2946    }
2947    Ok(())
2948}
2949
2950fn greater_than_helper(
2951    h: &handlebars::Helper,
2952    _: &handlebars::Handlebars,
2953    _: &handlebars::Context,
2954    _: &mut handlebars::RenderContext,
2955    out: &mut dyn handlebars::Output,
2956) -> handlebars::HelperResult {
2957    let param1 = h.param(0).unwrap().value();
2958    let param2 = h.param(1).unwrap().value();
2959
2960    if let (Some(v1), Some(v2)) = (param1.as_u64(), param2.as_u64()) {
2961        if v1 > v2 {
2962            out.write("true")?;
2963        }
2964    }
2965    Ok(())
2966}
2967
2968fn contains_helper(
2969    h: &handlebars::Helper,
2970    _: &handlebars::Handlebars,
2971    _: &handlebars::Context,
2972    _: &mut handlebars::RenderContext,
2973    out: &mut dyn handlebars::Output,
2974) -> handlebars::HelperResult {
2975    let haystack = h.param(0).unwrap().value();
2976    let needle = h.param(1).unwrap().value();
2977
2978    if let (Some(h_str), Some(n_str)) = (haystack.as_str(), needle.as_str()) {
2979        if h_str.contains(n_str) {
2980            out.write("true")?;
2981        }
2982    }
2983    Ok(())
2984}
2985
2986fn json_helper(
2987    h: &handlebars::Helper,
2988    _: &handlebars::Handlebars,
2989    _: &handlebars::Context,
2990    _: &mut handlebars::RenderContext,
2991    out: &mut dyn handlebars::Output,
2992) -> handlebars::HelperResult {
2993    let param = h.param(0).unwrap().value();
2994    let json_string = serde_json::to_string(param).map_err(|e| {
2995        handlebars::RenderErrorReason::Other(format!("Failed to serialize to JSON: {}", e))
2996    })?;
2997    out.write(&json_string)?;
2998    Ok(())
2999}
3000
3001impl Default for DashboardRenderer {
3002    fn default() -> Self {
3003        Self::new().expect("Failed to create dashboard renderer")
3004    }
3005}