Skip to main content

torsh_jit/
trace_viz.rs

1//! Trace visualization for JIT compilation
2//!
3//! This module provides comprehensive trace visualization capabilities for JIT-compiled
4//! code, including execution flow diagrams, performance heatmaps, and interactive visualization.
5
6use crate::{JitError, JitResult};
7use indexmap::IndexMap;
8use std::collections::HashMap;
9use std::time::{Duration, Instant};
10
11/// Trace visualization manager
12#[derive(Debug)]
13pub struct TraceVisualizationManager {
14    /// Visualization sessions
15    sessions: IndexMap<String, VisualizationSession>,
16
17    /// Visualization configuration
18    config: VisualizationConfig,
19
20    /// Trace data collectors
21    collectors: Vec<TraceCollector>,
22
23    /// Visualization renderers
24    renderers: HashMap<OutputFormat, Box<dyn VisualizationRenderer>>,
25
26    /// Statistics about visualization
27    stats: VisualizationStats,
28}
29
30/// Visualization session
31#[derive(Debug, Clone)]
32pub struct VisualizationSession {
33    /// Session ID
34    pub id: String,
35
36    /// Session name
37    pub name: String,
38
39    /// Start time
40    pub start_time: Instant,
41
42    /// Collected traces
43    pub traces: Vec<ExecutionTrace>,
44
45    /// Call graphs
46    pub call_graphs: Vec<CallGraph>,
47
48    /// Performance data
49    pub performance_data: PerformanceData,
50
51    /// Session metadata
52    pub metadata: HashMap<String, String>,
53
54    /// Session status
55    pub status: SessionStatus,
56}
57
58/// Session status
59#[derive(Debug, Clone, PartialEq)]
60pub enum SessionStatus {
61    Collecting,
62    Processing,
63    Ready,
64    Error(String),
65}
66
67/// Execution trace
68#[derive(Debug, Clone)]
69pub struct ExecutionTrace {
70    /// Trace ID
71    pub id: String,
72
73    /// Function name
74    pub function_name: String,
75
76    /// Execution events
77    pub events: Vec<TraceEvent>,
78
79    /// Total execution time
80    pub total_time: Duration,
81
82    /// Thread ID
83    pub thread_id: u64,
84
85    /// CPU utilization
86    pub cpu_utilization: f32,
87
88    /// Memory usage
89    pub memory_usage: MemoryUsage,
90}
91
92/// Trace event
93#[derive(Debug, Clone)]
94pub enum TraceEvent {
95    /// Function entry
96    FunctionEntry {
97        timestamp: Instant,
98        function_name: String,
99        address: u64,
100        parameters: Vec<TraceValue>,
101    },
102
103    /// Function exit
104    FunctionExit {
105        timestamp: Instant,
106        function_name: String,
107        return_value: Option<TraceValue>,
108        duration: Duration,
109    },
110
111    /// Kernel launch
112    KernelLaunch {
113        timestamp: Instant,
114        kernel_name: String,
115        grid_size: (u32, u32, u32),
116        block_size: (u32, u32, u32),
117    },
118
119    /// Kernel completion
120    KernelComplete {
121        timestamp: Instant,
122        kernel_name: String,
123        duration: Duration,
124        occupancy: f32,
125    },
126
127    /// Memory operation
128    MemoryOp {
129        timestamp: Instant,
130        operation: MemoryOperation,
131        address: u64,
132        size: usize,
133        duration: Duration,
134    },
135
136    /// Synchronization event
137    Synchronization {
138        timestamp: Instant,
139        sync_type: SynchronizationType,
140        duration: Duration,
141    },
142
143    /// Custom event
144    Custom {
145        timestamp: Instant,
146        name: String,
147        data: HashMap<String, TraceValue>,
148    },
149}
150
151/// Trace value types
152#[derive(Debug, Clone)]
153pub enum TraceValue {
154    Int(i64),
155    UInt(u64),
156    Float(f64),
157    Bool(bool),
158    String(String),
159    Pointer(u64),
160    Array(Vec<TraceValue>),
161    Struct(HashMap<String, TraceValue>),
162}
163
164/// Memory operations for tracing
165#[derive(Debug, Clone)]
166pub enum MemoryOperation {
167    Alloc,
168    Free,
169    Read,
170    Write,
171    Copy,
172}
173
174/// Synchronization types
175#[derive(Debug, Clone)]
176pub enum SynchronizationType {
177    Barrier,
178    Mutex,
179    Semaphore,
180    CondVar,
181    Atomic,
182}
183
184/// Memory usage information
185#[derive(Debug, Clone, Default)]
186pub struct MemoryUsage {
187    /// Peak memory usage
188    pub peak: usize,
189
190    /// Current memory usage
191    pub current: usize,
192
193    /// Total allocations
194    pub total_allocations: u64,
195
196    /// Total deallocations
197    pub total_deallocations: u64,
198
199    /// Allocation rate (allocations per second)
200    pub allocation_rate: f64,
201}
202
203/// Call graph representation
204#[derive(Debug, Clone)]
205pub struct CallGraph {
206    /// Graph nodes (functions)
207    pub nodes: IndexMap<String, CallGraphNode>,
208
209    /// Graph edges (function calls)
210    pub edges: Vec<CallGraphEdge>,
211
212    /// Root functions
213    pub roots: Vec<String>,
214
215    /// Graph statistics
216    pub stats: CallGraphStats,
217}
218
219/// Call graph node
220#[derive(Debug, Clone)]
221pub struct CallGraphNode {
222    /// Function name
223    pub name: String,
224
225    /// Function address
226    pub address: u64,
227
228    /// Total execution time
229    pub total_time: Duration,
230
231    /// Number of calls
232    pub call_count: u64,
233
234    /// Average execution time per call
235    pub avg_time: Duration,
236
237    /// CPU utilization
238    pub cpu_utilization: f32,
239
240    /// Memory usage
241    pub memory_usage: MemoryUsage,
242
243    /// Node metadata
244    pub metadata: HashMap<String, String>,
245}
246
247/// Call graph edge
248#[derive(Debug, Clone)]
249pub struct CallGraphEdge {
250    /// Caller function
251    pub from: String,
252
253    /// Callee function
254    pub to: String,
255
256    /// Number of calls
257    pub call_count: u64,
258
259    /// Total time spent in callee from this caller
260    pub total_time: Duration,
261
262    /// Edge weight (for visualization)
263    pub weight: f64,
264}
265
266/// Call graph statistics
267#[derive(Debug, Clone, Default)]
268pub struct CallGraphStats {
269    /// Total number of nodes
270    pub node_count: usize,
271
272    /// Total number of edges
273    pub edge_count: usize,
274
275    /// Maximum depth
276    pub max_depth: usize,
277
278    /// Average fan-out
279    pub avg_fanout: f64,
280
281    /// Critical path length
282    pub critical_path_length: Duration,
283}
284
285/// Performance data for visualization
286#[derive(Debug, Clone, Default)]
287pub struct PerformanceData {
288    /// Timeline data
289    pub timeline: Vec<TimelineEvent>,
290
291    /// Heatmap data
292    pub heatmaps: HashMap<String, Heatmap>,
293
294    /// Performance counters
295    pub counters: HashMap<String, Counter>,
296
297    /// Histogram data
298    pub histograms: HashMap<String, Histogram>,
299
300    /// Flamegraph data
301    pub flamegraph: Option<Flamegraph>,
302}
303
304/// Timeline event
305#[derive(Debug, Clone)]
306pub struct TimelineEvent {
307    /// Timestamp
308    pub timestamp: Instant,
309
310    /// Duration
311    pub duration: Duration,
312
313    /// Event name
314    pub name: String,
315
316    /// Event category
317    pub category: String,
318
319    /// Thread ID
320    pub thread_id: u64,
321
322    /// Process ID
323    pub process_id: u64,
324
325    /// Event arguments
326    pub args: HashMap<String, TraceValue>,
327}
328
329/// Heatmap data
330#[derive(Debug, Clone)]
331pub struct Heatmap {
332    /// Heatmap name
333    pub name: String,
334
335    /// Data points (x, y, intensity)
336    pub data: Vec<(f64, f64, f64)>,
337
338    /// X-axis label
339    pub x_label: String,
340
341    /// Y-axis label
342    pub y_label: String,
343
344    /// Color scale
345    pub color_scale: ColorScale,
346}
347
348/// Color scale for heatmaps
349#[derive(Debug, Clone)]
350pub enum ColorScale {
351    /// Heat scale (blue to red)
352    Heat,
353
354    /// Viridis scale
355    Viridis,
356
357    /// Plasma scale
358    Plasma,
359
360    /// Custom scale
361    Custom(Vec<(f64, String)>),
362}
363
364/// Performance counter
365#[derive(Debug, Clone)]
366pub struct Counter {
367    /// Counter name
368    pub name: String,
369
370    /// Current value
371    pub value: f64,
372
373    /// Unit
374    pub unit: String,
375
376    /// History
377    pub history: Vec<(Instant, f64)>,
378}
379
380/// Histogram data
381#[derive(Debug, Clone)]
382pub struct Histogram {
383    /// Histogram name
384    pub name: String,
385
386    /// Bins
387    pub bins: Vec<HistogramBin>,
388
389    /// Total count
390    pub total_count: u64,
391
392    /// Statistics
393    pub stats: HistogramStats,
394}
395
396/// Histogram bin
397#[derive(Debug, Clone)]
398pub struct HistogramBin {
399    /// Bin start value
400    pub start: f64,
401
402    /// Bin end value
403    pub end: f64,
404
405    /// Count in this bin
406    pub count: u64,
407}
408
409/// Histogram statistics
410#[derive(Debug, Clone)]
411pub struct HistogramStats {
412    /// Mean
413    pub mean: f64,
414
415    /// Standard deviation
416    pub std_dev: f64,
417
418    /// Minimum value
419    pub min: f64,
420
421    /// Maximum value
422    pub max: f64,
423
424    /// Percentiles
425    pub percentiles: HashMap<u8, f64>,
426}
427
428/// Flamegraph data
429#[derive(Debug, Clone)]
430pub struct Flamegraph {
431    /// Root node
432    pub root: FlamegraphNode,
433
434    /// Total duration
435    pub total_duration: Duration,
436
437    /// Color scheme
438    pub color_scheme: ColorScheme,
439}
440
441/// Flamegraph node
442#[derive(Debug, Clone)]
443pub struct FlamegraphNode {
444    /// Function name
445    pub name: String,
446
447    /// Self time
448    pub self_time: Duration,
449
450    /// Total time (including children)
451    pub total_time: Duration,
452
453    /// Children nodes
454    pub children: Vec<FlamegraphNode>,
455
456    /// Sample count
457    pub sample_count: u64,
458}
459
460/// Color schemes for visualization
461#[derive(Debug, Clone)]
462pub enum ColorScheme {
463    /// Default scheme
464    Default,
465
466    /// High contrast
467    HighContrast,
468
469    /// Colorblind friendly
470    ColorblindFriendly,
471
472    /// Custom scheme
473    Custom(Vec<String>),
474}
475
476/// Trace data collector
477#[derive(Debug, Clone)]
478pub struct TraceCollector {
479    /// Collector name
480    pub name: String,
481
482    /// Collection interval
483    pub interval: Duration,
484
485    /// Enabled flag
486    pub enabled: bool,
487
488    /// Filter criteria
489    pub filters: Vec<TraceFilter>,
490}
491
492/// Trace filter
493#[derive(Debug)]
494pub enum TraceFilter {
495    /// Function name filter
496    FunctionName(String),
497
498    /// Thread ID filter
499    ThreadId(u64),
500
501    /// Duration filter
502    MinDuration(Duration),
503
504    /// Custom filter (function pointer for Debug compatibility)
505    Custom(fn(&TraceEvent) -> bool),
506}
507
508/// Visualization renderer trait
509pub trait VisualizationRenderer: Send + Sync + std::fmt::Debug {
510    /// Render visualization
511    fn render(&self, session: &VisualizationSession, output_path: &str) -> JitResult<()>;
512
513    /// Get supported output format
514    fn output_format(&self) -> OutputFormat;
515
516    /// Get renderer name
517    fn name(&self) -> &str;
518}
519
520/// Output formats for visualization
521#[derive(Debug, Clone, PartialEq, Eq, Hash)]
522pub enum OutputFormat {
523    /// HTML with interactive JavaScript
524    Html,
525
526    /// SVG vector graphics
527    Svg,
528
529    /// PNG raster image
530    Png,
531
532    /// JSON data format
533    Json,
534
535    /// Chrome tracing format
536    ChromeTracing,
537
538    /// Flamegraph format
539    Flamegraph,
540}
541
542/// Visualization configuration
543#[derive(Debug, Clone)]
544pub struct VisualizationConfig {
545    /// Enable trace visualization
546    pub enabled: bool,
547
548    /// Default output format
549    pub default_format: OutputFormat,
550
551    /// Output directory
552    pub output_directory: String,
553
554    /// Maximum trace events per session
555    pub max_events: usize,
556
557    /// Enable real-time visualization
558    pub real_time: bool,
559
560    /// Sampling rate for real-time visualization
561    pub real_time_sampling_rate: f64,
562
563    /// Color scheme
564    pub color_scheme: ColorScheme,
565
566    /// Enable interactive features
567    pub interactive: bool,
568}
569
570/// Visualization statistics
571#[derive(Debug, Clone, Default)]
572pub struct VisualizationStats {
573    /// Total sessions created
574    pub total_sessions: u64,
575
576    /// Total traces collected
577    pub total_traces: u64,
578
579    /// Total visualizations generated
580    pub total_visualizations: u64,
581
582    /// Average processing time
583    pub avg_processing_time: Duration,
584
585    /// Total file size generated
586    pub total_file_size: u64,
587}
588
589impl Default for VisualizationConfig {
590    fn default() -> Self {
591        Self {
592            enabled: true,
593            default_format: OutputFormat::Html,
594            output_directory: std::env::temp_dir()
595                .join("torsh_visualizations")
596                .display()
597                .to_string(),
598            max_events: 1_000_000,
599            real_time: false,
600            real_time_sampling_rate: 0.1, // 10% sampling
601            color_scheme: ColorScheme::Default,
602            interactive: true,
603        }
604    }
605}
606
607impl TraceVisualizationManager {
608    /// Create a new trace visualization manager
609    pub fn new(config: VisualizationConfig) -> Self {
610        Self {
611            sessions: IndexMap::new(),
612            config,
613            collectors: Vec::new(),
614            renderers: HashMap::new(),
615            stats: VisualizationStats::default(),
616        }
617    }
618
619    /// Create a new manager with default configuration
620    pub fn with_defaults() -> Self {
621        Self::new(VisualizationConfig::default())
622    }
623
624    /// Start a new visualization session
625    pub fn start_session(&mut self, name: &str) -> JitResult<String> {
626        if !self.config.enabled {
627            return Err(JitError::RuntimeError(
628                "Trace visualization disabled".to_string(),
629            ));
630        }
631
632        let session_id = format!("viz_session_{}", self.sessions.len() + 1);
633        let session = VisualizationSession {
634            id: session_id.clone(),
635            name: name.to_string(),
636            start_time: Instant::now(),
637            traces: Vec::new(),
638            call_graphs: Vec::new(),
639            performance_data: PerformanceData::default(),
640            metadata: HashMap::new(),
641            status: SessionStatus::Collecting,
642        };
643
644        self.sessions.insert(session_id.clone(), session);
645        self.stats.total_sessions += 1;
646
647        Ok(session_id)
648    }
649
650    /// Stop a visualization session
651    pub fn stop_session(&mut self, session_id: &str) -> JitResult<()> {
652        if let Some(session) = self.sessions.get_mut(session_id) {
653            session.status = SessionStatus::Processing;
654
655            // Process collected data
656            self.process_session_data(session_id)?;
657
658            if let Some(session) = self.sessions.get_mut(session_id) {
659                session.status = SessionStatus::Ready;
660            }
661        } else {
662            return Err(JitError::RuntimeError(format!(
663                "Session {} not found",
664                session_id
665            )));
666        }
667
668        Ok(())
669    }
670
671    /// Add a trace event to a session
672    pub fn add_trace_event(&mut self, session_id: &str, event: TraceEvent) -> JitResult<()> {
673        if let Some(session) = self.sessions.get_mut(session_id) {
674            if session.traces.is_empty() {
675                session.traces.push(ExecutionTrace {
676                    id: "default_trace".to_string(),
677                    function_name: "main".to_string(),
678                    events: Vec::new(),
679                    total_time: Duration::default(),
680                    thread_id: 0,
681                    cpu_utilization: 0.0,
682                    memory_usage: MemoryUsage::default(),
683                });
684            }
685
686            if let Some(trace) = session.traces.first_mut() {
687                trace.events.push(event);
688                self.stats.total_traces += 1;
689            }
690        }
691
692        Ok(())
693    }
694
695    /// Process session data to generate visualizations
696    fn process_session_data(&mut self, session_id: &str) -> JitResult<()> {
697        // Extract traces to avoid borrowing issues
698        let traces = if let Some(session) = self.sessions.get(session_id) {
699            session.traces.clone()
700        } else {
701            return Ok(());
702        };
703
704        // Generate data outside of mutable borrow
705        let call_graph = self.generate_call_graph(&traces)?;
706        let performance_data = self.generate_performance_data(&traces)?;
707        let timeline = self.generate_timeline(&traces)?;
708        let flamegraph = Some(self.generate_flamegraph(&traces)?);
709
710        // Update session
711        if let Some(session) = self.sessions.get_mut(session_id) {
712            session.call_graphs.push(call_graph);
713            session.performance_data = performance_data;
714            session.performance_data.timeline = timeline;
715            session.performance_data.flamegraph = flamegraph;
716        }
717
718        Ok(())
719    }
720
721    /// Generate call graph from traces
722    fn generate_call_graph(&self, traces: &[ExecutionTrace]) -> JitResult<CallGraph> {
723        let mut nodes = IndexMap::new();
724        let mut edges = Vec::new();
725        let mut call_stack: Vec<String> = Vec::new();
726
727        for trace in traces {
728            for event in &trace.events {
729                match event {
730                    TraceEvent::FunctionEntry { function_name, .. } => {
731                        // Add node if not exists
732                        if !nodes.contains_key(function_name) {
733                            nodes.insert(
734                                function_name.clone(),
735                                CallGraphNode {
736                                    name: function_name.clone(),
737                                    address: 0,
738                                    total_time: Duration::default(),
739                                    call_count: 0,
740                                    avg_time: Duration::default(),
741                                    cpu_utilization: 0.0,
742                                    memory_usage: MemoryUsage::default(),
743                                    metadata: HashMap::new(),
744                                },
745                            );
746                        }
747
748                        // Add edge from parent if exists
749                        if let Some(parent) = call_stack.last() {
750                            edges.push(CallGraphEdge {
751                                from: parent.clone(),
752                                to: function_name.clone(),
753                                call_count: 1,
754                                total_time: Duration::default(),
755                                weight: 1.0,
756                            });
757                        }
758
759                        call_stack.push(function_name.clone());
760                    }
761                    TraceEvent::FunctionExit {
762                        function_name,
763                        duration,
764                        ..
765                    } => {
766                        if let Some(node) = nodes.get_mut(function_name) {
767                            node.total_time += *duration;
768                            node.call_count += 1;
769                            node.avg_time = node.total_time / node.call_count as u32;
770                        }
771
772                        call_stack.pop();
773                    }
774                    _ => {}
775                }
776            }
777        }
778
779        let stats = CallGraphStats {
780            node_count: nodes.len(),
781            edge_count: edges.len(),
782            max_depth: 0,
783            avg_fanout: if !nodes.is_empty() {
784                edges.len() as f64 / nodes.len() as f64
785            } else {
786                0.0
787            },
788            critical_path_length: Duration::default(),
789        };
790
791        Ok(CallGraph {
792            nodes,
793            edges,
794            roots: vec!["main".to_string()],
795            stats,
796        })
797    }
798
799    /// Generate performance data from traces
800    fn generate_performance_data(&self, traces: &[ExecutionTrace]) -> JitResult<PerformanceData> {
801        let mut performance_data = PerformanceData::default();
802
803        // Generate histograms
804        let mut execution_times = Vec::new();
805        for trace in traces {
806            for event in &trace.events {
807                if let TraceEvent::FunctionExit { duration, .. } = event {
808                    execution_times.push(duration.as_nanos() as f64);
809                }
810            }
811        }
812
813        if !execution_times.is_empty() {
814            let histogram = self.create_histogram("execution_times", &execution_times)?;
815            performance_data
816                .histograms
817                .insert("execution_times".to_string(), histogram);
818        }
819
820        Ok(performance_data)
821    }
822
823    /// Generate timeline from traces
824    fn generate_timeline(&self, traces: &[ExecutionTrace]) -> JitResult<Vec<TimelineEvent>> {
825        let mut timeline = Vec::new();
826
827        for trace in traces {
828            for event in &trace.events {
829                match event {
830                    TraceEvent::FunctionEntry {
831                        timestamp,
832                        function_name,
833                        ..
834                    } => {
835                        timeline.push(TimelineEvent {
836                            timestamp: *timestamp,
837                            duration: Duration::default(),
838                            name: function_name.clone(),
839                            category: "function".to_string(),
840                            thread_id: trace.thread_id,
841                            process_id: 0,
842                            args: HashMap::new(),
843                        });
844                    }
845                    _ => {}
846                }
847            }
848        }
849
850        // Sort by timestamp
851        timeline.sort_by_key(|event| event.timestamp);
852
853        Ok(timeline)
854    }
855
856    /// Generate flamegraph from traces
857    fn generate_flamegraph(&self, traces: &[ExecutionTrace]) -> JitResult<Flamegraph> {
858        let root = FlamegraphNode {
859            name: "root".to_string(),
860            self_time: Duration::default(),
861            total_time: traces.iter().map(|t| t.total_time).sum(),
862            children: Vec::new(),
863            sample_count: traces.len() as u64,
864        };
865
866        Ok(Flamegraph {
867            root,
868            total_duration: traces.iter().map(|t| t.total_time).sum(),
869            color_scheme: ColorScheme::Default,
870        })
871    }
872
873    /// Create histogram from data
874    fn create_histogram(&self, name: &str, data: &[f64]) -> JitResult<Histogram> {
875        if data.is_empty() {
876            return Ok(Histogram {
877                name: name.to_string(),
878                bins: Vec::new(),
879                total_count: 0,
880                stats: HistogramStats {
881                    mean: 0.0,
882                    std_dev: 0.0,
883                    min: 0.0,
884                    max: 0.0,
885                    percentiles: HashMap::new(),
886                },
887            });
888        }
889
890        let mut sorted_data = data.to_vec();
891        sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
892
893        let min = sorted_data[0];
894        let max = sorted_data[sorted_data.len() - 1];
895        let mean = sorted_data.iter().sum::<f64>() / sorted_data.len() as f64;
896
897        let variance =
898            sorted_data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / sorted_data.len() as f64;
899        let std_dev = variance.sqrt();
900
901        // Create bins
902        let bin_count = 20;
903        let bin_width = (max - min) / bin_count as f64;
904        let mut bins = Vec::new();
905
906        for i in 0..bin_count {
907            let start = min + i as f64 * bin_width;
908            let end = min + (i + 1) as f64 * bin_width;
909            let count = sorted_data
910                .iter()
911                .filter(|&&x| x >= start && x < end)
912                .count() as u64;
913
914            bins.push(HistogramBin { start, end, count });
915        }
916
917        Ok(Histogram {
918            name: name.to_string(),
919            bins,
920            total_count: data.len() as u64,
921            stats: HistogramStats {
922                mean,
923                std_dev,
924                min,
925                max,
926                percentiles: HashMap::new(),
927            },
928        })
929    }
930
931    /// Render visualization for a session
932    pub fn render_visualization(
933        &self,
934        session_id: &str,
935        format: OutputFormat,
936        output_path: &str,
937    ) -> JitResult<()> {
938        if let Some(session) = self.sessions.get(session_id) {
939            if let Some(renderer) = self.renderers.get(&format) {
940                renderer.render(session, output_path)?;
941            } else {
942                // Use default renderer
943                self.render_default(session, format, output_path)?;
944            }
945        } else {
946            return Err(JitError::RuntimeError(format!(
947                "Session {} not found",
948                session_id
949            )));
950        }
951
952        Ok(())
953    }
954
955    /// Default renderer implementation
956    fn render_default(
957        &self,
958        session: &VisualizationSession,
959        format: OutputFormat,
960        output_path: &str,
961    ) -> JitResult<()> {
962        match format {
963            OutputFormat::Json => {
964                let json_data = format!(
965                    r#"{{"session": "{}", "status": "{:?}", "traces": {}}}"#,
966                    session.name,
967                    session.status,
968                    session.traces.len()
969                );
970                std::fs::write(output_path, json_data)
971                    .map_err(|e| JitError::RuntimeError(format!("Failed to write JSON: {}", e)))?;
972            }
973            OutputFormat::Html => {
974                let html_content = self.generate_html_visualization(session)?;
975                std::fs::write(output_path, html_content)
976                    .map_err(|e| JitError::RuntimeError(format!("Failed to write HTML: {}", e)))?;
977            }
978            _ => {
979                return Err(JitError::RuntimeError(format!(
980                    "Unsupported format: {:?}",
981                    format
982                )));
983            }
984        }
985
986        Ok(())
987    }
988
989    /// Generate HTML visualization
990    fn generate_html_visualization(&self, session: &VisualizationSession) -> JitResult<String> {
991        let html = format!(
992            r#"<!DOCTYPE html>
993<html>
994<head>
995    <title>ToRSh JIT Trace Visualization - {}</title>
996    <style>
997        body {{ font-family: Arial, sans-serif; }}
998        .header {{ background-color: #f0f0f0; padding: 10px; }}
999        .content {{ padding: 20px; }}
1000        .metric {{ margin: 10px 0; }}
1001    </style>
1002</head>
1003<body>
1004    <div class="header">
1005        <h1>ToRSh JIT Trace Visualization</h1>
1006        <h2>Session: {}</h2>
1007    </div>
1008    <div class="content">
1009        <div class="metric">Status: {:?}</div>
1010        <div class="metric">Traces: {}</div>
1011        <div class="metric">Call Graphs: {}</div>
1012        <div class="metric">Timeline Events: {}</div>
1013    </div>
1014</body>
1015</html>"#,
1016            session.name,
1017            session.name,
1018            session.status,
1019            session.traces.len(),
1020            session.call_graphs.len(),
1021            session.performance_data.timeline.len()
1022        );
1023
1024        Ok(html)
1025    }
1026
1027    /// Get session
1028    pub fn get_session(&self, session_id: &str) -> Option<&VisualizationSession> {
1029        self.sessions.get(session_id)
1030    }
1031
1032    /// Get statistics
1033    pub fn get_stats(&self) -> &VisualizationStats {
1034        &self.stats
1035    }
1036
1037    /// Add renderer for a format
1038    pub fn add_renderer(&mut self, format: OutputFormat, renderer: Box<dyn VisualizationRenderer>) {
1039        self.renderers.insert(format, renderer);
1040    }
1041}
1042
1043// Implement Clone for TraceFilter (needed for TraceCollector Clone)
1044impl Clone for TraceFilter {
1045    fn clone(&self) -> Self {
1046        match self {
1047            TraceFilter::FunctionName(name) => TraceFilter::FunctionName(name.clone()),
1048            TraceFilter::ThreadId(id) => TraceFilter::ThreadId(*id),
1049            TraceFilter::MinDuration(duration) => TraceFilter::MinDuration(*duration),
1050            TraceFilter::Custom(func) => TraceFilter::Custom(*func),
1051        }
1052    }
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057    use super::*;
1058
1059    #[test]
1060    fn test_visualization_manager_creation() {
1061        let manager = TraceVisualizationManager::with_defaults();
1062        assert!(manager.config.enabled);
1063        assert_eq!(manager.config.default_format, OutputFormat::Html);
1064    }
1065
1066    #[test]
1067    fn test_session_lifecycle() {
1068        let mut manager = TraceVisualizationManager::with_defaults();
1069
1070        let session_id = manager.start_session("test_session").unwrap();
1071        assert!(!session_id.is_empty());
1072
1073        let session = manager.get_session(&session_id).unwrap();
1074        assert_eq!(session.name, "test_session");
1075        assert_eq!(session.status, SessionStatus::Collecting);
1076
1077        manager.stop_session(&session_id).unwrap();
1078
1079        let session = manager.get_session(&session_id).unwrap();
1080        assert_eq!(session.status, SessionStatus::Ready);
1081    }
1082
1083    #[test]
1084    fn test_trace_event_addition() {
1085        let mut manager = TraceVisualizationManager::with_defaults();
1086        let session_id = manager.start_session("test_session").unwrap();
1087
1088        let event = TraceEvent::FunctionEntry {
1089            timestamp: Instant::now(),
1090            function_name: "test_function".to_string(),
1091            address: 0x1000,
1092            parameters: Vec::new(),
1093        };
1094
1095        manager.add_trace_event(&session_id, event).unwrap();
1096
1097        let session = manager.get_session(&session_id).unwrap();
1098        assert_eq!(session.traces.len(), 1);
1099        assert_eq!(session.traces[0].events.len(), 1);
1100    }
1101
1102    #[test]
1103    fn test_html_generation() {
1104        let mut manager = TraceVisualizationManager::with_defaults();
1105        let session_id = manager.start_session("test_session").unwrap();
1106
1107        manager.stop_session(&session_id).unwrap();
1108
1109        let session = manager.get_session(&session_id).unwrap();
1110        let html = manager.generate_html_visualization(session).unwrap();
1111
1112        assert!(html.contains("ToRSh JIT Trace Visualization"));
1113        assert!(html.contains("test_session"));
1114    }
1115}