diff --git a/audit/internal/platform/sat.go b/audit/internal/platform/sat.go
index 0a87222..d3cf98b 100644
--- a/audit/internal/platform/sat.go
+++ b/audit/internal/platform/sat.go
@@ -262,6 +262,9 @@ func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) {
MemoryMB: memMB,
})
}
+ sort.Slice(gpus, func(i, j int) bool {
+ return gpus[i].Index < gpus[j].Index
+ })
return gpus, nil
}
diff --git a/audit/internal/webui/charts_svg.go b/audit/internal/webui/charts_svg.go
index 6cf357a..ecbeda5 100644
--- a/audit/internal/webui/charts_svg.go
+++ b/audit/internal/webui/charts_svg.go
@@ -54,9 +54,9 @@ var metricChartPalette = []string{
}
var gpuLabelCache struct {
- mu sync.Mutex
- loadedAt time.Time
- byIndex map[int]string
+ 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) {
@@ -125,8 +125,7 @@ func renderGPUOverviewChartSVG(idx int, samples []platform.LiveMetricSample, tim
temp := gpuDatasetByIndex(samples, idx, func(g platform.GPUMetricRow) float64 { return g.TempC })
power := gpuDatasetByIndex(samples, idx, func(g platform.GPUMetricRow) float64 { return g.PowerW })
coreClock := gpuDatasetByIndex(samples, idx, func(g platform.GPUMetricRow) float64 { return g.ClockMHz })
- memClock := gpuDatasetByIndex(samples, idx, func(g platform.GPUMetricRow) float64 { return g.MemClockMHz })
- if temp == nil && power == nil && coreClock == nil && memClock == nil {
+ if temp == nil && power == nil && coreClock == nil {
return nil, false, nil
}
labels := sampleTimeLabels(samples)
@@ -139,7 +138,6 @@ func renderGPUOverviewChartSVG(idx int, samples []platform.LiveMetricSample, tim
{Name: "Temp C", Values: coalesceDataset(temp, len(labels)), Color: "#f05a5a", AxisTitle: "Temp C"},
{Name: "Power W", Values: coalesceDataset(power, len(labels)), Color: "#ffb357", AxisTitle: "Power W"},
{Name: "Core Clock MHz", Values: coalesceDataset(coreClock, len(labels)), Color: "#73bf69", AxisTitle: "Core MHz"},
- {Name: "Memory Clock MHz", Values: coalesceDataset(memClock, len(labels)), Color: "#5794f2", AxisTitle: "Memory MHz"},
},
timeline,
)
@@ -150,8 +148,8 @@ func renderGPUOverviewChartSVG(idx int, samples []platform.LiveMetricSample, tim
}
func drawGPUOverviewChartSVG(title string, labels []string, times []time.Time, series []metricChartSeries, timeline []chartTimelineSegment) ([]byte, error) {
- if len(series) != 4 {
- return nil, fmt.Errorf("gpu overview requires 4 series, got %d", len(series))
+ if len(series) != 3 {
+ return nil, fmt.Errorf("gpu overview requires 3 series, got %d", len(series))
}
const (
width = 1400
@@ -165,7 +163,6 @@ func drawGPUOverviewChartSVG(title string, labels []string, times []time.Time, s
leftOuterAxis = 72
leftInnerAxis = 132
rightInnerAxis = 1268
- rightOuterAxis = 1328
)
layout := chartLayout{
Width: width,
@@ -175,7 +172,7 @@ func drawGPUOverviewChartSVG(title string, labels []string, times []time.Time, s
PlotTop: plotTop,
PlotBottom: plotBottom,
}
- axisX := []int{leftOuterAxis, leftInnerAxis, rightInnerAxis, rightOuterAxis}
+ axisX := []int{leftOuterAxis, leftInnerAxis, rightInnerAxis}
pointCount := len(labels)
if len(times) > pointCount {
pointCount = len(times)
diff --git a/audit/internal/webui/metricsdb.go b/audit/internal/webui/metricsdb.go
index 61379c2..ac6cb0a 100644
--- a/audit/internal/webui/metricsdb.go
+++ b/audit/internal/webui/metricsdb.go
@@ -22,6 +22,13 @@ type MetricsDB struct {
db *sql.DB
}
+func (m *MetricsDB) Close() error {
+ if m == nil || m.db == nil {
+ return nil
+ }
+ return m.db.Close()
+}
+
// openMetricsDB opens (or creates) the metrics database at the given path.
func openMetricsDB(path string) (*MetricsDB, error) {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
@@ -164,6 +171,23 @@ func (m *MetricsDB) LoadAll() ([]platform.LiveMetricSample, error) {
return m.loadSamples(`SELECT ts,cpu_load_pct,mem_load_pct,power_w FROM sys_metrics ORDER BY ts`, nil)
}
+// LoadBetween returns samples in chronological order within the given time window.
+func (m *MetricsDB) LoadBetween(start, end time.Time) ([]platform.LiveMetricSample, error) {
+ if m == nil {
+ return nil, nil
+ }
+ if start.IsZero() || end.IsZero() {
+ return nil, nil
+ }
+ if end.Before(start) {
+ start, end = end, start
+ }
+ return m.loadSamples(
+ `SELECT ts,cpu_load_pct,mem_load_pct,power_w FROM sys_metrics WHERE ts>=? AND ts<=? ORDER BY ts`,
+ start.Unix(), end.Unix(),
+ )
+}
+
// loadSamples reconstructs LiveMetricSample rows from the normalized tables.
func (m *MetricsDB) loadSamples(query string, args ...any) ([]platform.LiveMetricSample, error) {
rows, err := m.db.Query(query, args...)
@@ -364,9 +388,6 @@ func (m *MetricsDB) ExportCSV(w io.Writer) error {
return cw.Error()
}
-// Close closes the database.
-func (m *MetricsDB) Close() { _ = m.db.Close() }
-
func nullFloat(v float64) sql.NullFloat64 {
return sql.NullFloat64{Float64: v, Valid: true}
}
diff --git a/audit/internal/webui/metricsdb_test.go b/audit/internal/webui/metricsdb_test.go
index 9ac264d..4ec7d08 100644
--- a/audit/internal/webui/metricsdb_test.go
+++ b/audit/internal/webui/metricsdb_test.go
@@ -143,3 +143,32 @@ CREATE TABLE temp_metrics (
t.Fatalf("MemClockMHz=%v want 2600", got)
}
}
+
+func TestMetricsDBLoadBetweenFiltersWindow(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 < 5; i++ {
+ if err := db.Write(platform.LiveMetricSample{
+ Timestamp: base.Add(time.Duration(i) * time.Minute),
+ CPULoadPct: float64(i),
+ }); err != nil {
+ t.Fatalf("Write(%d): %v", i, err)
+ }
+ }
+
+ got, err := db.LoadBetween(base.Add(1*time.Minute), base.Add(3*time.Minute))
+ if err != nil {
+ t.Fatalf("LoadBetween: %v", err)
+ }
+ if len(got) != 3 {
+ t.Fatalf("LoadBetween len=%d want 3", len(got))
+ }
+ if !got[0].Timestamp.Equal(base.Add(1*time.Minute)) || !got[2].Timestamp.Equal(base.Add(3*time.Minute)) {
+ t.Fatalf("window=%v..%v", got[0].Timestamp, got[2].Timestamp)
+ }
+}
diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go
index 77ce0be..0ad7835 100644
--- a/audit/internal/webui/pages.go
+++ b/audit/internal/webui/pages.go
@@ -834,12 +834,6 @@ func renderMetrics() string {
-
Loading...