From c194924d13de12e2eb21e94f7987bb07fecc98b5 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 24 Jul 2026 01:43:48 -0700 Subject: [PATCH] telemetry: per-cluster size over time on the dashboard (#10417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard charted one summed disk-usage line, so a step in the total gave no hint which cluster moved. A new panel stacks each cluster's daily size as its own band: the top of the stack is the fleet total, each band is one cluster, and the clusters past the twentieth are summed into an "other" band so the stack still adds up to the total. The series is built from the per-cluster daily histories and served by /api/cluster-sizes. Clusters report roughly once a day at no fixed hour, so a day with no report carries the previous value forward — dropping it to zero would sag the total every day as the clusters that have not reported yet fall out from under it. A cluster that stops reporting past the active window ends at its last sample instead of holding capacity forever. Ranking is by the most recent day, tie-broken on cluster id so the colors do not shuffle between refreshes. Hover and click resolve to the band under the pointer: Chart.js's builtin interaction modes match the nearest line, which on a stack of thin bands is rarely the band being pointed at. Clicking one fills the per-cluster history lookup below it. --- telemetry/README.md | 3 + telemetry/server/api/handlers.go | 24 +++++ telemetry/server/dashboard/dashboard.go | 129 ++++++++++++++++++++++++ telemetry/server/main.go | 1 + telemetry/server/storage/history.go | 3 + telemetry/server/storage/prometheus.go | 2 +- telemetry/server/storage/sizes.go | 106 +++++++++++++++++++ telemetry/server/storage/sizes_test.go | 122 ++++++++++++++++++++++ 8 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 telemetry/server/storage/sizes.go create mode 100644 telemetry/server/storage/sizes_test.go diff --git a/telemetry/README.md b/telemetry/README.md index 6e1db0aeb..f07f43b88 100644 --- a/telemetry/README.md +++ b/telemetry/README.md @@ -177,6 +177,9 @@ GET /api/metrics?days=30 # Get one cluster's daily usage history (disk bytes, volumes, volume servers) GET /api/history?cluster_id=&days=90 + +# Get per-cluster disk usage over time, largest first, the rest summed as "other" +GET /api/cluster-sizes?days=30&limit=20 ``` ### Monitoring diff --git a/telemetry/server/api/handlers.go b/telemetry/server/api/handlers.go index b3faf234d..a4c767ebc 100644 --- a/telemetry/server/api/handlers.go +++ b/telemetry/server/api/handlers.go @@ -150,6 +150,30 @@ func (h *Handler) GetMetrics(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(metrics) } +func (h *Handler) GetClusterSizes(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + days := 30 // default + if daysStr := r.URL.Query().Get("days"); daysStr != "" { + if d, err := strconv.Atoi(daysStr); err == nil && d > 0 && d <= 365 { + days = d + } + } + + limit := 20 // default + if limitStr := r.URL.Query().Get("limit"); limitStr != "" { + if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 1000 { + limit = l + } + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(h.storage.GetClusterSizeSeries(days, limit)) +} + func (h *Handler) GetHistory(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) diff --git a/telemetry/server/dashboard/dashboard.go b/telemetry/server/dashboard/dashboard.go index 78301acc5..c977c4101 100644 --- a/telemetry/server/dashboard/dashboard.go +++ b/telemetry/server/dashboard/dashboard.go @@ -69,6 +69,10 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) { font-weight: bold; margin-bottom: 15px; } + .chart-subtitle { + color: #666; + margin: -10px 0 15px; + } .loading { text-align: center; padding: 40px; @@ -159,6 +163,14 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) { +
+
Cluster Sizes Over Time
+
+
+ +
+
+
Per-Cluster History
@@ -177,6 +189,7 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) {