use crate::analyzer::{AnalysisReport, MudaType};
use colored::{ColoredString, Colorize};
use std::io::{self, Write};
#[derive(Debug, Clone, Copy, Default)]
pub enum OutputFormat {
#[default]
Text,
Json,
}
#[must_use]
pub fn format_text(report: &AnalysisReport) -> String {
let mut output = String::new();
output.push_str(&format!(
"{} Analysis: {}\n",
report.target.cyan().bold(),
report.name.white().bold()
));
output.push_str(&"═".repeat(60));
output.push('\n');
let reg_status = if report.registers.total() < 64 {
"[OK]".green()
} else if report.registers.total() < 128 {
"[WARN]".yellow()
} else {
"[HIGH]".red()
};
output.push_str(&format!(
"\n{} {}\n",
"Register Pressure:".white().bold(),
reg_status
));
output.push_str(&format!(
" ├── .reg .f32: {} / 255 ({:.1}%)\n",
report.registers.f32_regs,
report.registers.f32_regs as f32 / 255.0 * 100.0
));
output.push_str(&format!(
" ├── .reg .b32: {} / 255 ({:.1}%)\n",
report.registers.b32_regs,
report.registers.b32_regs as f32 / 255.0 * 100.0
));
output.push_str(&format!(
" ├── .reg .b64: {} / 255 ({:.1}%)\n",
report.registers.b64_regs,
report.registers.b64_regs as f32 / 255.0 * 100.0
));
output.push_str(&format!(
" ├── .reg .pred: {} / 8 ({:.1}%)\n",
report.registers.pred_regs,
report.registers.pred_regs as f32 / 8.0 * 100.0
));
output.push_str(&format!(
" └── {}: {} registers → {:.0}% occupancy possible\n",
"Total".bold(),
report.registers.total(),
report.estimated_occupancy * 100.0
));
let mem_status = if report.memory.coalesced_ratio >= 0.9 {
"[OK]".green()
} else if report.memory.coalesced_ratio >= 0.7 {
"[WARN]".yellow()
} else {
"[BAD]".red()
};
output.push_str(&format!(
"\n{} {}\n",
"Memory Access Pattern:".white().bold(),
mem_status
));
output.push_str(&format!(
" ├── Global loads: {} (coalesced: {:.1}%)\n",
report.memory.global_loads,
report.memory.coalesced_ratio * 100.0
));
output.push_str(&format!(
" ├── Global stores: {}\n",
report.memory.global_stores
));
output.push_str(&format!(
" ├── Shared loads: {}\n",
report.memory.shared_loads
));
output.push_str(&format!(
" └── Shared stores: {}\n",
report.memory.shared_stores
));
output.push_str(&format!("\n{}\n", "Performance Estimate:".white().bold()));
output.push_str(&format!(
" ├── Arithmetic Intensity: {:.2} FLOPs/Byte\n",
report.roofline.arithmetic_intensity
));
output.push_str(&format!(
" └── Bottleneck: {}\n",
if report.roofline.memory_bound {
"Memory bandwidth".yellow()
} else {
"Compute".green()
}
));
if report.warnings.is_empty() {
output.push_str(&format!("\n{} No Muda detected\n", "✓".green()));
} else {
output.push_str(&format!("\n{}\n", "Muda (Waste) Detection:".white().bold()));
for warning in &report.warnings {
let icon = match warning.muda_type {
MudaType::Transport => "⚠".yellow(),
MudaType::Waiting => "⏳".yellow(),
MudaType::Overprocessing => "🔄".yellow(),
};
output.push_str(&format!(
" {} {}: {}\n",
icon,
muda_name(&warning.muda_type),
warning.description
));
if let Some(ref suggestion) = warning.suggestion {
output.push_str(&format!(
" └── {}: {}\n",
"Suggestion".cyan(),
suggestion
));
}
}
}
output
}
fn muda_name(muda: &MudaType) -> ColoredString {
match muda {
MudaType::Transport => "Muda of Transport (Spills)".yellow(),
MudaType::Waiting => "Muda of Waiting (Stalls)".yellow(),
MudaType::Overprocessing => "Muda of Overprocessing".yellow(),
}
}
pub fn format_json(report: &AnalysisReport) -> serde_json::Result<String> {
serde_json::to_string_pretty(report)
}
pub fn write_report(report: &AnalysisReport, format: OutputFormat) -> io::Result<()> {
let mut stdout = io::stdout().lock();
match format {
OutputFormat::Text => {
write!(stdout, "{}", format_text(report))?;
}
OutputFormat::Json => {
let json = format_json(report).map_err(io::Error::other)?;
writeln!(stdout, "{}", json)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analyzer::{MemoryPattern, MudaWarning, RegisterUsage, RooflineMetric};
fn sample_report() -> AnalysisReport {
AnalysisReport {
name: "test_kernel".to_string(),
target: "PTX".to_string(),
registers: RegisterUsage {
f32_regs: 24,
b32_regs: 18,
b64_regs: 12,
pred_regs: 4,
..Default::default()
},
memory: MemoryPattern {
global_loads: 100,
global_stores: 50,
coalesced_ratio: 0.95,
..Default::default()
},
roofline: RooflineMetric {
arithmetic_intensity: 2.5,
theoretical_peak_gflops: 15000.0,
memory_bound: true,
},
warnings: vec![],
instruction_count: 150,
estimated_occupancy: 0.875,
}
}
#[test]
fn test_format_text_contains_kernel_name() {
let report = sample_report();
let text = format_text(&report);
assert!(text.contains("test_kernel"));
}
#[test]
fn test_format_text_contains_registers() {
let report = sample_report();
let text = format_text(&report);
assert!(text.contains("24"));
assert!(text.contains("f32"));
}
#[test]
fn test_format_text_contains_memory() {
let report = sample_report();
let text = format_text(&report);
assert!(text.contains("Global loads"));
assert!(text.contains("100"));
}
#[test]
fn test_format_json_valid() {
let report = sample_report();
let json = format_json(&report).unwrap();
let parsed: AnalysisReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name, "test_kernel");
}
#[test]
fn test_format_text_with_warnings() {
let mut report = sample_report();
report.warnings.push(MudaWarning {
muda_type: MudaType::Transport,
description: "5 spills detected".to_string(),
impact: "High latency".to_string(),
line: None,
suggestion: Some("Reduce variables".to_string()),
});
let text = format_text(&report);
assert!(text.contains("Muda"));
assert!(text.contains("5 spills"));
assert!(text.contains("Suggestion"));
}
#[test]
fn test_format_text_no_warnings() {
let report = sample_report();
let text = format_text(&report);
assert!(text.contains("No Muda detected"));
}
}