memscope_rs/lockfree/
aggregator.rs

1//! Offline analysis tool for aggregating lock-free multi-threaded binary data.
2//!
3//! This module processes the binary files generated by thread-local trackers
4//! and provides comprehensive analysis across all threads.
5
6use crate::lockfree::analysis::{AllocationEvent, EventType, InteractionType};
7use crate::lockfree::tracker::{Event, FrequencyData};
8use bincode;
9// Removed unused serde imports
10use std::collections::HashMap;
11use std::path::Path;
12
13use crate::lockfree::analysis::{
14    BottleneckType, LockfreeAnalysis, MemoryPeak, PerformanceBottleneck, ThreadInteraction,
15    ThreadStats,
16};
17
18/// Lock-free multi-threaded data aggregator
19pub struct LockfreeAggregator {
20    output_dir: std::path::PathBuf,
21}
22
23impl LockfreeAggregator {
24    pub fn new(output_dir: std::path::PathBuf) -> Self {
25        Self { output_dir }
26    }
27
28    /// Discover and parse all thread binary files
29    pub fn aggregate_all_threads(&self) -> Result<LockfreeAnalysis, Box<dyn std::error::Error>> {
30        let mut thread_stats = HashMap::new();
31
32        // Discover all thread files
33        let thread_files = self.discover_thread_files()?;
34        let mut temp_files_to_cleanup = Vec::new();
35
36        for (thread_id, event_file, freq_file) in thread_files {
37            let events = self.parse_event_file(&event_file)?;
38            let frequencies = self.parse_frequency_file(&freq_file)?;
39
40            let stats = self.analyze_thread_data(thread_id, events, frequencies)?;
41            thread_stats.insert(thread_id, stats);
42
43            // Collect files for cleanup after successful processing
44            temp_files_to_cleanup.push(event_file);
45            temp_files_to_cleanup.push(freq_file);
46        }
47
48        // Perform cross-thread analysis
49        let hottest_call_stacks = self.find_hottest_call_stacks(&thread_stats);
50        let thread_interactions = self.analyze_thread_interactions(&thread_stats);
51        let memory_peaks = self.find_memory_peaks(&thread_stats);
52        let performance_bottlenecks = self.detect_performance_bottlenecks(&thread_stats);
53
54        let mut analysis = LockfreeAnalysis::new();
55        analysis.thread_stats = thread_stats;
56        analysis.hottest_call_stacks = hottest_call_stacks
57            .into_iter()
58            .map(
59                |(hash, freq, size)| crate::lockfree::analysis::HotCallStack {
60                    call_stack_hash: hash,
61                    total_frequency: freq,
62                    total_size: size,
63                    impact_score: freq * size as u64,
64                    threads: Vec::new(), // Could be populated from thread_stats
65                },
66            )
67            .collect();
68        analysis.thread_interactions = thread_interactions;
69        analysis.memory_peaks = memory_peaks;
70        analysis.performance_bottlenecks = performance_bottlenecks;
71
72        // IMPORTANT: Calculate summary statistics from thread data
73        analysis.calculate_summary(std::time::Instant::now());
74
75        // Clean up temporary files after successful aggregation
76        self.cleanup_temp_files(&temp_files_to_cleanup)?;
77
78        Ok(analysis)
79    }
80
81    /// Discover all thread binary files in output directory
82    #[allow(clippy::type_complexity)]
83    fn discover_thread_files(
84        &self,
85    ) -> Result<Vec<(u64, std::path::PathBuf, std::path::PathBuf)>, Box<dyn std::error::Error>>
86    {
87        let mut files = Vec::new();
88
89        if !self.output_dir.exists() {
90            return Ok(files);
91        }
92
93        for entry in std::fs::read_dir(&self.output_dir)? {
94            let entry = entry?;
95            let path = entry.path();
96
97            if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
98                if file_name.starts_with("memscope_thread_") && file_name.ends_with(".bin") {
99                    // Extract thread ID from filename
100                    let thread_id_str = file_name
101                        .strip_prefix("memscope_thread_")
102                        .and_then(|s| s.strip_suffix(".bin"))
103                        .ok_or("Invalid thread file name format")?;
104
105                    let thread_id: u64 = thread_id_str.parse()?;
106
107                    let freq_file = path.with_extension("freq");
108                    if freq_file.exists() {
109                        files.push((thread_id, path, freq_file));
110                    }
111                }
112            }
113        }
114
115        Ok(files)
116    }
117
118    /// Clean up temporary binary and frequency files after successful aggregation
119    fn cleanup_temp_files(
120        &self,
121        files: &[std::path::PathBuf],
122    ) -> Result<(), Box<dyn std::error::Error>> {
123        let mut cleaned_count = 0;
124        let mut failed_count = 0;
125
126        for file_path in files {
127            match std::fs::remove_file(file_path) {
128                Ok(()) => {
129                    cleaned_count += 1;
130                    eprintln!("๐Ÿ—‘๏ธ  Cleaned up temporary file: {:?}", file_path);
131                }
132                Err(e) => {
133                    failed_count += 1;
134                    eprintln!("โš ๏ธ  Failed to clean up file {:?}: {}", file_path, e);
135                }
136            }
137        }
138
139        if cleaned_count > 0 {
140            eprintln!(
141                "โœ… Successfully cleaned up {} temporary files",
142                cleaned_count
143            );
144        }
145
146        if failed_count > 0 {
147            eprintln!("โš ๏ธ  Failed to clean up {} files", failed_count);
148        }
149
150        Ok(())
151    }
152
153    /// Parse event file and return all events
154    fn parse_event_file(&self, file_path: &Path) -> Result<Vec<Event>, Box<dyn std::error::Error>> {
155        let file_content = std::fs::read(file_path)?;
156        let mut events = Vec::new();
157        let mut offset = 0;
158
159        // Read length-prefixed chunks
160        while offset + 4 <= file_content.len() {
161            // Read length (4 bytes, little endian)
162            let length_bytes = &file_content[offset..offset + 4];
163            let length = u32::from_le_bytes([
164                length_bytes[0],
165                length_bytes[1],
166                length_bytes[2],
167                length_bytes[3],
168            ]) as usize;
169            offset += 4;
170
171            if offset + length > file_content.len() {
172                break; // Incomplete chunk
173            }
174
175            // Deserialize events chunk
176            let chunk_data = &file_content[offset..offset + length];
177            let (chunk_events, _): (Vec<Event>, _) =
178                bincode::decode_from_slice(chunk_data, bincode::config::standard())?;
179            events.extend(chunk_events);
180            offset += length;
181        }
182
183        Ok(events)
184    }
185
186    /// Parse frequency file
187    fn parse_frequency_file(
188        &self,
189        file_path: &Path,
190    ) -> Result<Vec<FrequencyData>, Box<dyn std::error::Error>> {
191        let file_content = std::fs::read(file_path)?;
192        let (frequencies, _): (Vec<FrequencyData>, _) =
193            bincode::decode_from_slice(&file_content, bincode::config::standard())?;
194        Ok(frequencies)
195    }
196
197    /// Analyze single thread data and convert Event to AllocationEvent
198    fn analyze_thread_data(
199        &self,
200        thread_id: u64,
201        events: Vec<Event>,
202        frequencies: Vec<FrequencyData>,
203    ) -> Result<ThreadStats, Box<dyn std::error::Error>> {
204        let mut allocations = 0u64;
205        let mut deallocations = 0u64;
206        let mut current_memory = 0usize;
207        let mut peak_memory = 0usize;
208        let mut total_allocated = 0usize;
209        let mut allocation_sizes = Vec::new();
210
211        // Process events chronologically
212        let mut sorted_events = events;
213        sorted_events.sort_by_key(|e| e.timestamp);
214
215        for event in &sorted_events {
216            match event.event_type {
217                EventType::Allocation => {
218                    allocations += 1;
219                    current_memory += event.size;
220                    total_allocated += event.size;
221                    allocation_sizes.push(event.size);
222                    peak_memory = peak_memory.max(current_memory);
223                }
224                EventType::Deallocation => {
225                    deallocations += 1;
226                    // Note: We don't track exact deallocation sizes in current implementation
227                    // This could be enhanced by tracking allocation->deallocation mapping
228                }
229            }
230        }
231
232        let avg_allocation_size = if !allocation_sizes.is_empty() {
233            allocation_sizes.iter().sum::<usize>() as f64 / allocation_sizes.len() as f64
234        } else {
235            0.0
236        };
237
238        // Build frequency map from frequency data
239        let allocation_frequency: HashMap<u64, u64> = frequencies
240            .into_iter()
241            .map(|f| (f.call_stack_hash, f.frequency))
242            .collect();
243
244        // Convert Event to AllocationEvent for analysis compatibility
245        let timeline: Vec<AllocationEvent> = sorted_events
246            .into_iter()
247            .map(|event| AllocationEvent {
248                timestamp: event.timestamp,
249                ptr: event.ptr,
250                size: event.size,
251                call_stack_hash: event.call_stack_hash,
252                event_type: event.event_type,
253                thread_id: event.thread_id,
254            })
255            .collect();
256
257        Ok(ThreadStats {
258            thread_id,
259            total_allocations: allocations,
260            total_deallocations: deallocations,
261            peak_memory,
262            total_allocated,
263            allocation_frequency,
264            avg_allocation_size,
265            timeline,
266        })
267    }
268
269    /// Find hottest call stacks across all threads
270    fn find_hottest_call_stacks(
271        &self,
272        thread_stats: &HashMap<u64, ThreadStats>,
273    ) -> Vec<(u64, u64, usize)> {
274        let mut call_stack_totals: HashMap<u64, (u64, usize)> = HashMap::new();
275
276        for stats in thread_stats.values() {
277            for (&hash, &frequency) in &stats.allocation_frequency {
278                let entry = call_stack_totals.entry(hash).or_insert((0, 0));
279                entry.0 += frequency;
280                // Estimate total size (this could be more accurate with better tracking)
281                entry.1 += (stats.avg_allocation_size as usize) * frequency as usize;
282            }
283        }
284
285        let mut hottest: Vec<(u64, u64, usize)> = call_stack_totals
286            .into_iter()
287            .map(|(hash, (freq, size))| (hash, freq, size))
288            .collect();
289
290        // Sort by frequency * size to find most impactful call stacks
291        hottest.sort_by(|a, b| (b.1 * b.2 as u64).cmp(&(a.1 * a.2 as u64)));
292        hottest.truncate(50); // Top 50 hottest call stacks
293
294        hottest
295    }
296
297    /// Analyze interactions between threads
298    fn analyze_thread_interactions(
299        &self,
300        thread_stats: &HashMap<u64, ThreadStats>,
301    ) -> Vec<ThreadInteraction> {
302        let mut interactions = Vec::new();
303
304        // Simple heuristic: threads that allocate in similar memory regions might be interacting
305        let thread_ids: Vec<u64> = thread_stats.keys().copied().collect();
306
307        for i in 0..thread_ids.len() {
308            for j in i + 1..thread_ids.len() {
309                let thread_a = thread_ids[i];
310                let thread_b = thread_ids[j];
311
312                let stats_a = &thread_stats[&thread_a];
313                let stats_b = &thread_stats[&thread_b];
314
315                // Check for shared allocation patterns (simplified)
316                let mut shared_regions = Vec::new();
317                let mut interaction_count = 0u64;
318
319                // Find common call stack patterns (indicating similar allocation patterns)
320                for (&hash_a, &freq_a) in &stats_a.allocation_frequency {
321                    if let Some(&freq_b) = stats_b.allocation_frequency.get(&hash_a) {
322                        interaction_count += freq_a.min(freq_b);
323                        // Use call stack hash as proxy for shared pattern
324                        shared_regions.push(hash_a);
325                    }
326                }
327
328                if interaction_count > 0 {
329                    interactions.push(ThreadInteraction {
330                        thread_a,
331                        thread_b,
332                        shared_patterns: shared_regions,
333                        interaction_strength: interaction_count,
334                        interaction_type: InteractionType::SimilarPatterns,
335                    });
336                }
337            }
338        }
339
340        // Sort by interaction strength
341        interactions.sort_by(|a, b| b.interaction_strength.cmp(&a.interaction_strength));
342        interactions
343    }
344
345    /// Find memory usage peaks across all threads
346    fn find_memory_peaks(&self, thread_stats: &HashMap<u64, ThreadStats>) -> Vec<MemoryPeak> {
347        let mut peaks = Vec::new();
348
349        for stats in thread_stats.values() {
350            let mut current_memory = 0usize;
351            let mut current_allocations = 0u64;
352
353            for event in &stats.timeline {
354                match event.event_type {
355                    EventType::Allocation => {
356                        current_memory += event.size;
357                        current_allocations += 1;
358
359                        // Record peak if it's significant
360                        if current_memory > 1024 * 1024 {
361                            // > 1MB
362                            peaks.push(MemoryPeak {
363                                timestamp: event.timestamp,
364                                thread_id: stats.thread_id,
365                                memory_usage: current_memory,
366                                active_allocations: current_allocations,
367                                triggering_call_stack: event.call_stack_hash,
368                            });
369                        }
370                    }
371                    EventType::Deallocation => {
372                        current_allocations = current_allocations.saturating_sub(1);
373                        // Note: memory tracking for deallocation would need size info
374                    }
375                }
376            }
377        }
378
379        // Sort by memory usage
380        peaks.sort_by(|a, b| b.memory_usage.cmp(&a.memory_usage));
381        peaks.truncate(100); // Top 100 peaks
382
383        peaks
384    }
385
386    /// Detect performance bottlenecks
387    fn detect_performance_bottlenecks(
388        &self,
389        thread_stats: &HashMap<u64, ThreadStats>,
390    ) -> Vec<PerformanceBottleneck> {
391        let mut bottlenecks = Vec::new();
392
393        for stats in thread_stats.values() {
394            // Detect high-frequency small allocations
395            for (&hash, &frequency) in &stats.allocation_frequency {
396                if frequency > 1000 && stats.avg_allocation_size < 1024.0 {
397                    bottlenecks.push(PerformanceBottleneck {
398                        bottleneck_type: BottleneckType::HighFrequencySmallAllocation,
399                        thread_id: stats.thread_id,
400                        call_stack_hash: hash,
401                        severity: (frequency as f64 / 10000.0).min(1.0),
402                        description: format!(
403                            "High frequency ({}) small allocations (avg {}B) in thread {}",
404                            frequency, stats.avg_allocation_size as usize, stats.thread_id
405                        ),
406                        suggestion: "Consider using memory pools or batch allocation strategies"
407                            .to_string(),
408                    });
409                }
410            }
411
412            // Detect potential memory leaks
413            if stats.total_deallocations == 0 && stats.total_allocations > 100 {
414                bottlenecks.push(PerformanceBottleneck {
415                    bottleneck_type: BottleneckType::MemoryLeak,
416                    thread_id: stats.thread_id,
417                    call_stack_hash: 0, // General thread issue
418                    severity: 0.8,
419                    description: format!(
420                        "Potential memory leak: {} allocations, 0 deallocations in thread {}",
421                        stats.total_allocations, stats.thread_id
422                    ),
423                    suggestion: "Review deallocation logic and consider using RAII patterns"
424                        .to_string(),
425                });
426            }
427
428            // Detect large allocation spikes
429            if stats.peak_memory > 100 * 1024 * 1024 {
430                // > 100MB
431                bottlenecks.push(PerformanceBottleneck {
432                    bottleneck_type: BottleneckType::LargeAllocationSpike,
433                    thread_id: stats.thread_id,
434                    call_stack_hash: 0,
435                    severity: (stats.peak_memory as f64 / (1024.0 * 1024.0 * 1024.0)).min(1.0), // Severity based on GB
436                    description: format!(
437                        "Large memory spike: {}MB peak in thread {}",
438                        stats.peak_memory / (1024 * 1024),
439                        stats.thread_id
440                    ),
441                    suggestion: "Consider streaming or chunked processing for large data sets"
442                        .to_string(),
443                });
444            }
445        }
446
447        // Sort by severity
448        bottlenecks.sort_by(|a, b| {
449            b.severity
450                .partial_cmp(&a.severity)
451                .unwrap_or(std::cmp::Ordering::Equal)
452        });
453        bottlenecks
454    }
455
456    /// Export aggregated analysis to JSON
457    pub fn export_analysis(
458        &self,
459        analysis: &LockfreeAnalysis,
460        output_path: &Path,
461    ) -> Result<(), Box<dyn std::error::Error>> {
462        let json_content = serde_json::to_string_pretty(analysis)?;
463        std::fs::write(output_path, json_content)?;
464        Ok(())
465    }
466
467    #[allow(dead_code)]
468    fn build_html_report_legacy(
469        &self,
470        analysis: &LockfreeAnalysis,
471    ) -> Result<String, Box<dyn std::error::Error>> {
472        let mut html = String::new();
473
474        // Enhanced HTML with modern styling and comprehensive data display
475        html.push_str(
476            r#"<!DOCTYPE html>
477<html lang="en">
478<head>
479    <meta charset="UTF-8">
480    <meta name="viewport" content="width=device-width, initial-scale=1.0">
481    <title>Enhanced Multi-threaded Memory Analysis Report</title>
482    <style>
483        * { margin: 0; padding: 0; box-sizing: border-box; }
484        body { 
485            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 
486            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
487            min-height: 100vh;
488            padding: 20px;
489        }
490        .container { 
491            max-width: 1400px; 
492            margin: 0 auto; 
493            background: white; 
494            border-radius: 15px; 
495            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
496            overflow: hidden;
497        }
498        .header { 
499            background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); 
500            color: white; 
501            padding: 30px; 
502            text-align: center; 
503        }
504        .header h1 { font-size: 2.5em; margin-bottom: 10px; }
505        .header .subtitle { font-size: 1.2em; opacity: 0.9; }
506        .content { padding: 30px; }
507        .section { 
508            margin-bottom: 40px; 
509            background: #f8f9fa; 
510            border-radius: 10px; 
511            padding: 25px; 
512            box-shadow: 0 5px 15px rgba(0,0,0,0.05);
513        }
514        .section h2 { 
515            color: #2c3e50; 
516            border-bottom: 3px solid #3498db; 
517            padding-bottom: 10px; 
518            margin-bottom: 20px; 
519            font-size: 1.8em;
520        }
521        .stats-grid { 
522            display: grid; 
523            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 
524            gap: 20px; 
525            margin-bottom: 30px;
526        }
527        .stat-card { 
528            background: white; 
529            border-radius: 10px; 
530            padding: 20px; 
531            border-left: 5px solid #3498db;
532            box-shadow: 0 3px 10px rgba(0,0,0,0.1);
533        }
534        .stat-number { font-size: 2em; font-weight: bold; color: #2c3e50; }
535        .stat-label { color: #7f8c8d; font-size: 0.9em; }
536        .thread-detail { 
537            background: white; 
538            border-radius: 10px; 
539            padding: 20px; 
540            margin: 15px 0; 
541            border-left: 5px solid #e74c3c;
542            box-shadow: 0 3px 10px rgba(0,0,0,0.05);
543        }
544        .thread-header { 
545            display: flex; 
546            justify-content: space-between; 
547            align-items: center; 
548            margin-bottom: 15px;
549        }
550        .thread-title { color: #2c3e50; font-size: 1.4em; font-weight: bold; }
551        .thread-id { background: #3498db; color: white; padding: 5px 10px; border-radius: 15px; }
552        .metric-row { 
553            display: grid; 
554            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 
555            gap: 15px; 
556            margin: 10px 0;
557        }
558        .metric { text-align: center; }
559        .metric-value { font-size: 1.3em; font-weight: bold; color: #27ae60; }
560        .metric-name { font-size: 0.9em; color: #7f8c8d; }
561        .enhanced-table { 
562            width: 100%; 
563            border-collapse: collapse; 
564            background: white; 
565            border-radius: 10px; 
566            overflow: hidden;
567            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
568        }
569        .enhanced-table th { 
570            background: linear-gradient(135deg, #3498db, #2980b9); 
571            color: white; 
572            padding: 15px; 
573            text-align: left; 
574            font-weight: 600;
575        }
576        .enhanced-table td { 
577            padding: 12px 15px; 
578            border-bottom: 1px solid #ecf0f1; 
579        }
580        .enhanced-table tr:hover { background: #ecf0f1; }
581        .bottleneck { 
582            background: linear-gradient(135deg, #fff5f5, #fed7d7); 
583            border-left: 5px solid #e53e3e; 
584            padding: 20px; 
585            margin: 15px 0; 
586            border-radius: 10px;
587        }
588        .bottleneck-header { 
589            display: flex; 
590            justify-content: space-between; 
591            align-items: center; 
592            margin-bottom: 10px;
593        }
594        .severity-badge { 
595            background: #e53e3e; 
596            color: white; 
597            padding: 5px 10px; 
598            border-radius: 15px; 
599            font-size: 0.8em;
600        }
601        .interaction { 
602            background: linear-gradient(135deg, #ebf8ff, #bee3f8); 
603            border-left: 5px solid #3182ce; 
604            padding: 20px; 
605            margin: 15px 0; 
606            border-radius: 10px;
607        }
608        .tab-container { margin: 20px 0; }
609        .tab-buttons { 
610            display: flex; 
611            background: #ecf0f1; 
612            border-radius: 10px 10px 0 0; 
613            overflow: hidden;
614        }
615        .tab-button { 
616            background: #ecf0f1; 
617            border: none; 
618            padding: 15px 30px; 
619            cursor: pointer; 
620            transition: all 0.3s;
621        }
622        .tab-button.active { background: #3498db; color: white; }
623        .tab-content { 
624            background: white; 
625            padding: 30px; 
626            border-radius: 0 0 10px 10px; 
627            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
628        }
629        .progress-bar { 
630            background: #ecf0f1; 
631            height: 20px; 
632            border-radius: 10px; 
633            overflow: hidden; 
634            margin: 10px 0;
635        }
636        .progress-fill { 
637            background: linear-gradient(90deg, #27ae60, #2ecc71); 
638            height: 100%; 
639            transition: width 0.3s;
640        }
641        .call-stack-item { 
642            background: #f8f9fa; 
643            padding: 10px; 
644            margin: 5px 0; 
645            border-radius: 5px; 
646            border-left: 3px solid #3498db;
647        }
648        .hash-code { 
649            font-family: 'Courier New', monospace; 
650            background: #2c3e50; 
651            color: #ecf0f1; 
652            padding: 2px 6px; 
653            border-radius: 3px; 
654            font-size: 0.9em;
655        }
656    </style>
657</head>
658<body>
659    <div class="container">
660        <div class="header">
661            <h1>๐Ÿš€ Enhanced Memory Analysis Report</h1>
662            <div class="subtitle">Complete Multi-threaded Memory Tracking Analysis</div>
663        </div>
664        <div class="content">
665"#,
666        );
667
668        // Global Summary Section
669        html.push_str(
670            r#"<div class="section">
671                <h2>๐Ÿ“Š Executive Summary</h2>
672                <div class="stats-grid">"#,
673        );
674
675        html.push_str(&format!(
676            r#"
677                <div class="stat-card">
678                    <div class="stat-number">{}</div>
679                    <div class="stat-label">Threads Analyzed</div>
680                </div>
681                <div class="stat-card">
682                    <div class="stat-number">{}</div>
683                    <div class="stat-label">Total Allocations</div>
684                </div>
685                <div class="stat-card">
686                    <div class="stat-number">{}</div>
687                    <div class="stat-label">Total Deallocations</div>
688                </div>
689                <div class="stat-card">
690                    <div class="stat-number">{:.1} MB</div>
691                    <div class="stat-label">Peak Memory Usage</div>
692                </div>
693                <div class="stat-card">
694                    <div class="stat-number">{}</div>
695                    <div class="stat-label">Unique Call Stacks</div>
696                </div>
697                <div class="stat-card">
698                    <div class="stat-number">{}</div>
699                    <div class="stat-label">Thread Interactions</div>
700                </div>"#,
701            analysis.thread_stats.len(),
702            analysis.summary.total_allocations,
703            analysis.summary.total_deallocations,
704            analysis.summary.peak_memory_usage as f64 / (1024.0 * 1024.0),
705            analysis.summary.unique_call_stacks,
706            analysis.thread_interactions.len()
707        ));
708
709        html.push_str("</div></div>");
710
711        // Enhanced Thread Statistics
712        html.push_str(
713            r#"<div class="section">
714                <h2>๐Ÿงต Detailed Thread Analysis</h2>"#,
715        );
716
717        let mut sorted_threads: Vec<_> = analysis.thread_stats.iter().collect();
718        sorted_threads.sort_by(|a, b| b.1.total_allocations.cmp(&a.1.total_allocations));
719
720        for (thread_id, stats) in sorted_threads.iter().take(15) {
721            // Show top 15 threads
722            let efficiency = if stats.total_allocations > 0 {
723                (stats.total_deallocations as f64 / stats.total_allocations as f64 * 100.0)
724                    .min(100.0)
725            } else {
726                0.0
727            };
728
729            html.push_str(&format!(
730                r#"
731                <div class="thread-detail">
732                    <div class="thread-header">
733                        <div class="thread-title">Thread Analysis</div>
734                        <div class="thread-id">ID: {}</div>
735                    </div>
736                    <div class="metric-row">
737                        <div class="metric">
738                            <div class="metric-value">{}</div>
739                            <div class="metric-name">Allocations</div>
740                        </div>
741                        <div class="metric">
742                            <div class="metric-value">{}</div>
743                            <div class="metric-name">Deallocations</div>
744                        </div>
745                        <div class="metric">
746                            <div class="metric-value">{:.1}%</div>
747                            <div class="metric-name">Memory Efficiency</div>
748                        </div>
749                        <div class="metric">
750                            <div class="metric-value">{:.1} MB</div>
751                            <div class="metric-name">Peak Memory</div>
752                        </div>
753                        <div class="metric">
754                            <div class="metric-value">{:.0} B</div>
755                            <div class="metric-name">Avg Allocation</div>
756                        </div>
757                        <div class="metric">
758                            <div class="metric-value">{}</div>
759                            <div class="metric-name">Call Stack Patterns</div>
760                        </div>
761                    </div>
762                    <div class="progress-bar">
763                        <div class="progress-fill" style="width: {:.1}%"></div>
764                    </div>
765                </div>"#,
766                thread_id,
767                stats.total_allocations,
768                stats.total_deallocations,
769                efficiency,
770                stats.peak_memory as f64 / (1024.0 * 1024.0),
771                stats.avg_allocation_size,
772                stats.allocation_frequency.len(),
773                efficiency
774            ));
775        }
776
777        html.push_str("</div>");
778
779        // Performance Bottlenecks with Enhanced Display
780        html.push_str(
781            r#"<div class="section">
782                <h2>โš ๏ธ Performance Bottlenecks & Issues</h2>"#,
783        );
784
785        if analysis.performance_bottlenecks.is_empty() {
786            html.push_str(
787                r#"<div style="text-align: center; padding: 40px; color: #27ae60;">
788                    <h3>๐ŸŽ‰ No Performance Bottlenecks Detected!</h3>
789                    <p>Your application shows healthy memory allocation patterns.</p>
790                </div>"#,
791            );
792        } else {
793            for bottleneck in &analysis.performance_bottlenecks {
794                let severity_color = match bottleneck.severity {
795                    s if s > 0.8 => "#e53e3e",
796                    s if s > 0.5 => "#dd6b20",
797                    _ => "#f6ad55",
798                };
799
800                html.push_str(&format!(r#"
801                    <div class="bottleneck">
802                        <div class="bottleneck-header">
803                            <h4>{:?}</h4>
804                            <div class="severity-badge" style="background: {}">
805                                Severity: {:.1}/1.0
806                            </div>
807                        </div>
808                        <p><strong>Description:</strong> {}</p>
809                        <p><strong>Thread:</strong> {}, <strong>Call Stack:</strong> <span class="hash-code">0x{:x}</span></p>
810                        <p><strong>Recommendation:</strong> {}</p>
811                    </div>"#,
812                    bottleneck.bottleneck_type,
813                    severity_color,
814                    bottleneck.severity,
815                    bottleneck.description,
816                    bottleneck.thread_id,
817                    bottleneck.call_stack_hash,
818                    bottleneck.suggestion
819                ));
820            }
821        }
822
823        html.push_str("</div>");
824
825        // Thread Interactions Analysis
826        html.push_str(
827            r#"<div class="section">
828                <h2>๐Ÿ”— Thread Interaction Analysis</h2>"#,
829        );
830
831        if analysis.thread_interactions.is_empty() {
832            html.push_str(
833                r#"<div style="text-align: center; padding: 40px; color: #7f8c8d;">
834                    <h3>No Thread Interactions Detected</h3>
835                    <p>Threads are operating independently with minimal shared patterns.</p>
836                </div>"#,
837            );
838        } else {
839            for interaction in analysis.thread_interactions.iter().take(10) {
840                html.push_str(&format!(
841                    r#"
842                    <div class="interaction">
843                        <h4>Thread {} โ†” Thread {}</h4>
844                        <div class="metric-row">
845                            <div class="metric">
846                                <div class="metric-value">{}</div>
847                                <div class="metric-name">Interaction Strength</div>
848                            </div>
849                            <div class="metric">
850                                <div class="metric-value">{}</div>
851                                <div class="metric-name">Shared Patterns</div>
852                            </div>
853                            <div class="metric">
854                                <div class="metric-value">{:?}</div>
855                                <div class="metric-name">Interaction Type</div>
856                            </div>
857                        </div>
858                        <p><strong>Shared Pattern Examples:</strong></p>"#,
859                    interaction.thread_a,
860                    interaction.thread_b,
861                    interaction.interaction_strength,
862                    interaction.shared_patterns.len(),
863                    interaction.interaction_type
864                ));
865
866                for (i, pattern) in interaction.shared_patterns.iter().take(5).enumerate() {
867                    html.push_str(&format!(
868                        r#"<div class="call-stack-item">Pattern {}: <span class="hash-code">0x{:x}</span></div>"#,
869                        i + 1, pattern
870                    ));
871                }
872
873                html.push_str("</div>");
874            }
875        }
876
877        html.push_str("</div>");
878
879        // Hottest Call Stacks with Enhanced Table
880        html.push_str(r#"<div class="section">
881                <h2>๐Ÿ”ฅ Hottest Call Stack Analysis</h2>
882                <p style="margin-bottom: 20px;">The most frequently used and impactful allocation patterns across all threads.</p>
883                <table class="enhanced-table">
884                    <thead>
885                        <tr>
886                            <th>Rank</th>
887                            <th>Call Stack Hash</th>
888                            <th>Frequency</th>
889                            <th>Total Size</th>
890                            <th>Impact Score</th>
891                            <th>Avg Size</th>
892                            <th>Performance Impact</th>
893                        </tr>
894                    </thead>
895                    <tbody>"#);
896
897        for (i, hot_stack) in analysis.hottest_call_stacks.iter().take(25).enumerate() {
898            let avg_size = if hot_stack.total_frequency > 0 {
899                hot_stack.total_size as f64 / hot_stack.total_frequency as f64
900            } else {
901                0.0
902            };
903
904            let impact_level = match hot_stack.impact_score {
905                s if s > 1_000_000 => "๐Ÿ”ด Critical",
906                s if s > 100_000 => "๐ŸŸก High",
907                s if s > 10_000 => "๐ŸŸข Medium",
908                _ => "โšช Low",
909            };
910
911            html.push_str(&format!(
912                r#"
913                        <tr>
914                            <td><strong>{}</strong></td>
915                            <td><span class="hash-code">0x{:x}</span></td>
916                            <td>{}</td>
917                            <td>{:.1} KB</td>
918                            <td>{}</td>
919                            <td>{:.0} B</td>
920                            <td>{}</td>
921                        </tr>"#,
922                i + 1,
923                hot_stack.call_stack_hash,
924                hot_stack.total_frequency,
925                hot_stack.total_size as f64 / 1024.0,
926                hot_stack.impact_score,
927                avg_size,
928                impact_level
929            ));
930        }
931
932        html.push_str("</tbody></table></div>");
933
934        // Memory Peaks Analysis
935        if !analysis.memory_peaks.is_empty() {
936            html.push_str(
937                r#"<div class="section">
938                    <h2>๐Ÿ“ˆ Memory Peak Analysis</h2>
939                    <table class="enhanced-table">
940                        <thead>
941                            <tr>
942                                <th>Timestamp</th>
943                                <th>Thread ID</th>
944                                <th>Memory Usage</th>
945                                <th>Active Allocations</th>
946                                <th>Triggering Call Stack</th>
947                            </tr>
948                        </thead>
949                        <tbody>"#,
950            );
951
952            for peak in analysis.memory_peaks.iter().take(15) {
953                html.push_str(&format!(
954                    r#"
955                            <tr>
956                                <td>{}</td>
957                                <td>{}</td>
958                                <td>{:.2} MB</td>
959                                <td>{}</td>
960                                <td><span class="hash-code">0x{:x}</span></td>
961                            </tr>"#,
962                    peak.timestamp,
963                    peak.thread_id,
964                    peak.memory_usage as f64 / (1024.0 * 1024.0),
965                    peak.active_allocations,
966                    peak.triggering_call_stack
967                ));
968            }
969
970            html.push_str("</tbody></table></div>");
971        }
972
973        // Footer with metadata
974        html.push_str(&format!(
975            r#"
976            <div class="section" style="text-align: center; background: #f8f9fa; color: #7f8c8d;">
977                <h3>๐Ÿ“‹ Report Metadata</h3>
978                <p>Generated on: {}</p>
979                <p>Analysis covered {} threads with {} total operations</p>
980                <p>Memory efficiency: {:.1}% overall</p>
981                <p>Data quality: {} unique call stack patterns detected</p>
982            </div>
983        </div>
984    </div>
985</body>
986</html>"#,
987            std::time::SystemTime::now()
988                .duration_since(std::time::UNIX_EPOCH)
989                .unwrap_or_default()
990                .as_secs(),
991            analysis.thread_stats.len(),
992            analysis.summary.total_allocations + analysis.summary.total_deallocations,
993            if analysis.summary.total_allocations > 0 {
994                analysis.summary.total_deallocations as f64
995                    / analysis.summary.total_allocations as f64
996                    * 100.0
997            } else {
998                0.0
999            },
1000            analysis.summary.unique_call_stacks
1001        ));
1002
1003        Ok(html)
1004    }
1005}