mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-21 00:47:47 +02:00
Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com> Co-authored-by: xiaomiku01 <xiaomiku01@outlook.com>
155 lines
6.5 KiB
Go
155 lines
6.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/henrygd/beszel/internal/entities/monitor"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMonitorHistoryWindowCounts(t *testing.T) {
|
|
history := newMonitorHistory()
|
|
now := time.Now()
|
|
// This older success counts toward lifetime warm-up, but not this window.
|
|
history.record(monitorSample{responseUs: 1000, timestamp: now.Add(-2 * time.Minute)})
|
|
history.record(monitorSample{responseUs: 10, timestamp: now.Add(-30 * time.Second)})
|
|
history.record(monitorSample{responseUs: 21, timestamp: now.Add(-20 * time.Second)})
|
|
history.record(monitorSample{responseUs: -1, timestamp: now.Add(-10 * time.Second)})
|
|
result, ok := history.result(time.Minute, now)
|
|
require.True(t, ok)
|
|
assert.EqualValues(t, 4, result.SampleCount)
|
|
assert.EqualValues(t, 3, result.TotalCount)
|
|
assert.EqualValues(t, 2, result.SuccessCount)
|
|
assert.EqualValues(t, 31, result.ResponseSum, "preserve the sum before average rounding")
|
|
assert.EqualValues(t, 15, result.AvgResponse)
|
|
assert.Equal(t, 33.33, result.PacketLoss)
|
|
|
|
encoded, err := cbor.Marshal(result)
|
|
require.NoError(t, err)
|
|
var decoded monitor.Result
|
|
require.NoError(t, cbor.Unmarshal(encoded, &decoded))
|
|
assert.Equal(t, result, decoded)
|
|
stats := monitor.Stats{}.FromResult(decoded)
|
|
assert.Equal(t, result.TotalCount, stats.TotalCount)
|
|
assert.Equal(t, result.SuccessCount, stats.SuccessCount)
|
|
assert.Equal(t, result.ResponseSum, stats.ResponseSum)
|
|
|
|
// Reads do not consume samples. A short window's latest-sample fallback
|
|
// carries the count for that single failure, not the minute or lifetime count.
|
|
repeated, _ := history.result(time.Minute, now)
|
|
assert.Equal(t, result, repeated)
|
|
fallback, ok := history.result(time.Second, now)
|
|
require.True(t, ok)
|
|
assert.EqualValues(t, 1, fallback.TotalCount)
|
|
assert.Zero(t, fallback.SuccessCount)
|
|
assert.Zero(t, fallback.ResponseSum)
|
|
assert.Equal(t, 100.0, fallback.PacketLoss)
|
|
assert.EqualValues(t, 4, fallback.SampleCount)
|
|
}
|
|
|
|
func TestMonitorHistoryAggregateLockedUsesRawSamplesForShortWindows(t *testing.T) {
|
|
now := time.Date(2026, time.April, 21, 12, 0, 0, 0, time.UTC)
|
|
history := newMonitorHistory()
|
|
|
|
history.addSampleLocked(monitorSample{responseUs: 10, timestamp: now.Add(-90 * time.Second)})
|
|
history.addSampleLocked(monitorSample{responseUs: 20, timestamp: now.Add(-30 * time.Second)})
|
|
history.addSampleLocked(monitorSample{responseUs: -1, timestamp: now.Add(-10 * time.Second)})
|
|
|
|
agg := history.aggregateLocked(time.Minute, now)
|
|
require.True(t, agg.hasData())
|
|
assert.Equal(t, int64(2), agg.totalCount)
|
|
assert.Equal(t, int64(1), agg.successCount)
|
|
result := agg.result()
|
|
assert.Equal(t, int64(20), result.AvgResponse)
|
|
assert.Equal(t, int64(20), result.MinResponse)
|
|
assert.Equal(t, int64(20), result.MaxResponse)
|
|
assert.Equal(t, 50.0, result.PacketLoss)
|
|
}
|
|
|
|
func TestMonitorHistoryAggregateLockedUsesMinuteBucketsForLongWindows(t *testing.T) {
|
|
now := time.Date(2026, time.April, 21, 12, 0, 30, 0, time.UTC)
|
|
history := newMonitorHistory()
|
|
|
|
history.addSampleLocked(monitorSample{responseUs: 10, timestamp: now.Add(-11 * time.Minute)})
|
|
history.addSampleLocked(monitorSample{responseUs: 20, timestamp: now.Add(-9 * time.Minute)})
|
|
history.addSampleLocked(monitorSample{responseUs: 40, timestamp: now.Add(-5 * time.Minute)})
|
|
history.addSampleLocked(monitorSample{responseUs: -1, timestamp: now.Add(-90 * time.Second)})
|
|
history.addSampleLocked(monitorSample{responseUs: 30, timestamp: now.Add(-30 * time.Second)})
|
|
|
|
agg := history.aggregateLocked(10*time.Minute, now)
|
|
require.True(t, agg.hasData())
|
|
assert.Equal(t, int64(4), agg.totalCount)
|
|
assert.Equal(t, int64(3), agg.successCount)
|
|
result := agg.result()
|
|
assert.Equal(t, int64(30), result.AvgResponse)
|
|
assert.Equal(t, int64(20), result.MinResponse)
|
|
assert.Equal(t, int64(40), result.MaxResponse)
|
|
assert.Equal(t, 25.0, result.PacketLoss)
|
|
}
|
|
|
|
func TestMonitorHistoryAddSampleLockedTrimsRawSamplesButKeepsBucketHistory(t *testing.T) {
|
|
now := time.Date(2026, time.April, 21, 12, 0, 0, 0, time.UTC)
|
|
history := newMonitorHistory()
|
|
|
|
history.addSampleLocked(monitorSample{responseUs: 10, timestamp: now.Add(-10 * time.Minute)})
|
|
history.addSampleLocked(monitorSample{responseUs: 20, timestamp: now})
|
|
|
|
require.Len(t, history.samples, 1)
|
|
assert.Equal(t, int64(20), history.samples[0].responseUs)
|
|
|
|
agg := history.aggregateLocked(10*time.Minute, now)
|
|
require.True(t, agg.hasData())
|
|
assert.Equal(t, int64(2), agg.totalCount)
|
|
assert.Equal(t, int64(2), agg.successCount)
|
|
result := agg.result()
|
|
assert.Equal(t, int64(15), result.AvgResponse)
|
|
assert.Equal(t, int64(10), result.MinResponse)
|
|
assert.Equal(t, int64(20), result.MaxResponse)
|
|
assert.Equal(t, 0.0, result.PacketLoss)
|
|
}
|
|
|
|
func TestMonitorHistoryProbeTimestamp(t *testing.T) {
|
|
history := newMonitorHistory()
|
|
start := time.Date(2026, time.September, 14, 12, 0, 0, 0, time.UTC)
|
|
_, ok := history.result(time.Minute, start)
|
|
require.False(t, ok)
|
|
first := history.record(monitorSample{responseUs: 20, timestamp: start})
|
|
assert.Equal(t, start.UnixMilli(), first.LastProbeAt)
|
|
for minute := 0; minute < 5; minute++ {
|
|
now := start.Add(time.Duration(minute)*time.Minute + time.Second)
|
|
// Realtime reads must not consume freshness for the persistence request.
|
|
for _, window := range []time.Duration{time.Second, time.Minute} {
|
|
result, ok := history.result(window, now)
|
|
require.True(t, ok)
|
|
assert.Equal(t, first.LastProbeAt, result.LastProbeAt)
|
|
assert.Equal(t, int64(20), result.AvgResponse)
|
|
}
|
|
}
|
|
next := start.Add(5 * time.Minute)
|
|
failed := history.record(monitorSample{responseUs: -1, timestamp: next})
|
|
assert.Equal(t, next.UnixMilli(), failed.LastProbeAt)
|
|
assert.Equal(t, float64(100), failed.PacketLoss)
|
|
repeated, ok := history.result(time.Minute, next.Add(2*time.Minute))
|
|
require.True(t, ok)
|
|
assert.Equal(t, failed.LastProbeAt, repeated.LastProbeAt)
|
|
assert.Equal(t, float64(100), repeated.PacketLoss)
|
|
}
|
|
|
|
func TestMonitorHistorySampleCount(t *testing.T) {
|
|
history := newMonitorHistory()
|
|
now := time.Now()
|
|
// Both failed and successful probes count, including older samples so
|
|
// monitors with hourly intervals can finish warming up.
|
|
history.record(monitorSample{responseUs: -1, timestamp: now.Add(-2 * time.Hour)})
|
|
for i, response := range []int64{10, -1, 20} {
|
|
result := history.record(monitorSample{responseUs: response, timestamp: now.Add(time.Duration(i) * time.Second)})
|
|
assert.EqualValues(t, i+2, result.SampleCount)
|
|
}
|
|
result, ok := history.clone().result(time.Minute, now.Add(3*time.Second))
|
|
require.True(t, ok)
|
|
assert.EqualValues(t, 4, result.SampleCount)
|
|
}
|