1#![allow(dead_code)]
6use clap::ArgMatches;
7use std::error::Error;
8use std::path::Path;
9use std::process::{Command, Stdio};
10
11pub fn run_analyze(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
13 let command_args: Vec<&String> = matches
15 .get_many::<String>("command")
16 .ok_or("Command argument is required")?
17 .collect();
18 let export_format = matches
19 .get_one::<String>("export")
20 .map(|s| s.as_str())
21 .unwrap_or("html");
22 let output_path = matches
23 .get_one::<String>("output")
24 .map(|s| s.as_str())
25 .unwrap_or("memory_analysis");
26
27 let tracking_mode = matches
29 .get_one::<String>("mode")
30 .map(|s| s.as_str())
31 .unwrap_or("auto");
32 let strategy = matches
33 .get_one::<String>("strategy")
34 .map(|s| s.as_str())
35 .unwrap_or("auto");
36 let sample_rate = matches
37 .get_one::<f64>("sample-rate")
38 .copied()
39 .unwrap_or(1.0);
40
41 tracing::info!("🔍 Starting memory analysis...");
42 tracing::info!("Command: {:?}", command_args);
43 tracing::info!("Tracking mode: {}", tracking_mode);
44 tracing::info!("Strategy: {}", strategy);
45 tracing::info!("Sample rate: {:.2}", sample_rate);
46 tracing::info!("Export format: {}", export_format);
47 tracing::info!("Output path: {}", output_path);
48
49 match tracking_mode {
51 "unified" => {
52 run_unified_analysis(
53 &command_args,
54 strategy,
55 sample_rate,
56 export_format,
57 output_path,
58 )?;
59 }
60 "legacy" => {
61 run_legacy_analysis(&command_args, export_format, output_path)?;
62 }
63 "auto" => {
64 if should_use_unified_backend(&command_args) {
66 tracing::info!("🤖 Auto-detected: Using unified backend");
67 run_unified_analysis(
68 &command_args,
69 strategy,
70 sample_rate,
71 export_format,
72 output_path,
73 )?;
74 } else {
75 tracing::info!("🤖 Auto-detected: Using legacy backend");
76 run_legacy_analysis(&command_args, export_format, output_path)?;
77 }
78 }
79 _ => {
80 return Err(format!("Unsupported tracking mode: {}", tracking_mode).into());
81 }
82 }
83
84 Ok(())
85}
86
87fn run_unified_analysis(
89 command_args: &[&String],
90 strategy: &str,
91 sample_rate: f64,
92 export_format: &str,
93 output_path: &str,
94) -> Result<(), Box<dyn Error>> {
95 use crate::unified::{detect_environment, BackendConfig, UnifiedBackend};
96
97 tracing::info!("🔧 Initializing unified backend...");
98
99 let detected_environment = detect_environment()?;
101 tracing::info!("🌍 Environment detected: {:?}", detected_environment);
102
103 let config = BackendConfig {
105 auto_detect: strategy == "auto",
106 force_strategy: None,
107 sample_rate,
108 max_overhead_percent: 5.0,
109 };
110
111 let mut backend = UnifiedBackend::initialize(config)?;
113
114 let session = backend.start_tracking()?;
116 tracing::info!(
117 "✅ Unified tracking session started: {}",
118 session.session_id()
119 );
120
121 let result = execute_with_unified_tracking(command_args, session.session_id())?;
123
124 let analysis_data = backend.collect_data()?;
126 let tracking_data = analysis_data.raw_data;
127
128 tracing::info!(
129 "📊 Analysis completed. Collected {} bytes of data",
130 tracking_data.len()
131 );
132
133 export_unified_results(&tracking_data, export_format, output_path)?;
135
136 if result.success() {
137 tracing::info!("🎉 Unified analysis completed successfully");
138 Ok(())
139 } else {
140 Err(format!("Command failed with exit code: {:?}", result.code()).into())
141 }
142}
143
144fn run_legacy_analysis(
146 command_args: &[&String],
147 export_format: &str,
148 output_path: &str,
149) -> Result<(), Box<dyn Error>> {
150 tracing::info!("🔧 Initializing legacy backend...");
151
152 crate::init();
154
155 execute_with_tracking(command_args, &[])?;
157
158 tracing::info!("📊 Legacy analysis completed");
159 tracing::info!("💾 Results exported to: {}.{}", output_path, export_format);
160
161 Ok(())
162}
163
164fn should_use_unified_backend(command_args: &[&String]) -> bool {
166 if command_args.is_empty() {
167 return false;
168 }
169
170 let command = command_args[0].as_str();
171
172 if command.contains("tokio") || command.contains("async-std") {
174 tracing::debug!("Detected async runtime, recommending unified backend");
175 return true;
176 }
177
178 if command_args
180 .iter()
181 .any(|arg| arg.contains("--jobs") || arg.contains("-j") || arg.contains("parallel"))
182 {
183 tracing::debug!("Detected multi-threading hints, recommending unified backend");
184 return true;
185 }
186
187 if command == "cargo" && command_args.len() > 1 {
189 let subcommand = command_args[1].as_str();
190 match subcommand {
191 "test" | "bench" | "run" => {
192 tracing::debug!(
193 "Detected cargo {}, recommending unified backend",
194 subcommand
195 );
196 return true;
197 }
198 _ => {}
199 }
200 }
201
202 tracing::debug!("No unified backend indicators found, using legacy");
204 false
205}
206
207fn execute_with_unified_tracking(
209 command_args: &[&String],
210 session_id: &str,
211) -> Result<std::process::ExitStatus, Box<dyn Error>> {
212 if command_args.is_empty() {
213 return Err("No command provided".into());
214 }
215
216 let program = command_args[0];
217 let args = &command_args[1..];
218
219 let mut cmd = Command::new(program);
220 cmd.args(args);
221 cmd.stdout(Stdio::inherit());
222 cmd.stderr(Stdio::inherit());
223
224 cmd.env("MEMSCOPE_UNIFIED_ENABLED", "1");
226 cmd.env("MEMSCOPE_SESSION_ID", session_id);
227 cmd.env("MEMSCOPE_AUTO_EXPORT", "1");
228
229 let args_str = args
230 .iter()
231 .map(|s| s.as_str())
232 .collect::<Vec<_>>()
233 .join(" ");
234 tracing::info!("🔄 Executing: {} {}", program, args_str);
235
236 let status = cmd.status()?;
237 Ok(status)
238}
239
240fn export_unified_results(
242 tracking_data: &[u8],
243 export_format: &str,
244 output_path: &str,
245) -> Result<(), Box<dyn Error>> {
246 match export_format {
247 "json" => {
248 let json_path = format!("{}.json", output_path);
249 std::fs::write(&json_path, tracking_data)?;
250 tracing::info!("✅ JSON export completed: {}", json_path);
251 }
252 "html" => {
253 let html_path = format!("{}.html", output_path);
254 let html_content = generate_unified_html_report(tracking_data)?;
256 std::fs::write(&html_path, html_content)?;
257 tracing::info!("✅ HTML export completed: {}", html_path);
258 }
259 "svg" => {
260 let svg_path = format!("{}.svg", output_path);
261 let svg_content = generate_unified_svg_visualization(tracking_data)?;
263 std::fs::write(&svg_path, svg_content)?;
264 tracing::info!("✅ SVG export completed: {}", svg_path);
265 }
266 _ => {
267 tracing::warn!(
268 "Unsupported export format: {}, defaulting to JSON",
269 export_format
270 );
271 let json_path = format!("{}.json", output_path);
272 std::fs::write(&json_path, tracking_data)?;
273 }
274 }
275
276 Ok(())
277}
278
279fn generate_unified_html_report(tracking_data: &[u8]) -> Result<String, Box<dyn Error>> {
281 let html_content = format!(
282 r#"<!DOCTYPE html>
283<html lang="en">
284<head>
285 <meta charset="UTF-8">
286 <meta name="viewport" content="width=device-width, initial-scale=1.0">
287 <title>Unified Memory Analysis Report</title>
288 <style>
289 body {{ font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }}
290 .container {{ max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
291 .header {{ text-align: center; color: #333; border-bottom: 2px solid #007acc; padding-bottom: 10px; }}
292 .metric {{ display: inline-block; margin: 10px; padding: 15px; background: #e8f4ff; border-radius: 5px; }}
293 .metric-label {{ font-weight: bold; color: #007acc; }}
294 .metric-value {{ font-size: 1.2em; color: #333; }}
295 </style>
296</head>
297<body>
298 <div class="container">
299 <div class="header">
300 <h1>🔍 Unified Memory Analysis Report</h1>
301 <p>Generated with MemScope Unified Backend</p>
302 </div>
303 <div class="metrics">
304 <div class="metric">
305 <div class="metric-label">Data Collected</div>
306 <div class="metric-value">{} bytes</div>
307 </div>
308 <div class="metric">
309 <div class="metric-label">Backend Type</div>
310 <div class="metric-value">Unified</div>
311 </div>
312 <div class="metric">
313 <div class="metric-label">Analysis Status</div>
314 <div class="metric-value">✅ Complete</div>
315 </div>
316 </div>
317 <div class="content">
318 <h2>📊 Analysis Summary</h2>
319 <p>This report was generated using the unified memory tracking backend, providing comprehensive analysis across single-threaded, multi-threaded, and async execution contexts.</p>
320 <h3>🚀 Features Used</h3>
321 <ul>
322 <li>Intelligent strategy selection</li>
323 <li>Cross-context memory tracking</li>
324 <li>Performance-optimized data collection</li>
325 <li>Unified export format</li>
326 </ul>
327 </div>
328 </div>
329</body>
330</html>"#,
331 tracking_data.len()
332 );
333
334 Ok(html_content)
335}
336
337fn generate_unified_svg_visualization(tracking_data: &[u8]) -> Result<String, Box<dyn Error>> {
339 let data_size = tracking_data.len();
340 let svg_content = format!(
341 r#"<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
342 <rect width="800" height="600" fill="{}"/>
343 <text x="400" y="50" text-anchor="middle" font-family="Arial" font-size="24" fill="{}">
344 Unified Memory Analysis
345 </text>
346 <text x="400" y="80" text-anchor="middle" font-family="Arial" font-size="14" fill="{}">
347 Data Size: {} bytes
348 </text>
349 <rect x="100" y="150" width="600" height="300" fill="{}" stroke="{}" stroke-width="2"/>
350 <text x="400" y="320" text-anchor="middle" font-family="Arial" font-size="16" fill="{}">
351 Unified Backend Analysis Complete
352 </text>
353 </svg>"#,
354 "#f8f9fa", "#333", "#666", data_size, "#e8f4fd", "#1976d2", "#1976d2"
355 );
356
357 Ok(svg_content)
358}
359
360fn _original_main() {
361 use clap::{Arg, Command as ClapCommand};
362 let matches = ClapCommand::new("memscope")
363 .version("0.1.0")
364 .author("MemScope Team")
365 .about("Memory analysis tool for Rust programs")
366 .subcommand(
367 ClapCommand::new("run")
368 .about("Run and track program memory")
369 .arg(
370 Arg::new("command")
371 .help("Command to run (e.g., 'cargo run --release')")
372 .required(true)
373 .num_args(1..),
374 )
375 .arg(
376 Arg::new("export")
377 .long("export")
378 .value_name("FORMAT")
379 .help("Export format: json, html, or both")
380 .value_parser(["json", "html", "both"])
381 .default_value("json"),
382 )
383 .arg(
384 Arg::new("output")
385 .short('o')
386 .long("output")
387 .value_name("PATH")
388 .help("Output file path (without extension)")
389 .default_value("memscope_analysis"),
390 )
391 .arg(
392 Arg::new("auto-track")
393 .long("auto-track")
394 .help("Automatically track all allocations")
395 .action(clap::ArgAction::SetTrue),
396 )
397 .arg(
398 Arg::new("wait-completion")
399 .long("wait-completion")
400 .help("Wait for program completion before exporting")
401 .action(clap::ArgAction::SetTrue),
402 ),
403 )
404 .subcommand(
405 ClapCommand::new("analyze")
406 .about("Analyze existing memory snapshot")
407 .arg(Arg::new("input").help("Input JSON file").required(true))
408 .arg(Arg::new("output").help("Output HTML file").required(true))
409 .arg(
410 Arg::new("format")
411 .long("format")
412 .value_name("FORMAT")
413 .help("Output format: html, svg, or both")
414 .value_parser(["html", "svg", "both"])
415 .default_value("html"),
416 ),
417 )
418 .arg(
420 Arg::new("export")
421 .long("export")
422 .value_name("FORMAT")
423 .help("Export format: json, html, or both (legacy mode)")
424 .value_parser(["json", "html", "both"]),
425 )
426 .arg(
427 Arg::new("output")
428 .short('o')
429 .long("output")
430 .value_name("PATH")
431 .help("Output file path (legacy mode)")
432 .default_value("memscope_analysis"),
433 )
434 .arg(
435 Arg::new("auto-track")
436 .long("auto-track")
437 .help("Automatically track all allocations (legacy mode)")
438 .action(clap::ArgAction::SetTrue),
439 )
440 .arg(
441 Arg::new("command")
442 .help("Command to run (legacy mode)")
443 .num_args(1..),
444 )
445 .get_matches();
446
447 tracing::info!("🚀 MemScope - Memory Analysis Tool");
448
449 match matches.subcommand() {
450 Some(("run", _sub_matches)) => {
451 tracing::info!("Run command is deprecated. Use 'analyze' instead.");
453 }
454 Some(("analyze", sub_matches)) => {
455 if let Err(e) = handle_analyze_command(sub_matches) {
456 eprintln!("Error in analyze command: {e}");
457 std::process::exit(1);
458 }
459 }
460 _ => {
461 if let Err(e) = handle_legacy_mode(&matches) {
463 eprintln!("Error in legacy mode: {e}");
464 std::process::exit(1);
465 }
466 }
467 }
468}
469
470fn handle_run_command(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
471 let command_args: Vec<&String> = matches
472 .get_many::<String>("command")
473 .ok_or("Missing command arguments")?
474 .collect();
475 let export_format = matches
476 .get_one::<String>("export")
477 .ok_or("Missing export format")?;
478 let output_path = matches
479 .get_one::<String>("output")
480 .ok_or("Missing output path")?;
481 let auto_track = matches.get_flag("auto-track");
482 let wait_completion = matches.get_flag("wait-completion");
483
484 tracing::info!(
485 "📋 Command: {}",
486 command_args
487 .iter()
488 .map(|s| s.as_str())
489 .collect::<Vec<_>>()
490 .join(" ")
491 );
492 tracing::info!("📊 Export format: {export_format}");
493 tracing::info!("📁 Output path: {output_path}");
494
495 if auto_track {
496 tracing::info!("🔍 Auto-tracking enabled");
497 }
498
499 if wait_completion {
500 tracing::info!("⏳ Wait-for-completion enabled");
501 }
502
503 let mut env_vars = vec![
505 ("MEMSCOPE_ENABLED", "1"),
506 ("MEMSCOPE_AUTO_EXPORT", "1"),
507 ("MEMSCOPE_EXPORT_FORMAT", export_format),
508 ("MEMSCOPE_EXPORT_PATH", output_path),
509 ];
510
511 if auto_track {
512 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
513 }
514
515 if wait_completion {
516 env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
517 }
518
519 let result = execute_with_tracking(&command_args, &env_vars);
521
522 match result {
523 Ok(()) => {
524 tracing::info!("✅ Program execution completed successfully");
525 tracing::info!("📊 Memory analysis exported to: {output_path}");
526
527 Ok(())
530 }
531 Err(e) => {
532 tracing::error!("❌ Program execution failed: {e}");
533 Err(e)
534 }
535 }
536}
537
538fn handle_analyze_command(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
539 let input_path = matches
540 .get_one::<String>("input")
541 .ok_or("Missing input path")?;
542 let output_path = matches
543 .get_one::<String>("output")
544 .ok_or("Missing output path")?;
545 let format = matches
546 .get_one::<String>("format")
547 .ok_or("Missing format")?;
548
549 tracing::info!("🔍 Analyzing existing memory snapshot");
550 tracing::info!("📄 Input: {}", input_path);
551 tracing::info!("📄 Output: {}", output_path);
552 tracing::info!("📊 Format: {}", format);
553
554 let result: Result<(), Box<dyn std::error::Error>> = Ok(());
556
557 match result {
558 Ok(()) => {
559 tracing::info!("✅ Analysis completed successfully");
560 tracing::info!("📊 Report generated: {}", output_path);
561 Ok(())
562 }
563 Err(e) => {
564 tracing::error!("❌ Analysis failed: {}", e);
565 Err(e)
566 }
567 }
568}
569
570fn handle_legacy_mode(matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
571 let export_format = matches.get_one::<String>("export");
572 let output_path = matches
573 .get_one::<String>("output")
574 .ok_or("Missing output path")?;
575 let auto_track = matches.get_flag("auto-track");
576
577 if let Some(command_args) = matches.get_many::<String>("command") {
578 let command_args: Vec<&String> = command_args.collect();
579
580 tracing::info!("⚠️ Using legacy mode - consider using 'memscope run' instead");
581 tracing::info!(
582 "📋 Command: {}",
583 command_args
584 .iter()
585 .map(|s| s.as_str())
586 .collect::<Vec<_>>()
587 .join(" ")
588 );
589
590 if let Some(format) = export_format {
591 tracing::info!("📊 Export format: {}", format);
592 tracing::info!("📁 Output path: {}", output_path);
593 }
594
595 if auto_track {
596 tracing::info!("🔍 Auto-tracking enabled");
597 }
598
599 let mut env_vars = vec![("MEMSCOPE_ENABLED", "1"), ("MEMSCOPE_AUTO_EXPORT", "1")];
601
602 if auto_track {
603 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
604 }
605
606 if let Some(format) = export_format {
607 env_vars.push(("MEMSCOPE_EXPORT_FORMAT", format));
608 env_vars.push(("MEMSCOPE_EXPORT_PATH", output_path));
609 }
610
611 let result = execute_with_tracking(&command_args, &env_vars);
613
614 match result {
615 Ok(()) => {
616 tracing::info!("✅ Program execution completed successfully");
617
618 if export_format.is_some() {
619 tracing::info!("📊 Memory analysis exported to: {}", output_path);
620
621 }
624 Ok(())
625 }
626 Err(e) => {
627 tracing::error!("❌ Program execution failed: {}", e);
628 Err(e)
629 }
630 }
631 } else {
632 Err("No command provided. Use 'memscope run <command>' or 'memscope analyze <input> <output>'".into())
633 }
634}
635
636fn _analyze_existing_snapshot(
637 input_path: &str,
638 _output_path: &str,
639 format: &str,
640) -> Result<(), Box<dyn std::error::Error>> {
641 if !Path::new(input_path).exists() {
642 return Err(format!("Input file not found: {input_path}").into());
643 }
644
645 match format {
646 "html" => {
647 Err("HTML generation not implemented".into())
649 }
650 "svg" => {
651 Err("SVG generation not implemented".into())
653 }
654 "both" => {
655 Err("Both format generation not implemented".into())
657 }
658 _ => Err(format!("Unsupported format: {format}").into()),
659 }
660}
661
662fn generate_html_report(
663 input_path: &str,
664 output_path: &str,
665) -> Result<(), Box<dyn std::error::Error>> {
666 tracing::info!("🌐 Generating HTML report...");
667
668 let json_content = std::fs::read_to_string(input_path)?;
670
671 let html_content = format!(
673 "<!DOCTYPE html>\n<html>\n<head>\n <title>MemScope Analysis Report</title>\n <style>\n body {{ font-family: Arial, sans-serif; margin: 20px; }}\n .header {{ background: #f0f0f0; padding: 20px; border-radius: 5px; }}\n .section {{ margin: 20px 0; }}\n .data {{ background: #f9f9f9; padding: 10px; border-radius: 3px; }}\n </style>\n</head>\n<body>\n <div class=\"header\">\n <h1>🚀 MemScope Analysis Report</h1>\n <p>Generated from: {input_path}</p>\n </div>\n <div class=\"section\">\n <h2>📊 Memory Analysis Data</h2>\n <div class=\"data\">\n <pre>{json_content}</pre>\n </div>\n </div>\n</body>\n</html>",
674 );
675
676 std::fs::write(output_path, html_content)?;
677 Ok(())
678}
679
680fn generate_svg_visualization(
681 input_path: &str,
682 output_path: &str,
683) -> Result<(), Box<dyn std::error::Error>> {
684 tracing::info!("📈 Generating SVG visualization...");
685
686 let svg_content = format!(
688 "<svg width=\"800\" height=\"600\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"800\" height=\"600\" fill=\"#f0f0f0\"/>\n <text x=\"400\" y=\"50\" text-anchor=\"middle\" font-size=\"24\" font-weight=\"bold\">MemScope Visualization</text>\n <text x=\"400\" y=\"80\" text-anchor=\"middle\" font-size=\"14\">Generated from: {input_path}</text>\n <text x=\"400\" y=\"300\" text-anchor=\"middle\" font-size=\"16\">SVG visualization would be generated here</text>\n</svg>",
689 );
690
691 std::fs::write(output_path, svg_content)?;
692 Ok(())
693}
694
695fn execute_with_tracking(
696 command_args: &[&String],
697 env_vars: &[(&str, &str)],
698) -> Result<(), Box<dyn std::error::Error>> {
699 if command_args.is_empty() {
700 return Err("No command provided".into());
701 }
702
703 let program = command_args[0];
704 let args = &command_args[1..];
705
706 tracing::info!(
707 "🔄 Executing: {} {}",
708 program,
709 args.iter()
710 .map(|s| s.as_str())
711 .collect::<Vec<_>>()
712 .join(" ")
713 );
714
715 let mut cmd = Command::new(program);
716 cmd.args(args);
717
718 for (key, value) in env_vars {
720 cmd.env(key, value);
721 tracing::info!("🔧 Setting env: {}={}", key, value);
722 }
723
724 cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());
726
727 let status = cmd.status()?;
728
729 if !status.success() {
730 return Err(format!("Command failed with exit code: {:?}", status.code()).into());
731 }
732
733 tracing::info!("⏳ Waiting for cleanup to complete...");
736 std::thread::sleep(std::time::Duration::from_millis(200));
737
738 Ok(())
739}
740
741fn handle_run_command_wrapper(matches: &clap::ArgMatches) {
743 if let Err(e) = handle_run_command(matches) {
744 tracing::error!("❌ Run command failed: {}", e);
745 std::process::exit(1);
746 }
747}
748
749fn handle_analyze_command_wrapper(matches: &clap::ArgMatches) {
750 if let Err(e) = handle_analyze_command(matches) {
751 tracing::error!("❌ Analyze command failed: {}", e);
752 std::process::exit(1);
753 }
754}
755
756fn handle_legacy_mode_wrapper(matches: &clap::ArgMatches) {
757 if let Err(e) = handle_legacy_mode(matches) {
758 tracing::error!("❌ Legacy mode failed: {}", e);
759 std::process::exit(1);
760 }
761}
762
763fn _post_process_analysis(output_path: &str, format: &str) {
764 match format {
765 "json" => {
766 let json_path = format!("{output_path}.json");
767 if Path::new(&json_path).exists() {
768 tracing::info!("📄 JSON analysis: {}", json_path);
769 }
771 }
772 "html" => {
773 let html_path = format!("{output_path}.html");
774 if Path::new(&html_path).exists() {
775 tracing::info!("🌐 HTML dashboard: {}", html_path);
776 }
777 }
778 "both" => {
779 }
781 _ => {}
782 }
783}
784
785fn analyze_json_output(json_path: &str) {
786 if let Ok(content) = std::fs::read_to_string(json_path) {
788 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&content) {
789 if let Some(stats) = data
790 .get("memory_analysis")
791 .and_then(|ma| ma.get("statistics"))
792 .and_then(|s| s.get("lifecycle_analysis"))
793 {
794 tracing::info!("📈 Quick Analysis:");
795
796 if let Some(user_stats) = stats.get("user_allocations") {
797 if let Some(total) = user_stats.get("total_count") {
798 tracing::info!(" 👤 User allocations: {}", total);
799 }
800 if let Some(avg_lifetime) = user_stats.get("average_lifetime_ms") {
801 tracing::info!(" ⏱️ Average lifetime: {}ms", avg_lifetime);
802 }
803 }
804
805 if let Some(system_stats) = stats.get("system_allocations") {
806 if let Some(total) = system_stats.get("total_count") {
807 tracing::info!(" 🔧 System allocations: {}", total);
808 }
809 }
810 }
811 }
812 }
813}
814
815#[cfg(test)]
816mod tests {
817 use super::*;
818 use clap::{Arg, ArgMatches, Command as ClapCommand};
819 use std::fs;
820 use tempfile::TempDir;
821
822 fn create_test_matches_with_command(command: Vec<&str>) -> ArgMatches {
823 let command_strings: Vec<String> = command.iter().map(|s| s.to_string()).collect();
824 ClapCommand::new("test")
825 .arg(Arg::new("command").num_args(1..))
826 .arg(Arg::new("export").long("export"))
827 .arg(Arg::new("output").long("output"))
828 .try_get_matches_from(
829 std::iter::once("test".to_string())
830 .chain(command_strings)
831 .collect::<Vec<String>>(),
832 )
833 .expect("Failed to create test matches")
834 }
835
836 fn create_analyze_matches(input: &str, output: &str, format: &str) -> ArgMatches {
837 ClapCommand::new("test")
838 .arg(Arg::new("input"))
839 .arg(Arg::new("output"))
840 .arg(Arg::new("format").long("format"))
841 .try_get_matches_from(vec!["test", input, output, "--format", format])
842 .expect("Failed to create analyze matches")
843 }
844
845 #[test]
846 fn test_argument_extraction() {
847 let matches = create_test_matches_with_command(vec!["echo", "hello"]);
849
850 let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
851
852 assert_eq!(command_args.len(), 2);
853 assert_eq!(command_args[0], "echo");
854 assert_eq!(command_args[1], "hello");
855 }
856
857 #[test]
858 fn test_default_values() {
859 let matches = create_test_matches_with_command(vec!["echo", "test"]);
861
862 let export_format = matches
863 .get_one::<String>("export")
864 .map(|s| s.as_str())
865 .unwrap_or("html");
866 let output_path = matches
867 .get_one::<String>("output")
868 .map(|s| s.as_str())
869 .unwrap_or("memory_analysis");
870
871 assert_eq!(export_format, "html");
872 assert_eq!(output_path, "memory_analysis");
873 }
874
875 #[test]
876 fn test_environment_variable_setup() {
877 let export_format = "json";
879 let output_path = "test_output";
880 let auto_track = true;
881 let wait_completion = false;
882
883 let mut env_vars = vec![
884 ("MEMSCOPE_ENABLED", "1"),
885 ("MEMSCOPE_AUTO_EXPORT", "1"),
886 ("MEMSCOPE_EXPORT_FORMAT", export_format),
887 ("MEMSCOPE_EXPORT_PATH", output_path),
888 ];
889
890 if auto_track {
891 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
892 }
893
894 if wait_completion {
895 env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
896 }
897
898 assert_eq!(env_vars.len(), 5); assert!(env_vars.contains(&("MEMSCOPE_AUTO_TRACK", "1")));
900 assert!(!env_vars
901 .iter()
902 .any(|(k, _)| *k == "MEMSCOPE_WAIT_COMPLETION"));
903 }
904
905 #[test]
906 fn test_command_validation() {
907 let empty_command: Vec<&String> = vec![];
909 let valid_command = ["echo".to_string(), "test".to_string()];
910 let valid_command_refs: Vec<&String> = valid_command.iter().collect();
911
912 assert!(empty_command.is_empty());
914
915 assert!(!valid_command_refs.is_empty());
917 assert_eq!(valid_command_refs[0], "echo");
918 assert_eq!(valid_command_refs[1], "test");
919 }
920
921 #[test]
922 fn test_generate_html_report() {
923 let temp_dir = TempDir::new().expect("Failed to create temp directory");
925 let input_path = temp_dir.path().join("test_input.json");
926 let output_path = temp_dir.path().join("test_output.html");
927
928 let test_json = r#"{"memory_analysis": {"statistics": {"total_allocations": 100}}}"#;
930 fs::write(&input_path, test_json).expect("Failed to write test JSON");
931
932 let result =
934 generate_html_report(input_path.to_str().unwrap(), output_path.to_str().unwrap());
935
936 assert!(result.is_ok());
937 assert!(output_path.exists());
938
939 let html_content = fs::read_to_string(&output_path).expect("Failed to read HTML");
941 assert!(html_content.contains("<!DOCTYPE html>"));
942 assert!(html_content.contains("MemScope Analysis Report"));
943 }
944
945 #[test]
946 fn test_generate_svg_visualization() {
947 let temp_dir = TempDir::new().expect("Failed to create temp directory");
949 let input_path = temp_dir.path().join("test_input.json");
950 let output_path = temp_dir.path().join("test_output.svg");
951
952 fs::write(&input_path, "{}").expect("Failed to write test file");
954
955 let result =
957 generate_svg_visualization(input_path.to_str().unwrap(), output_path.to_str().unwrap());
958
959 assert!(result.is_ok());
960 assert!(output_path.exists());
961
962 let svg_content = fs::read_to_string(&output_path).expect("Failed to read SVG");
964 assert!(svg_content.contains("<svg"));
965 assert!(svg_content.contains("MemScope Visualization"));
966 }
967
968 #[test]
969 fn test_handle_analyze_command() {
970 let temp_dir = TempDir::new().expect("Failed to create temp directory");
972 let input_path = temp_dir.path().join("input.json");
973 let output_path = temp_dir.path().join("output.html");
974
975 fs::write(&input_path, "{}").expect("Failed to write test file");
977
978 let matches = create_analyze_matches(
979 input_path.to_str().unwrap(),
980 output_path.to_str().unwrap(),
981 "html",
982 );
983
984 let result = handle_analyze_command(&matches);
985 assert!(result.is_ok());
986 }
987
988 #[test]
989 fn test_analyze_json_output_parsing() {
990 let temp_dir = TempDir::new().expect("Failed to create temp directory");
992 let json_path = temp_dir.path().join("test_analysis.json");
993
994 let test_json = r#"{
996 "memory_analysis": {
997 "statistics": {
998 "lifecycle_analysis": {
999 "user_allocations": {
1000 "total_count": 150,
1001 "average_lifetime_ms": 250
1002 },
1003 "system_allocations": {
1004 "total_count": 75
1005 }
1006 }
1007 }
1008 }
1009 }"#;
1010
1011 fs::write(&json_path, test_json).expect("Failed to write test JSON");
1012
1013 analyze_json_output(json_path.to_str().unwrap());
1015
1016 assert!(json_path.exists());
1018 let content = fs::read_to_string(&json_path).expect("Failed to read JSON");
1019 let data: serde_json::Value = serde_json::from_str(&content).expect("Invalid JSON");
1020
1021 assert!(data.get("memory_analysis").is_some());
1022 }
1023
1024 #[test]
1025 fn test_format_validation() {
1026 let valid_formats = ["json", "html", "both", "svg"];
1028 let invalid_formats = ["xml", "csv", "txt"];
1029
1030 for format in valid_formats {
1031 assert!(["json", "html", "both", "svg"].contains(&format));
1033 }
1034
1035 for format in invalid_formats {
1036 assert!(!["json", "html", "both", "svg"].contains(&format));
1038 }
1039 }
1040
1041 #[test]
1042 fn test_path_handling() {
1043 let temp_dir = TempDir::new().expect("Failed to create temp directory");
1045 let valid_path = temp_dir.path().join("valid_file.json");
1046 let invalid_path = temp_dir
1047 .path()
1048 .join("nonexistent")
1049 .join("invalid_file.json");
1050
1051 fs::write(&valid_path, "{}").expect("Failed to write test file");
1053 assert!(valid_path.exists());
1054
1055 assert!(!invalid_path.exists());
1057 assert!(!invalid_path.parent().unwrap().exists());
1058 }
1059
1060 #[test]
1061 fn test_command_args_processing() {
1062 let test_cases = vec![
1064 (vec!["echo", "hello"], "echo hello"),
1065 (vec!["cargo", "run", "--release"], "cargo run --release"),
1066 (vec!["ls", "-la"], "ls -la"),
1067 ];
1068
1069 for (args, expected) in test_cases {
1070 let joined = args.join(" ");
1071 assert_eq!(joined, expected);
1072
1073 let program = args[0];
1075 let remaining_args = &args[1..];
1076
1077 assert_eq!(program, args[0]);
1078 assert_eq!(remaining_args.len(), args.len() - 1);
1079 }
1080 }
1081
1082 #[test]
1083 fn test_error_handling() {
1084 let empty_command: Vec<&String> = vec![];
1088 assert!(empty_command.is_empty());
1089
1090 let matches = ClapCommand::new("test")
1092 .arg(Arg::new("input"))
1093 .arg(Arg::new("output"))
1094 .try_get_matches_from(vec!["test"])
1095 .unwrap();
1096
1097 let missing_input = matches.get_one::<String>("input");
1098 let missing_output = matches.get_one::<String>("output");
1099
1100 assert!(missing_input.is_none());
1101 assert!(missing_output.is_none());
1102 }
1103
1104 #[test]
1105 fn test_legacy_mode_detection() {
1106 let matches_with_export = ClapCommand::new("test")
1108 .arg(Arg::new("export").long("export"))
1109 .arg(Arg::new("command").num_args(1..))
1110 .try_get_matches_from(vec!["test", "--export", "json", "echo", "test"])
1111 .unwrap();
1112
1113 let has_export = matches_with_export.get_one::<String>("export").is_some();
1114 let has_command = matches_with_export.get_many::<String>("command").is_some();
1115
1116 assert!(has_export);
1117 assert!(has_command);
1118
1119 let is_legacy_mode = has_export && has_command;
1121 assert!(is_legacy_mode);
1122 }
1123
1124 #[test]
1125 fn test_run_analyze_with_valid_args() {
1126 let matches = ClapCommand::new("test")
1128 .arg(Arg::new("command").num_args(1..))
1129 .arg(Arg::new("export").long("export").default_value("html"))
1130 .arg(
1131 Arg::new("output")
1132 .long("output")
1133 .default_value("memory_analysis"),
1134 )
1135 .try_get_matches_from(vec!["test", "echo", "hello"])
1136 .unwrap();
1137
1138 let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
1140 let export_format = matches
1141 .get_one::<String>("export")
1142 .map(|s| s.as_str())
1143 .unwrap_or("html");
1144 let output_path = matches
1145 .get_one::<String>("output")
1146 .map(|s| s.as_str())
1147 .unwrap_or("memory_analysis");
1148
1149 assert_eq!(command_args.len(), 2);
1150 assert_eq!(command_args[0], "echo");
1151 assert_eq!(command_args[1], "hello");
1152 assert_eq!(export_format, "html");
1153 assert_eq!(output_path, "memory_analysis");
1154 }
1155
1156 #[test]
1157 fn test_run_analyze_missing_command() {
1158 let matches = ClapCommand::new("test")
1160 .arg(Arg::new("command").num_args(1..))
1161 .try_get_matches_from(vec!["test"])
1162 .unwrap();
1163
1164 let command_result = matches.get_many::<String>("command");
1165 assert!(command_result.is_none());
1166 }
1167
1168 #[test]
1169 fn test_handle_run_command_logic() {
1170 let matches = ClapCommand::new("test")
1172 .arg(Arg::new("command").num_args(1..))
1173 .arg(Arg::new("export").long("export"))
1174 .arg(Arg::new("output").long("output"))
1175 .arg(
1176 Arg::new("auto-track")
1177 .long("auto-track")
1178 .action(clap::ArgAction::SetTrue),
1179 )
1180 .arg(
1181 Arg::new("wait-completion")
1182 .long("wait-completion")
1183 .action(clap::ArgAction::SetTrue),
1184 )
1185 .try_get_matches_from(vec![
1186 "test",
1187 "echo",
1188 "test",
1189 "--export",
1190 "json",
1191 "--output",
1192 "test_output",
1193 "--auto-track",
1194 "--wait-completion",
1195 ])
1196 .unwrap();
1197
1198 let command_args: Vec<&String> = matches.get_many::<String>("command").unwrap().collect();
1199 let export_format = matches.get_one::<String>("export").unwrap();
1200 let output_path = matches.get_one::<String>("output").unwrap();
1201 let auto_track = matches.get_flag("auto-track");
1202 let wait_completion = matches.get_flag("wait-completion");
1203
1204 assert_eq!(command_args.len(), 2);
1205 assert_eq!(export_format, "json");
1206 assert_eq!(output_path, "test_output");
1207 assert!(auto_track);
1208 assert!(wait_completion);
1209
1210 let mut env_vars = vec![
1212 ("MEMSCOPE_ENABLED", "1"),
1213 ("MEMSCOPE_AUTO_EXPORT", "1"),
1214 ("MEMSCOPE_EXPORT_FORMAT", export_format),
1215 ("MEMSCOPE_EXPORT_PATH", output_path),
1216 ];
1217
1218 if auto_track {
1219 env_vars.push(("MEMSCOPE_AUTO_TRACK", "1"));
1220 }
1221
1222 if wait_completion {
1223 env_vars.push(("MEMSCOPE_WAIT_COMPLETION", "1"));
1224 }
1225
1226 assert_eq!(env_vars.len(), 6);
1227 assert!(env_vars.contains(&("MEMSCOPE_AUTO_TRACK", "1")));
1228 assert!(env_vars.contains(&("MEMSCOPE_WAIT_COMPLETION", "1")));
1229 }
1230
1231 #[test]
1232 fn test_handle_legacy_mode_logic() {
1233 let matches = ClapCommand::new("test")
1235 .arg(Arg::new("export").long("export"))
1236 .arg(
1237 Arg::new("output")
1238 .long("output")
1239 .default_value("default_output"),
1240 )
1241 .arg(
1242 Arg::new("auto-track")
1243 .long("auto-track")
1244 .action(clap::ArgAction::SetTrue),
1245 )
1246 .arg(Arg::new("command").num_args(1..))
1247 .try_get_matches_from(vec![
1248 "test",
1249 "--export",
1250 "html",
1251 "--output",
1252 "legacy_output",
1253 "--auto-track",
1254 "cargo",
1255 "run",
1256 ])
1257 .unwrap();
1258
1259 let export_format = matches.get_one::<String>("export");
1260 let output_path = matches.get_one::<String>("output").unwrap();
1261 let auto_track = matches.get_flag("auto-track");
1262 let command_args: Option<clap::parser::ValuesRef<String>> =
1263 matches.get_many::<String>("command");
1264
1265 assert!(export_format.is_some());
1266 assert_eq!(export_format.unwrap(), "html");
1267 assert_eq!(output_path, "legacy_output");
1268 assert!(auto_track);
1269 assert!(command_args.is_some());
1270
1271 let command_vec: Vec<&String> = command_args.unwrap().collect();
1272 assert_eq!(command_vec.len(), 2);
1273 assert_eq!(command_vec[0], "cargo");
1274 assert_eq!(command_vec[1], "run");
1275 }
1276
1277 #[test]
1278 fn test_analyze_existing_snapshot_logic() {
1279 let temp_dir = TempDir::new().expect("Failed to create temp directory");
1281 let existing_file = temp_dir.path().join("existing.json");
1282 let nonexistent_file = temp_dir.path().join("nonexistent.json");
1283
1284 fs::write(&existing_file, "{}").expect("Failed to write test file");
1286
1287 assert!(existing_file.exists());
1289
1290 assert!(!nonexistent_file.exists());
1292
1293 let formats = ["html", "svg", "both", "invalid"];
1295 for format in formats {
1296 match format {
1297 "html" | "svg" | "both" => {
1298 assert!(["html", "svg", "both"].contains(&format));
1300 }
1301 _ => {
1302 assert!(!["html", "svg", "both"].contains(&format));
1304 }
1305 }
1306 }
1307 }
1308
1309 #[test]
1310 fn test_execute_with_tracking_validation() {
1311 let empty_command: Vec<&String> = vec![];
1313 let valid_command = ["echo".to_string(), "hello".to_string()];
1314 let valid_refs: Vec<&String> = valid_command.iter().collect();
1315
1316 assert!(empty_command.is_empty());
1318
1319 assert!(!valid_refs.is_empty());
1321 let program = valid_refs[0];
1322 let args = &valid_refs[1..];
1323
1324 assert_eq!(program, "echo");
1325 assert_eq!(args.len(), 1);
1326 assert_eq!(args[0], "hello");
1327
1328 let env_vars = [
1330 ("MEMSCOPE_ENABLED", "1"),
1331 ("MEMSCOPE_AUTO_EXPORT", "1"),
1332 ("TEST_VAR", "test_value"),
1333 ];
1334
1335 for (key, value) in env_vars {
1336 assert!(!key.is_empty());
1337 assert!(!value.is_empty());
1338 }
1339 }
1340
1341 #[test]
1342 fn test_html_content_generation() {
1343 let input_path = "test_input.json";
1345 let expected_elements = [
1346 "<!DOCTYPE html>",
1347 "<html>",
1348 "<head>",
1349 "<title>MemScope Analysis Report</title>",
1350 "<style>",
1351 "body { font-family: Arial, sans-serif; margin: 20px; }",
1352 ".header { background: #f0f0f0; padding: 20px; border-radius: 5px; }",
1353 "</head>",
1354 "<body>",
1355 "<h1>🚀 MemScope Analysis Report</h1>",
1356 ];
1357
1358 let html_start = format!(
1360 "<!DOCTYPE html>\n<html>\n<head>\n <title>MemScope Analysis Report</title>\n <style>\n body {{ font-family: Arial, sans-serif; margin: 20px; }}\n .header {{ background: #f0f0f0; padding: 20px; border-radius: 5px; }}\n .section {{ margin: 20px 0; }}\n .data {{ background: #f9f9f9; padding: 10px; border-radius: 3px; }}\n </style>\n</head>\n<body>\n <div class=\"header\">\n <h1>🚀 MemScope Analysis Report</h1>\n <p>Generated from: {}",
1361 input_path
1362 );
1363
1364 for element in expected_elements {
1365 if !element.contains("Generated from") {
1366 assert!(html_start.contains(element));
1367 }
1368 }
1369 }
1370
1371 #[test]
1372 fn test_svg_content_generation() {
1373 let input_path = "test_input.json";
1375 let svg_content = format!(
1376 "<svg width=\"800\" height=\"600\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"800\" height=\"600\" fill=\"#f0f0f0\"/>\n <text x=\"400\" y=\"50\" text-anchor=\"middle\" font-size=\"24\" font-weight=\"bold\">MemScope Visualization</text>\n <text x=\"400\" y=\"80\" text-anchor=\"middle\" font-size=\"14\">Generated from: {}</text>\n <text x=\"400\" y=\"300\" text-anchor=\"middle\" font-size=\"16\">SVG visualization would be generated here</text>\n</svg>",
1377 input_path
1378 );
1379
1380 let expected_elements = [
1381 "<svg",
1382 "width=\"800\"",
1383 "height=\"600\"",
1384 "xmlns=\"http://www.w3.org/2000/svg\"",
1385 "<rect",
1386 "fill=\"#f0f0f0\"",
1387 "<text",
1388 "MemScope Visualization",
1389 "Generated from:",
1390 "</svg>",
1391 ];
1392
1393 for element in expected_elements {
1394 assert!(svg_content.contains(element));
1395 }
1396 }
1397
1398 #[test]
1399 fn test_command_string_joining() {
1400 let test_cases = vec![
1402 (vec!["echo", "hello"], "echo hello"),
1403 (vec!["cargo", "run", "--release"], "cargo run --release"),
1404 (vec!["ls", "-la", "/tmp"], "ls -la /tmp"),
1405 (
1406 vec!["git", "commit", "-m", "test message"],
1407 "git commit -m test message",
1408 ),
1409 ];
1410
1411 for (args, expected) in test_cases {
1412 let joined = args.to_vec().join(" ");
1413 assert_eq!(joined, expected);
1414 }
1415 }
1416
1417 #[test]
1418 fn test_flag_combinations() {
1419 let matches = ClapCommand::new("test")
1421 .arg(
1422 Arg::new("auto-track")
1423 .long("auto-track")
1424 .action(clap::ArgAction::SetTrue),
1425 )
1426 .arg(
1427 Arg::new("wait-completion")
1428 .long("wait-completion")
1429 .action(clap::ArgAction::SetTrue),
1430 )
1431 .arg(
1432 Arg::new("verbose")
1433 .long("verbose")
1434 .action(clap::ArgAction::SetTrue),
1435 )
1436 .try_get_matches_from(vec!["test", "--auto-track", "--verbose"])
1437 .unwrap();
1438
1439 let auto_track = matches.get_flag("auto-track");
1440 let wait_completion = matches.get_flag("wait-completion");
1441 let verbose = matches.get_flag("verbose");
1442
1443 assert!(auto_track);
1444 assert!(!wait_completion);
1445 assert!(verbose);
1446
1447 let mut env_count = 0;
1449 if auto_track {
1450 env_count += 1;
1451 }
1452 if wait_completion {
1453 env_count += 1;
1454 }
1455 if verbose {
1456 env_count += 1;
1457 }
1458
1459 assert_eq!(env_count, 2); }
1461
1462 #[test]
1463 fn test_error_message_formatting() {
1464 let test_errors = vec![
1466 ("Missing command arguments", "Missing command arguments"),
1467 ("Missing export format", "Missing export format"),
1468 ("Missing output path", "Missing output path"),
1469 (
1470 "Command failed with exit code: Some(1)",
1471 "Command failed with exit code: Some(1)",
1472 ),
1473 ];
1474
1475 for (error_msg, expected) in test_errors {
1476 assert_eq!(error_msg, expected);
1477 assert!(!error_msg.is_empty());
1478 }
1479
1480 let exit_code = Some(1);
1482 let formatted_error = format!("Command failed with exit code: {:?}", exit_code);
1483 assert_eq!(formatted_error, "Command failed with exit code: Some(1)");
1484 }
1485
1486 #[test]
1487 fn test_file_extension_handling() {
1488 let test_paths = vec![
1490 ("output.html", "html"),
1491 ("output.svg", "svg"),
1492 ("output.json", "json"),
1493 ("output", ""),
1494 ];
1495
1496 for (path, expected_ext) in test_paths {
1497 let extension = path.split('.').next_back().unwrap_or("");
1498 if path.contains('.') {
1499 assert_eq!(extension, expected_ext);
1500 } else {
1501 assert_eq!(extension, path);
1502 }
1503 }
1504 }
1505
1506 #[test]
1507 fn test_timeout_duration() {
1508 let timeout_ms = 200;
1510 let duration = std::time::Duration::from_millis(timeout_ms);
1511
1512 assert_eq!(duration.as_millis(), 200);
1513 assert!(duration.as_millis() > 0);
1514 assert!(duration.as_millis() < 1000); }
1516}