mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-28 11:56:07 +00:00
telemetry: let the dashboard pick the confirmation window (#10904)
* telemetry: let the dashboard pick the confirmation window * telemetry: cover the serialized threshold map through the stats handler
This commit is contained in:
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -11,6 +12,9 @@ import (
|
||||
protobuf "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// promauto registers on the global registry: one storage per test binary.
|
||||
var testHandler = NewHandler(storage.NewPrometheusStorage())
|
||||
|
||||
func validReport() *proto.TelemetryData {
|
||||
return &proto.TelemetryData{
|
||||
TopologyId: "38422678-6a0d-4482-aa33-65b90010ac47",
|
||||
@@ -43,8 +47,7 @@ func marshalReport(t *testing.T, data *proto.TelemetryData) []byte {
|
||||
}
|
||||
|
||||
func TestCollectTelemetryValidation(t *testing.T) {
|
||||
// promauto registers on the global registry: one storage per test binary.
|
||||
h := NewHandler(storage.NewPrometheusStorage())
|
||||
h := testHandler
|
||||
|
||||
t.Run("valid report accepted", func(t *testing.T) {
|
||||
if w := postCollect(t, h, marshalReport(t, validReport()), "application/x-protobuf"); w.Code != http.StatusOK {
|
||||
@@ -96,3 +99,42 @@ func TestCollectTelemetryValidation(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// The dashboard looks confirmation windows up by the select's string value,
|
||||
// so the stats JSON must key confirmed_by_days by decimal strings and carry
|
||||
// unmet thresholds as zeros.
|
||||
func TestStatsSerializedThresholds(t *testing.T) {
|
||||
data := validReport()
|
||||
data.TopologyId = "49533789-7b1e-4593-bb44-76ca1121bd58"
|
||||
if w := postCollect(t, testHandler, marshalReport(t, data), "application/x-protobuf"); w.Code != http.StatusOK {
|
||||
t.Fatalf("collect: got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/stats", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler.GetStats(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("stats: got %d", w.Code)
|
||||
}
|
||||
|
||||
var stats struct {
|
||||
ConfirmedByDays map[string]int `json:"confirmed_by_days"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &stats); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if len(stats.ConfirmedByDays) != 5 {
|
||||
t.Fatalf("confirmed_by_days = %v, want the 5 thresholds", stats.ConfirmedByDays)
|
||||
}
|
||||
for _, key := range []string{"1", "3", "7", "14", "30"} {
|
||||
if _, ok := stats.ConfirmedByDays[key]; !ok {
|
||||
t.Errorf("confirmed_by_days missing %q: %v", key, stats.ConfirmedByDays)
|
||||
}
|
||||
}
|
||||
if stats.ConfirmedByDays["1"] < 1 {
|
||||
t.Errorf("fresh cluster missing from the 1-day count: %v", stats.ConfirmedByDays)
|
||||
}
|
||||
if stats.ConfirmedByDays["30"] != 0 {
|
||||
t.Errorf("unmet threshold not zero: %v", stats.ConfirmedByDays)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,15 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) {
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.stat-label select {
|
||||
border: none;
|
||||
background: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
.chart-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
@@ -129,7 +138,14 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) {
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="confirmedInstances">-</div>
|
||||
<div class="stat-label">Confirmed Clusters (7+ days)</div>
|
||||
<div class="stat-label">Confirmed Clusters
|
||||
(<select id="confirmDays" onchange="updateConfirmed()">
|
||||
<option value="1">1+</option>
|
||||
<option value="3">3+</option>
|
||||
<option value="7" selected>7+</option>
|
||||
<option value="14">14+</option>
|
||||
<option value="30">30+</option>
|
||||
</select> days)</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="totalVersions">-</div>
|
||||
@@ -229,14 +245,26 @@ func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
let latestStats = {};
|
||||
|
||||
function updateStats(stats) {
|
||||
latestStats = stats;
|
||||
document.getElementById('totalInstances').textContent = stats.total_instances || 0;
|
||||
document.getElementById('activeInstances').textContent = stats.active_instances || 0;
|
||||
document.getElementById('confirmedInstances').textContent = stats.confirmed_instances || 0;
|
||||
updateConfirmed();
|
||||
document.getElementById('totalVersions').textContent = Object.keys(stats.versions || {}).length;
|
||||
document.getElementById('totalOS').textContent = Object.keys(stats.os_distribution || {}).length;
|
||||
}
|
||||
|
||||
// Servers from before confirmed_by_days fall back to the fixed
|
||||
// 7-day count.
|
||||
function updateConfirmed() {
|
||||
const days = document.getElementById('confirmDays').value;
|
||||
const byDays = latestStats.confirmed_by_days || {};
|
||||
const count = byDays[days] !== undefined ? byDays[days] : latestStats.confirmed_instances;
|
||||
document.getElementById('confirmedInstances').textContent = count || 0;
|
||||
}
|
||||
|
||||
function updateCharts(stats) {
|
||||
createPieChart('versionChart', 'Version Distribution', stats.versions || {});
|
||||
createPieChart('osChart', 'Operating System Distribution', stats.os_distribution || {});
|
||||
|
||||
@@ -74,4 +74,14 @@ func TestConfirmedClusters(t *testing.T) {
|
||||
if _, ok := v["9.99"]; ok {
|
||||
t.Errorf("one-shot cluster polluted the distribution: %v", v)
|
||||
}
|
||||
|
||||
// Cluster A meets the 1/3/7-day thresholds, B only the 1-day one, and
|
||||
// unmet thresholds are present as zero so the dashboard can show them.
|
||||
byDays := stats["confirmed_by_days"].(map[int]int)
|
||||
want := map[int]int{1: 2, 3: 1, 7: 1, 14: 0, 30: 0}
|
||||
for threshold, expected := range want {
|
||||
if got, ok := byDays[threshold]; !ok || got != expected {
|
||||
t.Errorf("confirmed_by_days[%d] = %v (present=%v), want %d", threshold, got, ok, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import (
|
||||
// on before it counts as confirmed in the aggregated stats.
|
||||
const confirmDays = 7
|
||||
|
||||
// confirmThresholds are the confirmation windows the dashboard lets the
|
||||
// viewer pick between; confirmDays is the one everything else is built on.
|
||||
var confirmThresholds = []int{1, 3, 7, 14, 30}
|
||||
|
||||
// activeDays is how recently a cluster must have reported to count as active.
|
||||
const activeDays = 7
|
||||
|
||||
|
||||
@@ -207,6 +207,10 @@ func (s *PrometheusStorage) updateStats() {
|
||||
totalInstances := 0
|
||||
activeInstances := 0
|
||||
confirmedInstances := 0
|
||||
confirmedByDays := make(map[int]int, len(confirmThresholds))
|
||||
for _, threshold := range confirmThresholds {
|
||||
confirmedByDays[threshold] = 0
|
||||
}
|
||||
versionsAll := make(map[string]int)
|
||||
osAll := make(map[string]int)
|
||||
versionsConfirmed := make(map[string]int)
|
||||
@@ -223,7 +227,13 @@ func (s *PrometheusStorage) updateStats() {
|
||||
// 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 {
|
||||
daysSeen := len(s.histories[instance.TelemetryData.TopologyId])
|
||||
for _, threshold := range confirmThresholds {
|
||||
if daysSeen >= threshold {
|
||||
confirmedByDays[threshold]++
|
||||
}
|
||||
}
|
||||
if daysSeen >= confirmDays {
|
||||
confirmedInstances++
|
||||
versionsConfirmed[instance.TelemetryData.Version]++
|
||||
osConfirmed[instance.TelemetryData.Os]++
|
||||
@@ -249,6 +259,7 @@ func (s *PrometheusStorage) updateStats() {
|
||||
"total_instances": totalInstances,
|
||||
"active_instances": activeInstances,
|
||||
"confirmed_instances": confirmedInstances,
|
||||
"confirmed_by_days": confirmedByDays,
|
||||
"versions": versions,
|
||||
"os_distribution": osDistribution,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user