diff --git a/telemetry/README.md b/telemetry/README.md
index b734d1f12..4edd58e27 100644
--- a/telemetry/README.md
+++ b/telemetry/README.md
@@ -139,7 +139,7 @@ The telemetry server exposes these Prometheus metrics:
### Cluster Metrics
- `seaweedfs_telemetry_total_clusters`: Total unique clusters (30 days)
- `seaweedfs_telemetry_active_clusters`: Active clusters (7 days)
-- `seaweedfs_telemetry_confirmed_clusters`: Active clusters seen on 2+ distinct days — one-shot reports don't count, and the version/OS distributions in `/api/stats` are computed over these
+- `seaweedfs_telemetry_confirmed_clusters`: Active clusters seen on 7+ distinct days — one-shot reports don't count, and the version/OS distributions in `/api/stats` are computed over these
### Per-Cluster Metrics
- `seaweedfs_telemetry_volume_servers{cluster_id}`: Volume servers per cluster
diff --git a/telemetry/server/dashboard/dashboard.go b/telemetry/server/dashboard/dashboard.go
index 5a8b6e077..f5d30af00 100644
--- a/telemetry/server/dashboard/dashboard.go
+++ b/telemetry/server/dashboard/dashboard.go
@@ -129,7 +129,7 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) {
-
diff --git a/telemetry/server/go.mod b/telemetry/server/go.mod
index 8c5f7ecbf..4f0778b3d 100644
--- a/telemetry/server/go.mod
+++ b/telemetry/server/go.mod
@@ -1,6 +1,6 @@
module github.com/seaweedfs/seaweedfs/telemetry/server
-go 1.25.8
+go 1.26
require (
github.com/prometheus/client_golang v1.24.1
diff --git a/telemetry/server/storage/confirmed_test.go b/telemetry/server/storage/confirmed_test.go
index 4670d1433..51e5d2872 100644
--- a/telemetry/server/storage/confirmed_test.go
+++ b/telemetry/server/storage/confirmed_test.go
@@ -44,13 +44,18 @@ func TestConfirmedClusters(t *testing.T) {
t.Fatalf("fallback distribution missing active cluster: %v", v)
}
- // Give cluster A a sample from yesterday: now seen on 2 distinct days.
+ // Give cluster A samples from the six previous days: now seen on 7
+ // distinct days.
s.mu.Lock()
id := "aaaaaaaa-0000-0000-0000-000000000001"
- s.histories[id] = append([]HistorySample{{
- Ts: time.Now().AddDate(0, 0, -1).Unix(),
- TotalDiskBytes: 50,
- }}, s.histories[id]...)
+ var older []HistorySample
+ for offset := -6; offset < 0; offset++ {
+ older = append(older, HistorySample{
+ Ts: time.Now().AddDate(0, 0, offset).Unix(),
+ TotalDiskBytes: 50,
+ })
+ }
+ s.histories[id] = append(older, s.histories[id]...)
s.mu.Unlock()
// A one-shot cluster B arrives (like an injected report): it counts as
@@ -60,7 +65,7 @@ func TestConfirmedClusters(t *testing.T) {
}
stats = statsOf(t, s)
if stats["active_instances"] != 2 || stats["confirmed_instances"] != 1 {
- t.Fatalf("day two: active=%v confirmed=%v, want 2/1", stats["active_instances"], stats["confirmed_instances"])
+ t.Fatalf("day seven: active=%v confirmed=%v, want 2/1", stats["active_instances"], stats["confirmed_instances"])
}
v := stats["versions"].(map[string]int)
if v["4.40"] != 1 {
diff --git a/telemetry/server/storage/history.go b/telemetry/server/storage/history.go
index e51d9a786..9a25ba19f 100644
--- a/telemetry/server/storage/history.go
+++ b/telemetry/server/storage/history.go
@@ -8,7 +8,7 @@ import (
// confirmDays is how many distinct UTC days a cluster must have reported
// on before it counts as confirmed in the aggregated stats.
-const confirmDays = 2
+const confirmDays = 7
// activeDays is how recently a cluster must have reported to count as active.
const activeDays = 7
@@ -43,9 +43,9 @@ func (s *PrometheusStorage) appendHistory(data *proto.TelemetryData, receivedAt
}
// seriesHistories picks the clusters the fleet-wide series are built from: the
-// confirmed ones. A cluster that only ever reported on one day is usually a CI
-// or test cluster that lived for a minute, and those arrive faster than they
-// age out, so counting them makes every fleet total climb forever. Falls back to
+// confirmed ones. A cluster that reported for less than a week is usually a CI
+// or test cluster, and those arrive faster than they age out, so counting them
+// makes every fleet total climb forever. Falls back to
// all clusters while none is confirmed yet, so a fresh server still draws its
// charts. Callers must hold s.mu.
func (s *PrometheusStorage) seriesHistories() map[string][]HistorySample {
diff --git a/telemetry/server/storage/metrics_test.go b/telemetry/server/storage/metrics_test.go
index 185b631af..566c66924 100644
--- a/telemetry/server/storage/metrics_test.go
+++ b/telemetry/server/storage/metrics_test.go
@@ -15,7 +15,8 @@ func TestGetMetricsSumsEachDay(t *testing.T) {
seedSamples(s, "daily", HistorySample{TotalDiskBytes: 300, VolumeServerCount: 3},
-9, -8, -7, -6, -5, -4, -3, -2, -1, 0)
// Stopped reporting past the active window: counts on its own days only.
- seedSamples(s, "gone", HistorySample{TotalDiskBytes: 900, VolumeServerCount: 9}, -9, -8)
+ seedSamples(s, "gone", HistorySample{TotalDiskBytes: 900, VolumeServerCount: 9},
+ -14, -13, -12, -11, -10, -9, -8)
metrics, err := s.GetMetrics(10)
if err != nil {
@@ -40,7 +41,8 @@ func TestGetMetricsSumsEachDay(t *testing.T) {
func TestGetMetricsCarriesSkippedDaysForward(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
- seedSamples(s, "gappy", HistorySample{TotalDiskBytes: 500, VolumeServerCount: 5}, -3, -1)
+ seedSamples(s, "gappy", HistorySample{TotalDiskBytes: 500, VolumeServerCount: 5},
+ -8, -7, -6, -5, -4, -3, -1)
metrics, err := s.GetMetrics(4)
if err != nil {
@@ -56,21 +58,23 @@ func TestGetMetricsCarriesSkippedDaysForward(t *testing.T) {
// out of nothing on its first day of data.
func TestGetMetricsWindowStartsAtOldestSample(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
- seedSamples(s, "recent", HistorySample{TotalDiskBytes: 100, VolumeServerCount: 1}, -2, -1, 0)
+ seedSamples(s, "recent", HistorySample{TotalDiskBytes: 100, VolumeServerCount: 1},
+ -6, -5, -4, -3, -2, -1, 0)
metrics, err := s.GetMetrics(30)
if err != nil {
t.Fatal(err)
}
- if got := metrics["dates"].([]string); len(got) != 3 {
- t.Errorf("dates = %v, want the 3 days with history, not 30", got)
+ if got := metrics["dates"].([]string); len(got) != 7 {
+ t.Errorf("dates = %v, want the 7 days with history, not 30", got)
}
- if got := metrics["disk_usage"].([]uint64); !equal(got, []uint64{100, 100, 100}) {
+ if got := metrics["disk_usage"].([]uint64); !equal(got, []uint64{100, 100, 100, 100, 100, 100, 100}) {
t.Errorf("disk_usage = %v, want no leading zero days", got)
}
// History reaching past the requested window still clips to the window.
- seedSamples(s, "old", HistorySample{TotalDiskBytes: 50, VolumeServerCount: 1}, -40, -39)
+ seedSamples(s, "old", HistorySample{TotalDiskBytes: 50, VolumeServerCount: 1},
+ -45, -44, -43, -42, -41, -40, -39)
metrics, err = s.GetMetrics(10)
if err != nil {
t.Fatal(err)
@@ -86,8 +90,10 @@ func TestGetMetricsAgreesWithClusterSizes(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
seedSamples(s, "daily", HistorySample{TotalDiskBytes: 300, VolumeServerCount: 3},
-9, -8, -7, -6, -5, -4, -3, -2, -1, 0)
- seedSamples(s, "lagging", HistorySample{TotalDiskBytes: 200, VolumeServerCount: 2}, -3, -2)
- seedSamples(s, "gone", HistorySample{TotalDiskBytes: 900, VolumeServerCount: 9}, -9, -8)
+ seedSamples(s, "lagging", HistorySample{TotalDiskBytes: 200, VolumeServerCount: 2},
+ -8, -7, -6, -5, -4, -3, -2)
+ seedSamples(s, "gone", HistorySample{TotalDiskBytes: 900, VolumeServerCount: 9},
+ -14, -13, -12, -11, -10, -9, -8)
metrics, err := s.GetMetrics(10)
if err != nil {
@@ -111,7 +117,8 @@ func TestGetMetricsAgreesWithClusterSizes(t *testing.T) {
func TestGetMetricsExcludesUnconfirmedClusters(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
- seedSamples(s, "real", HistorySample{TotalDiskBytes: 300, VolumeServerCount: 3}, -3, -2, -1, 0)
+ seedSamples(s, "real", HistorySample{TotalDiskBytes: 300, VolumeServerCount: 3},
+ -6, -5, -4, -3, -2, -1, 0)
for _, id := range []string{"ci-1", "ci-2", "ci-3"} {
seedSamples(s, id, HistorySample{TotalDiskBytes: 5, VolumeServerCount: 14}, -1)
}
@@ -128,7 +135,7 @@ func TestGetMetricsExcludesUnconfirmedClusters(t *testing.T) {
}
}
-// Until any cluster has two days of history the charts fall back to every
+// Until any cluster has a week of history the charts fall back to every
// cluster, so a fresh server doesn't serve empty series.
func TestGetMetricsFallsBackWhenNoneConfirmed(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
diff --git a/telemetry/server/storage/prometheus.go b/telemetry/server/storage/prometheus.go
index 8c664e090..4cc90fab1 100644
--- a/telemetry/server/storage/prometheus.go
+++ b/telemetry/server/storage/prometheus.go
@@ -53,7 +53,7 @@ func newPrometheusStorage(reg prometheus.Registerer) *PrometheusStorage {
}),
confirmedClusters: promauto.NewGauge(prometheus.GaugeOpts{
Name: "seaweedfs_telemetry_confirmed_clusters",
- Help: "Active clusters seen on at least 2 distinct days (last 7 days)",
+ Help: "Active clusters seen on at least 7 distinct days (last 7 days)",
}),
volumeServerCount: promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "seaweedfs_telemetry_volume_servers",
@@ -220,9 +220,9 @@ func (s *PrometheusStorage) updateStats() {
activeInstances++
versionsAll[instance.TelemetryData.Version]++
osAll[instance.TelemetryData.Os]++
- // A cluster is confirmed once seen on >=2 distinct UTC days
- // (histories hold one sample per day), so one-shot reports
- // can't skew the distributions below.
+ // A cluster is confirmed once seen on confirmDays distinct UTC
+ // days (histories hold one sample per day), so short-lived
+ // clusters can't skew the distributions below.
if len(s.histories[instance.TelemetryData.TopologyId]) >= confirmDays {
confirmedInstances++
versionsConfirmed[instance.TelemetryData.Version]++
@@ -231,7 +231,7 @@ func (s *PrometheusStorage) updateStats() {
}
}
- // Before any cluster has two days of history (fresh server with no
+ // Before any cluster has a week of history (fresh server with no
// prior state), fall back to all active clusters so the dashboard
// distributions aren't empty.
versions, osDistribution := versionsConfirmed, osConfirmed
diff --git a/telemetry/server/storage/sizes_test.go b/telemetry/server/storage/sizes_test.go
index b2ea82c06..c4bec1663 100644
--- a/telemetry/server/storage/sizes_test.go
+++ b/telemetry/server/storage/sizes_test.go
@@ -25,11 +25,13 @@ func TestClusterSizeSeries(t *testing.T) {
// Reported every day of the window.
seedSamples(s, "daily", HistorySample{TotalDiskBytes: 300, VolumeServerCount: 3},
-9, -8, -7, -6, -5, -4, -3, -2, -1, 0)
- // Reported two days ago and not since: still active, so its size is held
- // to the right edge instead of dropping out of the stack.
- seedSamples(s, "lagging", HistorySample{TotalDiskBytes: 200, VolumeServerCount: 2}, -3, -2)
+ // Stopped reporting two days ago: still active, so its size is held to
+ // the right edge instead of dropping out of the stack.
+ seedSamples(s, "lagging", HistorySample{TotalDiskBytes: 200, VolumeServerCount: 2},
+ -8, -7, -6, -5, -4, -3, -2)
// Stopped reporting past the active window: its own days only.
- seedSamples(s, "gone", HistorySample{TotalDiskBytes: 900, VolumeServerCount: 9}, -9, -8)
+ seedSamples(s, "gone", HistorySample{TotalDiskBytes: 900, VolumeServerCount: 9},
+ -14, -13, -12, -11, -10, -9, -8)
// One day of history only: unconfirmed, so it stays out of the stack.
seedSamples(s, "oneshot", HistorySample{TotalDiskBytes: 400, VolumeServerCount: 4}, -1)
@@ -48,7 +50,7 @@ func TestClusterSizeSeries(t *testing.T) {
if got := byId["daily"].Disk; !equal(got, []uint64{300, 300, 300, 300, 300, 300, 300, 300, 300, 300}) {
t.Errorf("daily = %v, want 300 every day", got)
}
- if got := byId["lagging"].Disk; !equal(got, []uint64{0, 0, 0, 0, 0, 0, 200, 200, 200, 200}) {
+ if got := byId["lagging"].Disk; !equal(got, []uint64{0, 200, 200, 200, 200, 200, 200, 200, 200, 200}) {
t.Errorf("lagging = %v, want its size carried to the right edge", got)
}
if got := byId["gone"].Disk; !equal(got, []uint64{900, 900, 0, 0, 0, 0, 0, 0, 0, 0}) {
@@ -62,7 +64,7 @@ func TestClusterSizeSeries(t *testing.T) {
if got := byId["daily"].Servers; !equal(got, []uint64{3, 3, 3, 3, 3, 3, 3, 3, 3, 3}) {
t.Errorf("daily servers = %v, want 3 every day", got)
}
- if got := byId["lagging"].Servers; !equal(got, []uint64{0, 0, 0, 0, 0, 0, 2, 2, 2, 2}) {
+ if got := byId["lagging"].Servers; !equal(got, []uint64{0, 2, 2, 2, 2, 2, 2, 2, 2, 2}) {
t.Errorf("lagging servers = %v, want carried to the right edge", got)
}
if got := byId["gone"].Servers; !equal(got, []uint64{9, 9, 0, 0, 0, 0, 0, 0, 0, 0}) {
@@ -89,10 +91,10 @@ func TestClusterSizeSeries(t *testing.T) {
if series.Other == nil || series.Other.Count != 2 {
t.Fatalf("other = %+v, want 2 clusters", series.Other)
}
- if !equal(series.Other.Disk, []uint64{900, 900, 0, 0, 0, 0, 200, 200, 200, 200}) {
+ if !equal(series.Other.Disk, []uint64{900, 1100, 200, 200, 200, 200, 200, 200, 200, 200}) {
t.Errorf("other = %v, want lagging+gone summed per day", series.Other.Disk)
}
- if !equal(series.Other.Servers, []uint64{9, 9, 0, 0, 0, 0, 2, 2, 2, 2}) {
+ if !equal(series.Other.Servers, []uint64{9, 11, 2, 2, 2, 2, 2, 2, 2, 2}) {
t.Errorf("other servers = %v, want lagging+gone summed per day", series.Other.Servers)
}
if series.ClusterCount != 3 || series.TotalDisk != 500 || series.TotalServers != 5 {
diff --git a/telemetry/server/storage/versions_test.go b/telemetry/server/storage/versions_test.go
index 828770019..9fc012cc5 100644
--- a/telemetry/server/storage/versions_test.go
+++ b/telemetry/server/storage/versions_test.go
@@ -10,31 +10,31 @@ func TestVersionSeries(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
// Upgraded mid-window: its band leaves the old version for the new one.
- seedSamples(s, "upgraded", HistorySample{Version: "4.39"}, -4, -3)
+ seedSamples(s, "upgraded", HistorySample{Version: "4.39"}, -6, -5, -4, -3)
seedSamples(s, "upgraded", HistorySample{Version: "4.40"}, -2, -1, 0)
// Reported every day on the same version.
- seedSamples(s, "steady", HistorySample{Version: "4.40"}, -4, -3, -2, -1, 0)
+ seedSamples(s, "steady", HistorySample{Version: "4.40"}, -6, -5, -4, -3, -2, -1, 0)
// One day of history only: unconfirmed, so it stays out of the stack.
seedSamples(s, "oneshot", HistorySample{Version: "4.40"}, -1)
// Confirmed, but its samples predate versions being recorded.
- seedSamples(s, "versionless", HistorySample{}, -9, -8)
+ seedSamples(s, "versionless", HistorySample{}, -13, -12, -11, -10, -9, -8, -7)
series := s.GetVersionSeries(10, 0)
// The versionless days are dropped before the axis is built, so the chart
// spans the days a version is known for instead of climbing out of blanks.
- if len(series.Dates) != 5 {
- t.Fatalf("dates = %v, want the 5 days with versions", series.Dates)
+ if len(series.Dates) != 7 {
+ t.Fatalf("dates = %v, want the 7 days with versions", series.Dates)
}
if len(series.Versions) != 2 ||
series.Versions[0].Version != "4.39" || series.Versions[1].Version != "4.40" {
t.Fatalf("versions = %+v, want 4.39 then 4.40", series.Versions)
}
- if got := series.Versions[0].Clusters; !equal(got, []uint64{1, 1, 0, 0, 0}) {
- t.Errorf("4.39 = %v, want the upgraded cluster's first two days", got)
+ if got := series.Versions[0].Clusters; !equal(got, []uint64{1, 1, 1, 1, 0, 0, 0}) {
+ t.Errorf("4.39 = %v, want the upgraded cluster's first four days", got)
}
- if got := series.Versions[1].Clusters; !equal(got, []uint64{1, 1, 2, 2, 2}) {
- t.Errorf("4.40 = %v, want steady plus upgraded from day 3", got)
+ if got := series.Versions[1].Clusters; !equal(got, []uint64{1, 1, 1, 1, 2, 2, 2}) {
+ t.Errorf("4.40 = %v, want steady plus upgraded from day 5", got)
}
if series.TotalClusters != 2 {
t.Errorf("total_clusters = %d, want 2", series.TotalClusters)
@@ -48,12 +48,12 @@ func TestVersionSeriesHoldsForwardAndLimits(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
// Stopped reporting past the active window: its own days only.
- seedSamples(s, "gone", HistorySample{Version: "3.97"}, -9, -8)
+ seedSamples(s, "gone", HistorySample{Version: "3.97"}, -14, -13, -12, -11, -10, -9, -8)
// Reported every day of the window.
seedSamples(s, "daily", HistorySample{Version: "4.40"}, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0)
- // Reported three days ago and not since: still active, so it holds its
- // version to the right edge instead of dropping out of the stack.
- seedSamples(s, "lagging", HistorySample{Version: "4.30"}, -3, -2)
+ // Stopped reporting two days ago: still active, so it holds its version
+ // to the right edge instead of dropping out of the stack.
+ seedSamples(s, "lagging", HistorySample{Version: "4.30"}, -8, -7, -6, -5, -4, -3, -2)
series := s.GetVersionSeries(10, 0)
if len(series.Dates) != 10 {
@@ -66,7 +66,7 @@ func TestVersionSeriesHoldsForwardAndLimits(t *testing.T) {
if got := byVersion["3.97"]; !equal(got, []uint64{1, 1, 0, 0, 0, 0, 0, 0, 0, 0}) {
t.Errorf("3.97 = %v, want nothing after its last report", got)
}
- if got := byVersion["4.30"]; !equal(got, []uint64{0, 0, 0, 0, 0, 0, 1, 1, 1, 1}) {
+ if got := byVersion["4.30"]; !equal(got, []uint64{0, 1, 1, 1, 1, 1, 1, 1, 1, 1}) {
t.Errorf("4.30 = %v, want carried to the right edge", got)
}
if got := byVersion["4.40"]; !equal(got, []uint64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) {
@@ -85,7 +85,7 @@ func TestVersionSeriesHoldsForwardAndLimits(t *testing.T) {
if series.Other == nil || series.Other.Count != 2 {
t.Fatalf("other = %+v, want 2 versions", series.Other)
}
- if !equal(series.Other.Clusters, []uint64{1, 1, 0, 0, 0, 0, 1, 1, 1, 1}) {
+ if !equal(series.Other.Clusters, []uint64{1, 2, 1, 1, 1, 1, 1, 1, 1, 1}) {
t.Errorf("other = %v, want 3.97+4.30 summed per day", series.Other.Clusters)
}
if series.TotalClusters != 2 {