Files
seaweedfs/telemetry/server/storage/versions_test.go
T
Chris LuandGitHub a49cf11e16 telemetry: version distribution over time (#10625)
* telemetry: keep the reported version in the daily history

The version only ever lived on the instance record, which holds a cluster's
latest report, so there was no way to ask what anything ran last Tuesday.
Record it per sample, and let the daily axis carry strings as well as counts.

State written before this has no version on its samples. The newest sample is
the report the instance record itself came from, so fill that one in on load
rather than starting a version series a day late.

* telemetry: serve the fleet's version make-up over time

/api/versions gives how many clusters ran each release per day, on the same
axis and hold-forward rule as the cluster sizes. Releases are ordered by
number rather than by size: the caller stacks them, and a stack whose order
changes with the counts is unreadable over time. The tail past the limit is
summed into "other" so the stack still adds up.

Days with no version are dropped before the axis is built, so the series
spans the days it knows a version for instead of climbing out of blanks.

* telemetry: draw version distribution as a stacked growth chart

The pie only ever showed today. Stacked over 30 days the height is the
confirmed fleet and each band is a release, so one chart carries the growth
and the rollouts at once. Newest release on the floor, so the band being read
is anchored to the axis instead of riding on everything below it.

Eight fixed hues instead of the evenly spaced ones the cluster stacks use:
evenly spaced put a green and a cyan close enough to be hard to tell apart,
which matters for a set you read rather than a wall of anonymous ids.
Versions past the eighth fold into "other", and each band carries its own
number so the chart reads without matching colours against the legend.
2026-08-07 12:08:04 -07:00

110 lines
4.3 KiB
Go

package storage
import (
"testing"
"github.com/prometheus/client_golang/prometheus"
)
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.40"}, -2, -1, 0)
// Reported every day on the same version.
seedSamples(s, "steady", HistorySample{Version: "4.40"}, -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)
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.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[1].Clusters; !equal(got, []uint64{1, 1, 2, 2, 2}) {
t.Errorf("4.40 = %v, want steady plus upgraded from day 3", got)
}
if series.TotalClusters != 2 {
t.Errorf("total_clusters = %d, want 2", series.TotalClusters)
}
if series.Other != nil {
t.Errorf("other = %+v, want none without a limit", series.Other)
}
}
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)
// 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)
series := s.GetVersionSeries(10, 0)
if len(series.Dates) != 10 {
t.Fatalf("dates = %v, want 10 days", series.Dates)
}
byVersion := map[string][]uint64{}
for _, v := range series.Versions {
byVersion[v.Version] = v.Clusters
}
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}) {
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}) {
t.Errorf("4.40 = %v, want 1 every day", got)
}
if series.TotalClusters != 2 {
t.Errorf("total_clusters = %d, want the 2 clusters still reporting", series.TotalClusters)
}
// Past the limit, the versions the fewest clusters are on are summed into
// one band; ties on the last day keep the newer version.
series = s.GetVersionSeries(10, 1)
if len(series.Versions) != 1 || series.Versions[0].Version != "4.40" {
t.Fatalf("limited versions = %+v, want just 4.40", series.Versions)
}
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}) {
t.Errorf("other = %v, want 3.97+4.30 summed per day", series.Other.Clusters)
}
if series.TotalClusters != 2 {
t.Errorf("limit changed total_clusters: %d, want 2", series.TotalClusters)
}
}
func TestVersionLess(t *testing.T) {
// Ordered oldest to newest; every pair must compare in this order.
ordered := []string{"unknown", "3.97", "4.02", "4.30", "4.30-enterprise", "9.99", "10.02"}
for i := 0; i < len(ordered); i++ {
for j := i + 1; j < len(ordered); j++ {
if !versionLess(ordered[i], ordered[j]) {
t.Errorf("versionLess(%q, %q) = false, want true", ordered[i], ordered[j])
}
if versionLess(ordered[j], ordered[i]) {
t.Errorf("versionLess(%q, %q) = true, want false", ordered[j], ordered[i])
}
}
}
}