From cc2b49ea4154b13286ac783222d314a1e88a4adb Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Sun, 5 Apr 2026 17:50:13 +0300 Subject: [PATCH] Improve validate GPU runs and web UI feedback --- audit/internal/webui/charts_svg.go | 89 +++++++++-- audit/internal/webui/pages.go | 236 ++++++++++++++++++++++------ audit/internal/webui/server.go | 12 +- audit/internal/webui/server_test.go | 2 +- 4 files changed, 274 insertions(+), 65 deletions(-) diff --git a/audit/internal/webui/charts_svg.go b/audit/internal/webui/charts_svg.go index ee896e3..6cf357a 100644 --- a/audit/internal/webui/charts_svg.go +++ b/audit/internal/webui/charts_svg.go @@ -6,6 +6,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "bee/audit/internal/platform" @@ -52,6 +53,12 @@ var metricChartPalette = []string{ "#ffbe5c", } +var gpuLabelCache struct { + mu sync.Mutex + loadedAt time.Time + byIndex map[int]string +} + func renderMetricChartSVG(title string, labels []string, times []time.Time, datasets [][]float64, names []string, yMin, yMax *float64, canvasHeight int, timeline []chartTimelineSegment) ([]byte, error) { pointCount := len(labels) if len(times) > pointCount { @@ -76,15 +83,7 @@ func renderMetricChartSVG(title string, labels []string, times []time.Time, data } } - mn, avg, mx := globalStats(datasets) - if mx > 0 { - title = fmt.Sprintf("%s ↓%s ~%s ↑%s", - title, - chartLegendNumber(mn), - chartLegendNumber(avg), - chartLegendNumber(mx), - ) - } + statsLabel := chartStatsLabel(datasets) legendItems := []metricChartSeries{} for i, name := range names { @@ -106,7 +105,7 @@ func renderMetricChartSVG(title string, labels []string, times []time.Time, data var b strings.Builder writeSVGOpen(&b, layout.Width, layout.Height) - writeChartFrame(&b, title, layout.Width, layout.Height) + writeChartFrame(&b, title, statsLabel, layout.Width, layout.Height) writeTimelineIdleSpans(&b, layout, start, end, timeline) writeVerticalGrid(&b, layout, times, pointCount, 8) writeHorizontalGrid(&b, layout, scale) @@ -133,7 +132,7 @@ func renderGPUOverviewChartSVG(idx int, samples []platform.LiveMetricSample, tim labels := sampleTimeLabels(samples) times := sampleTimes(samples) svg, err := drawGPUOverviewChartSVG( - fmt.Sprintf("GPU %d Overview", idx), + gpuDisplayLabel(idx)+" Overview", labels, times, []metricChartSeries{ @@ -214,7 +213,7 @@ func drawGPUOverviewChartSVG(title string, labels []string, times []time.Time, s var b strings.Builder writeSVGOpen(&b, width, height) - writeChartFrame(&b, title, width, height) + writeChartFrame(&b, title, "", width, height) writeTimelineIdleSpans(&b, layout, start, end, timeline) writeVerticalGrid(&b, layout, times, pointCount, 8) writeHorizontalGrid(&b, layout, scales[0]) @@ -457,10 +456,14 @@ func writeSVGClose(b *strings.Builder) { b.WriteString("\n") } -func writeChartFrame(b *strings.Builder, title string, width, height int) { +func writeChartFrame(b *strings.Builder, title, subtitle string, width, height int) { fmt.Fprintf(b, ``+"\n", width, height) fmt.Fprintf(b, `%s`+"\n", width/2, sanitizeChartText(title)) + if strings.TrimSpace(subtitle) != "" { + fmt.Fprintf(b, `%s`+"\n", + width/2, sanitizeChartText(subtitle)) + } } func writePlotBorder(b *strings.Builder, layout chartLayout) { @@ -545,7 +548,21 @@ func writeSeriesPolyline(b *strings.Builder, layout chartLayout, times []time.Ti x := chartXForTime(chartPointTime(times, 0), start, end, layout.PlotLeft, layout.PlotRight) y := chartYForValue(values[0], scale, layout.PlotTop, layout.PlotBottom) fmt.Fprintf(b, ``+"\n", x, y, color) + return } + peakIdx := 0 + peakValue := values[0] + for idx, value := range values[1:] { + if value >= peakValue { + peakIdx = idx + 1 + peakValue = value + } + } + x := chartXForTime(chartPointTime(times, peakIdx), start, end, layout.PlotLeft, layout.PlotRight) + y := chartYForValue(peakValue, scale, layout.PlotTop, layout.PlotBottom) + fmt.Fprintf(b, ``+"\n", x, y, color) + fmt.Fprintf(b, ``+"\n", + x, y-10, x-5, y-18, x+5, y-18, color) } func writeLegend(b *strings.Builder, layout chartLayout, series []metricChartSeries) { @@ -711,3 +728,49 @@ func valueClamp(value float64, scale chartScale) float64 { } return value } + +func chartStatsLabel(datasets [][]float64) string { + mn, avg, mx := globalStats(datasets) + if mx <= 0 && avg <= 0 && mn <= 0 { + return "" + } + return fmt.Sprintf("min %s avg %s max %s", + chartLegendNumber(mn), + chartLegendNumber(avg), + chartLegendNumber(mx), + ) +} + +func gpuDisplayLabel(idx int) string { + if name := gpuModelNameByIndex(idx); name != "" { + return fmt.Sprintf("GPU %d — %s", idx, name) + } + return fmt.Sprintf("GPU %d", idx) +} + +func gpuModelNameByIndex(idx int) string { + now := time.Now() + gpuLabelCache.mu.Lock() + if now.Sub(gpuLabelCache.loadedAt) > 30*time.Second || gpuLabelCache.byIndex == nil { + gpuLabelCache.loadedAt = now + gpuLabelCache.byIndex = loadGPUModelNames() + } + name := strings.TrimSpace(gpuLabelCache.byIndex[idx]) + gpuLabelCache.mu.Unlock() + return name +} + +func loadGPUModelNames() map[int]string { + out := map[int]string{} + gpus, err := platform.New().ListNvidiaGPUs() + if err != nil { + return out + } + for _, gpu := range gpus { + name := strings.TrimSpace(gpu.Name) + if name != "" { + out[gpu.Index] = name + } + } + return out +} diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go index b9cdae1..77ce0be 100644 --- a/audit/internal/webui/pages.go +++ b/audit/internal/webui/pages.go @@ -860,6 +860,35 @@ func renderMetrics() string {