Files
seaweedfs/telemetry/server/storage/sizes_test.go
T
Chris Lu 0973634fd4 telemetry: keep only clusters that store at least 10 GiB (#11138)
* telemetry: tidy the server module after the protobuf bump

Claude-Session: https://claude.ai/code/session_01VGiphDxpsKMwu9XpUFhybQ

* telemetry: keep only clusters that store at least 10 GiB

Fresh weed server runs, CI jobs and throwaway containers each mint their
own cluster id. They came in at tens of thousands a day, were most of
the counted clusters and held almost none of the bytes, and the state
file and the metrics page grew with every one of them. Reports under
the floor are counted and dropped, and a state file written before the
floor sheds them on the first restart.

Claude-Session: https://claude.ai/code/session_01VGiphDxpsKMwu9XpUFhybQ

* master: report telemetry only once the cluster stores 10 GiB

A throwaway cluster no longer registers itself with its first report a
minute after start; a real one begins reporting at the first daily tick
after it crosses the floor.

Claude-Session: https://claude.ai/code/session_01VGiphDxpsKMwu9XpUFhybQ
2026-09-03 12:36:00 -07:00

158 lines
5.9 KiB
Go

package storage
import (
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/seaweedfs/seaweedfs/telemetry/proto"
)
// seedSamples gives a cluster one sample per listed day offset (0 is today).
// The sample's Ts is filled in per day offset.
func seedSamples(s *PrometheusStorage, id string, sample HistorySample, dayOffsets ...int) {
s.mu.Lock()
defer s.mu.Unlock()
for _, offset := range dayOffsets {
sample.Ts = time.Now().AddDate(0, 0, offset).Unix()
s.histories[id] = append(s.histories[id], sample)
}
}
func TestClusterSizeSeries(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
// Reported every day of the window.
seedSamples(s, "daily", HistorySample{TotalDiskBytes: 300, VolumeServerCount: 3},
-9, -8, -7, -6, -5, -4, -3, -2, -1, 0)
// 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},
-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)
series := s.GetClusterSizeSeries(10, 0)
if len(series.Dates) != 10 {
t.Fatalf("dates = %v, want 10 days", series.Dates)
}
if series.ClusterCount != 3 {
t.Fatalf("cluster_count = %d, want 3", series.ClusterCount)
}
byId := map[string]ClusterSeries{}
for _, c := range series.Clusters {
byId[c.ClusterId] = c
}
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, 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}) {
t.Errorf("gone = %v, want no capacity after its last report", got)
}
if _, ok := byId["oneshot"]; ok {
t.Errorf("unconfirmed cluster in the stack: %+v", byId["oneshot"])
}
// Volume servers ride the same axis and the same hold-forward rule.
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, 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}) {
t.Errorf("gone servers = %v, want none after its last report", got)
}
// The totals are the last day's stack height: daily + lagging, not gone.
if series.TotalDisk != 500 {
t.Errorf("total_disk = %d, want 500", series.TotalDisk)
}
if series.TotalServers != 5 {
t.Errorf("total_servers = %d, want 5", series.TotalServers)
}
// Largest on the last day comes first so the stack reads top-down.
if series.Clusters[0].ClusterId != "daily" {
t.Errorf("order = %s first, want daily", series.Clusters[0].ClusterId)
}
// Clusters past the limit are summed into "other", per day.
series = s.GetClusterSizeSeries(10, 1)
if len(series.Clusters) != 1 || series.Clusters[0].ClusterId != "daily" {
t.Fatalf("limited clusters = %+v, want just daily", series.Clusters)
}
if series.Other == nil || series.Other.Count != 2 {
t.Fatalf("other = %+v, want 2 clusters", series.Other)
}
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, 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 {
t.Errorf("limit changed totals: count=%d disk=%d servers=%d, want 3/500/5",
series.ClusterCount, series.TotalDisk, series.TotalServers)
}
}
// A report that lands after an earlier one on the same day replaces it, so the
// series shows one value per cluster per day.
func TestClusterSizeSeriesUsesLatestDailySample(t *testing.T) {
s := newPrometheusStorage(prometheus.NewRegistry())
data := &proto.TelemetryData{
TopologyId: "aaaaaaaa-0000-0000-0000-000000000001",
Version: "4.40",
Os: "linux/amd64",
TotalDiskBytes: proto.MinDiskBytes + 100,
VolumeServerCount: 4,
}
if err := s.StoreTelemetry(data); err != nil {
t.Fatal(err)
}
data.TotalDiskBytes = proto.MinDiskBytes + 700
data.VolumeServerCount = 6
if err := s.StoreTelemetry(data); err != nil {
t.Fatal(err)
}
// Nothing was reported before today, so the axis is today alone rather
// than the full 3 days padded out with zeros.
series := s.GetClusterSizeSeries(3, 0)
if len(series.Clusters) != 1 {
t.Fatalf("clusters = %+v, want 1", series.Clusters)
}
if today := time.Now().UTC().Format("2006-01-02"); len(series.Dates) != 1 || series.Dates[0] != today {
t.Errorf("dates = %v, want %s only", series.Dates, today)
}
if got := series.Clusters[0].Disk; !equal(got, []uint64{proto.MinDiskBytes + 700}) {
t.Errorf("disk = %v, want today's latest sample only", got)
}
if got := series.Clusters[0].Servers; !equal(got, []uint64{6}) {
t.Errorf("servers = %v, want today's latest sample only", got)
}
if series.TotalDisk != proto.MinDiskBytes+700 || series.TotalServers != 6 {
t.Errorf("totals = %d disk / %d servers, want %d/6", series.TotalDisk, series.TotalServers, proto.MinDiskBytes+700)
}
}
func equal(a, b []uint64) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}