P8(1): restore homepage overview curves on a memory-only sampling ring
Backend: new overviewMetrics store — a single-tier (2s x 360 = 12min) in-memory ring fed by ServerService.RefreshStatus for exactly the eight overview series (cpu/mem/swap/diskUsage/netUp/netDown/tcpCount/udpCount). No persistence, no gob snapshot, no system-history page: the only surface is GET /panel/api/server/history/:metric/:bucket restricted to those keys, which seeds the frontend curves. ~46KB bounded by construction; the ring starts empty on every panel start. Frontend: restore useOverviewHistory + the VitalTile/ThroughputCard/ ConnectionsCard sparklines and the IndexPage wiring exactly as they were before P7 removed them along with the history module.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The overview ring is a fresh store per test (the package singleton is fed
|
||||
// by RefreshStatus in production); these tests pin its contract: only the
|
||||
// overview keys are written by AppendOverviewSample, samples come back
|
||||
// through AggregateOverviewMetric, and the window is hard-bounded by the
|
||||
// single tier's capacity no matter how long the panel runs.
|
||||
|
||||
func overviewTestStatus(cpu float64) *Status {
|
||||
st := &Status{Cpu: cpu, TcpCount: 3, UdpCount: 2}
|
||||
st.Mem.Current = 1
|
||||
st.Mem.Total = 4
|
||||
st.Swap.Current = 0
|
||||
st.Swap.Total = 0
|
||||
st.Disk.Current = 1
|
||||
st.Disk.Total = 2
|
||||
st.NetIO.Up = 100
|
||||
st.NetIO.Down = 200
|
||||
return st
|
||||
}
|
||||
|
||||
func TestOverviewRingAppendAndAggregate(t *testing.T) {
|
||||
s := &ServerService{}
|
||||
base := time.Now().Add(-10 * time.Second).Truncate(2 * time.Second)
|
||||
for i := range 5 {
|
||||
s.AppendOverviewSample(base.Add(time.Duration(i*2)*time.Second), overviewTestStatus(float64(i)))
|
||||
}
|
||||
// overviewMetrics is the package singleton; the samples just appended
|
||||
// must be visible through the aggregate the controller serves.
|
||||
out := s.AggregateOverviewMetric("cpu", 2, 60)
|
||||
if len(out) == 0 {
|
||||
t.Fatal("AggregateOverviewMetric(cpu) returned no points after appends")
|
||||
}
|
||||
last := out[len(out)-1]
|
||||
if got, _ := last["v"].(float64); got != 4 {
|
||||
t.Fatalf("last cpu point = %v, want 4", last["v"])
|
||||
}
|
||||
for _, key := range OverviewMetricKeys {
|
||||
if pts := s.AggregateOverviewMetric(key, 2, 60); len(pts) == 0 {
|
||||
t.Fatalf("overview key %q has no points after AppendOverviewSample", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverviewRingIsBounded(t *testing.T) {
|
||||
h := newMetricHistoryWithTiers([]tierSpec{{resolution: 2, capacity: 360}})
|
||||
base := time.Unix(1_700_000_000, 0)
|
||||
// Feed ten windows' worth of samples; the ring must not grow past its
|
||||
// single tier capacity.
|
||||
for i := range 3600 {
|
||||
h.append("cpu", base.Add(time.Duration(i*2)*time.Second), float64(i))
|
||||
}
|
||||
// readSamples returns the closed buckets plus the still-open one, so the
|
||||
// hard bound is capacity+1.
|
||||
raw := h.series["cpu"].tiers[0].readSamples()
|
||||
if len(raw) > 361 {
|
||||
t.Fatalf("overview tier retained %d samples, want <= 361 (capacity + open bucket)", len(raw))
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
t.Fatal("overview tier retained no samples")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user