From 938e499ac2ef5858b53dd7e82c04771f0d405f52 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Wed, 1 Apr 2026 23:33:13 +0300 Subject: [PATCH] Serve charts from SQLite history only --- audit/internal/webui/server.go | 223 ++------------------------------- 1 file changed, 12 insertions(+), 211 deletions(-) diff --git a/audit/internal/webui/server.go b/audit/internal/webui/server.go index cce7d78..1dc1108 100644 --- a/audit/internal/webui/server.go +++ b/audit/internal/webui/server.go @@ -127,6 +127,8 @@ type namedMetricsRing struct { Ring *metricsRing } +const metricsChartWindow = 120 + // pendingNetChange tracks a network state change awaiting confirmation. type pendingNetChange struct { snapshot platform.NetworkSnapshot @@ -181,7 +183,7 @@ func NewHandler(opts HandlerOptions) http.Handler { // Open metrics DB and pre-fill ring buffers from history. if db, err := openMetricsDB(metricsDBPath); err == nil { h.metricsDB = db - if samples, err := db.LoadRecent(120); err == nil { + if samples, err := db.LoadRecent(metricsChartWindow); err == nil { for _, s := range samples { h.feedRings(s) } @@ -302,11 +304,11 @@ func (h *handler) startMetricsCollector() { defer ticker.Stop() for range ticker.C { sample := platform.SampleLiveMetrics() - h.feedRings(sample) - h.setLatestMetric(sample) if h.metricsDB != nil { _ = h.metricsDB.Write(sample) } + h.feedRings(sample) + h.setLatestMetric(sample) } }() } @@ -458,214 +460,13 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request) path := strings.TrimPrefix(r.URL.Path, "/api/metrics/chart/") path = strings.TrimSuffix(path, ".svg") - if h.metricsDB != nil { - if datasets, names, labels, title, yMin, yMax, ok := h.chartDataFromDB(path); ok { - buf, err := renderChartSVG(title, datasets, names, labels, yMin, yMax) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "image/svg+xml") - w.Header().Set("Cache-Control", "no-store") - _, _ = w.Write(buf) - return - } + if h.metricsDB == nil { + http.Error(w, "metrics database not available", http.StatusServiceUnavailable) + return } - - var datasets [][]float64 - var names []string - var labels []string - var title string - var yMin, yMax *float64 // nil = auto; for load charts fixed 0-100 - - switch { - // ── Server sub-charts ───────────────────────────────────────────────── - case path == "server-load": - title = "CPU / Memory Load" - vCPULoad, l := h.ringCPULoad.snapshot() - vMemLoad, _ := h.ringMemLoad.snapshot() - labels = l - datasets = [][]float64{vCPULoad, vMemLoad} - names = []string{"CPU Load %", "Mem Load %"} - yMin = floatPtr(0) - yMax = floatPtr(100) - - case path == "server-temp", path == "server-temp-cpu": - title = "CPU Temperature" - h.ringsMu.Lock() - datasets, names, labels = snapshotNamedRings(h.cpuTempRings) - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = autoMax120(datasets...) - - case path == "server-temp-gpu": - title = "GPU Temperature" - h.ringsMu.Lock() - for idx, gr := range h.gpuRings { - if gr == nil { - continue - } - vTemp, l := gr.Temp.snapshot() - datasets = append(datasets, vTemp) - names = append(names, fmt.Sprintf("GPU %d", idx)) - if len(labels) == 0 { - labels = l - } - } - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = autoMax120(datasets...) - - case path == "server-temp-ambient": - title = "Ambient / Other Sensors" - h.ringsMu.Lock() - datasets, names, labels = snapshotNamedRings(h.ambientTempRings) - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = autoMax120(datasets...) - - case path == "server-power": - title = "System Power" - vPower, l := h.ringPower.snapshot() - vPower = normalizePowerSeries(vPower) - labels = l - datasets = [][]float64{vPower} - names = []string{"Power W"} - yMin = floatPtr(0) - yMax = autoMax120(vPower) - - case path == "server-fans": - title = "Fan RPM" - h.ringsMu.Lock() - datasets, names, labels = snapshotFanRings(h.ringFans, h.fanNames) - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = autoMax120(datasets...) - - // ── Combined GPU charts (all GPUs on one chart) ─────────────────────── - case path == "gpu-all-load": - title = "GPU Compute Load" - h.ringsMu.Lock() - for idx, gr := range h.gpuRings { - if gr == nil { - continue - } - vUtil, l := gr.Util.snapshot() - datasets = append(datasets, vUtil) - names = append(names, fmt.Sprintf("GPU %d", idx)) - if len(labels) == 0 { - labels = l - } - } - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = floatPtr(100) - - case path == "gpu-all-memload": - title = "GPU Memory Load" - h.ringsMu.Lock() - for idx, gr := range h.gpuRings { - if gr == nil { - continue - } - vMem, l := gr.MemUtil.snapshot() - datasets = append(datasets, vMem) - names = append(names, fmt.Sprintf("GPU %d", idx)) - if len(labels) == 0 { - labels = l - } - } - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = floatPtr(100) - - case path == "gpu-all-power": - title = "GPU Power" - h.ringsMu.Lock() - for idx, gr := range h.gpuRings { - if gr == nil { - continue - } - vPow, l := gr.Power.snapshot() - datasets = append(datasets, vPow) - names = append(names, fmt.Sprintf("GPU %d", idx)) - if len(labels) == 0 { - labels = l - } - } - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = autoMax120(datasets...) - - case path == "gpu-all-temp": - title = "GPU Temperature" - h.ringsMu.Lock() - for idx, gr := range h.gpuRings { - if gr == nil { - continue - } - vTemp, l := gr.Temp.snapshot() - datasets = append(datasets, vTemp) - names = append(names, fmt.Sprintf("GPU %d", idx)) - if len(labels) == 0 { - labels = l - } - } - h.ringsMu.Unlock() - yMin = floatPtr(0) - yMax = autoMax120(datasets...) - - // ── Per-GPU sub-charts ──────────────────────────────────────────────── - case strings.HasPrefix(path, "gpu/"): - rest := strings.TrimPrefix(path, "gpu/") - // rest is either "{idx}-load", "{idx}-temp", "{idx}-power", or legacy "{idx}" - sub := "" - if i := strings.LastIndex(rest, "-"); i > 0 { - sub = rest[i+1:] - rest = rest[:i] - } - idx := 0 - fmt.Sscanf(rest, "%d", &idx) - h.ringsMu.Lock() - var gr *gpuRings - if idx < len(h.gpuRings) { - gr = h.gpuRings[idx] - } - h.ringsMu.Unlock() - if gr == nil { - http.NotFound(w, r) - return - } - switch sub { - case "load": - vUtil, l := gr.Util.snapshot() - vMemUtil, _ := gr.MemUtil.snapshot() - labels = l - title = fmt.Sprintf("GPU %d Load", idx) - datasets = [][]float64{vUtil, vMemUtil} - names = []string{"Load %", "Mem %"} - yMin = floatPtr(0) - yMax = floatPtr(100) - case "temp": - vTemp, l := gr.Temp.snapshot() - labels = l - title = fmt.Sprintf("GPU %d Temperature", idx) - datasets = [][]float64{vTemp} - names = []string{"Temp °C"} - yMin = floatPtr(0) - yMax = autoMax120(vTemp) - default: // "power" or legacy (no sub) - vPower, l := gr.Power.snapshot() - labels = l - title = fmt.Sprintf("GPU %d Power", idx) - datasets = [][]float64{vPower} - names = []string{"Power W"} - yMin = floatPtr(0) - yMax = autoMax120(vPower) - } - - default: - http.NotFound(w, r) + datasets, names, labels, title, yMin, yMax, ok := h.chartDataFromDB(path) + if !ok { + http.Error(w, "metrics history unavailable", http.StatusServiceUnavailable) return } @@ -680,7 +481,7 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request) } func (h *handler) chartDataFromDB(path string) ([][]float64, []string, []string, string, *float64, *float64, bool) { - samples, err := h.metricsDB.LoadAll() + samples, err := h.metricsDB.LoadRecent(metricsChartWindow) if err != nil || len(samples) == 0 { return nil, nil, nil, "", nil, nil, false }