diff --git a/audit/internal/platform/benchmark.go b/audit/internal/platform/benchmark.go index f308727..df350e3 100644 --- a/audit/internal/platform/benchmark.go +++ b/audit/internal/platform/benchmark.go @@ -243,7 +243,7 @@ func (s *System) RunNvidiaBenchmark(ctx context.Context, baseDir string, opts Nv return "", fmt.Errorf("write result.json: %w", err) } - report := renderBenchmarkReport(result) + report := renderBenchmarkReportWithCharts(result, loadBenchmarkReportCharts(runDir, selected)) if err := os.WriteFile(filepath.Join(runDir, "report.txt"), []byte(report), 0644); err != nil { return "", fmt.Errorf("write report.txt: %w", err) } diff --git a/audit/internal/platform/benchmark_report.go b/audit/internal/platform/benchmark_report.go index 13a3dcf..b709090 100644 --- a/audit/internal/platform/benchmark_report.go +++ b/audit/internal/platform/benchmark_report.go @@ -2,11 +2,25 @@ package platform import ( "fmt" + "os" + "path/filepath" + "regexp" "strings" "time" ) func renderBenchmarkReport(result NvidiaBenchmarkResult) string { + return renderBenchmarkReportWithCharts(result, nil) +} + +type benchmarkReportChart struct { + Title string + Content string +} + +var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;]*m`) + +func renderBenchmarkReportWithCharts(result NvidiaBenchmarkResult, charts []benchmarkReportChart) string { var b strings.Builder fmt.Fprintf(&b, "Bee NVIDIA Benchmark Report\n") fmt.Fprintf(&b, "===========================\n\n") @@ -93,6 +107,20 @@ func renderBenchmarkReport(result NvidiaBenchmarkResult) string { b.WriteString("\n") } + if len(charts) > 0 { + fmt.Fprintf(&b, "Terminal Charts\n") + fmt.Fprintf(&b, "---------------\n") + for _, chart := range charts { + content := strings.TrimSpace(stripANSIEscapeSequences(chart.Content)) + if content == "" { + continue + } + fmt.Fprintf(&b, "%s\n", chart.Title) + fmt.Fprintf(&b, "%s\n", strings.Repeat("~", len(chart.Title))) + fmt.Fprintf(&b, "%s\n\n", content) + } + } + fmt.Fprintf(&b, "Methodology\n") fmt.Fprintf(&b, "-----------\n") fmt.Fprintf(&b, "- Profile %s uses standardized baseline, warmup, steady-state, interconnect, and cooldown phases.\n", result.BenchmarkProfile) @@ -117,6 +145,36 @@ func renderBenchmarkReport(result NvidiaBenchmarkResult) string { return b.String() } +func loadBenchmarkReportCharts(runDir string, gpuIndices []int) []benchmarkReportChart { + phases := []struct { + name string + label string + }{ + {name: "baseline", label: "Baseline"}, + {name: "steady", label: "Steady State"}, + {name: "cooldown", label: "Cooldown"}, + } + var charts []benchmarkReportChart + for _, idx := range gpuIndices { + for _, phase := range phases { + path := filepath.Join(runDir, fmt.Sprintf("gpu-%d-%s-metrics-term.txt", idx, phase.name)) + raw, err := os.ReadFile(path) + if err != nil || len(raw) == 0 { + continue + } + charts = append(charts, benchmarkReportChart{ + Title: fmt.Sprintf("GPU %d %s", idx, phase.label), + Content: string(raw), + }) + } + } + return charts +} + +func stripANSIEscapeSequences(raw string) string { + return ansiEscapePattern.ReplaceAllString(raw, "") +} + func renderBenchmarkSummary(result NvidiaBenchmarkResult) string { var b strings.Builder fmt.Fprintf(&b, "run_at_utc=%s\n", result.GeneratedAt.Format(time.RFC3339)) diff --git a/audit/internal/platform/benchmark_test.go b/audit/internal/platform/benchmark_test.go index b15d1f8..3422e7a 100644 --- a/audit/internal/platform/benchmark_test.go +++ b/audit/internal/platform/benchmark_test.go @@ -145,3 +145,35 @@ func TestRenderBenchmarkReportIncludesFindingsAndScores(t *testing.T) { } } } + +func TestRenderBenchmarkReportIncludesTerminalChartsWithoutANSI(t *testing.T) { + t.Parallel() + + report := renderBenchmarkReportWithCharts(NvidiaBenchmarkResult{ + BenchmarkProfile: NvidiaBenchmarkProfileStandard, + OverallStatus: "OK", + SelectedGPUIndices: []int{0}, + Normalization: BenchmarkNormalization{ + Status: "full", + }, + }, []benchmarkReportChart{ + { + Title: "GPU 0 Steady State", + Content: "\x1b[31mGPU 0 chart\x1b[0m\n 42┤───", + }, + }) + + for _, needle := range []string{ + "Terminal Charts", + "GPU 0 Steady State", + "GPU 0 chart", + "42┤───", + } { + if !strings.Contains(report, needle) { + t.Fatalf("report missing %q\n%s", needle, report) + } + } + if strings.Contains(report, "\x1b[31m") { + t.Fatalf("report should not contain ANSI escapes\n%s", report) + } +}