memscope_rs/core/tracker/
export_html.rs

1//! HTML export functionality for memory tracking data.
2//!
3//! This module contains methods for exporting memory tracking data to HTML format,
4//! including interactive dashboards with embedded visualizations.
5
6use super::memory_tracker::MemoryTracker;
7use crate::core::types::TrackingResult;
8use std::path::Path;
9
10impl MemoryTracker {
11    /// Export interactive HTML dashboard with embedded SVG charts.
12    ///
13    /// This method creates a comprehensive HTML dashboard that includes:
14    /// - Memory usage statistics and charts
15    /// - Allocation timeline visualization
16    /// - Type-based memory analysis
17    /// - Interactive filtering and sorting
18    /// - Embedded CSS and JavaScript for offline viewing
19    ///
20    /// # Arguments
21    ///
22    /// * `path` - The output path for the HTML file
23    ///
24    /// # Examples
25    ///
26    /// ```text
27    /// // Export to default location
28    /// tracker.export_interactive_dashboard("memory_report.html")?;
29    ///
30    /// // Export to specific directory
31    /// tracker.export_interactive_dashboard("reports/detailed_analysis.html")?;
32    /// ```
33    ///
34    /// # Features
35    ///
36    /// - **Self-contained**: All CSS, JavaScript, and data embedded in single HTML file
37    /// - **Interactive**: Click, filter, and drill down into memory data
38    /// - **Responsive**: Works on desktop and mobile browsers
39    /// - **Offline**: No external dependencies, works without internet connection
40    /// - **Comprehensive**: Includes all major memory analysis views
41    ///
42    /// # Performance
43    ///
44    /// HTML export is generally fast (1-3 seconds) as it focuses on visualization
45    /// rather than comprehensive data processing. The file size depends on the
46    /// amount of tracking data but is typically 1-10MB.
47    pub fn export_interactive_dashboard<P: AsRef<Path>>(&self, path: P) -> TrackingResult<()> {
48        let output_path = self.ensure_memory_analysis_path(path);
49
50        // Delegate to the specialized HTML export module
51        crate::export::html_export::export_interactive_html(self, None, output_path)
52    }
53
54    /// Export HTML dashboard with custom unsafe FFI tracker data.
55    ///
56    /// This method allows including custom unsafe FFI analysis data in the HTML export,
57    /// providing enhanced security and safety analysis in the dashboard.
58    ///
59    /// # Arguments
60    ///
61    /// * `path` - The output path for the HTML file
62    /// * `unsafe_ffi_tracker` - Optional unsafe FFI tracker for enhanced analysis
63    ///
64    /// # Examples
65    ///
66    /// ```text
67    /// use memscope_rs::get_global_unsafe_ffi_tracker;
68    ///
69    /// let unsafe_tracker = get_global_unsafe_ffi_tracker();
70    /// tracker.export_interactive_dashboard_with_ffi(
71    ///     "enhanced_report.html",
72    ///     Some(&unsafe_tracker)
73    /// )?;
74    /// ```
75    pub fn export_interactive_dashboard_with_ffi<P: AsRef<Path>>(
76        &self,
77        path: P,
78        unsafe_ffi_tracker: Option<&crate::analysis::unsafe_ffi_tracker::UnsafeFFITracker>,
79    ) -> TrackingResult<()> {
80        let output_path = self.ensure_memory_analysis_path(path);
81
82        // Delegate to the specialized HTML export module with FFI data
83        crate::export::html_export::export_interactive_html(self, unsafe_ffi_tracker, output_path)
84    }
85
86    /// Generate HTML summary report with key metrics.
87    ///
88    /// This method creates a lightweight HTML summary that focuses on key metrics
89    /// and insights rather than comprehensive data visualization.
90    ///
91    /// # Arguments
92    ///
93    /// * `path` - The output path for the HTML summary
94    ///
95    /// # Examples
96    ///
97    /// ```text
98    /// // Generate quick summary report
99    /// tracker.export_html_summary("summary.html")?;
100    /// ```
101    ///
102    /// # Features
103    ///
104    /// - **Lightweight**: Smaller file size, faster generation
105    /// - **Key metrics**: Focus on most important memory statistics
106    /// - **Executive summary**: High-level insights and recommendations
107    /// - **Quick loading**: Optimized for fast viewing and sharing
108    pub fn export_html_summary<P: AsRef<Path>>(&self, path: P) -> TrackingResult<()> {
109        let output_path = self.ensure_memory_analysis_path(path);
110
111        // Generate summary data
112        let stats = self.get_stats()?;
113        let memory_by_type = self.get_memory_by_type()?;
114        let active_allocations = self.get_active_allocations()?;
115
116        // Create summary HTML content
117        let html_content =
118            self.generate_summary_html(&stats, &memory_by_type, &active_allocations)?;
119
120        // Write to file
121        std::fs::write(&output_path, html_content)
122            .map_err(|e| crate::core::types::TrackingError::IoError(e.to_string()))?;
123
124        tracing::info!("📊 HTML summary exported to: {}", output_path.display());
125        Ok(())
126    }
127
128    // Private helper methods
129
130    /// Generate HTML content for summary report
131    fn generate_summary_html(
132        &self,
133        stats: &crate::core::types::MemoryStats,
134        memory_by_type: &[crate::core::types::TypeMemoryUsage],
135        active_allocations: &[crate::core::types::AllocationInfo],
136    ) -> TrackingResult<String> {
137        // Calculate key metrics
138        let total_types = memory_by_type.len();
139        let avg_allocation_size = if stats.total_allocations > 0 {
140            stats.total_allocated / stats.total_allocations
141        } else {
142            0
143        };
144
145        let memory_efficiency = if stats.peak_memory > 0 {
146            (stats.active_memory as f64 / stats.peak_memory as f64) * 100.0
147        } else {
148            100.0
149        };
150
151        // Find top memory consumers
152        let mut top_types: Vec<_> = memory_by_type.iter().collect();
153        top_types.sort_by(|a, b| b.total_size.cmp(&a.total_size));
154        let top_5_types: Vec<_> = top_types.into_iter().take(5).collect();
155
156        // Generate HTML
157        let html = format!(
158            r#"<!DOCTYPE html>
159<html lang="en">
160<head>
161    <meta charset="UTF-8">
162    <meta name="viewport" content="width=device-width, initial-scale=1.0">
163    <title>Memory Analysis Summary</title>
164    <style>
165        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }}
166        .container {{ max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
167        .header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 8px 8px 0 0; }}
168        .header h1 {{ margin: 0; font-size: 2.5em; font-weight: 300; }}
169        .header p {{ margin: 10px 0 0 0; opacity: 0.9; }}
170        .content {{ padding: 30px; }}
171        .metrics {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }}
172        .metric {{ background: #f8f9fa; padding: 20px; border-radius: 6px; border-left: 4px solid #667eea; }}
173        .metric h3 {{ margin: 0 0 10px 0; color: #333; font-size: 0.9em; text-transform: uppercase; letter-spacing: 1px; }}
174        .metric .value {{ font-size: 2em; font-weight: bold; color: #667eea; }}
175        .metric .unit {{ font-size: 0.8em; color: #666; }}
176        .section {{ margin-bottom: 30px; }}
177        .section h2 {{ color: #333; border-bottom: 2px solid #eee; padding-bottom: 10px; }}
178        .type-list {{ list-style: none; padding: 0; }}
179        .type-item {{ display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee; }}
180        .type-name {{ font-weight: 500; }}
181        .type-size {{ color: #667eea; font-weight: bold; }}
182        .recommendations {{ background: #e8f5e8; border: 1px solid #c3e6c3; border-radius: 6px; padding: 20px; }}
183        .recommendations h3 {{ color: #2d5a2d; margin-top: 0; }}
184        .recommendations ul {{ margin: 0; }}
185        .footer {{ text-align: center; padding: 20px; color: #666; font-size: 0.9em; }}
186    </style>
187</head>
188<body>
189    <div class="container">
190        <div class="header">
191            <h1>Memory Analysis Summary</h1>
192            <p>Generated on {}</p>
193        </div>
194        
195        <div class="content">
196            <div class="metrics">
197                <div class="metric">
198                    <h3>Total Memory</h3>
199                    <div class="value">{}</div>
200                    <div class="unit">bytes allocated</div>
201                </div>
202                <div class="metric">
203                    <h3>Active Memory</h3>
204                    <div class="value">{}</div>
205                    <div class="unit">bytes in use</div>
206                </div>
207                <div class="metric">
208                    <h3>Peak Memory</h3>
209                    <div class="value">{}</div>
210                    <div class="unit">bytes maximum</div>
211                </div>
212                <div class="metric">
213                    <h3>Memory Efficiency</h3>
214                    <div class="value">{:.1}%</div>
215                    <div class="unit">active/peak ratio</div>
216                </div>
217                <div class="metric">
218                    <h3>Total Allocations</h3>
219                    <div class="value">{}</div>
220                    <div class="unit">allocation calls</div>
221                </div>
222                <div class="metric">
223                    <h3>Active Allocations</h3>
224                    <div class="value">{}</div>
225                    <div class="unit">currently active</div>
226                </div>
227                <div class="metric">
228                    <h3>Unique Types</h3>
229                    <div class="value">{}</div>
230                    <div class="unit">different types</div>
231                </div>
232                <div class="metric">
233                    <h3>Avg Allocation</h3>
234                    <div class="value">{}</div>
235                    <div class="unit">bytes average</div>
236                </div>
237            </div>
238
239            <div class="section">
240                <h2>Top Memory Consumers</h2>
241                <ul class="type-list">
242                    {}
243                </ul>
244            </div>
245
246            <div class="recommendations">
247                <h3>💡 Optimization Recommendations</h3>
248                <ul>
249                    {}
250                </ul>
251            </div>
252        </div>
253
254        <div class="footer">
255            Generated by memscope-rs v{} • {} active allocations analyzed
256        </div>
257    </div>
258</body>
259</html>"#,
260            chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
261            self.format_bytes(stats.total_allocated),
262            self.format_bytes(stats.active_memory),
263            self.format_bytes(stats.peak_memory),
264            memory_efficiency,
265            stats.total_allocations,
266            stats.active_allocations,
267            total_types,
268            self.format_bytes(avg_allocation_size),
269            top_5_types.iter().map(|t| format!(
270                r#"<li class="type-item"><span class="type-name">{}</span><span class="type-size">{}</span></li>"#,
271                t.type_name,
272                self.format_bytes(t.total_size)
273            )).collect::<Vec<_>>().join(""),
274            self.generate_recommendations_html(stats, memory_by_type),
275            env!("CARGO_PKG_VERSION"),
276            active_allocations.len()
277        );
278
279        Ok(html)
280    }
281
282    /// Format bytes in human-readable format
283    fn format_bytes(&self, bytes: usize) -> String {
284        const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
285        let mut size = bytes as f64;
286        let mut unit_index = 0;
287
288        while size >= 1024.0 && unit_index < UNITS.len() - 1 {
289            size /= 1024.0;
290            unit_index += 1;
291        }
292
293        if unit_index == 0 {
294            format!("{} {}", bytes, UNITS[unit_index])
295        } else {
296            format!("{:.1} {}", size, UNITS[unit_index])
297        }
298    }
299
300    /// Generate recommendations HTML
301    fn generate_recommendations_html(
302        &self,
303        stats: &crate::core::types::MemoryStats,
304        memory_by_type: &[crate::core::types::TypeMemoryUsage],
305    ) -> String {
306        let recommendations =
307            super::export_json::generate_optimization_recommendations(stats, memory_by_type);
308
309        recommendations
310            .iter()
311            .map(|rec| format!("<li>{rec}</li>"))
312            .collect::<Vec<_>>()
313            .join("")
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use crate::core::types::{
321        ConcurrencyAnalysis, FragmentationAnalysis, LibraryUsage, MemoryStats,
322        ScopeLifecycleMetrics, SystemLibraryStats, TypeMemoryUsage,
323    };
324    use std::collections::HashMap;
325    use tempfile::tempdir;
326
327    // Helper function to create a test MemoryTracker instance with sample data
328    fn create_test_tracker() -> MemoryTracker {
329        MemoryTracker::new()
330    }
331
332    // Create test data
333    fn create_test_data() -> (MemoryStats, HashMap<String, TypeMemoryUsage>) {
334        // Create a default LibraryUsage with some test data
335        let create_test_library_usage = || LibraryUsage {
336            allocation_count: 10,
337            total_bytes: 1024,
338            peak_bytes: 2048,
339            average_size: 102.4,
340            categories: HashMap::new(),
341            hotspot_functions: vec![],
342        };
343
344        let stats = MemoryStats {
345            total_allocations: 10,
346            total_allocated: 1024,
347            active_allocations: 5,
348            active_memory: 512,
349            peak_allocations: 10,
350            peak_memory: 1024,
351            total_deallocations: 5,
352            total_deallocated: 512,
353            leaked_allocations: 0,
354            leaked_memory: 0,
355            allocations: vec![],
356            fragmentation_analysis: FragmentationAnalysis {
357                fragmentation_ratio: 0.0,
358                largest_free_block: 0,
359                smallest_free_block: 0,
360                free_block_count: 0,
361                total_free_memory: 0,
362                external_fragmentation: 0.0,
363                internal_fragmentation: 0.0,
364            },
365            lifecycle_stats: ScopeLifecycleMetrics::default(),
366            system_library_stats: SystemLibraryStats {
367                std_collections: create_test_library_usage(),
368                async_runtime: create_test_library_usage(),
369                network_io: create_test_library_usage(),
370                file_system: create_test_library_usage(),
371                serialization: create_test_library_usage(),
372                regex_engine: create_test_library_usage(),
373                crypto_security: create_test_library_usage(),
374                database: create_test_library_usage(),
375                graphics_ui: create_test_library_usage(),
376                http_stack: create_test_library_usage(),
377            },
378            concurrency_analysis: ConcurrencyAnalysis {
379                thread_safety_allocations: 0,
380                shared_memory_bytes: 0,
381                mutex_protected: 0,
382                arc_shared: 0,
383                rc_shared: 0,
384                channel_buffers: 0,
385                thread_local_storage: 0,
386                atomic_operations: 0,
387                lock_contention_risk: "low".to_string(),
388            },
389        };
390
391        let mut type_usage = HashMap::new();
392
393        // Add a String type
394        type_usage.insert(
395            "String".to_string(),
396            TypeMemoryUsage {
397                type_name: "String".to_string(),
398                total_size: 1024,
399                allocation_count: 10,
400                average_size: 102.4,
401                peak_size: 1024,
402                current_size: 512,
403                efficiency_score: 0.5, // Lower score to trigger recommendations
404            },
405        );
406
407        // Add a Vec<u8> type to test the recommendation
408        type_usage.insert(
409            "Vec<u8>".to_string(),
410            TypeMemoryUsage {
411                type_name: "Vec<u8>".to_string(),
412                total_size: 2048,
413                allocation_count: 20,
414                average_size: 102.4,
415                peak_size: 2048,
416                current_size: 1024,
417                efficiency_score: 0.3, // Low score to ensure it triggers recommendations
418            },
419        );
420
421        (stats, type_usage)
422    }
423
424    #[test]
425    fn test_export_html_summary() -> TrackingResult<()> {
426        // Setup
427        let tracker = create_test_tracker();
428        let temp_dir = tempdir()?;
429        let output_path = temp_dir.path().join("summary.html");
430
431        // Test
432        tracker.export_html_summary(&output_path)?;
433
434        // Verify
435        assert!(output_path.exists(), "Summary HTML file was not created");
436        let content = std::fs::read_to_string(&output_path)?;
437        assert!(!content.is_empty(), "Summary HTML file is empty");
438        assert!(
439            content.contains("Memory Analysis Summary"),
440            "Summary title not found"
441        );
442
443        Ok(())
444    }
445
446    #[test]
447    fn test_format_bytes() {
448        let tracker = create_test_tracker();
449
450        // Test various byte sizes
451        assert_eq!(tracker.format_bytes(0), "0 B");
452        assert_eq!(tracker.format_bytes(1023), "1023 B");
453        assert_eq!(tracker.format_bytes(1024), "1.0 KB");
454        assert_eq!(tracker.format_bytes(1536), "1.5 KB");
455        assert_eq!(tracker.format_bytes(1024 * 1024), "1.0 MB");
456        assert_eq!(tracker.format_bytes(1024 * 1024 * 1024), "1.0 GB");
457        assert_eq!(tracker.format_bytes(1024 * 1024 * 1024 * 5), "5.0 GB");
458    }
459
460    #[test]
461    fn test_generate_recommendations_html() {
462        let tracker = create_test_tracker();
463
464        // Create test data
465        let (mut stats, type_usage) = create_test_data();
466        let memory_by_type: Vec<_> = type_usage.into_values().collect();
467
468        // Test with initial data
469        let recommendations = tracker.generate_recommendations_html(&stats, &memory_by_type);
470        assert!(!recommendations.is_empty());
471        assert!(recommendations.contains("<li>"));
472
473        // Check for memory efficiency or no optimizations needed
474        assert!(
475            recommendations.contains("memory efficiency")
476                || recommendations.contains("No immediate optimizations")
477                || recommendations.contains("large average allocations")
478        );
479
480        // Test with high fragmentation
481        stats.total_allocated = 1000;
482        stats.active_memory = 600; // 40% fragmentation
483        let frag_recommendations = tracker.generate_recommendations_html(&stats, &memory_by_type);
484        assert!(frag_recommendations.contains("fragmentation"));
485
486        // Test with high allocation-to-deallocation ratio
487        let mut alloc_stats = stats.clone();
488        alloc_stats.total_allocations = 1000;
489        alloc_stats.total_deallocations = 300; // High ratio
490        let alloc_recommendations =
491            tracker.generate_recommendations_html(&alloc_stats, &memory_by_type);
492        assert!(alloc_recommendations.contains("allocation-to-deallocation"));
493    }
494
495    #[test]
496    fn test_export_with_invalid_path() {
497        let tracker = create_test_tracker();
498
499        // Try to write to a directory that should be read-only
500        let result = tracker.export_interactive_dashboard("/proc/invalid/path/dashboard.html");
501
502        assert!(
503            result.is_err(),
504            "Expected error when writing to invalid path"
505        );
506    }
507
508    #[test]
509    fn test_generate_summary_html_empty_data() -> TrackingResult<()> {
510        let tracker = create_test_tracker();
511
512        let stats = MemoryStats::default();
513        let memory_by_type = Vec::new();
514        let active_allocations = Vec::new();
515
516        // Should not panic with empty data
517        let html = tracker.generate_summary_html(&stats, &memory_by_type, &active_allocations)?;
518
519        assert!(!html.is_empty());
520        assert!(html.contains("Memory Analysis Summary"));
521
522        Ok(())
523    }
524}