1use crate::{JitError, JitResult};
7use indexmap::IndexMap;
8use std::collections::HashMap;
9use std::time::{Duration, Instant};
10
11#[derive(Debug)]
13pub struct TraceVisualizationManager {
14 sessions: IndexMap<String, VisualizationSession>,
16
17 config: VisualizationConfig,
19
20 collectors: Vec<TraceCollector>,
22
23 renderers: HashMap<OutputFormat, Box<dyn VisualizationRenderer>>,
25
26 stats: VisualizationStats,
28}
29
30#[derive(Debug, Clone)]
32pub struct VisualizationSession {
33 pub id: String,
35
36 pub name: String,
38
39 pub start_time: Instant,
41
42 pub traces: Vec<ExecutionTrace>,
44
45 pub call_graphs: Vec<CallGraph>,
47
48 pub performance_data: PerformanceData,
50
51 pub metadata: HashMap<String, String>,
53
54 pub status: SessionStatus,
56}
57
58#[derive(Debug, Clone, PartialEq)]
60pub enum SessionStatus {
61 Collecting,
62 Processing,
63 Ready,
64 Error(String),
65}
66
67#[derive(Debug, Clone)]
69pub struct ExecutionTrace {
70 pub id: String,
72
73 pub function_name: String,
75
76 pub events: Vec<TraceEvent>,
78
79 pub total_time: Duration,
81
82 pub thread_id: u64,
84
85 pub cpu_utilization: f32,
87
88 pub memory_usage: MemoryUsage,
90}
91
92#[derive(Debug, Clone)]
94pub enum TraceEvent {
95 FunctionEntry {
97 timestamp: Instant,
98 function_name: String,
99 address: u64,
100 parameters: Vec<TraceValue>,
101 },
102
103 FunctionExit {
105 timestamp: Instant,
106 function_name: String,
107 return_value: Option<TraceValue>,
108 duration: Duration,
109 },
110
111 KernelLaunch {
113 timestamp: Instant,
114 kernel_name: String,
115 grid_size: (u32, u32, u32),
116 block_size: (u32, u32, u32),
117 },
118
119 KernelComplete {
121 timestamp: Instant,
122 kernel_name: String,
123 duration: Duration,
124 occupancy: f32,
125 },
126
127 MemoryOp {
129 timestamp: Instant,
130 operation: MemoryOperation,
131 address: u64,
132 size: usize,
133 duration: Duration,
134 },
135
136 Synchronization {
138 timestamp: Instant,
139 sync_type: SynchronizationType,
140 duration: Duration,
141 },
142
143 Custom {
145 timestamp: Instant,
146 name: String,
147 data: HashMap<String, TraceValue>,
148 },
149}
150
151#[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#[derive(Debug, Clone)]
166pub enum MemoryOperation {
167 Alloc,
168 Free,
169 Read,
170 Write,
171 Copy,
172}
173
174#[derive(Debug, Clone)]
176pub enum SynchronizationType {
177 Barrier,
178 Mutex,
179 Semaphore,
180 CondVar,
181 Atomic,
182}
183
184#[derive(Debug, Clone, Default)]
186pub struct MemoryUsage {
187 pub peak: usize,
189
190 pub current: usize,
192
193 pub total_allocations: u64,
195
196 pub total_deallocations: u64,
198
199 pub allocation_rate: f64,
201}
202
203#[derive(Debug, Clone)]
205pub struct CallGraph {
206 pub nodes: IndexMap<String, CallGraphNode>,
208
209 pub edges: Vec<CallGraphEdge>,
211
212 pub roots: Vec<String>,
214
215 pub stats: CallGraphStats,
217}
218
219#[derive(Debug, Clone)]
221pub struct CallGraphNode {
222 pub name: String,
224
225 pub address: u64,
227
228 pub total_time: Duration,
230
231 pub call_count: u64,
233
234 pub avg_time: Duration,
236
237 pub cpu_utilization: f32,
239
240 pub memory_usage: MemoryUsage,
242
243 pub metadata: HashMap<String, String>,
245}
246
247#[derive(Debug, Clone)]
249pub struct CallGraphEdge {
250 pub from: String,
252
253 pub to: String,
255
256 pub call_count: u64,
258
259 pub total_time: Duration,
261
262 pub weight: f64,
264}
265
266#[derive(Debug, Clone, Default)]
268pub struct CallGraphStats {
269 pub node_count: usize,
271
272 pub edge_count: usize,
274
275 pub max_depth: usize,
277
278 pub avg_fanout: f64,
280
281 pub critical_path_length: Duration,
283}
284
285#[derive(Debug, Clone, Default)]
287pub struct PerformanceData {
288 pub timeline: Vec<TimelineEvent>,
290
291 pub heatmaps: HashMap<String, Heatmap>,
293
294 pub counters: HashMap<String, Counter>,
296
297 pub histograms: HashMap<String, Histogram>,
299
300 pub flamegraph: Option<Flamegraph>,
302}
303
304#[derive(Debug, Clone)]
306pub struct TimelineEvent {
307 pub timestamp: Instant,
309
310 pub duration: Duration,
312
313 pub name: String,
315
316 pub category: String,
318
319 pub thread_id: u64,
321
322 pub process_id: u64,
324
325 pub args: HashMap<String, TraceValue>,
327}
328
329#[derive(Debug, Clone)]
331pub struct Heatmap {
332 pub name: String,
334
335 pub data: Vec<(f64, f64, f64)>,
337
338 pub x_label: String,
340
341 pub y_label: String,
343
344 pub color_scale: ColorScale,
346}
347
348#[derive(Debug, Clone)]
350pub enum ColorScale {
351 Heat,
353
354 Viridis,
356
357 Plasma,
359
360 Custom(Vec<(f64, String)>),
362}
363
364#[derive(Debug, Clone)]
366pub struct Counter {
367 pub name: String,
369
370 pub value: f64,
372
373 pub unit: String,
375
376 pub history: Vec<(Instant, f64)>,
378}
379
380#[derive(Debug, Clone)]
382pub struct Histogram {
383 pub name: String,
385
386 pub bins: Vec<HistogramBin>,
388
389 pub total_count: u64,
391
392 pub stats: HistogramStats,
394}
395
396#[derive(Debug, Clone)]
398pub struct HistogramBin {
399 pub start: f64,
401
402 pub end: f64,
404
405 pub count: u64,
407}
408
409#[derive(Debug, Clone)]
411pub struct HistogramStats {
412 pub mean: f64,
414
415 pub std_dev: f64,
417
418 pub min: f64,
420
421 pub max: f64,
423
424 pub percentiles: HashMap<u8, f64>,
426}
427
428#[derive(Debug, Clone)]
430pub struct Flamegraph {
431 pub root: FlamegraphNode,
433
434 pub total_duration: Duration,
436
437 pub color_scheme: ColorScheme,
439}
440
441#[derive(Debug, Clone)]
443pub struct FlamegraphNode {
444 pub name: String,
446
447 pub self_time: Duration,
449
450 pub total_time: Duration,
452
453 pub children: Vec<FlamegraphNode>,
455
456 pub sample_count: u64,
458}
459
460#[derive(Debug, Clone)]
462pub enum ColorScheme {
463 Default,
465
466 HighContrast,
468
469 ColorblindFriendly,
471
472 Custom(Vec<String>),
474}
475
476#[derive(Debug, Clone)]
478pub struct TraceCollector {
479 pub name: String,
481
482 pub interval: Duration,
484
485 pub enabled: bool,
487
488 pub filters: Vec<TraceFilter>,
490}
491
492#[derive(Debug)]
494pub enum TraceFilter {
495 FunctionName(String),
497
498 ThreadId(u64),
500
501 MinDuration(Duration),
503
504 Custom(fn(&TraceEvent) -> bool),
506}
507
508pub trait VisualizationRenderer: Send + Sync + std::fmt::Debug {
510 fn render(&self, session: &VisualizationSession, output_path: &str) -> JitResult<()>;
512
513 fn output_format(&self) -> OutputFormat;
515
516 fn name(&self) -> &str;
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, Hash)]
522pub enum OutputFormat {
523 Html,
525
526 Svg,
528
529 Png,
531
532 Json,
534
535 ChromeTracing,
537
538 Flamegraph,
540}
541
542#[derive(Debug, Clone)]
544pub struct VisualizationConfig {
545 pub enabled: bool,
547
548 pub default_format: OutputFormat,
550
551 pub output_directory: String,
553
554 pub max_events: usize,
556
557 pub real_time: bool,
559
560 pub real_time_sampling_rate: f64,
562
563 pub color_scheme: ColorScheme,
565
566 pub interactive: bool,
568}
569
570#[derive(Debug, Clone, Default)]
572pub struct VisualizationStats {
573 pub total_sessions: u64,
575
576 pub total_traces: u64,
578
579 pub total_visualizations: u64,
581
582 pub avg_processing_time: Duration,
584
585 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, color_scheme: ColorScheme::Default,
602 interactive: true,
603 }
604 }
605}
606
607impl TraceVisualizationManager {
608 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 pub fn with_defaults() -> Self {
621 Self::new(VisualizationConfig::default())
622 }
623
624 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 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 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 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 fn process_session_data(&mut self, session_id: &str) -> JitResult<()> {
697 let traces = if let Some(session) = self.sessions.get(session_id) {
699 session.traces.clone()
700 } else {
701 return Ok(());
702 };
703
704 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 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 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 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 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 fn generate_performance_data(&self, traces: &[ExecutionTrace]) -> JitResult<PerformanceData> {
801 let mut performance_data = PerformanceData::default();
802
803 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 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 timeline.sort_by_key(|event| event.timestamp);
852
853 Ok(timeline)
854 }
855
856 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 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 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 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 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 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 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 pub fn get_session(&self, session_id: &str) -> Option<&VisualizationSession> {
1029 self.sessions.get(session_id)
1030 }
1031
1032 pub fn get_stats(&self) -> &VisualizationStats {
1034 &self.stats
1035 }
1036
1037 pub fn add_renderer(&mut self, format: OutputFormat, renderer: Box<dyn VisualizationRenderer>) {
1039 self.renderers.insert(format, renderer);
1040 }
1041}
1042
1043impl 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}