1use super::{TaskId, TaskResourceProfile};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct VisualizationConfig {
13 pub title: String,
14 pub theme: Theme,
15 pub include_charts: bool,
16 pub include_baselines: bool,
17 pub include_rankings: bool,
18 pub include_efficiency_breakdown: bool,
19}
20
21impl Default for VisualizationConfig {
22 fn default() -> Self {
23 Self {
24 title: "Async Task Performance Analysis".to_string(),
25 theme: Theme::Dark,
26 include_charts: true,
27 include_baselines: true,
28 include_rankings: true,
29 include_efficiency_breakdown: true,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub enum Theme {
37 Dark,
38 Light,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PerformanceBaselines {
44 pub avg_cpu_percent: f64,
45 pub avg_memory_mb: f64,
46 pub avg_io_mbps: f64,
47 pub avg_network_mbps: f64,
48 pub avg_efficiency_score: f64,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CategoryRanking {
54 pub rank: usize,
55 pub total_in_category: usize,
56 pub category_name: String,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct PerformanceComparison {
62 pub value: f64,
63 pub baseline: f64,
64 pub difference_percent: f64,
65 pub comparison_type: ComparisonType,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub enum ComparisonType {
70 AboveAverage,
71 BelowAverage,
72 NearAverage,
73}
74
75pub struct VisualizationGenerator {
77 config: VisualizationConfig,
78}
79
80impl VisualizationGenerator {
81 pub fn new() -> Self {
83 Self {
84 config: VisualizationConfig::default(),
85 }
86 }
87
88 pub fn with_config(config: VisualizationConfig) -> Self {
90 Self { config }
91 }
92
93 pub fn generate_html_report(
95 &self,
96 profiles: &HashMap<TaskId, TaskResourceProfile>,
97 ) -> Result<String, VisualizationError> {
98 let analytics = self.analyze_profiles(profiles)?;
99 self.build_html_report(&analytics, profiles)
100 }
101
102 pub fn analyze_profiles(
104 &self,
105 profiles: &HashMap<TaskId, TaskResourceProfile>,
106 ) -> Result<PerformanceAnalytics, VisualizationError> {
107 if profiles.is_empty() {
108 return Err(VisualizationError::NoDataAvailable);
109 }
110
111 let baselines = self.calculate_baselines(profiles);
112 let rankings = self.calculate_rankings(profiles);
113 let comparisons = self.calculate_comparisons(profiles, &baselines);
114
115 Ok(PerformanceAnalytics {
116 baselines,
117 rankings,
118 comparisons,
119 total_tasks: profiles.len(),
120 })
121 }
122
123 fn calculate_baselines(
125 &self,
126 profiles: &HashMap<TaskId, TaskResourceProfile>,
127 ) -> PerformanceBaselines {
128 let total = profiles.len() as f64;
129 let mut totals = (0.0, 0.0, 0.0, 0.0, 0.0);
130
131 for profile in profiles.values() {
132 totals.0 += profile.cpu_metrics.usage_percent;
133 totals.1 += profile.memory_metrics.current_bytes as f64 / 1_048_576.0;
134 totals.2 += profile.io_metrics.bandwidth_mbps;
135 totals.3 += profile.network_metrics.throughput_mbps;
136 totals.4 += profile.efficiency_score;
137 }
138
139 PerformanceBaselines {
140 avg_cpu_percent: totals.0 / total,
141 avg_memory_mb: totals.1 / total,
142 avg_io_mbps: totals.2 / total,
143 avg_network_mbps: totals.3 / total,
144 avg_efficiency_score: totals.4 / total,
145 }
146 }
147
148 fn calculate_rankings(
150 &self,
151 profiles: &HashMap<TaskId, TaskResourceProfile>,
152 ) -> HashMap<TaskId, CategoryRanking> {
153 let mut rankings = HashMap::new();
154 let mut category_groups: HashMap<String, Vec<(TaskId, &TaskResourceProfile)>> =
155 HashMap::new();
156
157 for (task_id, profile) in profiles {
159 let category = format!("{:?}", profile.task_type);
160 category_groups
161 .entry(category)
162 .or_default()
163 .push((*task_id, profile));
164 }
165
166 for (category_name, mut tasks) in category_groups {
168 tasks.sort_by(|a, b| {
169 b.1.efficiency_score
170 .partial_cmp(&a.1.efficiency_score)
171 .unwrap_or(std::cmp::Ordering::Equal)
172 });
173
174 let total_in_category = tasks.len();
175 for (rank, (task_id, _)) in tasks.iter().enumerate() {
176 rankings.insert(
177 *task_id,
178 CategoryRanking {
179 rank: rank + 1,
180 total_in_category,
181 category_name: category_name.clone(),
182 },
183 );
184 }
185 }
186
187 rankings
188 }
189
190 fn calculate_comparisons(
192 &self,
193 profiles: &HashMap<TaskId, TaskResourceProfile>,
194 baselines: &PerformanceBaselines,
195 ) -> HashMap<TaskId, TaskComparisons> {
196 let mut comparisons = HashMap::new();
197
198 for (task_id, profile) in profiles {
199 let cpu_comp = self
200 .compare_to_baseline(profile.cpu_metrics.usage_percent, baselines.avg_cpu_percent);
201 let memory_comp = self.compare_to_baseline(
202 profile.memory_metrics.current_bytes as f64 / 1_048_576.0,
203 baselines.avg_memory_mb,
204 );
205 let io_comp =
206 self.compare_to_baseline(profile.io_metrics.bandwidth_mbps, baselines.avg_io_mbps);
207 let network_comp = self.compare_to_baseline(
208 profile.network_metrics.throughput_mbps,
209 baselines.avg_network_mbps,
210 );
211
212 comparisons.insert(
213 *task_id,
214 TaskComparisons {
215 cpu: cpu_comp,
216 memory: memory_comp,
217 io: io_comp,
218 network: network_comp,
219 },
220 );
221 }
222
223 comparisons
224 }
225
226 fn compare_to_baseline(&self, value: f64, baseline: f64) -> PerformanceComparison {
228 let difference_percent = if baseline != 0.0 {
229 ((value - baseline) / baseline) * 100.0
230 } else {
231 0.0
232 };
233
234 let comparison_type = if difference_percent.abs() < 5.0 {
235 ComparisonType::NearAverage
236 } else if difference_percent > 0.0 {
237 ComparisonType::AboveAverage
238 } else {
239 ComparisonType::BelowAverage
240 };
241
242 PerformanceComparison {
243 value,
244 baseline,
245 difference_percent,
246 comparison_type,
247 }
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct PerformanceAnalytics {
254 pub baselines: PerformanceBaselines,
255 pub rankings: HashMap<TaskId, CategoryRanking>,
256 pub comparisons: HashMap<TaskId, TaskComparisons>,
257 pub total_tasks: usize,
258}
259
260#[derive(Debug, Clone)]
262pub struct TaskComparisons {
263 pub cpu: PerformanceComparison,
264 pub memory: PerformanceComparison,
265 pub io: PerformanceComparison,
266 pub network: PerformanceComparison,
267}
268
269#[derive(Debug, thiserror::Error)]
271pub enum VisualizationError {
272 #[error("No data available for visualization")]
273 NoDataAvailable,
274 #[error("Invalid configuration: {0}")]
275 InvalidConfiguration(String),
276 #[error("Template generation error: {0}")]
277 TemplateError(String),
278 #[error("IO error: {0}")]
279 IoError(#[from] std::io::Error),
280}
281
282impl Default for VisualizationGenerator {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288impl VisualizationGenerator {
289 fn build_html_report(
291 &self,
292 analytics: &PerformanceAnalytics,
293 profiles: &HashMap<TaskId, TaskResourceProfile>,
294 ) -> Result<String, VisualizationError> {
295 let mut html = String::new();
296
297 html.push_str(&self.generate_html_header());
299
300 html.push_str(&self.generate_summary_section(analytics));
302
303 if self.config.include_charts {
305 html.push_str(&self.generate_charts_section(profiles)?);
306 }
307
308 html.push_str(&self.generate_tasks_section(analytics, profiles)?);
310
311 html.push_str(&self.generate_html_footer());
313
314 Ok(html)
315 }
316
317 fn generate_html_header(&self) -> String {
319 let theme_styles = match self.config.theme {
320 Theme::Dark => self.get_dark_theme_styles(),
321 Theme::Light => self.get_light_theme_styles(),
322 };
323
324 format!(
325 r#"<!DOCTYPE html>
326<html lang="en">
327<head>
328 <meta charset="UTF-8">
329 <meta name="viewport" content="width=device-width, initial-scale=1.0">
330 <title>{}</title>
331 <style>
332 {}
333 </style>
334</head>
335<body>
336 <div class="container">
337 <div class="header">
338 <h1>📊 {}</h1>
339 <p>Advanced performance analysis with baselines, rankings, and trends</p>
340 </div>
341"#,
342 self.config.title, theme_styles, self.config.title
343 )
344 }
345
346 fn generate_summary_section(&self, analytics: &PerformanceAnalytics) -> String {
348 format!(
349 r#"
350 <div class="summary">
351 <div class="summary-card">
352 <h3>Total Tasks</h3>
353 <div class="value">{}</div>
354 </div>
355 <div class="summary-card">
356 <h3>Avg CPU Usage</h3>
357 <div class="value">{:.1}%</div>
358 </div>
359 <div class="summary-card">
360 <h3>Avg Memory</h3>
361 <div class="value">{:.0}MB</div>
362 </div>
363 <div class="summary-card">
364 <h3>Avg Efficiency</h3>
365 <div class="value">{:.0}%</div>
366 </div>
367 </div>
368"#,
369 analytics.total_tasks,
370 analytics.baselines.avg_cpu_percent,
371 analytics.baselines.avg_memory_mb,
372 analytics.baselines.avg_efficiency_score * 100.0
373 )
374 }
375
376 fn generate_tasks_section(
378 &self,
379 analytics: &PerformanceAnalytics,
380 profiles: &HashMap<TaskId, TaskResourceProfile>,
381 ) -> Result<String, VisualizationError> {
382 let mut html = String::new();
383
384 html.push_str(
385 r#"
386 <div class="tasks-section">
387 <h2 class="section-title">Task Performance Details</h2>
388 <div class="tasks-grid">
389"#,
390 );
391
392 let mut sorted_profiles: Vec<_> = profiles.iter().collect();
394 sorted_profiles.sort_by(|a, b| {
395 b.1.efficiency_score
396 .partial_cmp(&a.1.efficiency_score)
397 .unwrap_or(std::cmp::Ordering::Equal)
398 });
399
400 for (task_id, profile) in sorted_profiles {
401 html.push_str(&self.generate_task_card(*task_id, profile, analytics)?);
402 }
403
404 html.push_str(
405 r#"
406 </div>
407 </div>
408"#,
409 );
410
411 Ok(html)
412 }
413
414 fn generate_task_card(
416 &self,
417 task_id: TaskId,
418 profile: &TaskResourceProfile,
419 analytics: &PerformanceAnalytics,
420 ) -> Result<String, VisualizationError> {
421 let ranking = analytics.rankings.get(&task_id);
422 let comparisons = analytics.comparisons.get(&task_id);
423
424 let task_type_class = format!("{:?}", profile.task_type).to_lowercase();
425
426 let rank_info = if let Some(ranking) = ranking {
427 let rank_class = match ranking.rank {
428 1 => "rank-1",
429 2 => "rank-2",
430 3 => "rank-3",
431 _ => "",
432 };
433 format!(
434 r#"<div class="ranking-badge {}">#{}/{}</div>"#,
435 rank_class, ranking.rank, ranking.total_in_category
436 )
437 } else {
438 String::new()
439 };
440
441 if let Some(comp) = comparisons {
442 self.generate_comparison_info(comp)
443 } else {
444 String::new()
445 };
446
447 let efficiency_tooltip = if self.config.include_efficiency_breakdown {
448 format!(
449 r#"
450 <div class="info-icon">?
451 <div class="tooltip">
452 <strong>Efficiency Breakdown:</strong><br>
453 CPU: {:.1}%<br>
454 Memory: {:.1}%<br>
455 IO: {:.1}%<br>
456 Network: {:.1}%<br>
457 Overall: {:.1}%
458 </div>
459 </div>
460"#,
461 profile
462 .efficiency_explanation
463 .component_scores
464 .cpu_efficiency
465 * 100.0,
466 profile
467 .efficiency_explanation
468 .component_scores
469 .memory_efficiency
470 * 100.0,
471 profile
472 .efficiency_explanation
473 .component_scores
474 .io_efficiency
475 * 100.0,
476 profile
477 .efficiency_explanation
478 .component_scores
479 .network_efficiency
480 * 100.0,
481 profile.efficiency_score * 100.0
482 )
483 } else {
484 String::new()
485 };
486
487 Ok(format!(
488 r#"
489 <div class="task-card">
490 {}
491 <div class="task-header {}">
492 <h3 class="task-name">{}</h3>
493 <span class="task-badge {}">{:?}</span>
494 </div>
495 <div class="task-content">
496 <div class="metrics-grid">
497 <div class="metric-item">
498 <div class="metric-label">CPU Usage</div>
499 <div class="metric-value">{:.1}%</div>
500 {}
501 </div>
502 <div class="metric-item">
503 <div class="metric-label">Memory</div>
504 <div class="metric-value">{:.0}MB</div>
505 {}
506 </div>
507 <div class="metric-item">
508 <div class="metric-label">IO Bandwidth</div>
509 <div class="metric-value">{:.1}MB/s</div>
510 {}
511 </div>
512 <div class="metric-item">
513 <div class="metric-label">Network</div>
514 <div class="metric-value">{:.1}Mbps</div>
515 {}
516 </div>
517 </div>
518
519 <div class="efficiency-section">
520 <div class="efficiency-title">
521 Efficiency Score
522 {}
523 </div>
524 <div class="efficiency-bar">
525 <div class="efficiency-fill" style="width: {:.1}%"></div>
526 </div>
527 <div class="efficiency-score">{:.1}%</div>
528 </div>
529
530 <div class="source-info">
531 <div class="source-title">Source Location</div>
532 <div class="source-detail">
533 <span class="source-label">File:</span>
534 <span class="source-value">{}</span>
535 </div>
536 <div class="source-detail">
537 <span class="source-label">Line:</span>
538 <span class="source-value">{}</span>
539 </div>
540 <div class="source-detail">
541 <span class="source-label">Function:</span>
542 <span class="source-value">{}</span>
543 </div>
544 </div>
545 </div>
546 </div>
547"#,
548 rank_info,
549 task_type_class,
550 profile.task_name,
551 task_type_class,
552 profile.task_type,
553 profile.cpu_metrics.usage_percent,
554 if let Some(comp) = comparisons {
555 format!(
556 "<div class=\"metric-comparison {}\">{}</div>",
557 self.get_comparison_class(&comp.cpu),
558 self.format_comparison(&comp.cpu)
559 )
560 } else {
561 String::new()
562 },
563 profile.memory_metrics.current_bytes as f64 / 1_048_576.0,
564 if let Some(comp) = comparisons {
565 format!(
566 "<div class=\"metric-comparison {}\">{}</div>",
567 self.get_comparison_class(&comp.memory),
568 self.format_comparison(&comp.memory)
569 )
570 } else {
571 String::new()
572 },
573 profile.io_metrics.bandwidth_mbps,
574 if let Some(comp) = comparisons {
575 format!(
576 "<div class=\"metric-comparison {}\">{}</div>",
577 self.get_comparison_class(&comp.io),
578 self.format_comparison(&comp.io)
579 )
580 } else {
581 String::new()
582 },
583 profile.network_metrics.throughput_mbps,
584 if let Some(comp) = comparisons {
585 format!(
586 "<div class=\"metric-comparison {}\">{}</div>",
587 self.get_comparison_class(&comp.network),
588 self.format_comparison(&comp.network)
589 )
590 } else {
591 String::new()
592 },
593 efficiency_tooltip,
594 profile.efficiency_score * 100.0,
595 profile.efficiency_score * 100.0,
596 profile.source_location.file_path,
597 profile.source_location.line_number,
598 profile.source_location.function_name
599 ))
600 }
601
602 fn generate_comparison_info(&self, _comparisons: &TaskComparisons) -> String {
604 String::new()
606 }
607
608 fn format_comparison(&self, comparison: &PerformanceComparison) -> String {
610 match comparison.comparison_type {
611 ComparisonType::NearAverage => "(≈ avg)".to_string(),
612 ComparisonType::AboveAverage => {
613 format!("(+{:.1}% vs avg)", comparison.difference_percent.abs())
614 }
615 ComparisonType::BelowAverage => {
616 format!("(-{:.1}% vs avg)", comparison.difference_percent.abs())
617 }
618 }
619 }
620
621 fn get_comparison_class(&self, comparison: &PerformanceComparison) -> &'static str {
623 match comparison.comparison_type {
624 ComparisonType::NearAverage => "comparison-average",
625 ComparisonType::AboveAverage => "comparison-above",
626 ComparisonType::BelowAverage => "comparison-below",
627 }
628 }
629
630 fn generate_charts_section(
632 &self,
633 profiles: &HashMap<TaskId, TaskResourceProfile>,
634 ) -> Result<String, VisualizationError> {
635 let mut html = String::new();
636
637 html.push_str(
638 r#"
639 <div class="charts-section">
640 <h2 class="section-title">📈 Performance Trends</h2>
641"#,
642 );
643
644 let chart_html = self.generate_chart_scripts(profiles)?;
646 html.push_str(&chart_html);
647
648 html.push_str(
649 r#"
650 </div>
651"#,
652 );
653
654 Ok(html)
655 }
656
657 fn generate_chart_scripts(
659 &self,
660 profiles: &HashMap<TaskId, TaskResourceProfile>,
661 ) -> Result<String, VisualizationError> {
662 let mut cpu_bars = String::new();
663 let mut memory_bars = String::new();
664
665 let max_cpu = profiles
667 .values()
668 .map(|p| p.cpu_metrics.usage_percent)
669 .fold(0.0, f64::max)
670 .max(100.0);
671 let max_memory = profiles
672 .values()
673 .map(|p| p.memory_metrics.current_bytes as f64 / 1_048_576.0)
674 .fold(0.0, f64::max);
675
676 for profile in profiles.values() {
677 let cpu_percent = profile.cpu_metrics.usage_percent;
678 let memory_mb = profile.memory_metrics.current_bytes as f64 / 1_048_576.0;
679
680 let cpu_width = (cpu_percent / max_cpu * 100.0).min(100.0);
681 let memory_width = if max_memory > 0.0 {
682 (memory_mb / max_memory * 100.0).min(100.0)
683 } else {
684 0.0
685 };
686
687 cpu_bars.push_str(&format!(
688 r#"
689 <div class="chart-bar">
690 <div class="bar-label">{}</div>
691 <div class="bar-container">
692 <div class="bar-fill cpu-bar" style="width: {:.1}%"></div>
693 <div class="bar-value">{:.1}%</div>
694 </div>
695 </div>
696"#,
697 profile.task_name, cpu_width, cpu_percent
698 ));
699
700 memory_bars.push_str(&format!(
701 r#"
702 <div class="chart-bar">
703 <div class="bar-label">{}</div>
704 <div class="bar-container">
705 <div class="bar-fill memory-bar" style="width: {:.1}%"></div>
706 <div class="bar-value">{:.1}MB</div>
707 </div>
708 </div>
709"#,
710 profile.task_name, memory_width, memory_mb
711 ));
712 }
713
714 let mut network_bars = String::new();
716 let max_network = profiles
717 .values()
718 .map(|p| p.network_metrics.throughput_mbps)
719 .fold(0.0, f64::max);
720
721 for profile in profiles.values() {
722 let network_mbps = profile.network_metrics.throughput_mbps;
723 let network_width = if max_network > 0.0 {
724 (network_mbps / max_network * 100.0).min(100.0)
725 } else {
726 0.0
727 };
728
729 network_bars.push_str(&format!(
730 r#"
731 <div class="chart-bar">
732 <div class="bar-label">{}</div>
733 <div class="bar-container">
734 <div class="bar-fill network-bar" style="width: {:.1}%"></div>
735 <div class="bar-value">{:.1}Mbps</div>
736 </div>
737 </div>
738"#,
739 profile.task_name, network_width, network_mbps
740 ));
741 }
742
743 Ok(format!(
744 r#"
745 <div class="simple-charts">
746 <div class="simple-chart">
747 <h4>CPU Usage Distribution</h4>
748 <div class="chart-bars">
749 {}
750 </div>
751 </div>
752 <div class="simple-chart">
753 <h4>Memory Usage Distribution</h4>
754 <div class="chart-bars">
755 {}
756 </div>
757 </div>
758 <div class="simple-chart">
759 <h4>Network Throughput Distribution</h4>
760 <div class="chart-bars">
761 {}
762 </div>
763 </div>
764 </div>
765"#,
766 cpu_bars, memory_bars, network_bars
767 ))
768 }
769
770 fn generate_html_footer(&self) -> String {
772 r#"
773 </div>
774</body>
775</html>
776"#
777 .to_string()
778 }
779
780 fn get_dark_theme_styles(&self) -> &'static str {
782 r#"
783 body {
784 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
785 margin: 0;
786 padding: 20px;
787 background: #0d1117;
788 color: #f0f6fc;
789 line-height: 1.6;
790 }
791
792 .container {
793 max-width: 1400px;
794 margin: 0 auto;
795 background: #161b22;
796 border: 1px solid #30363d;
797 border-radius: 12px;
798 overflow: hidden;
799 }
800
801 .header {
802 background: linear-gradient(135deg, #58a6ff 0%, #a5a5ff 100%);
803 padding: 2rem;
804 text-align: center;
805 color: white;
806 }
807
808 .header h1 {
809 margin: 0;
810 font-size: 2.5rem;
811 font-weight: 700;
812 }
813
814 .summary {
815 display: grid;
816 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
817 gap: 1.5rem;
818 padding: 2rem;
819 background: #21262d;
820 }
821
822 .summary-card {
823 background: #161b22;
824 border: 1px solid #30363d;
825 padding: 1.5rem;
826 border-radius: 8px;
827 text-align: center;
828 }
829
830 .summary-card h3 {
831 margin: 0 0 0.5rem 0;
832 color: #8b949e;
833 font-size: 0.9rem;
834 text-transform: uppercase;
835 }
836
837 .summary-card .value {
838 font-size: 2rem;
839 font-weight: 700;
840 color: #58a6ff;
841 }
842
843 .tasks-section {
844 padding: 2rem;
845 }
846
847 .section-title {
848 margin: 0 0 2rem 0;
849 font-size: 1.5rem;
850 font-weight: 600;
851 color: #f0f6fc;
852 text-align: center;
853 }
854
855 .tasks-grid {
856 display: grid;
857 grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
858 gap: 1.5rem;
859 }
860
861 .task-card {
862 background: #21262d;
863 border: 1px solid #30363d;
864 border-radius: 10px;
865 overflow: hidden;
866 transition: transform 0.2s ease;
867 position: relative;
868 }
869
870 .task-card:hover {
871 transform: translateY(-2px);
872 box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
873 }
874
875 .ranking-badge {
876 position: absolute;
877 top: 10px;
878 right: 10px;
879 background: linear-gradient(135deg, #f9c513, #ffd700);
880 color: #000;
881 padding: 0.25rem 0.5rem;
882 border-radius: 12px;
883 font-size: 0.7rem;
884 font-weight: 700;
885 z-index: 10;
886 }
887
888 .ranking-badge.rank-1 { background: linear-gradient(135deg, #ffd700, #ffed4e); }
889 .ranking-badge.rank-2 { background: linear-gradient(135deg, #c0c0c0, #e8e8e8); }
890 .ranking-badge.rank-3 { background: linear-gradient(135deg, #cd7f32, #daa520); }
891
892 .task-header {
893 padding: 1.5rem;
894 background: #161b22;
895 border-bottom: 1px solid #30363d;
896 position: relative;
897 }
898
899 .task-header::before {
900 content: '';
901 position: absolute;
902 top: 0;
903 left: 0;
904 right: 0;
905 height: 3px;
906 }
907
908 .task-header.cpuintensive::before { background: #f85149; }
909 .task-header.iointensive::before { background: #58a6ff; }
910 .task-header.networkintensive::before { background: #3fb950; }
911 .task-header.memoryintensive::before { background: #a5a5ff; }
912 .task-header.mixed::before { background: #f9c513; }
913
914 .task-name {
915 margin: 0 0 0.5rem 0;
916 font-size: 1.125rem;
917 font-weight: 600;
918 color: #f0f6fc;
919 }
920
921 .task-badge {
922 display: inline-block;
923 padding: 0.25rem 0.75rem;
924 border-radius: 12px;
925 font-size: 0.75rem;
926 font-weight: 600;
927 text-transform: uppercase;
928 }
929
930 .task-badge.cpuintensive { background: #f85149; color: white; }
931 .task-badge.iointensive { background: #58a6ff; color: white; }
932 .task-badge.networkintensive { background: #3fb950; color: white; }
933 .task-badge.memoryintensive { background: #a5a5ff; color: white; }
934 .task-badge.mixed { background: #f9c513; color: black; }
935
936 .task-content {
937 padding: 1.5rem;
938 }
939
940 .metrics-grid {
941 display: grid;
942 grid-template-columns: 1fr 1fr;
943 gap: 1rem;
944 margin-bottom: 1rem;
945 }
946
947 .metric-item {
948 background: #161b22;
949 border: 1px solid #30363d;
950 border-radius: 6px;
951 padding: 1rem;
952 text-align: center;
953 }
954
955 .metric-label {
956 font-size: 0.75rem;
957 color: #8b949e;
958 margin-bottom: 0.25rem;
959 text-transform: uppercase;
960 }
961
962 .metric-value {
963 font-size: 1.25rem;
964 font-weight: 700;
965 color: #f0f6fc;
966 }
967
968 .metric-comparison {
969 font-size: 0.7rem;
970 margin-top: 0.25rem;
971 }
972
973 .comparison-above { color: #f85149; }
974 .comparison-below { color: #3fb950; }
975 .comparison-average { color: #f9c513; }
976
977 .efficiency-section {
978 background: #161b22;
979 border: 1px solid #30363d;
980 border-radius: 6px;
981 padding: 1rem;
982 margin-top: 1rem;
983 }
984
985 .efficiency-title {
986 font-size: 0.875rem;
987 color: #8b949e;
988 margin-bottom: 0.5rem;
989 text-transform: uppercase;
990 display: flex;
991 align-items: center;
992 gap: 0.5rem;
993 }
994
995 .info-icon {
996 cursor: help;
997 background: #30363d;
998 color: #8b949e;
999 border-radius: 50%;
1000 width: 16px;
1001 height: 16px;
1002 display: flex;
1003 align-items: center;
1004 justify-content: center;
1005 font-size: 0.7rem;
1006 font-weight: bold;
1007 position: relative;
1008 }
1009
1010 .info-icon:hover {
1011 background: #58a6ff;
1012 color: white;
1013 }
1014
1015 .tooltip {
1016 position: absolute;
1017 bottom: 100%;
1018 right: 0;
1019 background: #161b22;
1020 border: 1px solid #30363d;
1021 border-radius: 6px;
1022 padding: 0.75rem;
1023 min-width: 200px;
1024 font-size: 0.8rem;
1025 color: #f0f6fc;
1026 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
1027 z-index: 1000;
1028 opacity: 0;
1029 visibility: hidden;
1030 transition: opacity 0.3s ease;
1031 }
1032
1033 .info-icon:hover .tooltip {
1034 opacity: 1;
1035 visibility: visible;
1036 }
1037
1038 .efficiency-bar {
1039 width: 100%;
1040 height: 8px;
1041 background: #30363d;
1042 border-radius: 4px;
1043 overflow: hidden;
1044 margin-bottom: 0.5rem;
1045 }
1046
1047 .efficiency-fill {
1048 height: 100%;
1049 background: linear-gradient(90deg, #3fb950, #f9c513, #f85149);
1050 border-radius: 4px;
1051 }
1052
1053 .efficiency-score {
1054 font-size: 1rem;
1055 font-weight: 700;
1056 color: #58a6ff;
1057 text-align: right;
1058 }
1059
1060 .source-info {
1061 background: #161b22;
1062 border: 1px solid #30363d;
1063 border-radius: 6px;
1064 padding: 1rem;
1065 margin-top: 1rem;
1066 }
1067
1068 .source-title {
1069 font-size: 0.875rem;
1070 color: #8b949e;
1071 margin-bottom: 0.5rem;
1072 text-transform: uppercase;
1073 }
1074
1075 .source-detail {
1076 display: flex;
1077 justify-content: space-between;
1078 margin-bottom: 0.25rem;
1079 font-size: 0.8rem;
1080 }
1081
1082 .source-label {
1083 color: #8b949e;
1084 }
1085
1086 .source-value {
1087 color: #f0f6fc;
1088 font-family: 'Courier New', monospace;
1089 }
1090
1091 .charts-section {
1092 padding: 2rem;
1093 background: #161b22;
1094 border-top: 1px solid #30363d;
1095 }
1096
1097 .charts-grid {
1098 display: grid;
1099 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1100 gap: 2rem;
1101 margin-top: 1.5rem;
1102 }
1103
1104 .chart-container {
1105 background: #21262d;
1106 border: 1px solid #30363d;
1107 border-radius: 8px;
1108 padding: 1.5rem;
1109 }
1110
1111 .chart-title {
1112 margin: 0 0 1rem 0;
1113 font-size: 1rem;
1114 font-weight: 600;
1115 color: #f0f6fc;
1116 text-align: center;
1117 }
1118
1119 /* Simple CSS Charts */
1120 .simple-charts {
1121 display: grid;
1122 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1123 gap: 2rem;
1124 padding: 2rem;
1125 }
1126
1127 .simple-chart {
1128 background: #21262d;
1129 border: 1px solid #30363d;
1130 border-radius: 8px;
1131 padding: 1.5rem;
1132 }
1133
1134 .simple-chart h4 {
1135 margin: 0 0 1rem 0;
1136 font-size: 1rem;
1137 font-weight: 600;
1138 color: #f0f6fc;
1139 text-align: center;
1140 }
1141
1142 .chart-bars {
1143 display: flex;
1144 flex-direction: column;
1145 gap: 0.75rem;
1146 }
1147
1148 .chart-bar {
1149 display: flex;
1150 align-items: center;
1151 gap: 1rem;
1152 }
1153
1154 .bar-label {
1155 min-width: 120px;
1156 font-size: 0.8rem;
1157 color: #8b949e;
1158 text-align: right;
1159 }
1160
1161 .bar-container {
1162 flex: 1;
1163 display: flex;
1164 align-items: center;
1165 gap: 0.5rem;
1166 position: relative;
1167 }
1168
1169 .bar-fill {
1170 height: 20px;
1171 border-radius: 4px;
1172 transition: width 0.6s ease;
1173 position: relative;
1174 }
1175
1176 .cpu-bar {
1177 background: linear-gradient(90deg, #f85149, #ff6b6b);
1178 }
1179
1180 .memory-bar {
1181 background: linear-gradient(90deg, #a5a5ff, #b39ddb);
1182 }
1183
1184 .network-bar {
1185 background: linear-gradient(90deg, #3fb950, #a5d6a7);
1186 }
1187
1188 .bar-value {
1189 font-size: 0.8rem;
1190 color: #f0f6fc;
1191 font-weight: 600;
1192 min-width: 50px;
1193 }
1194
1195 @media (max-width: 1024px) {
1196 .simple-charts {
1197 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
1198 }
1199
1200 .bar-label {
1201 min-width: 100px;
1202 font-size: 0.75rem;
1203 }
1204 }
1205
1206 @media (max-width: 768px) {
1207 .tasks-grid {
1208 grid-template-columns: 1fr;
1209 }
1210 .metrics-grid {
1211 grid-template-columns: 1fr;
1212 }
1213 }
1214 "#
1215 }
1216
1217 fn get_light_theme_styles(&self) -> &'static str {
1219 r#"
1220 body {
1221 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
1222 margin: 0;
1223 padding: 20px;
1224 background: #ffffff;
1225 color: #24292f;
1226 line-height: 1.6;
1227 }
1228
1229 .container {
1230 max-width: 1400px;
1231 margin: 0 auto;
1232 background: #f6f8fa;
1233 border: 1px solid #d0d7de;
1234 border-radius: 12px;
1235 overflow: hidden;
1236 }
1237
1238 .header {
1239 background: linear-gradient(135deg, #0969da 0%, #8250df 100%);
1240 padding: 2rem;
1241 text-align: center;
1242 color: white;
1243 }
1244
1245 .summary {
1246 display: grid;
1247 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1248 gap: 1.5rem;
1249 padding: 2rem;
1250 background: #ffffff;
1251 }
1252
1253 .summary-card {
1254 background: #f6f8fa;
1255 border: 1px solid #d0d7de;
1256 padding: 1.5rem;
1257 border-radius: 8px;
1258 text-align: center;
1259 }
1260
1261 .task-card {
1262 background: #ffffff;
1263 border: 1px solid #d0d7de;
1264 border-radius: 10px;
1265 overflow: hidden;
1266 transition: transform 0.2s ease;
1267 position: relative;
1268 }
1269
1270 .task-card:hover {
1271 transform: translateY(-2px);
1272 box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
1273 }
1274
1275 /* Add more light theme styles as needed */
1276 "#
1277 }
1278}
1279
1280#[cfg(test)]
1281mod tests {
1282 use super::*;
1283 use crate::async_memory::resource_monitor::{
1284 BottleneckType, ComponentScores, CpuMetrics, CriticalPathAnalysis, EfficiencyExplanation,
1285 HotMetrics, IoMetrics, MemoryMetrics, NetworkMetrics, SourceLocation, TaskResourceProfile,
1286 TaskType,
1287 };
1288
1289 fn create_test_profile(
1291 task_name: &str,
1292 task_type: TaskType,
1293 cpu_usage: f64,
1294 memory_bytes: u64,
1295 efficiency: f64,
1296 ) -> TaskResourceProfile {
1297 TaskResourceProfile {
1298 task_id: 1u128,
1299 task_name: task_name.to_string(),
1300 task_type,
1301 start_time: 1000,
1302 end_time: Some(2000),
1303 duration_ms: Some(1000.0),
1304 cpu_metrics: CpuMetrics {
1305 usage_percent: cpu_usage,
1306 time_user_ms: 100.0,
1307 time_kernel_ms: 50.0,
1308 context_switches: 10,
1309 cpu_cycles: 1000000,
1310 instructions: 500000,
1311 cache_misses: 100,
1312 branch_misses: 50,
1313 core_affinity: vec![0],
1314 },
1315 memory_metrics: MemoryMetrics {
1316 allocated_bytes: memory_bytes,
1317 peak_bytes: memory_bytes + 1024,
1318 current_bytes: memory_bytes,
1319 allocation_count: 5,
1320 deallocation_count: 3,
1321 page_faults: 10,
1322 heap_fragmentation: 0.1,
1323 memory_bandwidth_mbps: 1000.0,
1324 },
1325 io_metrics: IoMetrics {
1326 bytes_read: 1024,
1327 bytes_written: 512,
1328 read_operations: 10,
1329 write_operations: 5,
1330 sync_operations: 3,
1331 async_operations: 12,
1332 avg_latency_us: 100.0,
1333 bandwidth_mbps: 10.0,
1334 queue_depth: 4,
1335 io_wait_percent: 5.0,
1336 },
1337 network_metrics: NetworkMetrics {
1338 bytes_sent: 2048,
1339 bytes_received: 1536,
1340 packets_sent: 100,
1341 packets_received: 95,
1342 connections_active: 5,
1343 connections_established: 10,
1344 connection_errors: 1,
1345 latency_avg_ms: 10.0,
1346 throughput_mbps: 5.0,
1347 retransmissions: 2,
1348 },
1349 gpu_metrics: None,
1350 efficiency_score: efficiency,
1351 resource_balance: 0.8,
1352 bottleneck_type: BottleneckType::Balanced,
1353 source_location: SourceLocation {
1354 file_path: "test.rs".to_string(),
1355 line_number: 42,
1356 function_name: "test_function".to_string(),
1357 module_path: "test::module".to_string(),
1358 crate_name: "test_crate".to_string(),
1359 },
1360 hot_metrics: HotMetrics {
1361 cpu_hotspots: vec![],
1362 memory_hotspots: vec![],
1363 io_hotspots: vec![],
1364 network_hotspots: vec![],
1365 critical_path_analysis: CriticalPathAnalysis {
1366 total_execution_time_ms: 1000.0,
1367 critical_path_time_ms: 800.0,
1368 parallelization_potential: 0.5,
1369 blocking_operations: vec![],
1370 },
1371 },
1372 efficiency_explanation: EfficiencyExplanation {
1373 overall_score: efficiency,
1374 component_scores: ComponentScores {
1375 cpu_efficiency: efficiency,
1376 memory_efficiency: efficiency,
1377 io_efficiency: efficiency,
1378 network_efficiency: efficiency,
1379 resource_balance: 0.8,
1380 },
1381 recommendations: vec![],
1382 bottleneck_analysis: "No bottlenecks detected".to_string(),
1383 optimization_potential: 0.2,
1384 },
1385 }
1386 }
1387
1388 #[test]
1389 fn test_visualization_config_default() {
1390 let config = VisualizationConfig::default();
1391
1392 assert_eq!(config.title, "Async Task Performance Analysis");
1393 assert!(matches!(config.theme, Theme::Dark));
1394 assert!(config.include_charts);
1395 assert!(config.include_baselines);
1396 assert!(config.include_rankings);
1397 assert!(config.include_efficiency_breakdown);
1398 }
1399
1400 #[test]
1401 fn test_visualization_generator_new() {
1402 let generator = VisualizationGenerator::new();
1403 assert_eq!(generator.config.title, "Async Task Performance Analysis");
1404 }
1405
1406 #[test]
1407 fn test_visualization_generator_with_config() {
1408 let custom_config = VisualizationConfig {
1409 title: "Custom Analysis".to_string(),
1410 theme: Theme::Light,
1411 include_charts: false,
1412 include_baselines: false,
1413 include_rankings: false,
1414 include_efficiency_breakdown: false,
1415 };
1416
1417 let generator = VisualizationGenerator::with_config(custom_config.clone());
1418 assert_eq!(generator.config.title, "Custom Analysis");
1419 assert!(matches!(generator.config.theme, Theme::Light));
1420 assert!(!generator.config.include_charts);
1421 }
1422
1423 #[test]
1424 fn test_calculate_baselines() {
1425 let generator = VisualizationGenerator::new();
1426 let mut profiles = HashMap::new();
1427
1428 profiles.insert(
1429 1u128,
1430 create_test_profile("task1", TaskType::CpuIntensive, 50.0, 1024 * 1024, 0.8),
1431 );
1432 profiles.insert(
1433 2u128,
1434 create_test_profile("task2", TaskType::IoIntensive, 30.0, 2048 * 1024, 0.6),
1435 );
1436
1437 let baselines = generator.calculate_baselines(&profiles);
1438
1439 assert_eq!(baselines.avg_cpu_percent, 40.0);
1440 assert_eq!(baselines.avg_memory_mb, 1.5);
1441 assert_eq!(baselines.avg_io_mbps, 10.0);
1442 assert_eq!(baselines.avg_network_mbps, 5.0);
1443 assert_eq!(baselines.avg_efficiency_score, 0.7);
1444 }
1445
1446 #[test]
1447 fn test_calculate_rankings() {
1448 let generator = VisualizationGenerator::new();
1449 let mut profiles = HashMap::new();
1450
1451 profiles.insert(
1453 1u128,
1454 create_test_profile("task1", TaskType::CpuIntensive, 50.0, 1024 * 1024, 0.9),
1455 );
1456 profiles.insert(
1457 2u128,
1458 create_test_profile("task2", TaskType::CpuIntensive, 30.0, 2048 * 1024, 0.7),
1459 );
1460 profiles.insert(
1461 3u128,
1462 create_test_profile("task3", TaskType::IoIntensive, 20.0, 512 * 1024, 0.8),
1463 );
1464
1465 let rankings = generator.calculate_rankings(&profiles);
1466
1467 let task1_ranking = rankings.get(&1u128).expect("Task 1 should have ranking");
1469 let task2_ranking = rankings.get(&2u128).expect("Task 2 should have ranking");
1470
1471 assert_eq!(task1_ranking.rank, 1); assert_eq!(task1_ranking.total_in_category, 2);
1473 assert_eq!(task1_ranking.category_name, "CpuIntensive");
1474
1475 assert_eq!(task2_ranking.rank, 2);
1476 assert_eq!(task2_ranking.total_in_category, 2);
1477
1478 let task3_ranking = rankings.get(&3u128).expect("Task 3 should have ranking");
1480 assert_eq!(task3_ranking.rank, 1);
1481 assert_eq!(task3_ranking.total_in_category, 1);
1482 assert_eq!(task3_ranking.category_name, "IoIntensive");
1483 }
1484
1485 #[test]
1486 fn test_compare_to_baseline() {
1487 let generator = VisualizationGenerator::new();
1488
1489 let comp = generator.compare_to_baseline(110.0, 100.0);
1491 assert!(matches!(comp.comparison_type, ComparisonType::AboveAverage));
1492 assert_eq!(comp.difference_percent, 10.0);
1493
1494 let comp = generator.compare_to_baseline(90.0, 100.0);
1496 assert!(matches!(comp.comparison_type, ComparisonType::BelowAverage));
1497 assert_eq!(comp.difference_percent, -10.0);
1498
1499 let comp = generator.compare_to_baseline(102.0, 100.0);
1501 assert!(matches!(comp.comparison_type, ComparisonType::NearAverage));
1502 assert_eq!(comp.difference_percent, 2.0);
1503
1504 let comp = generator.compare_to_baseline(50.0, 0.0);
1506 assert_eq!(comp.difference_percent, 0.0);
1507 }
1508
1509 #[test]
1510 fn test_analyze_profiles_empty() {
1511 let generator = VisualizationGenerator::new();
1512 let profiles = HashMap::new();
1513
1514 let result = generator.analyze_profiles(&profiles);
1515 assert!(result.is_err());
1516 assert!(matches!(
1517 result.unwrap_err(),
1518 VisualizationError::NoDataAvailable
1519 ));
1520 }
1521
1522 #[test]
1523 fn test_format_comparison() {
1524 let generator = VisualizationGenerator::new();
1525
1526 let comp_above = PerformanceComparison {
1527 value: 110.0,
1528 baseline: 100.0,
1529 difference_percent: 10.5,
1530 comparison_type: ComparisonType::AboveAverage,
1531 };
1532 assert_eq!(generator.format_comparison(&comp_above), "(+10.5% vs avg)");
1533
1534 let comp_below = PerformanceComparison {
1535 value: 85.0,
1536 baseline: 100.0,
1537 difference_percent: -15.0,
1538 comparison_type: ComparisonType::BelowAverage,
1539 };
1540 assert_eq!(generator.format_comparison(&comp_below), "(-15.0% vs avg)");
1541
1542 let comp_average = PerformanceComparison {
1543 value: 102.0,
1544 baseline: 100.0,
1545 difference_percent: 2.0,
1546 comparison_type: ComparisonType::NearAverage,
1547 };
1548 assert_eq!(generator.format_comparison(&comp_average), "(≈ avg)");
1549 }
1550
1551 #[test]
1552 fn test_get_comparison_class() {
1553 let generator = VisualizationGenerator::new();
1554
1555 let comp_above = PerformanceComparison {
1556 value: 110.0,
1557 baseline: 100.0,
1558 difference_percent: 10.0,
1559 comparison_type: ComparisonType::AboveAverage,
1560 };
1561 assert_eq!(
1562 generator.get_comparison_class(&comp_above),
1563 "comparison-above"
1564 );
1565
1566 let comp_below = PerformanceComparison {
1567 value: 90.0,
1568 baseline: 100.0,
1569 difference_percent: -10.0,
1570 comparison_type: ComparisonType::BelowAverage,
1571 };
1572 assert_eq!(
1573 generator.get_comparison_class(&comp_below),
1574 "comparison-below"
1575 );
1576
1577 let comp_average = PerformanceComparison {
1578 value: 102.0,
1579 baseline: 100.0,
1580 difference_percent: 2.0,
1581 comparison_type: ComparisonType::NearAverage,
1582 };
1583 assert_eq!(
1584 generator.get_comparison_class(&comp_average),
1585 "comparison-average"
1586 );
1587 }
1588
1589 #[test]
1590 fn test_theme_styles() {
1591 let generator = VisualizationGenerator::new();
1592
1593 let dark_styles = generator.get_dark_theme_styles();
1594 assert!(dark_styles.contains("background: #0d1117"));
1595 assert!(dark_styles.contains("color: #f0f6fc"));
1596
1597 let light_styles = generator.get_light_theme_styles();
1598 assert!(light_styles.contains("background: #ffffff"));
1599 assert!(light_styles.contains("color: #24292f"));
1600 }
1601
1602 #[test]
1603 fn test_visualization_error_display() {
1604 let err = VisualizationError::NoDataAvailable;
1605 assert_eq!(format!("{}", err), "No data available for visualization");
1606
1607 let err = VisualizationError::InvalidConfiguration("test error".to_string());
1608 assert_eq!(format!("{}", err), "Invalid configuration: test error");
1609
1610 let err = VisualizationError::TemplateError("template failed".to_string());
1611 assert_eq!(
1612 format!("{}", err),
1613 "Template generation error: template failed"
1614 );
1615 }
1616}