diff --git a/audit/internal/webui/server.go b/audit/internal/webui/server.go index 1dc1108..5b4ca6b 100644 --- a/audit/internal/webui/server.go +++ b/audit/internal/webui/server.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path/filepath" + "sort" "strings" "sync" "time" @@ -643,6 +644,7 @@ func namedTempDatasets(samples []platform.LiveMetricSample, group string) ([][]f } } } + sort.Strings(names) datasets := make([][]float64, 0, len(names)) for _, name := range names { ds := make([]float64, len(samples)) @@ -670,6 +672,7 @@ func namedFanDatasets(samples []platform.LiveMetricSample) ([][]float64, []strin } } } + sort.Strings(names) datasets := make([][]float64, 0, len(names)) for _, name := range names { ds := make([]float64, len(samples)) @@ -697,6 +700,7 @@ func gpuDatasets(samples []platform.LiveMetricSample, pick func(platform.GPUMetr } } } + sort.Ints(indices) datasets := make([][]float64, 0, len(indices)) names := make([]string, 0, len(indices)) for _, idx := range indices { diff --git a/audit/internal/webui/server_test.go b/audit/internal/webui/server_test.go index 99cd95d..927d5a6 100644 --- a/audit/internal/webui/server_test.go +++ b/audit/internal/webui/server_test.go @@ -89,6 +89,53 @@ func TestChartDataFromSamplesUsesFullHistory(t *testing.T) { } } +func TestChartDataFromSamplesKeepsStableGPUSeriesOrder(t *testing.T) { + samples := []platform.LiveMetricSample{ + { + Timestamp: time.Now().Add(-2 * time.Minute), + GPUs: []platform.GPUMetricRow{ + {GPUIndex: 7, PowerW: 170}, + {GPUIndex: 2, PowerW: 120}, + {GPUIndex: 0, PowerW: 100}, + }, + }, + { + Timestamp: time.Now().Add(-1 * time.Minute), + GPUs: []platform.GPUMetricRow{ + {GPUIndex: 0, PowerW: 101}, + {GPUIndex: 7, PowerW: 171}, + {GPUIndex: 2, PowerW: 121}, + }, + }, + } + + datasets, names, _, title, _, _, ok := chartDataFromSamples("gpu-all-power", samples) + if !ok { + t.Fatal("chartDataFromSamples returned ok=false") + } + if title != "GPU Power" { + t.Fatalf("title=%q", title) + } + wantNames := []string{"GPU 0", "GPU 2", "GPU 7"} + if len(names) != len(wantNames) { + t.Fatalf("names len=%d want %d: %v", len(names), len(wantNames), names) + } + for i := range wantNames { + if names[i] != wantNames[i] { + t.Fatalf("names[%d]=%q want %q; full=%v", i, names[i], wantNames[i], names) + } + } + if got := datasets[0]; len(got) != 2 || got[0] != 100 || got[1] != 101 { + t.Fatalf("GPU 0 dataset=%v want [100 101]", got) + } + if got := datasets[1]; len(got) != 2 || got[0] != 120 || got[1] != 121 { + t.Fatalf("GPU 2 dataset=%v want [120 121]", got) + } + if got := datasets[2]; len(got) != 2 || got[0] != 170 || got[1] != 171 { + t.Fatalf("GPU 7 dataset=%v want [170 171]", got) + } +} + func TestNormalizePowerSeriesHoldsLastPositive(t *testing.T) { got := normalizePowerSeries([]float64{0, 480, 0, 0, 510, 0}) want := []float64{0, 480, 480, 480, 510, 510}