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