1use super::{TaskId, TaskResourceProfile};
7use handlebars::Handlebars;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
13struct TaskTypeMetrics {
14 pub cpu_count: usize,
15 pub cpu_efficiency: f64,
16 pub memory_count: usize,
17 pub memory_efficiency: f64,
18 pub io_count: usize,
19 pub io_efficiency: f64,
20 pub network_count: usize,
21 pub network_efficiency: f64,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct VisualizationConfig {
27 pub title: String,
28 pub theme: Theme,
29 pub include_charts: bool,
30 pub include_baselines: bool,
31 pub include_rankings: bool,
32 pub include_efficiency_breakdown: bool,
33}
34
35impl Default for VisualizationConfig {
36 fn default() -> Self {
37 Self {
38 title: "Async Task Performance Analysis".to_string(),
39 theme: Theme::Dark,
40 include_charts: true,
41 include_baselines: true,
42 include_rankings: true,
43 include_efficiency_breakdown: true,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub enum Theme {
51 Dark,
52 Light,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct PerformanceBaselines {
58 pub avg_cpu_percent: f64,
59 pub avg_memory_mb: f64,
60 pub avg_io_mbps: f64,
61 pub avg_network_mbps: f64,
62 pub avg_efficiency_score: f64,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct CategoryRanking {
68 pub rank: usize,
69 pub total_in_category: usize,
70 pub category_name: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct PerformanceComparison {
76 pub value: f64,
77 pub baseline: f64,
78 pub difference_percent: f64,
79 pub comparison_type: ComparisonType,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub enum ComparisonType {
84 AboveAverage,
85 BelowAverage,
86 NearAverage,
87}
88
89pub struct VisualizationGenerator {
91 config: VisualizationConfig,
92}
93
94impl VisualizationGenerator {
95 pub fn new() -> Self {
97 Self {
98 config: VisualizationConfig::default(),
99 }
100 }
101
102 pub fn with_config(config: VisualizationConfig) -> Self {
104 Self { config }
105 }
106
107 pub fn generate_html_report(
109 &self,
110 profiles: &HashMap<TaskId, TaskResourceProfile>,
111 ) -> Result<String, VisualizationError> {
112 match self.generate_templated_html_report(profiles) {
114 Ok(html) => Ok(html),
115 Err(_) => self.generate_hardcoded_html_report(profiles), }
117 }
118
119 fn generate_templated_html_report(
121 &self,
122 profiles: &HashMap<TaskId, TaskResourceProfile>,
123 ) -> Result<String, VisualizationError> {
124 let template_content =
126 std::fs::read_to_string("templates/async_template.html").map_err(|e| {
127 VisualizationError::TemplateError(format!(
128 "Failed to read enhanced template: {}",
129 e
130 ))
131 })?;
132
133 let mut handlebars = Handlebars::new();
135 handlebars
136 .register_template_string("async_dashboard", template_content)
137 .map_err(|e| {
138 VisualizationError::TemplateError(format!("Failed to register template: {}", e))
139 })?;
140
141 let template_data = self.build_template_data(profiles)?;
143
144 let rendered = handlebars
146 .render("async_dashboard", &template_data)
147 .map_err(|e| {
148 VisualizationError::TemplateError(format!("Failed to render template: {}", e))
149 })?;
150
151 Ok(rendered)
152 }
153
154 fn generate_hardcoded_html_report(
156 &self,
157 profiles: &HashMap<TaskId, TaskResourceProfile>,
158 ) -> Result<String, VisualizationError> {
159 let analytics = self.analyze_profiles(profiles)?;
160 self.build_html_report(&analytics, profiles)
161 }
162
163 pub fn analyze_profiles(
165 &self,
166 profiles: &HashMap<TaskId, TaskResourceProfile>,
167 ) -> Result<PerformanceAnalytics, VisualizationError> {
168 if profiles.is_empty() {
169 return Err(VisualizationError::NoDataAvailable);
170 }
171
172 let baselines = self.calculate_baselines(profiles);
173 let rankings = self.calculate_rankings(profiles);
174 let comparisons = self.calculate_comparisons(profiles, &baselines);
175
176 Ok(PerformanceAnalytics {
177 baselines,
178 rankings,
179 comparisons,
180 total_tasks: profiles.len(),
181 })
182 }
183
184 fn calculate_baselines(
186 &self,
187 profiles: &HashMap<TaskId, TaskResourceProfile>,
188 ) -> PerformanceBaselines {
189 let total = profiles.len() as f64;
190 let mut totals = (0.0, 0.0, 0.0, 0.0, 0.0);
191
192 for profile in profiles.values() {
193 totals.0 += profile.cpu_metrics.usage_percent;
194 totals.1 += profile.memory_metrics.current_bytes as f64 / 1_048_576.0;
195 totals.2 += profile.io_metrics.bandwidth_mbps;
196 totals.3 += profile.network_metrics.throughput_mbps;
197 totals.4 += profile.efficiency_score;
198 }
199
200 PerformanceBaselines {
201 avg_cpu_percent: totals.0 / total,
202 avg_memory_mb: totals.1 / total,
203 avg_io_mbps: totals.2 / total,
204 avg_network_mbps: totals.3 / total,
205 avg_efficiency_score: totals.4 / total,
206 }
207 }
208
209 fn calculate_rankings(
211 &self,
212 profiles: &HashMap<TaskId, TaskResourceProfile>,
213 ) -> HashMap<TaskId, CategoryRanking> {
214 let mut rankings = HashMap::new();
215 let mut category_groups: HashMap<String, Vec<(TaskId, &TaskResourceProfile)>> =
216 HashMap::new();
217
218 for (task_id, profile) in profiles {
220 let category = format!("{:?}", profile.task_type);
221 category_groups
222 .entry(category)
223 .or_default()
224 .push((*task_id, profile));
225 }
226
227 for (category_name, mut tasks) in category_groups {
229 tasks.sort_by(|a, b| {
230 b.1.efficiency_score
231 .partial_cmp(&a.1.efficiency_score)
232 .unwrap_or(std::cmp::Ordering::Equal)
233 });
234
235 let total_in_category = tasks.len();
236 for (rank, (task_id, _)) in tasks.iter().enumerate() {
237 rankings.insert(
238 *task_id,
239 CategoryRanking {
240 rank: rank + 1,
241 total_in_category,
242 category_name: category_name.clone(),
243 },
244 );
245 }
246 }
247
248 rankings
249 }
250
251 fn calculate_comparisons(
253 &self,
254 profiles: &HashMap<TaskId, TaskResourceProfile>,
255 baselines: &PerformanceBaselines,
256 ) -> HashMap<TaskId, TaskComparisons> {
257 let mut comparisons = HashMap::new();
258
259 for (task_id, profile) in profiles {
260 let cpu_comp = self
261 .compare_to_baseline(profile.cpu_metrics.usage_percent, baselines.avg_cpu_percent);
262 let memory_comp = self.compare_to_baseline(
263 profile.memory_metrics.current_bytes as f64 / 1_048_576.0,
264 baselines.avg_memory_mb,
265 );
266 let io_comp =
267 self.compare_to_baseline(profile.io_metrics.bandwidth_mbps, baselines.avg_io_mbps);
268 let network_comp = self.compare_to_baseline(
269 profile.network_metrics.throughput_mbps,
270 baselines.avg_network_mbps,
271 );
272
273 comparisons.insert(
274 *task_id,
275 TaskComparisons {
276 cpu: cpu_comp,
277 memory: memory_comp,
278 io: io_comp,
279 network: network_comp,
280 },
281 );
282 }
283
284 comparisons
285 }
286
287 fn compare_to_baseline(&self, value: f64, baseline: f64) -> PerformanceComparison {
289 let difference_percent = if baseline != 0.0 {
290 ((value - baseline) / baseline) * 100.0
291 } else {
292 0.0
293 };
294
295 let comparison_type = if difference_percent.abs() < 5.0 {
296 ComparisonType::NearAverage
297 } else if difference_percent > 0.0 {
298 ComparisonType::AboveAverage
299 } else {
300 ComparisonType::BelowAverage
301 };
302
303 PerformanceComparison {
304 value,
305 baseline,
306 difference_percent,
307 comparison_type,
308 }
309 }
310}
311
312#[derive(Debug, Clone)]
314pub struct PerformanceAnalytics {
315 pub baselines: PerformanceBaselines,
316 pub rankings: HashMap<TaskId, CategoryRanking>,
317 pub comparisons: HashMap<TaskId, TaskComparisons>,
318 pub total_tasks: usize,
319}
320
321#[derive(Debug, Clone)]
323pub struct TaskComparisons {
324 pub cpu: PerformanceComparison,
325 pub memory: PerformanceComparison,
326 pub io: PerformanceComparison,
327 pub network: PerformanceComparison,
328}
329
330#[derive(Debug, thiserror::Error)]
332pub enum VisualizationError {
333 #[error("No data available for visualization")]
334 NoDataAvailable,
335 #[error("Invalid configuration: {0}")]
336 InvalidConfiguration(String),
337 #[error("Template generation error: {0}")]
338 TemplateError(String),
339 #[error("IO error: {0}")]
340 IoError(#[from] std::io::Error),
341}
342
343impl Default for VisualizationGenerator {
344 fn default() -> Self {
345 Self::new()
346 }
347}
348
349impl VisualizationGenerator {
350 fn build_html_report(
352 &self,
353 analytics: &PerformanceAnalytics,
354 profiles: &HashMap<TaskId, TaskResourceProfile>,
355 ) -> Result<String, VisualizationError> {
356 let mut html = String::new();
357
358 html.push_str(&self.generate_html_header());
360
361 html.push_str(&self.generate_summary_section(analytics));
363
364 if self.config.include_charts {
366 html.push_str(&self.generate_charts_section(profiles)?);
367 }
368
369 html.push_str(&self.generate_tasks_section(analytics, profiles)?);
371
372 html.push_str(&self.generate_html_footer());
374
375 Ok(html)
376 }
377
378 fn generate_html_header(&self) -> String {
380 let theme_styles = match self.config.theme {
381 Theme::Dark => self.get_dark_theme_styles(),
382 Theme::Light => self.get_light_theme_styles(),
383 };
384
385 format!(
386 r#"<!DOCTYPE html>
387<html lang="en">
388<head>
389 <meta charset="UTF-8">
390 <meta name="viewport" content="width=device-width, initial-scale=1.0">
391 <title>{}</title>
392 <style>
393 {}
394 </style>
395</head>
396<body>
397 <div class="container">
398 <div class="header">
399 <h1>📊 {}</h1>
400 <p>Advanced performance analysis with baselines, rankings, and trends</p>
401 </div>
402"#,
403 self.config.title, theme_styles, self.config.title
404 )
405 }
406
407 fn generate_summary_section(&self, analytics: &PerformanceAnalytics) -> String {
409 format!(
410 r#"
411 <div class="summary">
412 <div class="summary-card">
413 <h3>Total Tasks</h3>
414 <div class="value">{}</div>
415 </div>
416 <div class="summary-card">
417 <h3>Avg CPU Usage</h3>
418 <div class="value">{:.1}%</div>
419 </div>
420 <div class="summary-card">
421 <h3>Avg Memory</h3>
422 <div class="value">{:.0}MB</div>
423 </div>
424 <div class="summary-card">
425 <h3>Avg Efficiency</h3>
426 <div class="value">{:.0}%</div>
427 </div>
428 </div>
429"#,
430 analytics.total_tasks,
431 analytics.baselines.avg_cpu_percent,
432 analytics.baselines.avg_memory_mb,
433 analytics.baselines.avg_efficiency_score * 100.0
434 )
435 }
436
437 fn generate_tasks_section(
439 &self,
440 analytics: &PerformanceAnalytics,
441 profiles: &HashMap<TaskId, TaskResourceProfile>,
442 ) -> Result<String, VisualizationError> {
443 let mut html = String::new();
444
445 html.push_str(
446 r#"
447 <div class="tasks-section">
448 <h2 class="section-title">Task Performance Details</h2>
449 <div class="tasks-grid">
450"#,
451 );
452
453 let mut sorted_profiles: Vec<_> = profiles.iter().collect();
455 sorted_profiles.sort_by(|a, b| {
456 b.1.efficiency_score
457 .partial_cmp(&a.1.efficiency_score)
458 .unwrap_or(std::cmp::Ordering::Equal)
459 });
460
461 for (task_id, profile) in sorted_profiles {
462 html.push_str(&self.generate_task_card(*task_id, profile, analytics)?);
463 }
464
465 html.push_str(
466 r#"
467 </div>
468 </div>
469"#,
470 );
471
472 Ok(html)
473 }
474
475 fn generate_task_card(
477 &self,
478 task_id: TaskId,
479 profile: &TaskResourceProfile,
480 analytics: &PerformanceAnalytics,
481 ) -> Result<String, VisualizationError> {
482 let ranking = analytics.rankings.get(&task_id);
483 let comparisons = analytics.comparisons.get(&task_id);
484
485 let task_type_class = format!("{:?}", profile.task_type).to_lowercase();
486
487 let rank_info = if let Some(ranking) = ranking {
488 let rank_class = match ranking.rank {
489 1 => "rank-1",
490 2 => "rank-2",
491 3 => "rank-3",
492 _ => "",
493 };
494 format!(
495 r#"<div class="ranking-badge {}">#{}/{}</div>"#,
496 rank_class, ranking.rank, ranking.total_in_category
497 )
498 } else {
499 String::new()
500 };
501
502 if let Some(comp) = comparisons {
503 self.generate_comparison_info(comp)
504 } else {
505 String::new()
506 };
507
508 let efficiency_tooltip = if self.config.include_efficiency_breakdown {
509 format!(
510 r#"
511 <div class="info-icon">?
512 <div class="tooltip">
513 <strong>Efficiency Breakdown:</strong><br>
514 CPU: {:.1}%<br>
515 Memory: {:.1}%<br>
516 IO: {:.1}%<br>
517 Network: {:.1}%<br>
518 Overall: {:.1}%
519 </div>
520 </div>
521"#,
522 profile
523 .efficiency_explanation
524 .component_scores
525 .cpu_efficiency
526 * 100.0,
527 profile
528 .efficiency_explanation
529 .component_scores
530 .memory_efficiency
531 * 100.0,
532 profile
533 .efficiency_explanation
534 .component_scores
535 .io_efficiency
536 * 100.0,
537 profile
538 .efficiency_explanation
539 .component_scores
540 .network_efficiency
541 * 100.0,
542 profile.efficiency_score * 100.0
543 )
544 } else {
545 String::new()
546 };
547
548 Ok(format!(
549 r#"
550 <div class="task-card">
551 {}
552 <div class="task-header {}">
553 <h3 class="task-name">{}</h3>
554 <span class="task-badge {}">{:?}</span>
555 </div>
556 <div class="task-content">
557 <div class="metrics-grid">
558 <div class="metric-item">
559 <div class="metric-label">CPU Usage</div>
560 <div class="metric-value">{:.1}%</div>
561 {}
562 </div>
563 <div class="metric-item">
564 <div class="metric-label">Memory</div>
565 <div class="metric-value">{:.0}MB</div>
566 {}
567 </div>
568 <div class="metric-item">
569 <div class="metric-label">IO Bandwidth</div>
570 <div class="metric-value">{:.1}MB/s</div>
571 {}
572 </div>
573 <div class="metric-item">
574 <div class="metric-label">Network</div>
575 <div class="metric-value">{:.1}Mbps</div>
576 {}
577 </div>
578 </div>
579
580 <div class="efficiency-section">
581 <div class="efficiency-title">
582 Efficiency Score
583 {}
584 </div>
585 <div class="efficiency-bar">
586 <div class="efficiency-fill" style="width: {:.1}%"></div>
587 </div>
588 <div class="efficiency-score">{:.1}%</div>
589 </div>
590
591 <div class="source-info">
592 <div class="source-title">Source Location</div>
593 <div class="source-detail">
594 <span class="source-label">File:</span>
595 <span class="source-value">{}</span>
596 </div>
597 <div class="source-detail">
598 <span class="source-label">Line:</span>
599 <span class="source-value">{}</span>
600 </div>
601 <div class="source-detail">
602 <span class="source-label">Function:</span>
603 <span class="source-value">{}</span>
604 </div>
605 </div>
606 </div>
607 </div>
608"#,
609 rank_info,
610 task_type_class,
611 profile.task_name,
612 task_type_class,
613 profile.task_type,
614 profile.cpu_metrics.usage_percent,
615 if let Some(comp) = comparisons {
616 format!(
617 "<div class=\"metric-comparison {}\">{}</div>",
618 self.get_comparison_class(&comp.cpu),
619 self.format_comparison(&comp.cpu)
620 )
621 } else {
622 String::new()
623 },
624 profile.memory_metrics.current_bytes as f64 / 1_048_576.0,
625 if let Some(comp) = comparisons {
626 format!(
627 "<div class=\"metric-comparison {}\">{}</div>",
628 self.get_comparison_class(&comp.memory),
629 self.format_comparison(&comp.memory)
630 )
631 } else {
632 String::new()
633 },
634 profile.io_metrics.bandwidth_mbps,
635 if let Some(comp) = comparisons {
636 format!(
637 "<div class=\"metric-comparison {}\">{}</div>",
638 self.get_comparison_class(&comp.io),
639 self.format_comparison(&comp.io)
640 )
641 } else {
642 String::new()
643 },
644 profile.network_metrics.throughput_mbps,
645 if let Some(comp) = comparisons {
646 format!(
647 "<div class=\"metric-comparison {}\">{}</div>",
648 self.get_comparison_class(&comp.network),
649 self.format_comparison(&comp.network)
650 )
651 } else {
652 String::new()
653 },
654 efficiency_tooltip,
655 profile.efficiency_score * 100.0,
656 profile.efficiency_score * 100.0,
657 profile.source_location.file_path,
658 profile.source_location.line_number,
659 profile.source_location.function_name
660 ))
661 }
662
663 fn generate_comparison_info(&self, _comparisons: &TaskComparisons) -> String {
665 String::new()
667 }
668
669 fn format_comparison(&self, comparison: &PerformanceComparison) -> String {
671 match comparison.comparison_type {
672 ComparisonType::NearAverage => "(≈ avg)".to_string(),
673 ComparisonType::AboveAverage => {
674 format!("(+{:.1}% vs avg)", comparison.difference_percent.abs())
675 }
676 ComparisonType::BelowAverage => {
677 format!("(-{:.1}% vs avg)", comparison.difference_percent.abs())
678 }
679 }
680 }
681
682 fn get_comparison_class(&self, comparison: &PerformanceComparison) -> &'static str {
684 match comparison.comparison_type {
685 ComparisonType::NearAverage => "comparison-average",
686 ComparisonType::AboveAverage => "comparison-above",
687 ComparisonType::BelowAverage => "comparison-below",
688 }
689 }
690
691 fn generate_charts_section(
693 &self,
694 profiles: &HashMap<TaskId, TaskResourceProfile>,
695 ) -> Result<String, VisualizationError> {
696 let mut html = String::new();
697
698 html.push_str(
699 r#"
700 <div class="charts-section">
701 <h2 class="section-title">📈 Performance Trends</h2>
702"#,
703 );
704
705 let chart_html = self.generate_chart_scripts(profiles)?;
707 html.push_str(&chart_html);
708
709 html.push_str(
710 r#"
711 </div>
712"#,
713 );
714
715 Ok(html)
716 }
717
718 fn build_template_data(
720 &self,
721 profiles: &HashMap<TaskId, TaskResourceProfile>,
722 ) -> Result<serde_json::Value, VisualizationError> {
723 if profiles.is_empty() {
724 return Err(VisualizationError::NoDataAvailable);
725 }
726
727 let total_tasks = profiles.len();
729 let cpu_usage_avg = profiles
730 .values()
731 .map(|p| p.cpu_metrics.usage_percent)
732 .sum::<f64>()
733 / total_tasks as f64;
734 let cpu_usage_peak = profiles
735 .values()
736 .map(|p| p.cpu_metrics.usage_percent)
737 .fold(0.0f64, |a, b| a.max(b));
738 let total_memory_mb = profiles
739 .values()
740 .map(|p| p.memory_metrics.allocated_bytes as f64 / 1024.0 / 1024.0)
741 .sum::<f64>();
742 let peak_memory_mb = profiles
743 .values()
744 .map(|p| p.memory_metrics.peak_bytes as f64 / 1024.0 / 1024.0)
745 .fold(0.0f64, |a, b| a.max(b));
746
747 let total_context_switches = profiles
749 .values()
750 .map(|p| p.cpu_metrics.context_switches)
751 .sum::<u64>();
752 let total_allocations = profiles
753 .values()
754 .map(|p| p.memory_metrics.allocation_count)
755 .sum::<u64>();
756 let avg_efficiency =
757 profiles.values().map(|p| p.efficiency_score).sum::<f64>() / total_tasks as f64;
758
759 let total_io_ops = profiles
761 .values()
762 .map(|p| p.io_metrics.read_operations + p.io_metrics.write_operations)
763 .sum::<u64>();
764 let total_read_mb = profiles
765 .values()
766 .map(|p| p.io_metrics.bytes_read as f64 / 1024.0 / 1024.0)
767 .sum::<f64>();
768 let total_write_mb = profiles
769 .values()
770 .map(|p| p.io_metrics.bytes_written as f64 / 1024.0 / 1024.0)
771 .sum::<f64>();
772 let io_throughput = if total_tasks > 0 {
773 (total_read_mb + total_write_mb) / total_tasks as f64
774 } else {
775 0.0
776 };
777
778 let total_sent_mb = profiles
779 .values()
780 .map(|p| p.network_metrics.bytes_sent as f64 / 1024.0 / 1024.0)
781 .sum::<f64>();
782 let total_received_mb = profiles
783 .values()
784 .map(|p| p.network_metrics.bytes_received as f64 / 1024.0 / 1024.0)
785 .sum::<f64>();
786 let network_throughput = if total_tasks > 0 {
787 profiles
788 .values()
789 .map(|p| p.network_metrics.throughput_mbps)
790 .sum::<f64>()
791 / total_tasks as f64
792 } else {
793 0.0
794 };
795 let avg_latency = if total_tasks > 0 {
796 profiles
797 .values()
798 .map(|p| p.network_metrics.latency_avg_ms)
799 .sum::<f64>()
800 / total_tasks as f64
801 } else {
802 0.0
803 };
804
805 let task_type_counts = self.calculate_task_type_metrics(profiles);
807
808 let cpu_intensive_tasks =
810 self.build_task_category_data(profiles, &crate::async_memory::TaskType::CpuIntensive);
811 let memory_intensive_tasks = self
812 .build_task_category_data(profiles, &crate::async_memory::TaskType::MemoryIntensive);
813 let io_intensive_tasks =
814 self.build_task_category_data(profiles, &crate::async_memory::TaskType::IoIntensive);
815 let network_intensive_tasks = self
816 .build_task_category_data(profiles, &crate::async_memory::TaskType::NetworkIntensive);
817
818 let futures_count = total_tasks; let total_polls = total_context_switches; let avg_poll_time = if total_polls > 0 {
822 cpu_usage_avg * 10.0
823 } else {
824 0.0
825 }; let ready_rate = if total_tasks > 0 {
827 avg_efficiency * 100.0
828 } else {
829 0.0
830 };
831
832 let mut template_data = serde_json::Map::new();
834
835 template_data.insert(
837 "title".to_string(),
838 serde_json::Value::String("Rust Async Performance Analysis".to_string()),
839 );
840 template_data.insert("subtitle".to_string(), serde_json::Value::String(format!("Advanced analysis of {} Rust async tasks with detailed performance metrics and Future polling insights", total_tasks)));
841
842 template_data.insert(
844 "total_tasks".to_string(),
845 serde_json::Value::Number(serde_json::Number::from(total_tasks)),
846 );
847 template_data.insert(
848 "active_tasks".to_string(),
849 serde_json::Value::Number(serde_json::Number::from(0)),
850 );
851 template_data.insert(
852 "completed_tasks".to_string(),
853 serde_json::Value::Number(serde_json::Number::from(total_tasks)),
854 );
855 template_data.insert(
856 "failed_tasks".to_string(),
857 serde_json::Value::Number(serde_json::Number::from(0)),
858 );
859
860 template_data.insert(
862 "cpu_usage_avg".to_string(),
863 serde_json::Value::String(format!("{:.1}", cpu_usage_avg)),
864 );
865 template_data.insert(
866 "cpu_usage_peak".to_string(),
867 serde_json::Value::String(format!("{:.1}", cpu_usage_peak)),
868 );
869 template_data.insert(
870 "cpu_cores".to_string(),
871 serde_json::Value::Number(serde_json::Number::from(8)),
872 );
873 template_data.insert(
874 "context_switches".to_string(),
875 serde_json::Value::Number(serde_json::Number::from(total_context_switches)),
876 );
877
878 template_data.insert(
880 "total_memory_mb".to_string(),
881 serde_json::Value::String(format!("{:.1}", total_memory_mb)),
882 );
883 template_data.insert(
884 "peak_memory_mb".to_string(),
885 serde_json::Value::String(format!("{:.1}", peak_memory_mb)),
886 );
887 template_data.insert(
888 "total_allocations".to_string(),
889 serde_json::Value::Number(serde_json::Number::from(total_allocations)),
890 );
891 template_data.insert(
892 "memory_efficiency".to_string(),
893 serde_json::Value::String(format!("{:.1}", avg_efficiency * 100.0)),
894 );
895
896 template_data.insert(
898 "io_throughput".to_string(),
899 serde_json::Value::String(format!("{:.1}", io_throughput)),
900 );
901 template_data.insert(
902 "total_read_mb".to_string(),
903 serde_json::Value::String(format!("{:.1}", total_read_mb)),
904 );
905 template_data.insert(
906 "total_write_mb".to_string(),
907 serde_json::Value::String(format!("{:.1}", total_write_mb)),
908 );
909 template_data.insert(
910 "total_io_ops".to_string(),
911 serde_json::Value::Number(serde_json::Number::from(total_io_ops)),
912 );
913
914 template_data.insert(
916 "network_throughput".to_string(),
917 serde_json::Value::String(format!("{:.1}", network_throughput)),
918 );
919 template_data.insert(
920 "total_sent_mb".to_string(),
921 serde_json::Value::String(format!("{:.1}", total_sent_mb)),
922 );
923 template_data.insert(
924 "total_received_mb".to_string(),
925 serde_json::Value::String(format!("{:.1}", total_received_mb)),
926 );
927 template_data.insert(
928 "avg_latency".to_string(),
929 serde_json::Value::String(format!("{:.1}", avg_latency)),
930 );
931
932 let resource_balance =
934 profiles.values().map(|p| p.resource_balance).sum::<f64>() / total_tasks as f64 * 100.0;
935 let bottleneck_count = profiles
936 .values()
937 .filter(|p| {
938 !matches!(
939 p.bottleneck_type,
940 crate::async_memory::BottleneckType::Balanced
941 )
942 })
943 .count();
944
945 template_data.insert(
946 "efficiency_score".to_string(),
947 serde_json::Value::String(format!("{:.1}", avg_efficiency * 100.0)),
948 );
949 template_data.insert(
950 "resource_balance".to_string(),
951 serde_json::Value::String(format!("{:.1}", resource_balance)),
952 );
953 template_data.insert(
954 "bottleneck_count".to_string(),
955 serde_json::Value::Number(serde_json::Number::from(bottleneck_count)),
956 );
957 template_data.insert(
958 "optimization_potential".to_string(),
959 serde_json::Value::String(format!("{:.1}", (1.0 - avg_efficiency) * 100.0)),
960 );
961
962 template_data.insert(
964 "futures_count".to_string(),
965 serde_json::Value::Number(serde_json::Number::from(futures_count)),
966 );
967 template_data.insert(
968 "total_polls".to_string(),
969 serde_json::Value::Number(serde_json::Number::from(total_polls)),
970 );
971 template_data.insert(
972 "avg_poll_time".to_string(),
973 serde_json::Value::String(format!("{:.1}", avg_poll_time)),
974 );
975 template_data.insert(
976 "ready_rate".to_string(),
977 serde_json::Value::String(format!("{:.1}", ready_rate)),
978 );
979
980 template_data.insert(
982 "cpu_intensive_count".to_string(),
983 serde_json::Value::Number(serde_json::Number::from(task_type_counts.cpu_count)),
984 );
985 template_data.insert(
986 "cpu_avg_efficiency".to_string(),
987 serde_json::Value::String(format!("{:.1}", task_type_counts.cpu_efficiency)),
988 );
989 template_data.insert(
990 "memory_intensive_count".to_string(),
991 serde_json::Value::Number(serde_json::Number::from(task_type_counts.memory_count)),
992 );
993 template_data.insert(
994 "memory_avg_efficiency".to_string(),
995 serde_json::Value::String(format!("{:.1}", task_type_counts.memory_efficiency)),
996 );
997 template_data.insert(
998 "io_intensive_count".to_string(),
999 serde_json::Value::Number(serde_json::Number::from(task_type_counts.io_count)),
1000 );
1001 template_data.insert(
1002 "io_avg_efficiency".to_string(),
1003 serde_json::Value::String(format!("{:.1}", task_type_counts.io_efficiency)),
1004 );
1005 template_data.insert(
1006 "network_intensive_count".to_string(),
1007 serde_json::Value::Number(serde_json::Number::from(task_type_counts.network_count)),
1008 );
1009 template_data.insert(
1010 "network_avg_efficiency".to_string(),
1011 serde_json::Value::String(format!("{:.1}", task_type_counts.network_efficiency)),
1012 );
1013
1014 template_data.insert(
1016 "cpu_intensive_tasks".to_string(),
1017 serde_json::Value::Array(cpu_intensive_tasks),
1018 );
1019 template_data.insert(
1020 "memory_intensive_tasks".to_string(),
1021 serde_json::Value::Array(memory_intensive_tasks),
1022 );
1023 template_data.insert(
1024 "io_intensive_tasks".to_string(),
1025 serde_json::Value::Array(io_intensive_tasks),
1026 );
1027 template_data.insert(
1028 "network_intensive_tasks".to_string(),
1029 serde_json::Value::Array(network_intensive_tasks),
1030 );
1031
1032 let avg_fragmentation = profiles
1034 .values()
1035 .map(|p| p.memory_metrics.heap_fragmentation)
1036 .sum::<f64>()
1037 / total_tasks as f64
1038 * 100.0;
1039 let blocking_tasks_count = profiles
1040 .values()
1041 .filter(|p| p.cpu_metrics.usage_percent > 80.0)
1042 .count();
1043
1044 template_data.insert(
1045 "avg_poll_duration".to_string(),
1046 serde_json::Value::String(format!("{:.1}", avg_poll_time)),
1047 );
1048 template_data.insert(
1049 "immediate_ready_percent".to_string(),
1050 serde_json::Value::String(format!("{:.1}", ready_rate * 0.8)),
1051 );
1052 template_data.insert(
1053 "waker_efficiency".to_string(),
1054 serde_json::Value::String(format!("{:.1}", avg_efficiency * 95.0)),
1055 );
1056 template_data.insert(
1057 "peak_alloc_rate".to_string(),
1058 serde_json::Value::String(format!("{}", total_allocations / total_tasks as u64)),
1059 );
1060 template_data.insert(
1061 "avg_fragmentation".to_string(),
1062 serde_json::Value::String(format!("{:.1}", avg_fragmentation)),
1063 );
1064 template_data.insert(
1065 "gc_pressure".to_string(),
1066 serde_json::Value::String(format!("{:.1}", (1.0 - avg_efficiency) * 50.0)),
1067 );
1068 template_data.insert(
1069 "executor_utilization".to_string(),
1070 serde_json::Value::String(format!("{:.1}", cpu_usage_avg * 0.8)),
1071 );
1072 template_data.insert(
1073 "avg_queue_length".to_string(),
1074 serde_json::Value::String(format!("{:.1}", total_tasks as f64 * 0.1)),
1075 );
1076 template_data.insert(
1077 "blocking_tasks_count".to_string(),
1078 serde_json::Value::Number(serde_json::Number::from(blocking_tasks_count)),
1079 );
1080 template_data.insert(
1081 "deadlock_risk".to_string(),
1082 serde_json::Value::String(format!(
1083 "{:.1}",
1084 if total_tasks > 10 {
1085 total_tasks as f64 * 0.05
1086 } else {
1087 0.0
1088 }
1089 )),
1090 );
1091
1092 let template_data = serde_json::Value::Object(template_data);
1093
1094 Ok(template_data)
1095 }
1096
1097 fn calculate_task_type_metrics(
1099 &self,
1100 profiles: &HashMap<TaskId, TaskResourceProfile>,
1101 ) -> TaskTypeMetrics {
1102 let mut cpu_count = 0;
1103 let mut cpu_efficiency_sum = 0.0;
1104 let mut memory_count = 0;
1105 let mut memory_efficiency_sum = 0.0;
1106 let mut io_count = 0;
1107 let mut io_efficiency_sum = 0.0;
1108 let mut network_count = 0;
1109 let mut network_efficiency_sum = 0.0;
1110
1111 for profile in profiles.values() {
1112 match profile.task_type {
1113 crate::async_memory::TaskType::CpuIntensive => {
1114 cpu_count += 1;
1115 cpu_efficiency_sum += profile.efficiency_score;
1116 }
1117 crate::async_memory::TaskType::MemoryIntensive => {
1118 memory_count += 1;
1119 memory_efficiency_sum += profile.efficiency_score;
1120 }
1121 crate::async_memory::TaskType::IoIntensive => {
1122 io_count += 1;
1123 io_efficiency_sum += profile.efficiency_score;
1124 }
1125 crate::async_memory::TaskType::NetworkIntensive => {
1126 network_count += 1;
1127 network_efficiency_sum += profile.efficiency_score;
1128 }
1129 _ => {} }
1131 }
1132
1133 TaskTypeMetrics {
1134 cpu_count,
1135 cpu_efficiency: if cpu_count > 0 {
1136 cpu_efficiency_sum / cpu_count as f64 * 100.0
1137 } else {
1138 0.0
1139 },
1140 memory_count,
1141 memory_efficiency: if memory_count > 0 {
1142 memory_efficiency_sum / memory_count as f64 * 100.0
1143 } else {
1144 0.0
1145 },
1146 io_count,
1147 io_efficiency: if io_count > 0 {
1148 io_efficiency_sum / io_count as f64 * 100.0
1149 } else {
1150 0.0
1151 },
1152 network_count,
1153 network_efficiency: if network_count > 0 {
1154 network_efficiency_sum / network_count as f64 * 100.0
1155 } else {
1156 0.0
1157 },
1158 }
1159 }
1160
1161 fn build_task_category_data(
1163 &self,
1164 profiles: &HashMap<TaskId, TaskResourceProfile>,
1165 task_type: &crate::async_memory::TaskType,
1166 ) -> Vec<serde_json::Value> {
1167 profiles
1168 .iter()
1169 .filter(|(_, profile)| {
1170 std::mem::discriminant(&profile.task_type) == std::mem::discriminant(task_type)
1171 })
1172 .map(|(task_id, profile)| {
1173 let mut task_data = serde_json::Map::new();
1174
1175 task_data.insert(
1177 "task_id".to_string(),
1178 serde_json::Value::String(task_id.to_string()),
1179 );
1180 task_data.insert(
1181 "task_name".to_string(),
1182 serde_json::Value::String(profile.task_name.clone()),
1183 );
1184 task_data.insert(
1185 "source_file".to_string(),
1186 serde_json::Value::String(
1187 profile
1188 .source_location
1189 .file_path
1190 .split('/')
1191 .next_back()
1192 .unwrap_or("unknown.rs")
1193 .to_string(),
1194 ),
1195 );
1196 task_data.insert(
1197 "source_line".to_string(),
1198 serde_json::Value::Number(serde_json::Number::from(
1199 profile.source_location.line_number,
1200 )),
1201 );
1202 task_data.insert(
1203 "status".to_string(),
1204 serde_json::Value::String("completed".to_string()),
1205 );
1206 task_data.insert(
1207 "status_class".to_string(),
1208 serde_json::Value::String("completed".to_string()),
1209 );
1210 task_data.insert(
1211 "duration_ms".to_string(),
1212 serde_json::Value::Number(
1213 serde_json::Number::from_f64(profile.duration_ms.unwrap_or(0.0))
1214 .unwrap_or(serde_json::Number::from(0)),
1215 ),
1216 );
1217
1218 task_data.insert(
1220 "cpu_usage".to_string(),
1221 serde_json::Value::String(format!("{:.1}", profile.cpu_metrics.usage_percent)),
1222 );
1223 task_data.insert(
1224 "cpu_cycles".to_string(),
1225 serde_json::Value::String(format!(
1226 "{:.1}",
1227 profile.cpu_metrics.cpu_cycles as f64 / 1_000_000.0
1228 )),
1229 );
1230 task_data.insert(
1231 "instructions".to_string(),
1232 serde_json::Value::String(format!(
1233 "{:.1}",
1234 profile.cpu_metrics.instructions as f64 / 1_000_000.0
1235 )),
1236 );
1237 task_data.insert(
1238 "cache_misses".to_string(),
1239 serde_json::Value::String(format!(
1240 "{:.1}",
1241 profile.cpu_metrics.cache_misses as f64 / 1_000.0
1242 )),
1243 );
1244
1245 task_data.insert(
1247 "allocated_mb".to_string(),
1248 serde_json::Value::String(format!(
1249 "{:.1}",
1250 profile.memory_metrics.allocated_bytes as f64 / 1024.0 / 1024.0
1251 )),
1252 );
1253 task_data.insert(
1254 "peak_memory_mb".to_string(),
1255 serde_json::Value::String(format!(
1256 "{:.1}",
1257 profile.memory_metrics.peak_bytes as f64 / 1024.0 / 1024.0
1258 )),
1259 );
1260 task_data.insert(
1261 "allocation_count".to_string(),
1262 serde_json::Value::Number(serde_json::Number::from(
1263 profile.memory_metrics.allocation_count,
1264 )),
1265 );
1266 task_data.insert(
1267 "heap_fragmentation".to_string(),
1268 serde_json::Value::String(format!(
1269 "{:.1}",
1270 profile.memory_metrics.heap_fragmentation * 100.0
1271 )),
1272 );
1273 task_data.insert(
1274 "memory_usage_percent".to_string(),
1275 serde_json::Value::String(format!(
1276 "{:.1}",
1277 (profile.memory_metrics.current_bytes as f64
1278 / profile.memory_metrics.allocated_bytes.max(1) as f64)
1279 * 100.0
1280 )),
1281 );
1282
1283 task_data.insert(
1285 "bytes_read_mb".to_string(),
1286 serde_json::Value::String(format!(
1287 "{:.1}",
1288 profile.io_metrics.bytes_read as f64 / 1024.0 / 1024.0
1289 )),
1290 );
1291 task_data.insert(
1292 "bytes_written_mb".to_string(),
1293 serde_json::Value::String(format!(
1294 "{:.1}",
1295 profile.io_metrics.bytes_written as f64 / 1024.0 / 1024.0
1296 )),
1297 );
1298 task_data.insert(
1299 "avg_latency_us".to_string(),
1300 serde_json::Value::String(format!("{:.1}", profile.io_metrics.avg_latency_us)),
1301 );
1302 task_data.insert(
1303 "queue_depth".to_string(),
1304 serde_json::Value::Number(serde_json::Number::from(
1305 profile.io_metrics.queue_depth,
1306 )),
1307 );
1308 task_data.insert(
1309 "io_usage_percent".to_string(),
1310 serde_json::Value::String(format!("{:.1}", profile.io_metrics.io_wait_percent)),
1311 );
1312
1313 task_data.insert(
1315 "bytes_sent_mb".to_string(),
1316 serde_json::Value::String(format!(
1317 "{:.1}",
1318 profile.network_metrics.bytes_sent as f64 / 1024.0 / 1024.0
1319 )),
1320 );
1321 task_data.insert(
1322 "bytes_received_mb".to_string(),
1323 serde_json::Value::String(format!(
1324 "{:.1}",
1325 profile.network_metrics.bytes_received as f64 / 1024.0 / 1024.0
1326 )),
1327 );
1328 task_data.insert(
1329 "active_connections".to_string(),
1330 serde_json::Value::Number(serde_json::Number::from(
1331 profile.network_metrics.connections_active,
1332 )),
1333 );
1334 task_data.insert(
1335 "avg_latency_ms".to_string(),
1336 serde_json::Value::String(format!(
1337 "{:.1}",
1338 profile.network_metrics.latency_avg_ms
1339 )),
1340 );
1341 task_data.insert(
1342 "network_usage_percent".to_string(),
1343 serde_json::Value::String(format!(
1344 "{:.1}",
1345 (profile.network_metrics.throughput_mbps / 100.0).min(100.0)
1346 )),
1347 );
1348
1349 serde_json::Value::Object(task_data)
1350 })
1351 .collect()
1352 }
1353
1354 fn generate_chart_scripts(
1356 &self,
1357 profiles: &HashMap<TaskId, TaskResourceProfile>,
1358 ) -> Result<String, VisualizationError> {
1359 let mut cpu_bars = String::new();
1360 let mut memory_bars = String::new();
1361
1362 let max_cpu = profiles
1364 .values()
1365 .map(|p| p.cpu_metrics.usage_percent)
1366 .fold(0.0, f64::max)
1367 .max(100.0);
1368 let max_memory = profiles
1369 .values()
1370 .map(|p| p.memory_metrics.current_bytes as f64 / 1_048_576.0)
1371 .fold(0.0, f64::max);
1372
1373 for profile in profiles.values() {
1374 let cpu_percent = profile.cpu_metrics.usage_percent;
1375 let memory_mb = profile.memory_metrics.current_bytes as f64 / 1_048_576.0;
1376
1377 let cpu_width = (cpu_percent / max_cpu * 100.0).min(100.0);
1378 let memory_width = if max_memory > 0.0 {
1379 (memory_mb / max_memory * 100.0).min(100.0)
1380 } else {
1381 0.0
1382 };
1383
1384 cpu_bars.push_str(&format!(
1385 r#"
1386 <div class="chart-bar">
1387 <div class="bar-label">{}</div>
1388 <div class="bar-container">
1389 <div class="bar-fill cpu-bar" style="width: {:.1}%"></div>
1390 <div class="bar-value">{:.1}%</div>
1391 </div>
1392 </div>
1393"#,
1394 profile.task_name, cpu_width, cpu_percent
1395 ));
1396
1397 memory_bars.push_str(&format!(
1398 r#"
1399 <div class="chart-bar">
1400 <div class="bar-label">{}</div>
1401 <div class="bar-container">
1402 <div class="bar-fill memory-bar" style="width: {:.1}%"></div>
1403 <div class="bar-value">{:.1}MB</div>
1404 </div>
1405 </div>
1406"#,
1407 profile.task_name, memory_width, memory_mb
1408 ));
1409 }
1410
1411 let mut network_bars = String::new();
1413 let max_network = profiles
1414 .values()
1415 .map(|p| p.network_metrics.throughput_mbps)
1416 .fold(0.0, f64::max);
1417
1418 for profile in profiles.values() {
1419 let network_mbps = profile.network_metrics.throughput_mbps;
1420 let network_width = if max_network > 0.0 {
1421 (network_mbps / max_network * 100.0).min(100.0)
1422 } else {
1423 0.0
1424 };
1425
1426 network_bars.push_str(&format!(
1427 r#"
1428 <div class="chart-bar">
1429 <div class="bar-label">{}</div>
1430 <div class="bar-container">
1431 <div class="bar-fill network-bar" style="width: {:.1}%"></div>
1432 <div class="bar-value">{:.1}Mbps</div>
1433 </div>
1434 </div>
1435"#,
1436 profile.task_name, network_width, network_mbps
1437 ));
1438 }
1439
1440 Ok(format!(
1441 r#"
1442 <div class="simple-charts">
1443 <div class="simple-chart">
1444 <h4>CPU Usage Distribution</h4>
1445 <div class="chart-bars">
1446 {}
1447 </div>
1448 </div>
1449 <div class="simple-chart">
1450 <h4>Memory Usage Distribution</h4>
1451 <div class="chart-bars">
1452 {}
1453 </div>
1454 </div>
1455 <div class="simple-chart">
1456 <h4>Network Throughput Distribution</h4>
1457 <div class="chart-bars">
1458 {}
1459 </div>
1460 </div>
1461 </div>
1462"#,
1463 cpu_bars, memory_bars, network_bars
1464 ))
1465 }
1466
1467 fn generate_html_footer(&self) -> String {
1469 r#"
1470 </div>
1471</body>
1472</html>
1473"#
1474 .to_string()
1475 }
1476
1477 fn get_dark_theme_styles(&self) -> &'static str {
1479 r#"
1480 body {
1481 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
1482 margin: 0;
1483 padding: 20px;
1484 background: #0d1117;
1485 color: #f0f6fc;
1486 line-height: 1.6;
1487 }
1488
1489 .container {
1490 max-width: 1400px;
1491 margin: 0 auto;
1492 background: #161b22;
1493 border: 1px solid #30363d;
1494 border-radius: 12px;
1495 overflow: hidden;
1496 }
1497
1498 .header {
1499 background: linear-gradient(135deg, #58a6ff 0%, #a5a5ff 100%);
1500 padding: 2rem;
1501 text-align: center;
1502 color: white;
1503 }
1504
1505 .header h1 {
1506 margin: 0;
1507 font-size: 2.5rem;
1508 font-weight: 700;
1509 }
1510
1511 .summary {
1512 display: grid;
1513 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1514 gap: 1.5rem;
1515 padding: 2rem;
1516 background: #21262d;
1517 }
1518
1519 .summary-card {
1520 background: #161b22;
1521 border: 1px solid #30363d;
1522 padding: 1.5rem;
1523 border-radius: 8px;
1524 text-align: center;
1525 }
1526
1527 .summary-card h3 {
1528 margin: 0 0 0.5rem 0;
1529 color: #8b949e;
1530 font-size: 0.9rem;
1531 text-transform: uppercase;
1532 }
1533
1534 .summary-card .value {
1535 font-size: 2rem;
1536 font-weight: 700;
1537 color: #58a6ff;
1538 }
1539
1540 .tasks-section {
1541 padding: 2rem;
1542 }
1543
1544 .section-title {
1545 margin: 0 0 2rem 0;
1546 font-size: 1.5rem;
1547 font-weight: 600;
1548 color: #f0f6fc;
1549 text-align: center;
1550 }
1551
1552 .tasks-grid {
1553 display: grid;
1554 grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
1555 gap: 1.5rem;
1556 }
1557
1558 .task-card {
1559 background: #21262d;
1560 border: 1px solid #30363d;
1561 border-radius: 10px;
1562 overflow: hidden;
1563 transition: transform 0.2s ease;
1564 position: relative;
1565 }
1566
1567 .task-card:hover {
1568 transform: translateY(-2px);
1569 box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
1570 }
1571
1572 .ranking-badge {
1573 position: absolute;
1574 top: 10px;
1575 right: 10px;
1576 background: linear-gradient(135deg, #f9c513, #ffd700);
1577 color: #000;
1578 padding: 0.25rem 0.5rem;
1579 border-radius: 12px;
1580 font-size: 0.7rem;
1581 font-weight: 700;
1582 z-index: 10;
1583 }
1584
1585 .ranking-badge.rank-1 { background: linear-gradient(135deg, #ffd700, #ffed4e); }
1586 .ranking-badge.rank-2 { background: linear-gradient(135deg, #c0c0c0, #e8e8e8); }
1587 .ranking-badge.rank-3 { background: linear-gradient(135deg, #cd7f32, #daa520); }
1588
1589 .task-header {
1590 padding: 1.5rem;
1591 background: #161b22;
1592 border-bottom: 1px solid #30363d;
1593 position: relative;
1594 }
1595
1596 .task-header::before {
1597 content: '';
1598 position: absolute;
1599 top: 0;
1600 left: 0;
1601 right: 0;
1602 height: 3px;
1603 }
1604
1605 .task-header.cpuintensive::before { background: #f85149; }
1606 .task-header.iointensive::before { background: #58a6ff; }
1607 .task-header.networkintensive::before { background: #3fb950; }
1608 .task-header.memoryintensive::before { background: #a5a5ff; }
1609 .task-header.mixed::before { background: #f9c513; }
1610
1611 .task-name {
1612 margin: 0 0 0.5rem 0;
1613 font-size: 1.125rem;
1614 font-weight: 600;
1615 color: #f0f6fc;
1616 }
1617
1618 .task-badge {
1619 display: inline-block;
1620 padding: 0.25rem 0.75rem;
1621 border-radius: 12px;
1622 font-size: 0.75rem;
1623 font-weight: 600;
1624 text-transform: uppercase;
1625 }
1626
1627 .task-badge.cpuintensive { background: #f85149; color: white; }
1628 .task-badge.iointensive { background: #58a6ff; color: white; }
1629 .task-badge.networkintensive { background: #3fb950; color: white; }
1630 .task-badge.memoryintensive { background: #a5a5ff; color: white; }
1631 .task-badge.mixed { background: #f9c513; color: black; }
1632
1633 .task-content {
1634 padding: 1.5rem;
1635 }
1636
1637 .metrics-grid {
1638 display: grid;
1639 grid-template-columns: 1fr 1fr;
1640 gap: 1rem;
1641 margin-bottom: 1rem;
1642 }
1643
1644 .metric-item {
1645 background: #161b22;
1646 border: 1px solid #30363d;
1647 border-radius: 6px;
1648 padding: 1rem;
1649 text-align: center;
1650 }
1651
1652 .metric-label {
1653 font-size: 0.75rem;
1654 color: #8b949e;
1655 margin-bottom: 0.25rem;
1656 text-transform: uppercase;
1657 }
1658
1659 .metric-value {
1660 font-size: 1.25rem;
1661 font-weight: 700;
1662 color: #f0f6fc;
1663 }
1664
1665 .metric-comparison {
1666 font-size: 0.7rem;
1667 margin-top: 0.25rem;
1668 }
1669
1670 .comparison-above { color: #f85149; }
1671 .comparison-below { color: #3fb950; }
1672 .comparison-average { color: #f9c513; }
1673
1674 .efficiency-section {
1675 background: #161b22;
1676 border: 1px solid #30363d;
1677 border-radius: 6px;
1678 padding: 1rem;
1679 margin-top: 1rem;
1680 }
1681
1682 .efficiency-title {
1683 font-size: 0.875rem;
1684 color: #8b949e;
1685 margin-bottom: 0.5rem;
1686 text-transform: uppercase;
1687 display: flex;
1688 align-items: center;
1689 gap: 0.5rem;
1690 }
1691
1692 .info-icon {
1693 cursor: help;
1694 background: #30363d;
1695 color: #8b949e;
1696 border-radius: 50%;
1697 width: 16px;
1698 height: 16px;
1699 display: flex;
1700 align-items: center;
1701 justify-content: center;
1702 font-size: 0.7rem;
1703 font-weight: bold;
1704 position: relative;
1705 }
1706
1707 .info-icon:hover {
1708 background: #58a6ff;
1709 color: white;
1710 }
1711
1712 .tooltip {
1713 position: absolute;
1714 bottom: 100%;
1715 right: 0;
1716 background: #161b22;
1717 border: 1px solid #30363d;
1718 border-radius: 6px;
1719 padding: 0.75rem;
1720 min-width: 200px;
1721 font-size: 0.8rem;
1722 color: #f0f6fc;
1723 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
1724 z-index: 1000;
1725 opacity: 0;
1726 visibility: hidden;
1727 transition: opacity 0.3s ease;
1728 }
1729
1730 .info-icon:hover .tooltip {
1731 opacity: 1;
1732 visibility: visible;
1733 }
1734
1735 .efficiency-bar {
1736 width: 100%;
1737 height: 8px;
1738 background: #30363d;
1739 border-radius: 4px;
1740 overflow: hidden;
1741 margin-bottom: 0.5rem;
1742 }
1743
1744 .efficiency-fill {
1745 height: 100%;
1746 background: linear-gradient(90deg, #3fb950, #f9c513, #f85149);
1747 border-radius: 4px;
1748 }
1749
1750 .efficiency-score {
1751 font-size: 1rem;
1752 font-weight: 700;
1753 color: #58a6ff;
1754 text-align: right;
1755 }
1756
1757 .source-info {
1758 background: #161b22;
1759 border: 1px solid #30363d;
1760 border-radius: 6px;
1761 padding: 1rem;
1762 margin-top: 1rem;
1763 }
1764
1765 .source-title {
1766 font-size: 0.875rem;
1767 color: #8b949e;
1768 margin-bottom: 0.5rem;
1769 text-transform: uppercase;
1770 }
1771
1772 .source-detail {
1773 display: flex;
1774 justify-content: space-between;
1775 margin-bottom: 0.25rem;
1776 font-size: 0.8rem;
1777 }
1778
1779 .source-label {
1780 color: #8b949e;
1781 }
1782
1783 .source-value {
1784 color: #f0f6fc;
1785 font-family: 'Courier New', monospace;
1786 }
1787
1788 .charts-section {
1789 padding: 2rem;
1790 background: #161b22;
1791 border-top: 1px solid #30363d;
1792 }
1793
1794 .charts-grid {
1795 display: grid;
1796 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1797 gap: 2rem;
1798 margin-top: 1.5rem;
1799 }
1800
1801 .chart-container {
1802 background: #21262d;
1803 border: 1px solid #30363d;
1804 border-radius: 8px;
1805 padding: 1.5rem;
1806 }
1807
1808 .chart-title {
1809 margin: 0 0 1rem 0;
1810 font-size: 1rem;
1811 font-weight: 600;
1812 color: #f0f6fc;
1813 text-align: center;
1814 }
1815
1816 /* Simple CSS Charts */
1817 .simple-charts {
1818 display: grid;
1819 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1820 gap: 2rem;
1821 padding: 2rem;
1822 }
1823
1824 .simple-chart {
1825 background: #21262d;
1826 border: 1px solid #30363d;
1827 border-radius: 8px;
1828 padding: 1.5rem;
1829 }
1830
1831 .simple-chart h4 {
1832 margin: 0 0 1rem 0;
1833 font-size: 1rem;
1834 font-weight: 600;
1835 color: #f0f6fc;
1836 text-align: center;
1837 }
1838
1839 .chart-bars {
1840 display: flex;
1841 flex-direction: column;
1842 gap: 0.75rem;
1843 }
1844
1845 .chart-bar {
1846 display: flex;
1847 align-items: center;
1848 gap: 1rem;
1849 }
1850
1851 .bar-label {
1852 min-width: 120px;
1853 font-size: 0.8rem;
1854 color: #8b949e;
1855 text-align: right;
1856 }
1857
1858 .bar-container {
1859 flex: 1;
1860 display: flex;
1861 align-items: center;
1862 gap: 0.5rem;
1863 position: relative;
1864 }
1865
1866 .bar-fill {
1867 height: 20px;
1868 border-radius: 4px;
1869 transition: width 0.6s ease;
1870 position: relative;
1871 }
1872
1873 .cpu-bar {
1874 background: linear-gradient(90deg, #f85149, #ff6b6b);
1875 }
1876
1877 .memory-bar {
1878 background: linear-gradient(90deg, #a5a5ff, #b39ddb);
1879 }
1880
1881 .network-bar {
1882 background: linear-gradient(90deg, #3fb950, #a5d6a7);
1883 }
1884
1885 .bar-value {
1886 font-size: 0.8rem;
1887 color: #f0f6fc;
1888 font-weight: 600;
1889 min-width: 50px;
1890 }
1891
1892 @media (max-width: 1024px) {
1893 .simple-charts {
1894 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
1895 }
1896
1897 .bar-label {
1898 min-width: 100px;
1899 font-size: 0.75rem;
1900 }
1901 }
1902
1903 @media (max-width: 768px) {
1904 .tasks-grid {
1905 grid-template-columns: 1fr;
1906 }
1907 .metrics-grid {
1908 grid-template-columns: 1fr;
1909 }
1910 }
1911 "#
1912 }
1913
1914 fn get_light_theme_styles(&self) -> &'static str {
1916 r#"
1917 body {
1918 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
1919 margin: 0;
1920 padding: 20px;
1921 background: #ffffff;
1922 color: #24292f;
1923 line-height: 1.6;
1924 }
1925
1926 .container {
1927 max-width: 1400px;
1928 margin: 0 auto;
1929 background: #f6f8fa;
1930 border: 1px solid #d0d7de;
1931 border-radius: 12px;
1932 overflow: hidden;
1933 }
1934
1935 .header {
1936 background: linear-gradient(135deg, #0969da 0%, #8250df 100%);
1937 padding: 2rem;
1938 text-align: center;
1939 color: white;
1940 }
1941
1942 .summary {
1943 display: grid;
1944 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1945 gap: 1.5rem;
1946 padding: 2rem;
1947 background: #ffffff;
1948 }
1949
1950 .summary-card {
1951 background: #f6f8fa;
1952 border: 1px solid #d0d7de;
1953 padding: 1.5rem;
1954 border-radius: 8px;
1955 text-align: center;
1956 }
1957
1958 .task-card {
1959 background: #ffffff;
1960 border: 1px solid #d0d7de;
1961 border-radius: 10px;
1962 overflow: hidden;
1963 transition: transform 0.2s ease;
1964 position: relative;
1965 }
1966
1967 .task-card:hover {
1968 transform: translateY(-2px);
1969 box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
1970 }
1971
1972 /* Add more light theme styles as needed */
1973 "#
1974 }
1975}
1976
1977#[cfg(test)]
1978mod tests {
1979 use super::*;
1980 use crate::async_memory::resource_monitor::{
1981 BottleneckType, ComponentScores, CpuMetrics, CriticalPathAnalysis, EfficiencyExplanation,
1982 HotMetrics, IoMetrics, MemoryMetrics, NetworkMetrics, SourceLocation, TaskResourceProfile,
1983 TaskType,
1984 };
1985
1986 fn create_test_profile(
1988 task_name: &str,
1989 task_type: TaskType,
1990 cpu_usage: f64,
1991 memory_bytes: u64,
1992 efficiency: f64,
1993 ) -> TaskResourceProfile {
1994 TaskResourceProfile {
1995 task_id: 1u128,
1996 task_name: task_name.to_string(),
1997 task_type,
1998 start_time: 1000,
1999 end_time: Some(2000),
2000 duration_ms: Some(1000.0),
2001 cpu_metrics: CpuMetrics {
2002 usage_percent: cpu_usage,
2003 time_user_ms: 100.0,
2004 time_kernel_ms: 50.0,
2005 context_switches: 10,
2006 cpu_cycles: 1000000,
2007 instructions: 500000,
2008 cache_misses: 100,
2009 branch_misses: 50,
2010 core_affinity: vec![0],
2011 },
2012 memory_metrics: MemoryMetrics {
2013 allocated_bytes: memory_bytes,
2014 peak_bytes: memory_bytes + 1024,
2015 current_bytes: memory_bytes,
2016 allocation_count: 5,
2017 deallocation_count: 3,
2018 page_faults: 10,
2019 heap_fragmentation: 0.1,
2020 memory_bandwidth_mbps: 1000.0,
2021 },
2022 io_metrics: IoMetrics {
2023 bytes_read: 1024,
2024 bytes_written: 512,
2025 read_operations: 10,
2026 write_operations: 5,
2027 sync_operations: 3,
2028 async_operations: 12,
2029 avg_latency_us: 100.0,
2030 bandwidth_mbps: 10.0,
2031 queue_depth: 4,
2032 io_wait_percent: 5.0,
2033 },
2034 network_metrics: NetworkMetrics {
2035 bytes_sent: 2048,
2036 bytes_received: 1536,
2037 packets_sent: 100,
2038 packets_received: 95,
2039 connections_active: 5,
2040 connections_established: 10,
2041 connection_errors: 1,
2042 latency_avg_ms: 10.0,
2043 throughput_mbps: 5.0,
2044 retransmissions: 2,
2045 },
2046 gpu_metrics: None,
2047 efficiency_score: efficiency,
2048 resource_balance: 0.8,
2049 bottleneck_type: BottleneckType::Balanced,
2050 source_location: SourceLocation {
2051 file_path: "test.rs".to_string(),
2052 line_number: 42,
2053 function_name: "test_function".to_string(),
2054 module_path: "test::module".to_string(),
2055 crate_name: "test_crate".to_string(),
2056 },
2057 hot_metrics: HotMetrics {
2058 cpu_hotspots: vec![],
2059 memory_hotspots: vec![],
2060 io_hotspots: vec![],
2061 network_hotspots: vec![],
2062 critical_path_analysis: CriticalPathAnalysis {
2063 total_execution_time_ms: 1000.0,
2064 critical_path_time_ms: 800.0,
2065 parallelization_potential: 0.5,
2066 blocking_operations: vec![],
2067 },
2068 },
2069 efficiency_explanation: EfficiencyExplanation {
2070 overall_score: efficiency,
2071 component_scores: ComponentScores {
2072 cpu_efficiency: efficiency,
2073 memory_efficiency: efficiency,
2074 io_efficiency: efficiency,
2075 network_efficiency: efficiency,
2076 resource_balance: 0.8,
2077 },
2078 recommendations: vec![],
2079 bottleneck_analysis: "No bottlenecks detected".to_string(),
2080 optimization_potential: 0.2,
2081 },
2082 }
2083 }
2084
2085 #[test]
2086 fn test_visualization_config_default() {
2087 let config = VisualizationConfig::default();
2088
2089 assert_eq!(config.title, "Async Task Performance Analysis");
2090 assert!(matches!(config.theme, Theme::Dark));
2091 assert!(config.include_charts);
2092 assert!(config.include_baselines);
2093 assert!(config.include_rankings);
2094 assert!(config.include_efficiency_breakdown);
2095 }
2096
2097 #[test]
2098 fn test_visualization_generator_new() {
2099 let generator = VisualizationGenerator::new();
2100 assert_eq!(generator.config.title, "Async Task Performance Analysis");
2101 }
2102
2103 #[test]
2104 fn test_visualization_generator_with_config() {
2105 let custom_config = VisualizationConfig {
2106 title: "Custom Analysis".to_string(),
2107 theme: Theme::Light,
2108 include_charts: false,
2109 include_baselines: false,
2110 include_rankings: false,
2111 include_efficiency_breakdown: false,
2112 };
2113
2114 let generator = VisualizationGenerator::with_config(custom_config.clone());
2115 assert_eq!(generator.config.title, "Custom Analysis");
2116 assert!(matches!(generator.config.theme, Theme::Light));
2117 assert!(!generator.config.include_charts);
2118 }
2119
2120 #[test]
2121 fn test_calculate_baselines() {
2122 let generator = VisualizationGenerator::new();
2123 let mut profiles = HashMap::new();
2124
2125 profiles.insert(
2126 1u128,
2127 create_test_profile("task1", TaskType::CpuIntensive, 50.0, 1024 * 1024, 0.8),
2128 );
2129 profiles.insert(
2130 2u128,
2131 create_test_profile("task2", TaskType::IoIntensive, 30.0, 2048 * 1024, 0.6),
2132 );
2133
2134 let baselines = generator.calculate_baselines(&profiles);
2135
2136 assert_eq!(baselines.avg_cpu_percent, 40.0);
2137 assert_eq!(baselines.avg_memory_mb, 1.5);
2138 assert_eq!(baselines.avg_io_mbps, 10.0);
2139 assert_eq!(baselines.avg_network_mbps, 5.0);
2140 assert_eq!(baselines.avg_efficiency_score, 0.7);
2141 }
2142
2143 #[test]
2144 fn test_calculate_rankings() {
2145 let generator = VisualizationGenerator::new();
2146 let mut profiles = HashMap::new();
2147
2148 profiles.insert(
2150 1u128,
2151 create_test_profile("task1", TaskType::CpuIntensive, 50.0, 1024 * 1024, 0.9),
2152 );
2153 profiles.insert(
2154 2u128,
2155 create_test_profile("task2", TaskType::CpuIntensive, 30.0, 2048 * 1024, 0.7),
2156 );
2157 profiles.insert(
2158 3u128,
2159 create_test_profile("task3", TaskType::IoIntensive, 20.0, 512 * 1024, 0.8),
2160 );
2161
2162 let rankings = generator.calculate_rankings(&profiles);
2163
2164 let task1_ranking = rankings.get(&1u128).expect("Task 1 should have ranking");
2166 let task2_ranking = rankings.get(&2u128).expect("Task 2 should have ranking");
2167
2168 assert_eq!(task1_ranking.rank, 1); assert_eq!(task1_ranking.total_in_category, 2);
2170 assert_eq!(task1_ranking.category_name, "CpuIntensive");
2171
2172 assert_eq!(task2_ranking.rank, 2);
2173 assert_eq!(task2_ranking.total_in_category, 2);
2174
2175 let task3_ranking = rankings.get(&3u128).expect("Task 3 should have ranking");
2177 assert_eq!(task3_ranking.rank, 1);
2178 assert_eq!(task3_ranking.total_in_category, 1);
2179 assert_eq!(task3_ranking.category_name, "IoIntensive");
2180 }
2181
2182 #[test]
2183 fn test_compare_to_baseline() {
2184 let generator = VisualizationGenerator::new();
2185
2186 let comp = generator.compare_to_baseline(110.0, 100.0);
2188 assert!(matches!(comp.comparison_type, ComparisonType::AboveAverage));
2189 assert_eq!(comp.difference_percent, 10.0);
2190
2191 let comp = generator.compare_to_baseline(90.0, 100.0);
2193 assert!(matches!(comp.comparison_type, ComparisonType::BelowAverage));
2194 assert_eq!(comp.difference_percent, -10.0);
2195
2196 let comp = generator.compare_to_baseline(102.0, 100.0);
2198 assert!(matches!(comp.comparison_type, ComparisonType::NearAverage));
2199 assert_eq!(comp.difference_percent, 2.0);
2200
2201 let comp = generator.compare_to_baseline(50.0, 0.0);
2203 assert_eq!(comp.difference_percent, 0.0);
2204 }
2205
2206 #[test]
2207 fn test_analyze_profiles_empty() {
2208 let generator = VisualizationGenerator::new();
2209 let profiles = HashMap::new();
2210
2211 let result = generator.analyze_profiles(&profiles);
2212 assert!(result.is_err());
2213 assert!(matches!(
2214 result.unwrap_err(),
2215 VisualizationError::NoDataAvailable
2216 ));
2217 }
2218
2219 #[test]
2220 fn test_format_comparison() {
2221 let generator = VisualizationGenerator::new();
2222
2223 let comp_above = PerformanceComparison {
2224 value: 110.0,
2225 baseline: 100.0,
2226 difference_percent: 10.5,
2227 comparison_type: ComparisonType::AboveAverage,
2228 };
2229 assert_eq!(generator.format_comparison(&comp_above), "(+10.5% vs avg)");
2230
2231 let comp_below = PerformanceComparison {
2232 value: 85.0,
2233 baseline: 100.0,
2234 difference_percent: -15.0,
2235 comparison_type: ComparisonType::BelowAverage,
2236 };
2237 assert_eq!(generator.format_comparison(&comp_below), "(-15.0% vs avg)");
2238
2239 let comp_average = PerformanceComparison {
2240 value: 102.0,
2241 baseline: 100.0,
2242 difference_percent: 2.0,
2243 comparison_type: ComparisonType::NearAverage,
2244 };
2245 assert_eq!(generator.format_comparison(&comp_average), "(≈ avg)");
2246 }
2247
2248 #[test]
2249 fn test_get_comparison_class() {
2250 let generator = VisualizationGenerator::new();
2251
2252 let comp_above = PerformanceComparison {
2253 value: 110.0,
2254 baseline: 100.0,
2255 difference_percent: 10.0,
2256 comparison_type: ComparisonType::AboveAverage,
2257 };
2258 assert_eq!(
2259 generator.get_comparison_class(&comp_above),
2260 "comparison-above"
2261 );
2262
2263 let comp_below = PerformanceComparison {
2264 value: 90.0,
2265 baseline: 100.0,
2266 difference_percent: -10.0,
2267 comparison_type: ComparisonType::BelowAverage,
2268 };
2269 assert_eq!(
2270 generator.get_comparison_class(&comp_below),
2271 "comparison-below"
2272 );
2273
2274 let comp_average = PerformanceComparison {
2275 value: 102.0,
2276 baseline: 100.0,
2277 difference_percent: 2.0,
2278 comparison_type: ComparisonType::NearAverage,
2279 };
2280 assert_eq!(
2281 generator.get_comparison_class(&comp_average),
2282 "comparison-average"
2283 );
2284 }
2285
2286 #[test]
2287 fn test_theme_styles() {
2288 let generator = VisualizationGenerator::new();
2289
2290 let dark_styles = generator.get_dark_theme_styles();
2291 assert!(dark_styles.contains("background: #0d1117"));
2292 assert!(dark_styles.contains("color: #f0f6fc"));
2293
2294 let light_styles = generator.get_light_theme_styles();
2295 assert!(light_styles.contains("background: #ffffff"));
2296 assert!(light_styles.contains("color: #24292f"));
2297 }
2298
2299 #[test]
2300 fn test_visualization_error_display() {
2301 let err = VisualizationError::NoDataAvailable;
2302 assert_eq!(format!("{}", err), "No data available for visualization");
2303
2304 let err = VisualizationError::InvalidConfiguration("test error".to_string());
2305 assert_eq!(format!("{}", err), "Invalid configuration: test error");
2306
2307 let err = VisualizationError::TemplateError("template failed".to_string());
2308 assert_eq!(
2309 format!("{}", err),
2310 "Template generation error: template failed"
2311 );
2312 }
2313}