diff --git a/audit/internal/webui/metricsdb.go b/audit/internal/webui/metricsdb.go index 1e31280..3039e69 100644 --- a/audit/internal/webui/metricsdb.go +++ b/audit/internal/webui/metricsdb.go @@ -120,7 +120,7 @@ func (m *MetricsDB) Write(s platform.LiveMetricSample) error { // LoadRecent returns up to n samples in chronological order (oldest first). func (m *MetricsDB) LoadRecent(n int) ([]platform.LiveMetricSample, error) { - return m.loadSamples(`SELECT ts,cpu_load_pct,mem_load_pct,power_w FROM sys_metrics ORDER BY ts DESC LIMIT ?`, n) + return m.loadSamples(`SELECT ts,cpu_load_pct,mem_load_pct,power_w FROM (SELECT ts,cpu_load_pct,mem_load_pct,power_w FROM sys_metrics ORDER BY ts DESC LIMIT ?) ORDER BY ts`, n) } // LoadAll returns all persisted samples in chronological order (oldest first). @@ -151,11 +151,6 @@ func (m *MetricsDB) loadSamples(query string, args ...any) ([]platform.LiveMetri if len(sysRows) == 0 { return nil, nil } - // Reverse to chronological order - for i, j := 0, len(sysRows)-1; i < j; i, j = i+1, j-1 { - sysRows[i], sysRows[j] = sysRows[j], sysRows[i] - } - // Collect min/max ts for range query minTS := sysRows[0].ts maxTS := sysRows[len(sysRows)-1].ts diff --git a/audit/internal/webui/metricsdb_test.go b/audit/internal/webui/metricsdb_test.go new file mode 100644 index 0000000..f2484fa --- /dev/null +++ b/audit/internal/webui/metricsdb_test.go @@ -0,0 +1,69 @@ +package webui + +import ( + "path/filepath" + "testing" + "time" + + "bee/audit/internal/platform" +) + +func TestMetricsDBLoadSamplesKeepsChronologicalRangeForGPUs(t *testing.T) { + db, err := openMetricsDB(filepath.Join(t.TempDir(), "metrics.db")) + if err != nil { + t.Fatalf("openMetricsDB: %v", err) + } + defer db.Close() + + base := time.Unix(1_700_000_000, 0).UTC() + for i := 0; i < 3; i++ { + err := db.Write(platform.LiveMetricSample{ + Timestamp: base.Add(time.Duration(i) * time.Second), + CPULoadPct: float64(10 + i), + MemLoadPct: float64(20 + i), + PowerW: float64(300 + i), + GPUs: []platform.GPUMetricRow{ + {GPUIndex: 0, PowerW: float64(100 + i)}, + {GPUIndex: 2, PowerW: float64(200 + i)}, + }, + }) + if err != nil { + t.Fatalf("Write(%d): %v", i, err) + } + } + + all, err := db.LoadAll() + if err != nil { + t.Fatalf("LoadAll: %v", err) + } + if len(all) != 3 { + t.Fatalf("LoadAll len=%d want 3", len(all)) + } + for i, sample := range all { + if len(sample.GPUs) != 2 { + t.Fatalf("LoadAll sample %d GPUs=%v want 2 rows", i, sample.GPUs) + } + if sample.GPUs[0].GPUIndex != 0 || sample.GPUs[0].PowerW != float64(100+i) { + t.Fatalf("LoadAll sample %d GPU0=%+v", i, sample.GPUs[0]) + } + if sample.GPUs[1].GPUIndex != 2 || sample.GPUs[1].PowerW != float64(200+i) { + t.Fatalf("LoadAll sample %d GPU1=%+v", i, sample.GPUs[1]) + } + } + + recent, err := db.LoadRecent(2) + if err != nil { + t.Fatalf("LoadRecent: %v", err) + } + if len(recent) != 2 { + t.Fatalf("LoadRecent len=%d want 2", len(recent)) + } + if !recent[0].Timestamp.Before(recent[1].Timestamp) { + t.Fatalf("LoadRecent timestamps not ascending: %v >= %v", recent[0].Timestamp, recent[1].Timestamp) + } + for i, sample := range recent { + if len(sample.GPUs) != 2 { + t.Fatalf("LoadRecent sample %d GPUs=%v want 2 rows", i, sample.GPUs) + } + } +}