memscope_rs/core/tracker/
export_html.rs1use super::memory_tracker::MemoryTracker;
7use crate::core::types::TrackingResult;
8use std::path::Path;
9
10impl MemoryTracker {
11 pub fn export_interactive_dashboard<P: AsRef<Path>>(&self, path: P) -> TrackingResult<()> {
48 let output_path = self.ensure_memory_analysis_path(path);
49
50 crate::export::html_export::export_interactive_html(self, None, output_path)
52 }
53
54 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 crate::export::html_export::export_interactive_html(self, unsafe_ffi_tracker, output_path)
84 }
85
86 pub fn export_html_summary<P: AsRef<Path>>(&self, path: P) -> TrackingResult<()> {
109 let output_path = self.ensure_memory_analysis_path(path);
110
111 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 let html_content =
118 self.generate_summary_html(&stats, &memory_by_type, &active_allocations)?;
119
120 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 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 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 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 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 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 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 fn create_test_tracker() -> MemoryTracker {
329 MemoryTracker::new()
330 }
331
332 fn create_test_data() -> (MemoryStats, HashMap<String, TypeMemoryUsage>) {
334 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 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, },
405 );
406
407 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, },
419 );
420
421 (stats, type_usage)
422 }
423
424 #[test]
425 fn test_export_html_summary() -> TrackingResult<()> {
426 let tracker = create_test_tracker();
428 let temp_dir = tempdir()?;
429 let output_path = temp_dir.path().join("summary.html");
430
431 tracker.export_html_summary(&output_path)?;
433
434 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 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 let (mut stats, type_usage) = create_test_data();
466 let memory_by_type: Vec<_> = type_usage.into_values().collect();
467
468 let recommendations = tracker.generate_recommendations_html(&stats, &memory_by_type);
470 assert!(!recommendations.is_empty());
471 assert!(recommendations.contains("<li>"));
472
473 assert!(
475 recommendations.contains("memory efficiency")
476 || recommendations.contains("No immediate optimizations")
477 || recommendations.contains("large average allocations")
478 );
479
480 stats.total_allocated = 1000;
482 stats.active_memory = 600; let frag_recommendations = tracker.generate_recommendations_html(&stats, &memory_by_type);
484 assert!(frag_recommendations.contains("fragmentation"));
485
486 let mut alloc_stats = stats.clone();
488 alloc_stats.total_allocations = 1000;
489 alloc_stats.total_deallocations = 300; 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 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 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}