memscope_rs/async_memory/
visualization.rs

1//! Async Task Performance Visualization Module
2//!
3//! This module provides comprehensive visualization capabilities for async task performance data.
4//! It generates interactive HTML reports with charts, baselines, rankings, and detailed analytics.
5
6use super::{TaskId, TaskResourceProfile};
7use handlebars::Handlebars;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Task type metrics for categorization
12#[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/// Visualization configuration options
25#[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/// UI Theme options
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub enum Theme {
51    Dark,
52    Light,
53}
54
55/// Baseline performance metrics for comparison
56#[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/// Task ranking within its category
66#[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/// Performance comparison result
74#[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
89/// Main visualization generator
90pub struct VisualizationGenerator {
91    config: VisualizationConfig,
92}
93
94impl VisualizationGenerator {
95    /// Create a new visualization generator with default config
96    pub fn new() -> Self {
97        Self {
98            config: VisualizationConfig::default(),
99        }
100    }
101
102    /// Create a new visualization generator with custom config
103    pub fn with_config(config: VisualizationConfig) -> Self {
104        Self { config }
105    }
106
107    /// Generate complete HTML report from task profiles
108    pub fn generate_html_report(
109        &self,
110        profiles: &HashMap<TaskId, TaskResourceProfile>,
111    ) -> Result<String, VisualizationError> {
112        // Try template-based generation first, fallback to hardcoded if template not found
113        match self.generate_templated_html_report(profiles) {
114            Ok(html) => Ok(html),
115            Err(_) => self.generate_hardcoded_html_report(profiles), // Fallback to original
116        }
117    }
118
119    /// Generate HTML using Handlebars template (NEW)
120    fn generate_templated_html_report(
121        &self,
122        profiles: &HashMap<TaskId, TaskResourceProfile>,
123    ) -> Result<String, VisualizationError> {
124        // Read enhanced template file
125        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        // Create Handlebars registry
134        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        // Build template data
142        let template_data = self.build_template_data(profiles)?;
143
144        // Render template
145        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    /// Generate hardcoded HTML (ORIGINAL method)
155    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    /// Generate analytics data from profiles
164    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    /// Calculate baseline metrics
185    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    /// Calculate category rankings
210    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        // Group by task type
219        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        // Calculate rankings within each category
228        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    /// Calculate performance comparisons
252    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    /// Compare a value to its baseline
288    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/// Complete analytics data
313#[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/// Task-specific comparison data
322#[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/// Visualization errors
331#[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    /// Build complete HTML report
351    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 Header
359        html.push_str(&self.generate_html_header());
360
361        // Summary Section
362        html.push_str(&self.generate_summary_section(analytics));
363
364        // Charts Section (if enabled) - moved to top
365        if self.config.include_charts {
366            html.push_str(&self.generate_charts_section(profiles)?);
367        }
368
369        // Tasks Section
370        html.push_str(&self.generate_tasks_section(analytics, profiles)?);
371
372        // HTML Footer
373        html.push_str(&self.generate_html_footer());
374
375        Ok(html)
376    }
377
378    /// Generate HTML header with styles
379    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    /// Generate summary statistics section
408    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    /// Generate tasks section with cards
438    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        // Sort tasks by efficiency score
454        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    /// Generate individual task card
476    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    /// Generate comparison information display
664    fn generate_comparison_info(&self, _comparisons: &TaskComparisons) -> String {
665        // Implementation for comparison display
666        String::new()
667    }
668
669    /// Format comparison for display
670    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    /// Get CSS class for comparison
683    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    /// Generate charts section
692    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        // Generate simple CSS charts
706        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    /// Build enhanced template data from task profiles with comprehensive Rust async metrics
719    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        // Calculate aggregated metrics
728        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        // Enhanced async-specific metrics
748        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        // Network and I/O totals
760        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        // Count task types and calculate efficiency by type
806        let task_type_counts = self.calculate_task_type_metrics(profiles);
807
808        // Build categorized task data
809        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        // Async-specific metrics for Rust
819        let futures_count = total_tasks; // Each task represents a Future
820        let total_polls = total_context_switches; // Context switches approximate polling
821        let avg_poll_time = if total_polls > 0 {
822            cpu_usage_avg * 10.0
823        } else {
824            0.0
825        }; // Estimated poll time in microseconds
826        let ready_rate = if total_tasks > 0 {
827            avg_efficiency * 100.0
828        } else {
829            0.0
830        };
831
832        // Build template data in smaller chunks to avoid recursion limit
833        let mut template_data = serde_json::Map::new();
834
835        // Basic info
836        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        // Core metrics
843        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        // CPU metrics
861        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        // Memory metrics
879        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        // I/O metrics
897        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        // Network metrics
915        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        // Overall efficiency
933        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        // Async-specific Rust metrics
963        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        // Task type counts and efficiency
981        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        // Categorized task data
1015        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        // Advanced analytics data
1033        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    /// Calculate task type metrics and efficiency (legacy method for compatibility)
1098    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                _ => {} // Handle other types as needed
1130            }
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    /// Build task data for specific category
1162    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                // Basic task info
1176                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                // CPU-specific metrics
1219                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                // Memory-specific metrics
1246                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                // I/O-specific metrics
1284                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                // Network-specific metrics
1314                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    /// Generate simple CSS charts (no JavaScript)
1355    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        // Find max values for scaling
1363        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        // Generate network bars
1412        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    /// Generate HTML footer
1468    fn generate_html_footer(&self) -> String {
1469        r#"
1470    </div>
1471</body>
1472</html>
1473"#
1474        .to_string()
1475    }
1476
1477    /// Get dark theme CSS styles
1478    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    /// Get light theme CSS styles
1915    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    /// Helper function to create a test task profile
1987    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        // Create tasks with different efficiency scores in same category
2149        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        // Check CPU intensive tasks ranking
2165        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); // Higher efficiency should rank first
2169        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        // Check IO intensive task ranking
2176        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        // Test above average
2187        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        // Test below average
2192        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        // Test near average
2197        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        // Test zero baseline
2202        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}