1use crate::{
7 performance_dashboards::{PerformancePoint, RegressionSeverity},
8 regression_detection::AdvancedRegressionResult,
9 BenchResult,
10};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct VisualizationConfig {
17 pub width: u32,
19 pub height: u32,
21 pub theme: ChartTheme,
23 pub interactive: bool,
25 pub animation_duration: u32,
27 pub color_palette: Vec<String>,
29 pub font_family: String,
31 pub font_size: u32,
33 pub export_format: ExportFormat,
35}
36
37impl Default for VisualizationConfig {
38 fn default() -> Self {
39 Self {
40 width: 800,
41 height: 600,
42 theme: ChartTheme::Light,
43 interactive: true,
44 animation_duration: 750,
45 color_palette: vec![
46 "#3498db".to_string(),
47 "#e74c3c".to_string(),
48 "#2ecc71".to_string(),
49 "#f39c12".to_string(),
50 "#9b59b6".to_string(),
51 "#1abc9c".to_string(),
52 "#34495e".to_string(),
53 "#e67e22".to_string(),
54 ],
55 font_family: "Arial, sans-serif".to_string(),
56 font_size: 12,
57 export_format: ExportFormat::Html,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum ChartTheme {
65 Light,
66 Dark,
67 Minimal,
68 Professional,
69 Colorful,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74pub enum ExportFormat {
75 Html,
76 Svg,
77 Png,
78 Pdf,
79 Json,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84pub enum ChartType {
85 Line,
86 Bar,
87 Scatter,
88 Heatmap,
89 Box,
90 Violin,
91 Histogram,
92 Radar,
93 Treemap,
94 Sunburst,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct VisualizationPoint {
100 pub x: f64,
102 pub y: f64,
104 pub label: String,
106 pub metadata: HashMap<String, String>,
108 pub color: Option<String>,
110 pub size: Option<f64>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ChartSeries {
117 pub name: String,
119 pub data: Vec<VisualizationPoint>,
121 pub chart_type: ChartType,
123 pub color: Option<String>,
125 pub width: Option<f64>,
127 pub fill_opacity: Option<f64>,
129 pub dash_pattern: Option<Vec<f64>>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct Chart {
136 pub title: String,
138 pub subtitle: Option<String>,
140 pub x_axis: AxisConfig,
142 pub y_axis: AxisConfig,
144 pub series: Vec<ChartSeries>,
146 pub chart_type: ChartType,
148 pub legend: LegendConfig,
150 pub tooltip: TooltipConfig,
152 pub annotations: Vec<Annotation>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AxisConfig {
159 pub title: String,
161 pub axis_type: AxisType,
163 pub min: Option<f64>,
165 pub max: Option<f64>,
167 pub tick_interval: Option<f64>,
169 pub log_scale: bool,
171 pub grid: bool,
173 pub label_format: String,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179pub enum AxisType {
180 Linear,
181 Logarithmic,
182 DateTime,
183 Category,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct LegendConfig {
189 pub show: bool,
191 pub position: LegendPosition,
193 pub orientation: LegendOrientation,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub enum LegendPosition {
200 Top,
201 Bottom,
202 Left,
203 Right,
204 TopLeft,
205 TopRight,
206 BottomLeft,
207 BottomRight,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
212pub enum LegendOrientation {
213 Horizontal,
214 Vertical,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct TooltipConfig {
220 pub show: bool,
222 pub trigger: TooltipTrigger,
224 pub format: Option<String>,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230pub enum TooltipTrigger {
231 Hover,
232 Click,
233 Focus,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct Annotation {
239 pub annotation_type: AnnotationType,
241 pub x: f64,
243 pub y: f64,
245 pub text: String,
247 pub color: Option<String>,
249 pub font_size: Option<u32>,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
255pub enum AnnotationType {
256 Text,
257 Arrow,
258 Line,
259 Rectangle,
260 Circle,
261}
262
263pub struct VisualizationGenerator {
265 config: VisualizationConfig,
267 templates: HashMap<String, Chart>,
269}
270
271impl VisualizationGenerator {
272 pub fn new(config: VisualizationConfig) -> Self {
274 Self {
275 config,
276 templates: HashMap::new(),
277 }
278 }
279
280 pub fn default() -> Self {
282 Self::new(VisualizationConfig::default())
283 }
284
285 pub fn generate_performance_trend(&self, points: &[PerformancePoint]) -> Chart {
287 let mut data = Vec::new();
288
289 for point in points {
290 data.push(VisualizationPoint {
291 x: point.timestamp.timestamp_millis() as f64,
292 y: point.mean_time_ns / 1_000_000.0, label: format!("{} ({})", point.benchmark_name, point.size),
294 metadata: {
295 let mut meta = HashMap::new();
296 meta.insert("benchmark".to_string(), point.benchmark_name.clone());
297 meta.insert("size".to_string(), point.size.to_string());
298 meta.insert("dtype".to_string(), point.dtype.clone());
299 meta.insert(
300 "throughput".to_string(),
301 point
302 .throughput
303 .map(|t| t.to_string())
304 .unwrap_or("N/A".to_string()),
305 );
306 meta
307 },
308 color: None,
309 size: None,
310 });
311 }
312
313 Chart {
314 title: "Performance Trends Over Time".to_string(),
315 subtitle: Some("Execution time trends for benchmark operations".to_string()),
316 x_axis: AxisConfig {
317 title: "Time".to_string(),
318 axis_type: AxisType::DateTime,
319 min: None,
320 max: None,
321 tick_interval: None,
322 log_scale: false,
323 grid: true,
324 label_format: "%Y-%m-%d %H:%M".to_string(),
325 },
326 y_axis: AxisConfig {
327 title: "Execution Time (ms)".to_string(),
328 axis_type: AxisType::Linear,
329 min: Some(0.0),
330 max: None,
331 tick_interval: None,
332 log_scale: false,
333 grid: true,
334 label_format: "{:.2f}".to_string(),
335 },
336 series: vec![ChartSeries {
337 name: "Performance".to_string(),
338 data,
339 chart_type: ChartType::Line,
340 color: Some(self.config.color_palette[0].clone()),
341 width: Some(2.0),
342 fill_opacity: None,
343 dash_pattern: None,
344 }],
345 chart_type: ChartType::Line,
346 legend: LegendConfig {
347 show: true,
348 position: LegendPosition::TopRight,
349 orientation: LegendOrientation::Vertical,
350 },
351 tooltip: TooltipConfig {
352 show: true,
353 trigger: TooltipTrigger::Hover,
354 format: None,
355 },
356 annotations: Vec::new(),
357 }
358 }
359
360 pub fn generate_throughput_comparison(&self, results: &[BenchResult]) -> Chart {
362 let mut series_map: HashMap<String, Vec<VisualizationPoint>> = HashMap::new();
363
364 for result in results {
365 if let Some(throughput) = result.throughput {
366 let series_name = format!("{:?}", result.dtype);
367 let entry = series_map.entry(series_name).or_insert_with(Vec::new);
368
369 entry.push(VisualizationPoint {
370 x: result.size as f64,
371 y: throughput,
372 label: format!("{} ({})", result.name, result.size),
373 metadata: {
374 let mut meta = HashMap::new();
375 meta.insert("benchmark".to_string(), result.name.clone());
376 meta.insert("size".to_string(), result.size.to_string());
377 meta.insert(
378 "mean_time".to_string(),
379 (result.mean_time_ns / 1_000_000.0).to_string(),
380 );
381 meta
382 },
383 color: None,
384 size: None,
385 });
386 }
387 }
388
389 let mut series = Vec::new();
390 for (i, (name, data)) in series_map.into_iter().enumerate() {
391 series.push(ChartSeries {
392 name,
393 data,
394 chart_type: ChartType::Bar,
395 color: Some(self.config.color_palette[i % self.config.color_palette.len()].clone()),
396 width: Some(0.8),
397 fill_opacity: Some(0.8),
398 dash_pattern: None,
399 });
400 }
401
402 Chart {
403 title: "Throughput Comparison".to_string(),
404 subtitle: Some("Operations per second across different data types".to_string()),
405 x_axis: AxisConfig {
406 title: "Input Size".to_string(),
407 axis_type: AxisType::Linear,
408 min: None,
409 max: None,
410 tick_interval: None,
411 log_scale: false,
412 grid: true,
413 label_format: "{:.0}".to_string(),
414 },
415 y_axis: AxisConfig {
416 title: "Throughput (ops/sec)".to_string(),
417 axis_type: AxisType::Linear,
418 min: Some(0.0),
419 max: None,
420 tick_interval: None,
421 log_scale: false,
422 grid: true,
423 label_format: "{:.0}".to_string(),
424 },
425 series,
426 chart_type: ChartType::Bar,
427 legend: LegendConfig {
428 show: true,
429 position: LegendPosition::TopRight,
430 orientation: LegendOrientation::Vertical,
431 },
432 tooltip: TooltipConfig {
433 show: true,
434 trigger: TooltipTrigger::Hover,
435 format: None,
436 },
437 annotations: Vec::new(),
438 }
439 }
440
441 pub fn generate_performance_heatmap(&self, results: &[BenchResult]) -> Chart {
443 let mut data = Vec::new();
444 let mut benchmarks = Vec::new();
445 let mut sizes = Vec::new();
446
447 for result in results {
449 if !benchmarks.contains(&result.name) {
450 benchmarks.push(result.name.clone());
451 }
452 if !sizes.contains(&result.size) {
453 sizes.push(result.size);
454 }
455 }
456
457 benchmarks.sort();
458 sizes.sort();
459
460 for (i, benchmark) in benchmarks.iter().enumerate() {
462 for (j, &size) in sizes.iter().enumerate() {
463 if let Some(result) = results
464 .iter()
465 .find(|r| r.name == *benchmark && r.size == size)
466 {
467 data.push(VisualizationPoint {
468 x: j as f64,
469 y: i as f64,
470 label: format!("{} ({})", benchmark, size),
471 metadata: {
472 let mut meta = HashMap::new();
473 meta.insert("benchmark".to_string(), benchmark.clone());
474 meta.insert("size".to_string(), size.to_string());
475 meta.insert(
476 "value".to_string(),
477 (result.mean_time_ns / 1_000_000.0).to_string(),
478 );
479 meta
480 },
481 color: None,
482 size: Some(result.mean_time_ns / 1_000_000.0),
483 });
484 }
485 }
486 }
487
488 Chart {
489 title: "Performance Heatmap".to_string(),
490 subtitle: Some("Execution time across benchmarks and input sizes".to_string()),
491 x_axis: AxisConfig {
492 title: "Input Size".to_string(),
493 axis_type: AxisType::Category,
494 min: None,
495 max: None,
496 tick_interval: None,
497 log_scale: false,
498 grid: false,
499 label_format: "{:.0}".to_string(),
500 },
501 y_axis: AxisConfig {
502 title: "Benchmark".to_string(),
503 axis_type: AxisType::Category,
504 min: None,
505 max: None,
506 tick_interval: None,
507 log_scale: false,
508 grid: false,
509 label_format: "{}".to_string(),
510 },
511 series: vec![ChartSeries {
512 name: "Execution Time".to_string(),
513 data,
514 chart_type: ChartType::Heatmap,
515 color: None,
516 width: None,
517 fill_opacity: Some(0.8),
518 dash_pattern: None,
519 }],
520 chart_type: ChartType::Heatmap,
521 legend: LegendConfig {
522 show: true,
523 position: LegendPosition::Right,
524 orientation: LegendOrientation::Vertical,
525 },
526 tooltip: TooltipConfig {
527 show: true,
528 trigger: TooltipTrigger::Hover,
529 format: Some("Value: {:.2f} ms".to_string()),
530 },
531 annotations: Vec::new(),
532 }
533 }
534
535 pub fn generate_regression_analysis(&self, regressions: &[AdvancedRegressionResult]) -> Chart {
537 let mut data = Vec::new();
538
539 for (i, regression) in regressions.iter().enumerate() {
540 data.push(VisualizationPoint {
541 x: i as f64,
542 y: regression.effect_size * 100.0,
543 label: regression.benchmark_id.clone(),
544 metadata: {
545 let mut meta = HashMap::new();
546 meta.insert("benchmark".to_string(), regression.benchmark_id.clone());
547 meta.insert("p_value".to_string(), regression.p_value.to_string());
548 meta.insert(
549 "effect_size".to_string(),
550 regression.effect_size.to_string(),
551 );
552 meta.insert("severity".to_string(), format!("{:?}", regression.severity));
553 meta
554 },
555 color: Some(match regression.severity {
556 RegressionSeverity::Minor => "#f39c12".to_string(),
557 RegressionSeverity::Moderate => "#e67e22".to_string(),
558 RegressionSeverity::Major => "#d35400".to_string(),
559 RegressionSeverity::Critical => "#c0392b".to_string(),
560 }),
561 size: Some(regression.p_value / 10.0),
562 });
563 }
564
565 Chart {
566 title: "Performance Regression Analysis".to_string(),
567 subtitle: Some("Performance changes with severity indicators".to_string()),
568 x_axis: AxisConfig {
569 title: "Benchmark Index".to_string(),
570 axis_type: AxisType::Linear,
571 min: None,
572 max: None,
573 tick_interval: None,
574 log_scale: false,
575 grid: true,
576 label_format: "{:.0}".to_string(),
577 },
578 y_axis: AxisConfig {
579 title: "Performance Change (%)".to_string(),
580 axis_type: AxisType::Linear,
581 min: None,
582 max: None,
583 tick_interval: None,
584 log_scale: false,
585 grid: true,
586 label_format: "{:.1f}%".to_string(),
587 },
588 series: vec![ChartSeries {
589 name: "Regressions".to_string(),
590 data,
591 chart_type: ChartType::Scatter,
592 color: None,
593 width: None,
594 fill_opacity: Some(0.7),
595 dash_pattern: None,
596 }],
597 chart_type: ChartType::Scatter,
598 legend: LegendConfig {
599 show: false,
600 position: LegendPosition::TopRight,
601 orientation: LegendOrientation::Vertical,
602 },
603 tooltip: TooltipConfig {
604 show: true,
605 trigger: TooltipTrigger::Hover,
606 format: Some("Change: {:.1f}%, Confidence: {:.1f}%".to_string()),
607 },
608 annotations: vec![
609 Annotation {
610 annotation_type: AnnotationType::Line,
611 x: 0.0,
612 y: 0.0,
613 text: "No Change".to_string(),
614 color: Some("#7f8c8d".to_string()),
615 font_size: None,
616 },
617 Annotation {
618 annotation_type: AnnotationType::Line,
619 x: 0.0,
620 y: 10.0,
621 text: "10% Regression Threshold".to_string(),
622 color: Some("#e74c3c".to_string()),
623 font_size: None,
624 },
625 ],
626 }
627 }
628
629 pub fn generate_memory_analysis(&self, results: &[BenchResult]) -> Chart {
631 let mut execution_data = Vec::new();
632 let mut memory_data = Vec::new();
633
634 for result in results {
635 if let Some(memory) = result.memory_usage {
636 execution_data.push(VisualizationPoint {
637 x: result.size as f64,
638 y: result.mean_time_ns / 1_000_000.0,
639 label: format!("{} ({})", result.name, result.size),
640 metadata: HashMap::new(),
641 color: None,
642 size: None,
643 });
644
645 memory_data.push(VisualizationPoint {
646 x: result.size as f64,
647 y: memory as f64 / 1_048_576.0, label: format!("{} ({})", result.name, result.size),
649 metadata: HashMap::new(),
650 color: None,
651 size: None,
652 });
653 }
654 }
655
656 Chart {
657 title: "Memory Usage vs Execution Time".to_string(),
658 subtitle: Some("Correlation between memory usage and performance".to_string()),
659 x_axis: AxisConfig {
660 title: "Input Size".to_string(),
661 axis_type: AxisType::Linear,
662 min: None,
663 max: None,
664 tick_interval: None,
665 log_scale: false,
666 grid: true,
667 label_format: "{:.0}".to_string(),
668 },
669 y_axis: AxisConfig {
670 title: "Value".to_string(),
671 axis_type: AxisType::Linear,
672 min: Some(0.0),
673 max: None,
674 tick_interval: None,
675 log_scale: false,
676 grid: true,
677 label_format: "{:.2f}".to_string(),
678 },
679 series: vec![
680 ChartSeries {
681 name: "Execution Time (ms)".to_string(),
682 data: execution_data,
683 chart_type: ChartType::Line,
684 color: Some(self.config.color_palette[0].clone()),
685 width: Some(2.0),
686 fill_opacity: None,
687 dash_pattern: None,
688 },
689 ChartSeries {
690 name: "Memory Usage (MB)".to_string(),
691 data: memory_data,
692 chart_type: ChartType::Bar,
693 color: Some(self.config.color_palette[1].clone()),
694 width: Some(0.6),
695 fill_opacity: Some(0.7),
696 dash_pattern: None,
697 },
698 ],
699 chart_type: ChartType::Line,
700 legend: LegendConfig {
701 show: true,
702 position: LegendPosition::TopLeft,
703 orientation: LegendOrientation::Vertical,
704 },
705 tooltip: TooltipConfig {
706 show: true,
707 trigger: TooltipTrigger::Hover,
708 format: None,
709 },
710 annotations: Vec::new(),
711 }
712 }
713
714 pub fn generate_distribution_chart(&self, results: &[BenchResult]) -> Chart {
716 let mut data = Vec::new();
717 let mut benchmarks = Vec::new();
718
719 let mut benchmark_groups: HashMap<String, Vec<&BenchResult>> = HashMap::new();
721 for result in results {
722 benchmark_groups
723 .entry(result.name.clone())
724 .or_insert_with(Vec::new)
725 .push(result);
726 }
727
728 for (i, (benchmark, group)) in benchmark_groups.iter().enumerate() {
729 benchmarks.push(benchmark.clone());
730
731 let mut times: Vec<f64> = group.iter().map(|r| r.mean_time_ns / 1_000_000.0).collect();
733 times.sort_by(|a, b| a.partial_cmp(b).expect("NaN values in benchmark times"));
734
735 let min = times[0];
736 let max = times[times.len() - 1];
737 let median = times[times.len() / 2];
738 let q1 = times[times.len() / 4];
739 let q3 = times[3 * times.len() / 4];
740
741 data.push(VisualizationPoint {
742 x: i as f64,
743 y: median,
744 label: benchmark.clone(),
745 metadata: {
746 let mut meta = HashMap::new();
747 meta.insert("min".to_string(), min.to_string());
748 meta.insert("q1".to_string(), q1.to_string());
749 meta.insert("median".to_string(), median.to_string());
750 meta.insert("q3".to_string(), q3.to_string());
751 meta.insert("max".to_string(), max.to_string());
752 meta
753 },
754 color: None,
755 size: None,
756 });
757 }
758
759 Chart {
760 title: "Performance Distribution".to_string(),
761 subtitle: Some("Statistical distribution of benchmark execution times".to_string()),
762 x_axis: AxisConfig {
763 title: "Benchmark".to_string(),
764 axis_type: AxisType::Category,
765 min: None,
766 max: None,
767 tick_interval: None,
768 log_scale: false,
769 grid: false,
770 label_format: "{}".to_string(),
771 },
772 y_axis: AxisConfig {
773 title: "Execution Time (ms)".to_string(),
774 axis_type: AxisType::Linear,
775 min: Some(0.0),
776 max: None,
777 tick_interval: None,
778 log_scale: false,
779 grid: true,
780 label_format: "{:.2f}".to_string(),
781 },
782 series: vec![ChartSeries {
783 name: "Distribution".to_string(),
784 data,
785 chart_type: ChartType::Box,
786 color: Some(self.config.color_palette[0].clone()),
787 width: Some(0.8),
788 fill_opacity: Some(0.7),
789 dash_pattern: None,
790 }],
791 chart_type: ChartType::Box,
792 legend: LegendConfig {
793 show: false,
794 position: LegendPosition::TopRight,
795 orientation: LegendOrientation::Vertical,
796 },
797 tooltip: TooltipConfig {
798 show: true,
799 trigger: TooltipTrigger::Hover,
800 format: Some("Median: {:.2f} ms".to_string()),
801 },
802 annotations: Vec::new(),
803 }
804 }
805
806 pub fn export_to_html(&self, chart: &Chart, output_path: &str) -> std::io::Result<()> {
808 let html = self.generate_html_chart(chart);
809 std::fs::write(output_path, html)?;
810 Ok(())
811 }
812
813 fn generate_html_chart(&self, chart: &Chart) -> String {
815 let chart_json = serde_json::to_string_pretty(chart).unwrap_or_default();
816 let theme_css = self.get_theme_css();
817
818 format!(
819 r#"
820<!DOCTYPE html>
821<html lang="en">
822<head>
823 <meta charset="UTF-8">
824 <meta name="viewport" content="width=device-width, initial-scale=1.0">
825 <title>{}</title>
826 <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
827 <style>
828 body {{
829 font-family: {};
830 margin: 20px;
831 background: {};
832 }}
833 .chart-container {{
834 width: {}px;
835 height: {}px;
836 margin: 20px auto;
837 background: white;
838 border-radius: 8px;
839 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
840 padding: 20px;
841 }}
842 .chart-title {{
843 text-align: center;
844 font-size: 24px;
845 font-weight: bold;
846 margin-bottom: 10px;
847 color: #2c3e50;
848 }}
849 .chart-subtitle {{
850 text-align: center;
851 font-size: 16px;
852 color: #7f8c8d;
853 margin-bottom: 20px;
854 }}
855 {}
856 </style>
857</head>
858<body>
859 <div class="chart-container">
860 <div class="chart-title">{}</div>
861 {}
862 <div id="chart"></div>
863 </div>
864
865 <script>
866 const chartData = {};
867
868 // Convert chart data to Plotly format
869 const plotlyData = chartData.series.map(series => ({{
870 x: series.data.map(point => point.x),
871 y: series.data.map(point => point.y),
872 type: getPlotlyType(series.chart_type),
873 name: series.name,
874 marker: {{ color: series.color }},
875 mode: 'lines+markers'
876 }}));
877
878 const layout = {{
879 title: chartData.title,
880 xaxis: {{ title: chartData.x_axis.title }},
881 yaxis: {{ title: chartData.y_axis.title }},
882 showlegend: chartData.legend.show,
883 font: {{ family: '{}', size: {} }},
884 plot_bgcolor: 'rgba(0,0,0,0)',
885 paper_bgcolor: 'rgba(0,0,0,0)',
886 autosize: true,
887 responsive: true
888 }};
889
890 const config = {{
891 responsive: true,
892 displayModeBar: {},
893 modeBarButtonsToRemove: ['lasso2d', 'select2d']
894 }};
895
896 Plotly.newPlot('chart', plotlyData, layout, config);
897
898 function getPlotlyType(chartType) {{
899 switch (chartType) {{
900 case 'Line': return 'scatter';
901 case 'Bar': return 'bar';
902 case 'Scatter': return 'scatter';
903 case 'Heatmap': return 'heatmap';
904 case 'Box': return 'box';
905 case 'Histogram': return 'histogram';
906 default: return 'scatter';
907 }}
908 }}
909 </script>
910</body>
911</html>
912 "#,
913 chart.title,
914 self.config.font_family,
915 self.get_background_color(),
916 self.config.width,
917 self.config.height,
918 theme_css,
919 chart.title,
920 chart
921 .subtitle
922 .as_ref()
923 .map(|s| format!("<div class=\"chart-subtitle\">{}</div>", s))
924 .unwrap_or_default(),
925 chart_json,
926 self.config.font_family,
927 self.config.font_size,
928 self.config.interactive.to_string()
929 )
930 }
931
932 fn get_theme_css(&self) -> String {
934 match self.config.theme {
935 ChartTheme::Dark => r#"
936 body { background: #2c3e50; color: white; }
937 .chart-container { background: #34495e; }
938 .chart-title { color: #ecf0f1; }
939 .chart-subtitle { color: #bdc3c7; }
940 "#
941 .to_string(),
942 ChartTheme::Minimal => r#"
943 body { background: #fafafa; }
944 .chart-container { box-shadow: none; border: 1px solid #e0e0e0; }
945 "#
946 .to_string(),
947 ChartTheme::Professional => r#"
948 body { background: #f8f9fa; }
949 .chart-container { box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
950 .chart-title { color: #343a40; }
951 "#
952 .to_string(),
953 _ => String::new(),
954 }
955 }
956
957 fn get_background_color(&self) -> &'static str {
959 match self.config.theme {
960 ChartTheme::Dark => "#2c3e50",
961 ChartTheme::Minimal => "#fafafa",
962 ChartTheme::Professional => "#f8f9fa",
963 _ => "#f5f5f5",
964 }
965 }
966
967 pub fn save_template(&mut self, name: &str, chart: Chart) {
969 self.templates.insert(name.to_string(), chart);
970 }
971
972 pub fn load_template(&self, name: &str) -> Option<&Chart> {
974 self.templates.get(name)
975 }
976
977 pub fn generate_dashboard(
979 &self,
980 results: &[BenchResult],
981 points: &[PerformancePoint],
982 regressions: &[AdvancedRegressionResult],
983 output_dir: &str,
984 ) -> std::io::Result<()> {
985 std::fs::create_dir_all(output_dir)?;
986
987 let trend_chart = self.generate_performance_trend(points);
989 self.export_to_html(&trend_chart, &format!("{}/trend.html", output_dir))?;
990
991 let throughput_chart = self.generate_throughput_comparison(results);
992 self.export_to_html(
993 &throughput_chart,
994 &format!("{}/throughput.html", output_dir),
995 )?;
996
997 let heatmap_chart = self.generate_performance_heatmap(results);
998 self.export_to_html(&heatmap_chart, &format!("{}/heatmap.html", output_dir))?;
999
1000 let regression_chart = self.generate_regression_analysis(regressions);
1001 self.export_to_html(
1002 ®ression_chart,
1003 &format!("{}/regressions.html", output_dir),
1004 )?;
1005
1006 let memory_chart = self.generate_memory_analysis(results);
1007 self.export_to_html(&memory_chart, &format!("{}/memory.html", output_dir))?;
1008
1009 let distribution_chart = self.generate_distribution_chart(results);
1010 self.export_to_html(
1011 &distribution_chart,
1012 &format!("{}/distribution.html", output_dir),
1013 )?;
1014
1015 let dashboard_html = self.generate_dashboard_index();
1017 std::fs::write(format!("{}/index.html", output_dir), dashboard_html)?;
1018
1019 Ok(())
1020 }
1021
1022 fn generate_dashboard_index(&self) -> String {
1024 r#"
1025<!DOCTYPE html>
1026<html lang="en">
1027<head>
1028 <meta charset="UTF-8">
1029 <meta name="viewport" content="width=device-width, initial-scale=1.0">
1030 <title>ToRSh Benchmark Visualization Dashboard</title>
1031 <style>
1032 body {
1033 font-family: Arial, sans-serif;
1034 margin: 0;
1035 padding: 20px;
1036 background: #f5f5f5;
1037 }
1038 .header {
1039 text-align: center;
1040 background: #2c3e50;
1041 color: white;
1042 padding: 30px;
1043 border-radius: 8px;
1044 margin-bottom: 30px;
1045 }
1046 .grid {
1047 display: grid;
1048 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1049 gap: 20px;
1050 margin-bottom: 30px;
1051 }
1052 .card {
1053 background: white;
1054 padding: 20px;
1055 border-radius: 8px;
1056 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1057 text-align: center;
1058 }
1059 .card h3 {
1060 color: #2c3e50;
1061 margin-top: 0;
1062 }
1063 .card p {
1064 color: #7f8c8d;
1065 margin-bottom: 20px;
1066 }
1067 .btn {
1068 display: inline-block;
1069 padding: 10px 20px;
1070 background: #3498db;
1071 color: white;
1072 text-decoration: none;
1073 border-radius: 5px;
1074 transition: background 0.3s;
1075 }
1076 .btn:hover {
1077 background: #2980b9;
1078 }
1079 .footer {
1080 text-align: center;
1081 color: #7f8c8d;
1082 margin-top: 30px;
1083 }
1084 </style>
1085</head>
1086<body>
1087 <div class="header">
1088 <h1>🚀 ToRSh Benchmark Visualization Dashboard</h1>
1089 <p>Comprehensive performance analysis and visualization tools</p>
1090 </div>
1091
1092 <div class="grid">
1093 <div class="card">
1094 <h3>📈 Performance Trends</h3>
1095 <p>Track performance changes over time with interactive trend analysis</p>
1096 <a href="trend.html" class="btn">View Trends</a>
1097 </div>
1098
1099 <div class="card">
1100 <h3>🏃 Throughput Comparison</h3>
1101 <p>Compare throughput across different data types and operations</p>
1102 <a href="throughput.html" class="btn">View Throughput</a>
1103 </div>
1104
1105 <div class="card">
1106 <h3>🔥 Performance Heatmap</h3>
1107 <p>Visualize performance patterns across benchmarks and sizes</p>
1108 <a href="heatmap.html" class="btn">View Heatmap</a>
1109 </div>
1110
1111 <div class="card">
1112 <h3>⚠️ Regression Analysis</h3>
1113 <p>Identify and analyze performance regressions with severity levels</p>
1114 <a href="regressions.html" class="btn">View Regressions</a>
1115 </div>
1116
1117 <div class="card">
1118 <h3>💾 Memory Analysis</h3>
1119 <p>Analyze memory usage patterns and correlations with performance</p>
1120 <a href="memory.html" class="btn">View Memory</a>
1121 </div>
1122
1123 <div class="card">
1124 <h3>📊 Statistical Distribution</h3>
1125 <p>Explore statistical distributions of benchmark results</p>
1126 <a href="distribution.html" class="btn">View Distribution</a>
1127 </div>
1128 </div>
1129
1130 <div class="footer">
1131 <p>Generated by ToRSh Benchmark Visualization System</p>
1132 </div>
1133</body>
1134</html>
1135 "#
1136 .to_string()
1137 }
1138}
1139
1140pub struct VisualizationBuilder {
1142 config: VisualizationConfig,
1143}
1144
1145impl VisualizationBuilder {
1146 pub fn new() -> Self {
1148 Self {
1149 config: VisualizationConfig::default(),
1150 }
1151 }
1152
1153 pub fn dimensions(mut self, width: u32, height: u32) -> Self {
1155 self.config.width = width;
1156 self.config.height = height;
1157 self
1158 }
1159
1160 pub fn theme(mut self, theme: ChartTheme) -> Self {
1162 self.config.theme = theme;
1163 self
1164 }
1165
1166 pub fn interactive(mut self, interactive: bool) -> Self {
1168 self.config.interactive = interactive;
1169 self
1170 }
1171
1172 pub fn colors(mut self, colors: Vec<String>) -> Self {
1174 self.config.color_palette = colors;
1175 self
1176 }
1177
1178 pub fn build(self) -> VisualizationGenerator {
1180 VisualizationGenerator::new(self.config)
1181 }
1182}
1183
1184impl Default for VisualizationBuilder {
1185 fn default() -> Self {
1186 Self::new()
1187 }
1188}
1189
1190#[cfg(test)]
1191mod tests {
1192 use super::*;
1193 use chrono::Utc;
1194
1195 #[test]
1196 fn test_visualization_config() {
1197 let config = VisualizationConfig::default();
1198 assert_eq!(config.width, 800);
1199 assert_eq!(config.height, 600);
1200 assert_eq!(config.theme, ChartTheme::Light);
1201 assert!(config.interactive);
1202 }
1203
1204 #[test]
1205 fn test_visualization_builder() {
1206 let generator = VisualizationBuilder::new()
1207 .dimensions(1024, 768)
1208 .theme(ChartTheme::Dark)
1209 .interactive(false)
1210 .build();
1211
1212 assert_eq!(generator.config.width, 1024);
1213 assert_eq!(generator.config.height, 768);
1214 assert_eq!(generator.config.theme, ChartTheme::Dark);
1215 assert!(!generator.config.interactive);
1216 }
1217
1218 #[test]
1219 fn test_throughput_chart_generation() {
1220 let generator = VisualizationGenerator::default();
1221 let results = vec![BenchResult {
1222 name: "test_benchmark".to_string(),
1223 size: 1024,
1224 dtype: torsh_core::dtype::DType::F32,
1225 mean_time_ns: 1000.0,
1226 std_dev_ns: 100.0,
1227 throughput: Some(1000.0),
1228 memory_usage: Some(1024),
1229 peak_memory: Some(2048),
1230 metrics: HashMap::new(),
1231 }];
1232
1233 let chart = generator.generate_throughput_comparison(&results);
1234 assert_eq!(chart.title, "Throughput Comparison");
1235 assert!(!chart.series.is_empty());
1236 assert_eq!(chart.chart_type, ChartType::Bar);
1237 }
1238
1239 #[test]
1240 fn test_performance_trend_generation() {
1241 let generator = VisualizationGenerator::default();
1242 let points = vec![PerformancePoint {
1243 timestamp: Utc::now(),
1244 benchmark_name: "test".to_string(),
1245 size: 1024,
1246 dtype: "F32".to_string(),
1247 mean_time_ns: 1000.0,
1248 std_dev_ns: 100.0,
1249 throughput: Some(1000.0),
1250 memory_usage: Some(1024),
1251 peak_memory: Some(2048),
1252 git_commit: None,
1253 build_config: "release".to_string(),
1254 metadata: HashMap::new(),
1255 }];
1256
1257 let chart = generator.generate_performance_trend(&points);
1258 assert_eq!(chart.title, "Performance Trends Over Time");
1259 assert!(!chart.series.is_empty());
1260 assert_eq!(chart.chart_type, ChartType::Line);
1261 }
1262}