1use crate::analyzer::{AnalysisReport, MudaType};
6use colored::{ColoredString, Colorize};
7use std::io::{self, Write};
8
9#[derive(Debug, Clone, Copy, Default)]
11pub enum OutputFormat {
12 #[default]
14 Text,
15 Json,
17}
18
19#[must_use]
21pub fn format_text(report: &AnalysisReport) -> String {
22 let mut output = String::new();
23
24 output.push_str(&format!(
26 "{} Analysis: {}\n",
27 report.target.cyan().bold(),
28 report.name.white().bold()
29 ));
30 output.push_str(&"═".repeat(60));
31 output.push('\n');
32
33 let reg_status = if report.registers.total() < 64 {
35 "[OK]".green()
36 } else if report.registers.total() < 128 {
37 "[WARN]".yellow()
38 } else {
39 "[HIGH]".red()
40 };
41
42 output.push_str(&format!(
43 "\n{} {}\n",
44 "Register Pressure:".white().bold(),
45 reg_status
46 ));
47 output.push_str(&format!(
48 " ├── .reg .f32: {} / 255 ({:.1}%)\n",
49 report.registers.f32_regs,
50 report.registers.f32_regs as f32 / 255.0 * 100.0
51 ));
52 output.push_str(&format!(
53 " ├── .reg .b32: {} / 255 ({:.1}%)\n",
54 report.registers.b32_regs,
55 report.registers.b32_regs as f32 / 255.0 * 100.0
56 ));
57 output.push_str(&format!(
58 " ├── .reg .b64: {} / 255 ({:.1}%)\n",
59 report.registers.b64_regs,
60 report.registers.b64_regs as f32 / 255.0 * 100.0
61 ));
62 output.push_str(&format!(
64 " ├── .reg .pred: {} / 8 ({:.1}%)\n",
65 report.registers.pred_regs,
66 report.registers.pred_regs as f32 / 8.0 * 100.0
67 ));
68 output.push_str(&format!(
69 " └── {}: {} registers → {:.0}% occupancy possible\n",
70 "Total".bold(),
71 report.registers.total(),
72 report.estimated_occupancy * 100.0
73 ));
74
75 let mem_status = if report.memory.coalesced_ratio >= 0.9 {
77 "[OK]".green()
78 } else if report.memory.coalesced_ratio >= 0.7 {
79 "[WARN]".yellow()
80 } else {
81 "[BAD]".red()
82 };
83
84 output.push_str(&format!(
85 "\n{} {}\n",
86 "Memory Access Pattern:".white().bold(),
87 mem_status
88 ));
89 output.push_str(&format!(
90 " ├── Global loads: {} (coalesced: {:.1}%)\n",
91 report.memory.global_loads,
92 report.memory.coalesced_ratio * 100.0
93 ));
94 output.push_str(&format!(
95 " ├── Global stores: {}\n",
96 report.memory.global_stores
97 ));
98 output.push_str(&format!(
99 " ├── Shared loads: {}\n",
100 report.memory.shared_loads
101 ));
102 output.push_str(&format!(
103 " └── Shared stores: {}\n",
104 report.memory.shared_stores
105 ));
106
107 output.push_str(&format!("\n{}\n", "Performance Estimate:".white().bold()));
109 output.push_str(&format!(
110 " ├── Arithmetic Intensity: {:.2} FLOPs/Byte\n",
111 report.roofline.arithmetic_intensity
112 ));
113 output.push_str(&format!(
114 " └── Bottleneck: {}\n",
115 if report.roofline.memory_bound {
116 "Memory bandwidth".yellow()
117 } else {
118 "Compute".green()
119 }
120 ));
121
122 if report.warnings.is_empty() {
124 output.push_str(&format!("\n{} No Muda detected\n", "✓".green()));
125 } else {
126 output.push_str(&format!("\n{}\n", "Muda (Waste) Detection:".white().bold()));
127 for warning in &report.warnings {
128 let icon = match warning.muda_type {
129 MudaType::Transport => "⚠".yellow(),
130 MudaType::Waiting => "⏳".yellow(),
131 MudaType::Overprocessing => "🔄".yellow(),
132 };
133 output.push_str(&format!(
134 " {} {}: {}\n",
135 icon,
136 muda_name(&warning.muda_type),
137 warning.description
138 ));
139 if let Some(ref suggestion) = warning.suggestion {
140 output.push_str(&format!(
141 " └── {}: {}\n",
142 "Suggestion".cyan(),
143 suggestion
144 ));
145 }
146 }
147 }
148
149 output
150}
151
152fn muda_name(muda: &MudaType) -> ColoredString {
153 match muda {
154 MudaType::Transport => "Muda of Transport (Spills)".yellow(),
155 MudaType::Waiting => "Muda of Waiting (Stalls)".yellow(),
156 MudaType::Overprocessing => "Muda of Overprocessing".yellow(),
157 }
158}
159
160pub fn format_json(report: &AnalysisReport) -> serde_json::Result<String> {
166 serde_json::to_string_pretty(report)
167}
168
169pub fn write_report(report: &AnalysisReport, format: OutputFormat) -> io::Result<()> {
175 let mut stdout = io::stdout().lock();
176
177 match format {
178 OutputFormat::Text => {
179 write!(stdout, "{}", format_text(report))?;
180 }
181 OutputFormat::Json => {
182 let json = format_json(report).map_err(io::Error::other)?;
183 writeln!(stdout, "{}", json)?;
184 }
185 }
186
187 Ok(())
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::analyzer::{MemoryPattern, MudaWarning, RegisterUsage, RooflineMetric};
194
195 fn sample_report() -> AnalysisReport {
196 AnalysisReport {
197 name: "test_kernel".to_string(),
198 target: "PTX".to_string(),
199 registers: RegisterUsage {
200 f32_regs: 24,
201 b32_regs: 18,
202 b64_regs: 12,
203 pred_regs: 4,
204 ..Default::default()
205 },
206 memory: MemoryPattern {
207 global_loads: 100,
208 global_stores: 50,
209 coalesced_ratio: 0.95,
210 ..Default::default()
211 },
212 roofline: RooflineMetric {
213 arithmetic_intensity: 2.5,
214 theoretical_peak_gflops: 15000.0,
215 memory_bound: true,
216 },
217 warnings: vec![],
218 instruction_count: 150,
219 estimated_occupancy: 0.875,
220 }
221 }
222
223 #[test]
224 fn test_format_text_contains_kernel_name() {
225 let report = sample_report();
226 let text = format_text(&report);
227 assert!(text.contains("test_kernel"));
228 }
229
230 #[test]
231 fn test_format_text_contains_registers() {
232 let report = sample_report();
233 let text = format_text(&report);
234 assert!(text.contains("24"));
235 assert!(text.contains("f32"));
236 }
237
238 #[test]
239 fn test_format_text_contains_memory() {
240 let report = sample_report();
241 let text = format_text(&report);
242 assert!(text.contains("Global loads"));
243 assert!(text.contains("100"));
244 }
245
246 #[test]
247 fn test_format_json_valid() {
248 let report = sample_report();
249 let json = format_json(&report).unwrap();
250
251 let parsed: AnalysisReport = serde_json::from_str(&json).unwrap();
253 assert_eq!(parsed.name, "test_kernel");
254 }
255
256 #[test]
257 fn test_format_text_with_warnings() {
258 let mut report = sample_report();
259 report.warnings.push(MudaWarning {
260 muda_type: MudaType::Transport,
261 description: "5 spills detected".to_string(),
262 impact: "High latency".to_string(),
263 line: None,
264 suggestion: Some("Reduce variables".to_string()),
265 });
266
267 let text = format_text(&report);
268 assert!(text.contains("Muda"));
269 assert!(text.contains("5 spills"));
270 assert!(text.contains("Suggestion"));
271 }
272
273 #[test]
274 fn test_format_text_no_warnings() {
275 let report = sample_report();
276 let text = format_text(&report);
277 assert!(text.contains("No Muda detected"));
278 }
279}