1use crate::lockfree::analysis::{AllocationEvent, EventType, InteractionType};
7use crate::lockfree::tracker::{Event, FrequencyData};
8use bincode;
9use std::collections::HashMap;
11use std::path::Path;
12
13use crate::lockfree::analysis::{
14 BottleneckType, LockfreeAnalysis, MemoryPeak, PerformanceBottleneck, ThreadInteraction,
15 ThreadStats,
16};
17
18pub 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 pub fn aggregate_all_threads(&self) -> Result<LockfreeAnalysis, Box<dyn std::error::Error>> {
30 let mut thread_stats = HashMap::new();
31
32 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 temp_files_to_cleanup.push(event_file);
45 temp_files_to_cleanup.push(freq_file);
46 }
47
48 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(), },
66 )
67 .collect();
68 analysis.thread_interactions = thread_interactions;
69 analysis.memory_peaks = memory_peaks;
70 analysis.performance_bottlenecks = performance_bottlenecks;
71
72 analysis.calculate_summary(std::time::Instant::now());
74
75 self.cleanup_temp_files(&temp_files_to_cleanup)?;
77
78 Ok(analysis)
79 }
80
81 #[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 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 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 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 while offset + 4 <= file_content.len() {
161 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; }
174
175 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 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 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 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 }
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 let allocation_frequency: HashMap<u64, u64> = frequencies
240 .into_iter()
241 .map(|f| (f.call_stack_hash, f.frequency))
242 .collect();
243
244 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 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 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 hottest.sort_by(|a, b| (b.1 * b.2 as u64).cmp(&(a.1 * a.2 as u64)));
292 hottest.truncate(50); hottest
295 }
296
297 fn analyze_thread_interactions(
299 &self,
300 thread_stats: &HashMap<u64, ThreadStats>,
301 ) -> Vec<ThreadInteraction> {
302 let mut interactions = Vec::new();
303
304 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 let mut shared_regions = Vec::new();
317 let mut interaction_count = 0u64;
318
319 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 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 interactions.sort_by(|a, b| b.interaction_strength.cmp(&a.interaction_strength));
342 interactions
343 }
344
345 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 if current_memory > 1024 * 1024 {
361 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 }
375 }
376 }
377 }
378
379 peaks.sort_by(|a, b| b.memory_usage.cmp(&a.memory_usage));
381 peaks.truncate(100); peaks
384 }
385
386 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 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 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, 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 if stats.peak_memory > 100 * 1024 * 1024 {
430 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), 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 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 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 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 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 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 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 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 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 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 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 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}