use crate::{JitError, JitResult};
use indexmap::IndexMap;
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct TraceVisualizationManager {
sessions: IndexMap<String, VisualizationSession>,
config: VisualizationConfig,
collectors: Vec<TraceCollector>,
renderers: HashMap<OutputFormat, Box<dyn VisualizationRenderer>>,
stats: VisualizationStats,
}
#[derive(Debug, Clone)]
pub struct VisualizationSession {
pub id: String,
pub name: String,
pub start_time: Instant,
pub traces: Vec<ExecutionTrace>,
pub call_graphs: Vec<CallGraph>,
pub performance_data: PerformanceData,
pub metadata: HashMap<String, String>,
pub status: SessionStatus,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SessionStatus {
Collecting,
Processing,
Ready,
Error(String),
}
#[derive(Debug, Clone)]
pub struct ExecutionTrace {
pub id: String,
pub function_name: String,
pub events: Vec<TraceEvent>,
pub total_time: Duration,
pub thread_id: u64,
pub cpu_utilization: f32,
pub memory_usage: MemoryUsage,
}
#[derive(Debug, Clone)]
pub enum TraceEvent {
FunctionEntry {
timestamp: Instant,
function_name: String,
address: u64,
parameters: Vec<TraceValue>,
},
FunctionExit {
timestamp: Instant,
function_name: String,
return_value: Option<TraceValue>,
duration: Duration,
},
KernelLaunch {
timestamp: Instant,
kernel_name: String,
grid_size: (u32, u32, u32),
block_size: (u32, u32, u32),
},
KernelComplete {
timestamp: Instant,
kernel_name: String,
duration: Duration,
occupancy: f32,
},
MemoryOp {
timestamp: Instant,
operation: MemoryOperation,
address: u64,
size: usize,
duration: Duration,
},
Synchronization {
timestamp: Instant,
sync_type: SynchronizationType,
duration: Duration,
},
Custom {
timestamp: Instant,
name: String,
data: HashMap<String, TraceValue>,
},
}
#[derive(Debug, Clone)]
pub enum TraceValue {
Int(i64),
UInt(u64),
Float(f64),
Bool(bool),
String(String),
Pointer(u64),
Array(Vec<TraceValue>),
Struct(HashMap<String, TraceValue>),
}
#[derive(Debug, Clone)]
pub enum MemoryOperation {
Alloc,
Free,
Read,
Write,
Copy,
}
#[derive(Debug, Clone)]
pub enum SynchronizationType {
Barrier,
Mutex,
Semaphore,
CondVar,
Atomic,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryUsage {
pub peak: usize,
pub current: usize,
pub total_allocations: u64,
pub total_deallocations: u64,
pub allocation_rate: f64,
}
#[derive(Debug, Clone)]
pub struct CallGraph {
pub nodes: IndexMap<String, CallGraphNode>,
pub edges: Vec<CallGraphEdge>,
pub roots: Vec<String>,
pub stats: CallGraphStats,
}
#[derive(Debug, Clone)]
pub struct CallGraphNode {
pub name: String,
pub address: u64,
pub total_time: Duration,
pub call_count: u64,
pub avg_time: Duration,
pub cpu_utilization: f32,
pub memory_usage: MemoryUsage,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct CallGraphEdge {
pub from: String,
pub to: String,
pub call_count: u64,
pub total_time: Duration,
pub weight: f64,
}
#[derive(Debug, Clone, Default)]
pub struct CallGraphStats {
pub node_count: usize,
pub edge_count: usize,
pub max_depth: usize,
pub avg_fanout: f64,
pub critical_path_length: Duration,
}
#[derive(Debug, Clone, Default)]
pub struct PerformanceData {
pub timeline: Vec<TimelineEvent>,
pub heatmaps: HashMap<String, Heatmap>,
pub counters: HashMap<String, Counter>,
pub histograms: HashMap<String, Histogram>,
pub flamegraph: Option<Flamegraph>,
}
#[derive(Debug, Clone)]
pub struct TimelineEvent {
pub timestamp: Instant,
pub duration: Duration,
pub name: String,
pub category: String,
pub thread_id: u64,
pub process_id: u64,
pub args: HashMap<String, TraceValue>,
}
#[derive(Debug, Clone)]
pub struct Heatmap {
pub name: String,
pub data: Vec<(f64, f64, f64)>,
pub x_label: String,
pub y_label: String,
pub color_scale: ColorScale,
}
#[derive(Debug, Clone)]
pub enum ColorScale {
Heat,
Viridis,
Plasma,
Custom(Vec<(f64, String)>),
}
#[derive(Debug, Clone)]
pub struct Counter {
pub name: String,
pub value: f64,
pub unit: String,
pub history: Vec<(Instant, f64)>,
}
#[derive(Debug, Clone)]
pub struct Histogram {
pub name: String,
pub bins: Vec<HistogramBin>,
pub total_count: u64,
pub stats: HistogramStats,
}
#[derive(Debug, Clone)]
pub struct HistogramBin {
pub start: f64,
pub end: f64,
pub count: u64,
}
#[derive(Debug, Clone)]
pub struct HistogramStats {
pub mean: f64,
pub std_dev: f64,
pub min: f64,
pub max: f64,
pub percentiles: HashMap<u8, f64>,
}
#[derive(Debug, Clone)]
pub struct Flamegraph {
pub root: FlamegraphNode,
pub total_duration: Duration,
pub color_scheme: ColorScheme,
}
#[derive(Debug, Clone)]
pub struct FlamegraphNode {
pub name: String,
pub self_time: Duration,
pub total_time: Duration,
pub children: Vec<FlamegraphNode>,
pub sample_count: u64,
}
#[derive(Debug, Clone)]
pub enum ColorScheme {
Default,
HighContrast,
ColorblindFriendly,
Custom(Vec<String>),
}
#[derive(Debug, Clone)]
pub struct TraceCollector {
pub name: String,
pub interval: Duration,
pub enabled: bool,
pub filters: Vec<TraceFilter>,
}
#[derive(Debug)]
pub enum TraceFilter {
FunctionName(String),
ThreadId(u64),
MinDuration(Duration),
Custom(fn(&TraceEvent) -> bool),
}
pub trait VisualizationRenderer: Send + Sync + std::fmt::Debug {
fn render(&self, session: &VisualizationSession, output_path: &str) -> JitResult<()>;
fn output_format(&self) -> OutputFormat;
fn name(&self) -> &str;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum OutputFormat {
Html,
Svg,
Png,
Json,
ChromeTracing,
Flamegraph,
}
#[derive(Debug, Clone)]
pub struct VisualizationConfig {
pub enabled: bool,
pub default_format: OutputFormat,
pub output_directory: String,
pub max_events: usize,
pub real_time: bool,
pub real_time_sampling_rate: f64,
pub color_scheme: ColorScheme,
pub interactive: bool,
}
#[derive(Debug, Clone, Default)]
pub struct VisualizationStats {
pub total_sessions: u64,
pub total_traces: u64,
pub total_visualizations: u64,
pub avg_processing_time: Duration,
pub total_file_size: u64,
}
impl Default for VisualizationConfig {
fn default() -> Self {
Self {
enabled: true,
default_format: OutputFormat::Html,
output_directory: std::env::temp_dir()
.join("torsh_visualizations")
.display()
.to_string(),
max_events: 1_000_000,
real_time: false,
real_time_sampling_rate: 0.1, color_scheme: ColorScheme::Default,
interactive: true,
}
}
}
impl TraceVisualizationManager {
pub fn new(config: VisualizationConfig) -> Self {
Self {
sessions: IndexMap::new(),
config,
collectors: Vec::new(),
renderers: HashMap::new(),
stats: VisualizationStats::default(),
}
}
pub fn with_defaults() -> Self {
Self::new(VisualizationConfig::default())
}
pub fn start_session(&mut self, name: &str) -> JitResult<String> {
if !self.config.enabled {
return Err(JitError::RuntimeError(
"Trace visualization disabled".to_string(),
));
}
let session_id = format!("viz_session_{}", self.sessions.len() + 1);
let session = VisualizationSession {
id: session_id.clone(),
name: name.to_string(),
start_time: Instant::now(),
traces: Vec::new(),
call_graphs: Vec::new(),
performance_data: PerformanceData::default(),
metadata: HashMap::new(),
status: SessionStatus::Collecting,
};
self.sessions.insert(session_id.clone(), session);
self.stats.total_sessions += 1;
Ok(session_id)
}
pub fn stop_session(&mut self, session_id: &str) -> JitResult<()> {
if let Some(session) = self.sessions.get_mut(session_id) {
session.status = SessionStatus::Processing;
self.process_session_data(session_id)?;
if let Some(session) = self.sessions.get_mut(session_id) {
session.status = SessionStatus::Ready;
}
} else {
return Err(JitError::RuntimeError(format!(
"Session {} not found",
session_id
)));
}
Ok(())
}
pub fn add_trace_event(&mut self, session_id: &str, event: TraceEvent) -> JitResult<()> {
if let Some(session) = self.sessions.get_mut(session_id) {
if session.traces.is_empty() {
session.traces.push(ExecutionTrace {
id: "default_trace".to_string(),
function_name: "main".to_string(),
events: Vec::new(),
total_time: Duration::default(),
thread_id: 0,
cpu_utilization: 0.0,
memory_usage: MemoryUsage::default(),
});
}
if let Some(trace) = session.traces.first_mut() {
trace.events.push(event);
self.stats.total_traces += 1;
}
}
Ok(())
}
fn process_session_data(&mut self, session_id: &str) -> JitResult<()> {
let traces = if let Some(session) = self.sessions.get(session_id) {
session.traces.clone()
} else {
return Ok(());
};
let call_graph = self.generate_call_graph(&traces)?;
let performance_data = self.generate_performance_data(&traces)?;
let timeline = self.generate_timeline(&traces)?;
let flamegraph = Some(self.generate_flamegraph(&traces)?);
if let Some(session) = self.sessions.get_mut(session_id) {
session.call_graphs.push(call_graph);
session.performance_data = performance_data;
session.performance_data.timeline = timeline;
session.performance_data.flamegraph = flamegraph;
}
Ok(())
}
fn generate_call_graph(&self, traces: &[ExecutionTrace]) -> JitResult<CallGraph> {
let mut nodes = IndexMap::new();
let mut edges = Vec::new();
let mut call_stack: Vec<String> = Vec::new();
for trace in traces {
for event in &trace.events {
match event {
TraceEvent::FunctionEntry { function_name, .. } => {
if !nodes.contains_key(function_name) {
nodes.insert(
function_name.clone(),
CallGraphNode {
name: function_name.clone(),
address: 0,
total_time: Duration::default(),
call_count: 0,
avg_time: Duration::default(),
cpu_utilization: 0.0,
memory_usage: MemoryUsage::default(),
metadata: HashMap::new(),
},
);
}
if let Some(parent) = call_stack.last() {
edges.push(CallGraphEdge {
from: parent.clone(),
to: function_name.clone(),
call_count: 1,
total_time: Duration::default(),
weight: 1.0,
});
}
call_stack.push(function_name.clone());
}
TraceEvent::FunctionExit {
function_name,
duration,
..
} => {
if let Some(node) = nodes.get_mut(function_name) {
node.total_time += *duration;
node.call_count += 1;
node.avg_time = node.total_time / node.call_count as u32;
}
call_stack.pop();
}
_ => {}
}
}
}
let stats = CallGraphStats {
node_count: nodes.len(),
edge_count: edges.len(),
max_depth: 0,
avg_fanout: if !nodes.is_empty() {
edges.len() as f64 / nodes.len() as f64
} else {
0.0
},
critical_path_length: Duration::default(),
};
Ok(CallGraph {
nodes,
edges,
roots: vec!["main".to_string()],
stats,
})
}
fn generate_performance_data(&self, traces: &[ExecutionTrace]) -> JitResult<PerformanceData> {
let mut performance_data = PerformanceData::default();
let mut execution_times = Vec::new();
for trace in traces {
for event in &trace.events {
if let TraceEvent::FunctionExit { duration, .. } = event {
execution_times.push(duration.as_nanos() as f64);
}
}
}
if !execution_times.is_empty() {
let histogram = self.create_histogram("execution_times", &execution_times)?;
performance_data
.histograms
.insert("execution_times".to_string(), histogram);
}
Ok(performance_data)
}
fn generate_timeline(&self, traces: &[ExecutionTrace]) -> JitResult<Vec<TimelineEvent>> {
let mut timeline = Vec::new();
for trace in traces {
for event in &trace.events {
match event {
TraceEvent::FunctionEntry {
timestamp,
function_name,
..
} => {
timeline.push(TimelineEvent {
timestamp: *timestamp,
duration: Duration::default(),
name: function_name.clone(),
category: "function".to_string(),
thread_id: trace.thread_id,
process_id: 0,
args: HashMap::new(),
});
}
_ => {}
}
}
}
timeline.sort_by_key(|event| event.timestamp);
Ok(timeline)
}
fn generate_flamegraph(&self, traces: &[ExecutionTrace]) -> JitResult<Flamegraph> {
let root = FlamegraphNode {
name: "root".to_string(),
self_time: Duration::default(),
total_time: traces.iter().map(|t| t.total_time).sum(),
children: Vec::new(),
sample_count: traces.len() as u64,
};
Ok(Flamegraph {
root,
total_duration: traces.iter().map(|t| t.total_time).sum(),
color_scheme: ColorScheme::Default,
})
}
fn create_histogram(&self, name: &str, data: &[f64]) -> JitResult<Histogram> {
if data.is_empty() {
return Ok(Histogram {
name: name.to_string(),
bins: Vec::new(),
total_count: 0,
stats: HistogramStats {
mean: 0.0,
std_dev: 0.0,
min: 0.0,
max: 0.0,
percentiles: HashMap::new(),
},
});
}
let mut sorted_data = data.to_vec();
sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let min = sorted_data[0];
let max = sorted_data[sorted_data.len() - 1];
let mean = sorted_data.iter().sum::<f64>() / sorted_data.len() as f64;
let variance =
sorted_data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / sorted_data.len() as f64;
let std_dev = variance.sqrt();
let bin_count = 20;
let bin_width = (max - min) / bin_count as f64;
let mut bins = Vec::new();
for i in 0..bin_count {
let start = min + i as f64 * bin_width;
let end = min + (i + 1) as f64 * bin_width;
let count = sorted_data
.iter()
.filter(|&&x| x >= start && x < end)
.count() as u64;
bins.push(HistogramBin { start, end, count });
}
Ok(Histogram {
name: name.to_string(),
bins,
total_count: data.len() as u64,
stats: HistogramStats {
mean,
std_dev,
min,
max,
percentiles: HashMap::new(),
},
})
}
pub fn render_visualization(
&self,
session_id: &str,
format: OutputFormat,
output_path: &str,
) -> JitResult<()> {
if let Some(session) = self.sessions.get(session_id) {
if let Some(renderer) = self.renderers.get(&format) {
renderer.render(session, output_path)?;
} else {
self.render_default(session, format, output_path)?;
}
} else {
return Err(JitError::RuntimeError(format!(
"Session {} not found",
session_id
)));
}
Ok(())
}
fn render_default(
&self,
session: &VisualizationSession,
format: OutputFormat,
output_path: &str,
) -> JitResult<()> {
match format {
OutputFormat::Json => {
let json_data = format!(
r#"{{"session": "{}", "status": "{:?}", "traces": {}}}"#,
session.name,
session.status,
session.traces.len()
);
std::fs::write(output_path, json_data)
.map_err(|e| JitError::RuntimeError(format!("Failed to write JSON: {}", e)))?;
}
OutputFormat::Html => {
let html_content = self.generate_html_visualization(session)?;
std::fs::write(output_path, html_content)
.map_err(|e| JitError::RuntimeError(format!("Failed to write HTML: {}", e)))?;
}
_ => {
return Err(JitError::RuntimeError(format!(
"Unsupported format: {:?}",
format
)));
}
}
Ok(())
}
fn generate_html_visualization(&self, session: &VisualizationSession) -> JitResult<String> {
let html = format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>ToRSh JIT Trace Visualization - {}</title>
<style>
body {{ font-family: Arial, sans-serif; }}
.header {{ background-color: #f0f0f0; padding: 10px; }}
.content {{ padding: 20px; }}
.metric {{ margin: 10px 0; }}
</style>
</head>
<body>
<div class="header">
<h1>ToRSh JIT Trace Visualization</h1>
<h2>Session: {}</h2>
</div>
<div class="content">
<div class="metric">Status: {:?}</div>
<div class="metric">Traces: {}</div>
<div class="metric">Call Graphs: {}</div>
<div class="metric">Timeline Events: {}</div>
</div>
</body>
</html>"#,
session.name,
session.name,
session.status,
session.traces.len(),
session.call_graphs.len(),
session.performance_data.timeline.len()
);
Ok(html)
}
pub fn get_session(&self, session_id: &str) -> Option<&VisualizationSession> {
self.sessions.get(session_id)
}
pub fn get_stats(&self) -> &VisualizationStats {
&self.stats
}
pub fn add_renderer(&mut self, format: OutputFormat, renderer: Box<dyn VisualizationRenderer>) {
self.renderers.insert(format, renderer);
}
}
impl Clone for TraceFilter {
fn clone(&self) -> Self {
match self {
TraceFilter::FunctionName(name) => TraceFilter::FunctionName(name.clone()),
TraceFilter::ThreadId(id) => TraceFilter::ThreadId(*id),
TraceFilter::MinDuration(duration) => TraceFilter::MinDuration(*duration),
TraceFilter::Custom(func) => TraceFilter::Custom(*func),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_visualization_manager_creation() {
let manager = TraceVisualizationManager::with_defaults();
assert!(manager.config.enabled);
assert_eq!(manager.config.default_format, OutputFormat::Html);
}
#[test]
fn test_session_lifecycle() {
let mut manager = TraceVisualizationManager::with_defaults();
let session_id = manager.start_session("test_session").unwrap();
assert!(!session_id.is_empty());
let session = manager.get_session(&session_id).unwrap();
assert_eq!(session.name, "test_session");
assert_eq!(session.status, SessionStatus::Collecting);
manager.stop_session(&session_id).unwrap();
let session = manager.get_session(&session_id).unwrap();
assert_eq!(session.status, SessionStatus::Ready);
}
#[test]
fn test_trace_event_addition() {
let mut manager = TraceVisualizationManager::with_defaults();
let session_id = manager.start_session("test_session").unwrap();
let event = TraceEvent::FunctionEntry {
timestamp: Instant::now(),
function_name: "test_function".to_string(),
address: 0x1000,
parameters: Vec::new(),
};
manager.add_trace_event(&session_id, event).unwrap();
let session = manager.get_session(&session_id).unwrap();
assert_eq!(session.traces.len(), 1);
assert_eq!(session.traces[0].events.len(), 1);
}
#[test]
fn test_html_generation() {
let mut manager = TraceVisualizationManager::with_defaults();
let session_id = manager.start_session("test_session").unwrap();
manager.stop_session(&session_id).unwrap();
let session = manager.get_session(&session_id).unwrap();
let html = manager.generate_html_visualization(session).unwrap();
assert!(html.contains("ToRSh JIT Trace Visualization"));
assert!(html.contains("test_session"));
}
}