Files
beszel-ipv6/internal/records/records_averaging_test.go

821 lines
25 KiB
Go

//go:build testing
package records_test
import (
"sort"
"testing"
"github.com/henrygd/beszel/internal/entities/container"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/records"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAverageSystemStatsSlice_Empty(t *testing.T) {
result := records.AverageSystemStatsSlice(nil)
assert.Equal(t, system.Stats{}, result)
result = records.AverageSystemStatsSlice([]system.Stats{})
assert.Equal(t, system.Stats{}, result)
}
func TestAverageSystemStatsSlice_SingleRecord(t *testing.T) {
input := []system.Stats{
{
Cpu: 45.67,
Mem: 16.0,
MemUsed: 8.5,
MemPct: 53.12,
MemBuffCache: 2.0,
Swap: 4.0,
SwapUsed: 1.0,
DiskTotal: 500.0,
DiskUsed: 250.0,
DiskPct: 50.0,
DiskReadPs: 100.5,
DiskWritePs: 200.75,
NetworkSent: 10.5,
NetworkRecv: 20.25,
LoadAvg: [3]float64{1.5, 2.0, 3.5},
Bandwidth: [2]uint64{1000, 2000},
DiskIO: [2]uint64{500, 600},
Battery: [2]uint8{80, 1},
},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 45.67, result.Cpu)
assert.Equal(t, 16.0, result.Mem)
assert.Equal(t, 8.5, result.MemUsed)
assert.Equal(t, 53.12, result.MemPct)
assert.Equal(t, 2.0, result.MemBuffCache)
assert.Equal(t, 4.0, result.Swap)
assert.Equal(t, 1.0, result.SwapUsed)
assert.Equal(t, 500.0, result.DiskTotal)
assert.Equal(t, 250.0, result.DiskUsed)
assert.Equal(t, 50.0, result.DiskPct)
assert.Equal(t, 100.5, result.DiskReadPs)
assert.Equal(t, 200.75, result.DiskWritePs)
assert.Equal(t, 10.5, result.NetworkSent)
assert.Equal(t, 20.25, result.NetworkRecv)
assert.Equal(t, [3]float64{1.5, 2.0, 3.5}, result.LoadAvg)
assert.Equal(t, [2]uint64{1000, 2000}, result.Bandwidth)
assert.Equal(t, [2]uint64{500, 600}, result.DiskIO)
assert.Equal(t, uint8(80), result.Battery[0])
assert.Equal(t, uint8(1), result.Battery[1])
}
func TestAverageSystemStatsSlice_BasicAveraging(t *testing.T) {
input := []system.Stats{
{
Cpu: 20.0,
Mem: 16.0,
MemUsed: 6.0,
MemPct: 37.5,
MemBuffCache: 1.0,
MemZfsArc: 0.5,
Swap: 4.0,
SwapUsed: 1.0,
DiskTotal: 500.0,
DiskUsed: 200.0,
DiskPct: 40.0,
DiskReadPs: 100.0,
DiskWritePs: 200.0,
NetworkSent: 10.0,
NetworkRecv: 20.0,
LoadAvg: [3]float64{1.0, 2.0, 3.0},
Bandwidth: [2]uint64{1000, 2000},
DiskIO: [2]uint64{400, 600},
Battery: [2]uint8{80, 1},
},
{
Cpu: 40.0,
Mem: 16.0,
MemUsed: 10.0,
MemPct: 62.5,
MemBuffCache: 3.0,
MemZfsArc: 1.5,
Swap: 4.0,
SwapUsed: 3.0,
DiskTotal: 500.0,
DiskUsed: 300.0,
DiskPct: 60.0,
DiskReadPs: 200.0,
DiskWritePs: 400.0,
NetworkSent: 30.0,
NetworkRecv: 40.0,
LoadAvg: [3]float64{3.0, 4.0, 5.0},
Bandwidth: [2]uint64{3000, 4000},
DiskIO: [2]uint64{600, 800},
Battery: [2]uint8{60, 1},
},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 30.0, result.Cpu)
assert.Equal(t, 16.0, result.Mem)
assert.Equal(t, 8.0, result.MemUsed)
assert.Equal(t, 50.0, result.MemPct)
assert.Equal(t, 2.0, result.MemBuffCache)
assert.Equal(t, 1.0, result.MemZfsArc)
assert.Equal(t, 4.0, result.Swap)
assert.Equal(t, 2.0, result.SwapUsed)
assert.Equal(t, 500.0, result.DiskTotal)
assert.Equal(t, 250.0, result.DiskUsed)
assert.Equal(t, 50.0, result.DiskPct)
assert.Equal(t, 150.0, result.DiskReadPs)
assert.Equal(t, 300.0, result.DiskWritePs)
assert.Equal(t, 20.0, result.NetworkSent)
assert.Equal(t, 30.0, result.NetworkRecv)
assert.Equal(t, [3]float64{2.0, 3.0, 4.0}, result.LoadAvg)
assert.Equal(t, [2]uint64{2000, 3000}, result.Bandwidth)
assert.Equal(t, [2]uint64{500, 700}, result.DiskIO)
assert.Equal(t, uint8(70), result.Battery[0])
assert.Equal(t, uint8(1), result.Battery[1])
}
func TestAverageSystemStatsSlice_PeakValues(t *testing.T) {
input := []system.Stats{
{
Cpu: 20.0,
MaxCpu: 25.0,
MemUsed: 6.0,
MaxMem: 7.0,
NetworkSent: 10.0,
MaxNetworkSent: 15.0,
NetworkRecv: 20.0,
MaxNetworkRecv: 25.0,
DiskReadPs: 100.0,
MaxDiskReadPs: 120.0,
DiskWritePs: 200.0,
MaxDiskWritePs: 220.0,
Bandwidth: [2]uint64{1000, 2000},
MaxBandwidth: [2]uint64{1500, 2500},
DiskIO: [2]uint64{400, 600},
MaxDiskIO: [2]uint64{500, 700},
DiskIoStats: [6]float64{10.0, 20.0, 30.0, 5.0, 8.0, 12.0},
MaxDiskIoStats: [6]float64{15.0, 25.0, 35.0, 6.0, 9.0, 14.0},
},
{
Cpu: 40.0,
MaxCpu: 50.0,
MemUsed: 10.0,
MaxMem: 12.0,
NetworkSent: 30.0,
MaxNetworkSent: 35.0,
NetworkRecv: 40.0,
MaxNetworkRecv: 45.0,
DiskReadPs: 200.0,
MaxDiskReadPs: 210.0,
DiskWritePs: 400.0,
MaxDiskWritePs: 410.0,
Bandwidth: [2]uint64{3000, 4000},
MaxBandwidth: [2]uint64{3500, 4500},
DiskIO: [2]uint64{600, 800},
MaxDiskIO: [2]uint64{650, 850},
DiskIoStats: [6]float64{50.0, 60.0, 70.0, 15.0, 18.0, 22.0},
MaxDiskIoStats: [6]float64{55.0, 65.0, 75.0, 16.0, 19.0, 23.0},
},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 50.0, result.MaxCpu)
assert.Equal(t, 12.0, result.MaxMem)
assert.Equal(t, 35.0, result.MaxNetworkSent)
assert.Equal(t, 45.0, result.MaxNetworkRecv)
assert.Equal(t, 210.0, result.MaxDiskReadPs)
assert.Equal(t, 410.0, result.MaxDiskWritePs)
assert.Equal(t, [2]uint64{3500, 4500}, result.MaxBandwidth)
assert.Equal(t, [2]uint64{650, 850}, result.MaxDiskIO)
assert.Equal(t, [6]float64{30.0, 40.0, 50.0, 10.0, 13.0, 17.0}, result.DiskIoStats)
assert.Equal(t, [6]float64{55.0, 65.0, 75.0, 16.0, 19.0, 23.0}, result.MaxDiskIoStats)
}
func TestAverageSystemStatsSlice_DiskIoStats(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
DiskIoStats: [6]float64{10.0, 20.0, 30.0, 5.0, 8.0, 12.0},
MaxDiskIoStats: [6]float64{12.0, 22.0, 32.0, 6.0, 9.0, 13.0},
},
{
Cpu: 20.0,
DiskIoStats: [6]float64{30.0, 40.0, 50.0, 15.0, 18.0, 22.0},
MaxDiskIoStats: [6]float64{28.0, 38.0, 48.0, 14.0, 17.0, 21.0},
},
{
Cpu: 30.0,
DiskIoStats: [6]float64{20.0, 30.0, 40.0, 10.0, 12.0, 16.0},
MaxDiskIoStats: [6]float64{25.0, 35.0, 45.0, 11.0, 13.0, 17.0},
},
}
result := records.AverageSystemStatsSlice(input)
// Average: (10+30+20)/3=20, (20+40+30)/3=30, (30+50+40)/3=40, (5+15+10)/3=10, (8+18+12)/3≈12.67, (12+22+16)/3≈16.67
assert.Equal(t, 20.0, result.DiskIoStats[0])
assert.Equal(t, 30.0, result.DiskIoStats[1])
assert.Equal(t, 40.0, result.DiskIoStats[2])
assert.Equal(t, 10.0, result.DiskIoStats[3])
assert.Equal(t, 12.67, result.DiskIoStats[4])
assert.Equal(t, 16.67, result.DiskIoStats[5])
// Max: current DiskIoStats[0] wins for record 2 (30 > MaxDiskIoStats 28)
assert.Equal(t, 30.0, result.MaxDiskIoStats[0])
assert.Equal(t, 40.0, result.MaxDiskIoStats[1])
assert.Equal(t, 50.0, result.MaxDiskIoStats[2])
assert.Equal(t, 15.0, result.MaxDiskIoStats[3])
assert.Equal(t, 18.0, result.MaxDiskIoStats[4])
assert.Equal(t, 22.0, result.MaxDiskIoStats[5])
}
// Tests that current DiskIoStats values are considered when computing MaxDiskIoStats.
func TestAverageSystemStatsSlice_DiskIoStatsPeakFromCurrentValues(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, DiskIoStats: [6]float64{95.0, 90.0, 85.0, 50.0, 60.0, 80.0}, MaxDiskIoStats: [6]float64{80.0, 80.0, 80.0, 40.0, 50.0, 70.0}},
{Cpu: 20.0, DiskIoStats: [6]float64{10.0, 10.0, 10.0, 5.0, 6.0, 8.0}, MaxDiskIoStats: [6]float64{20.0, 20.0, 20.0, 10.0, 12.0, 16.0}},
}
result := records.AverageSystemStatsSlice(input)
// Current value from first record (95, 90, 85, 50, 60, 80) beats MaxDiskIoStats in both records
assert.Equal(t, 95.0, result.MaxDiskIoStats[0])
assert.Equal(t, 90.0, result.MaxDiskIoStats[1])
assert.Equal(t, 85.0, result.MaxDiskIoStats[2])
assert.Equal(t, 50.0, result.MaxDiskIoStats[3])
assert.Equal(t, 60.0, result.MaxDiskIoStats[4])
assert.Equal(t, 80.0, result.MaxDiskIoStats[5])
}
// Tests that current values are considered when computing peaks
// (i.e., current cpu > MaxCpu should still win).
func TestAverageSystemStatsSlice_PeakFromCurrentValues(t *testing.T) {
input := []system.Stats{
{Cpu: 95.0, MaxCpu: 80.0, MemUsed: 15.0, MaxMem: 10.0},
{Cpu: 10.0, MaxCpu: 20.0, MemUsed: 5.0, MaxMem: 8.0},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 95.0, result.MaxCpu)
assert.Equal(t, 15.0, result.MaxMem)
}
// Tests that records without temperature data are excluded from the temperature average.
func TestAverageSystemStatsSlice_Temperatures(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
Temperatures: map[string]float64{"cpu": 60.0, "gpu": 70.0},
},
{
Cpu: 20.0,
Temperatures: map[string]float64{"cpu": 80.0, "gpu": 90.0},
},
{
// No temperatures - should not affect temp averaging
Cpu: 30.0,
},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.Temperatures)
// Average over 2 records that had temps, not 3
assert.Equal(t, 70.0, result.Temperatures["cpu"])
assert.Equal(t, 80.0, result.Temperatures["gpu"])
}
func TestAverageSystemStatsSlice_NetworkInterfaces(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
NetworkInterfaces: map[string][4]uint64{
"eth0": {100, 200, 150, 250},
"eth1": {50, 60, 70, 80},
},
},
{
Cpu: 20.0,
NetworkInterfaces: map[string][4]uint64{
"eth0": {200, 400, 300, 500},
"eth1": {150, 160, 170, 180},
},
},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.NetworkInterfaces)
// [0] and [1] are averaged, [2] and [3] are max
assert.Equal(t, [4]uint64{150, 300, 300, 500}, result.NetworkInterfaces["eth0"])
assert.Equal(t, [4]uint64{100, 110, 170, 180}, result.NetworkInterfaces["eth1"])
}
func TestAverageSystemStatsSlice_ExtraFs(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
ExtraFs: map[string]*system.FsStats{
"/data": {
DiskTotal: 1000.0,
DiskUsed: 400.0,
DiskReadPs: 50.0,
DiskWritePs: 100.0,
MaxDiskReadPS: 60.0,
MaxDiskWritePS: 110.0,
DiskReadBytes: 5000,
DiskWriteBytes: 10000,
MaxDiskReadBytes: 6000,
MaxDiskWriteBytes: 11000,
DiskIoStats: [6]float64{10.0, 20.0, 30.0, 5.0, 8.0, 12.0},
MaxDiskIoStats: [6]float64{12.0, 22.0, 32.0, 6.0, 9.0, 13.0},
},
},
},
{
Cpu: 20.0,
ExtraFs: map[string]*system.FsStats{
"/data": {
DiskTotal: 1000.0,
DiskUsed: 600.0,
DiskReadPs: 150.0,
DiskWritePs: 200.0,
MaxDiskReadPS: 160.0,
MaxDiskWritePS: 210.0,
DiskReadBytes: 15000,
DiskWriteBytes: 20000,
MaxDiskReadBytes: 16000,
MaxDiskWriteBytes: 21000,
DiskIoStats: [6]float64{50.0, 60.0, 70.0, 15.0, 18.0, 22.0},
MaxDiskIoStats: [6]float64{55.0, 65.0, 75.0, 16.0, 19.0, 23.0},
},
},
},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.ExtraFs)
require.NotNil(t, result.ExtraFs["/data"])
fs := result.ExtraFs["/data"]
assert.Equal(t, 1000.0, fs.DiskTotal)
assert.Equal(t, 500.0, fs.DiskUsed)
assert.Equal(t, 100.0, fs.DiskReadPs)
assert.Equal(t, 150.0, fs.DiskWritePs)
assert.Equal(t, 160.0, fs.MaxDiskReadPS)
assert.Equal(t, 210.0, fs.MaxDiskWritePS)
assert.Equal(t, uint64(10000), fs.DiskReadBytes)
assert.Equal(t, uint64(15000), fs.DiskWriteBytes)
assert.Equal(t, uint64(16000), fs.MaxDiskReadBytes)
assert.Equal(t, uint64(21000), fs.MaxDiskWriteBytes)
assert.Equal(t, [6]float64{30.0, 40.0, 50.0, 10.0, 13.0, 17.0}, fs.DiskIoStats)
assert.Equal(t, [6]float64{55.0, 65.0, 75.0, 16.0, 19.0, 23.0}, fs.MaxDiskIoStats)
}
// Tests that ExtraFs DiskIoStats peak considers current values, not just previous peaks.
func TestAverageSystemStatsSlice_ExtraFsDiskIoStatsPeakFromCurrentValues(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
ExtraFs: map[string]*system.FsStats{
"/data": {
DiskIoStats: [6]float64{95.0, 90.0, 85.0, 50.0, 60.0, 80.0}, // exceeds MaxDiskIoStats
MaxDiskIoStats: [6]float64{80.0, 80.0, 80.0, 40.0, 50.0, 70.0},
},
},
},
{
Cpu: 20.0,
ExtraFs: map[string]*system.FsStats{
"/data": {
DiskIoStats: [6]float64{10.0, 10.0, 10.0, 5.0, 6.0, 8.0},
MaxDiskIoStats: [6]float64{20.0, 20.0, 20.0, 10.0, 12.0, 16.0},
},
},
},
}
result := records.AverageSystemStatsSlice(input)
fs := result.ExtraFs["/data"]
assert.Equal(t, 95.0, fs.MaxDiskIoStats[0])
assert.Equal(t, 90.0, fs.MaxDiskIoStats[1])
assert.Equal(t, 85.0, fs.MaxDiskIoStats[2])
assert.Equal(t, 50.0, fs.MaxDiskIoStats[3])
assert.Equal(t, 60.0, fs.MaxDiskIoStats[4])
assert.Equal(t, 80.0, fs.MaxDiskIoStats[5])
}
// Tests that extra FS peak values consider current values, not just previous peaks.
func TestAverageSystemStatsSlice_ExtraFsPeaksFromCurrentValues(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
ExtraFs: map[string]*system.FsStats{
"/data": {
DiskReadPs: 500.0, // exceeds MaxDiskReadPS
MaxDiskReadPS: 100.0,
DiskReadBytes: 50000,
MaxDiskReadBytes: 10000,
},
},
},
{
Cpu: 20.0,
ExtraFs: map[string]*system.FsStats{
"/data": {
DiskReadPs: 50.0,
MaxDiskReadPS: 200.0,
DiskReadBytes: 5000,
MaxDiskReadBytes: 20000,
},
},
},
}
result := records.AverageSystemStatsSlice(input)
fs := result.ExtraFs["/data"]
assert.Equal(t, 500.0, fs.MaxDiskReadPS)
assert.Equal(t, uint64(50000), fs.MaxDiskReadBytes)
}
func TestAverageSystemStatsSlice_GPUData(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
GPUData: map[string]system.GPUData{
"gpu0": {
Name: "RTX 4090",
Temperature: 60.0,
MemoryUsed: 4.0,
MemoryTotal: 24.0,
Usage: 30.0,
Power: 200.0,
Count: 1.0,
Engines: map[string]float64{
"3D": 50.0,
"Video": 10.0,
},
},
},
},
{
Cpu: 20.0,
GPUData: map[string]system.GPUData{
"gpu0": {
Name: "RTX 4090",
Temperature: 80.0,
MemoryUsed: 8.0,
MemoryTotal: 24.0,
Usage: 70.0,
Power: 300.0,
Count: 1.0,
Engines: map[string]float64{
"3D": 90.0,
"Video": 30.0,
},
},
},
},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.GPUData)
gpu := result.GPUData["gpu0"]
assert.Equal(t, "RTX 4090", gpu.Name)
assert.Equal(t, 70.0, gpu.Temperature)
assert.Equal(t, 6.0, gpu.MemoryUsed)
assert.Equal(t, 24.0, gpu.MemoryTotal)
assert.Equal(t, 50.0, gpu.Usage)
assert.Equal(t, 250.0, gpu.Power)
assert.Equal(t, 1.0, gpu.Count)
require.NotNil(t, gpu.Engines)
assert.Equal(t, 70.0, gpu.Engines["3D"])
assert.Equal(t, 20.0, gpu.Engines["Video"])
}
func TestAverageSystemStatsSlice_MultipleGPUs(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
GPUData: map[string]system.GPUData{
"gpu0": {Name: "GPU A", Usage: 20.0, Temperature: 50.0},
"gpu1": {Name: "GPU B", Usage: 60.0, Temperature: 70.0},
},
},
{
Cpu: 20.0,
GPUData: map[string]system.GPUData{
"gpu0": {Name: "GPU A", Usage: 40.0, Temperature: 60.0},
"gpu1": {Name: "GPU B", Usage: 80.0, Temperature: 80.0},
},
},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.GPUData)
assert.Equal(t, 30.0, result.GPUData["gpu0"].Usage)
assert.Equal(t, 55.0, result.GPUData["gpu0"].Temperature)
assert.Equal(t, 70.0, result.GPUData["gpu1"].Usage)
assert.Equal(t, 75.0, result.GPUData["gpu1"].Temperature)
}
func TestAverageSystemStatsSlice_CpuCoresUsage(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, CpuCoresUsage: system.Uint8Slice{10, 20, 30, 40}},
{Cpu: 20.0, CpuCoresUsage: system.Uint8Slice{30, 40, 50, 60}},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.CpuCoresUsage)
assert.Equal(t, system.Uint8Slice{20, 30, 40, 50}, result.CpuCoresUsage)
}
// Tests that per-core usage rounds correctly (e.g., 15.5 -> 16 via math.Round).
func TestAverageSystemStatsSlice_CpuCoresUsageRounding(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, CpuCoresUsage: system.Uint8Slice{11}},
{Cpu: 20.0, CpuCoresUsage: system.Uint8Slice{20}},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.CpuCoresUsage)
// (11+20)/2 = 15.5, rounds to 16
assert.Equal(t, uint8(16), result.CpuCoresUsage[0])
}
func TestAverageSystemStatsSlice_CpuBreakdown(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, CpuBreakdown: []float64{5.0, 3.0, 1.0, 0.5, 90.5}},
{Cpu: 20.0, CpuBreakdown: []float64{15.0, 7.0, 3.0, 1.5, 73.5}},
}
result := records.AverageSystemStatsSlice(input)
require.NotNil(t, result.CpuBreakdown)
assert.Equal(t, []float64{10.0, 5.0, 2.0, 1.0, 82.0}, result.CpuBreakdown)
}
// Tests that Battery[1] (charge state) uses the last record's value.
func TestAverageSystemStatsSlice_BatteryLastChargeState(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, Battery: [2]uint8{100, 1}}, // charging
{Cpu: 20.0, Battery: [2]uint8{90, 0}}, // not charging
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, uint8(95), result.Battery[0])
assert.Equal(t, uint8(0), result.Battery[1]) // last record's charge state
}
func TestAverageSystemStatsSlice_ThreeRecordsRounding(t *testing.T) {
input := []system.Stats{
{Cpu: 10.0, Mem: 8.0},
{Cpu: 20.0, Mem: 8.0},
{Cpu: 30.0, Mem: 8.0},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 20.0, result.Cpu)
assert.Equal(t, 8.0, result.Mem)
}
// Tests records where some have optional fields and others don't.
func TestAverageSystemStatsSlice_MixedOptionalFields(t *testing.T) {
input := []system.Stats{
{
Cpu: 10.0,
CpuCoresUsage: system.Uint8Slice{50, 60},
CpuBreakdown: []float64{5.0, 3.0, 1.0, 0.5, 90.5},
GPUData: map[string]system.GPUData{
"gpu0": {Name: "GPU", Usage: 40.0},
},
},
{
Cpu: 20.0,
// No CpuCoresUsage, CpuBreakdown, or GPUData
},
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 15.0, result.Cpu)
// CpuCoresUsage: only 1 record had it, so sum/2
require.NotNil(t, result.CpuCoresUsage)
assert.Equal(t, uint8(25), result.CpuCoresUsage[0])
assert.Equal(t, uint8(30), result.CpuCoresUsage[1])
// CpuBreakdown: only 1 record had it, so sum/2
require.NotNil(t, result.CpuBreakdown)
assert.Equal(t, 2.5, result.CpuBreakdown[0])
// GPUData: only 1 record had it, so sum/2
require.NotNil(t, result.GPUData)
assert.Equal(t, 20.0, result.GPUData["gpu0"].Usage)
}
// Tests with 10 records matching the common real-world case (10 x 1m -> 1 x 10m).
func TestAverageSystemStatsSlice_TenRecords(t *testing.T) {
input := make([]system.Stats, 10)
for i := range input {
input[i] = system.Stats{
Cpu: float64(i * 10), // 0, 10, 20, ..., 90
Mem: 16.0,
MemUsed: float64(4 + i), // 4, 5, 6, ..., 13
MemPct: float64(25 + i), // 25, 26, ..., 34
DiskTotal: 500.0,
DiskUsed: 250.0,
DiskPct: 50.0,
NetworkSent: float64(i),
NetworkRecv: float64(i * 2),
Bandwidth: [2]uint64{uint64(i * 1000), uint64(i * 2000)},
LoadAvg: [3]float64{float64(i), float64(i) * 0.5, float64(i) * 0.25},
}
}
result := records.AverageSystemStatsSlice(input)
assert.Equal(t, 45.0, result.Cpu) // avg of 0..90
assert.Equal(t, 16.0, result.Mem) // constant
assert.Equal(t, 8.5, result.MemUsed) // avg of 4..13
assert.Equal(t, 29.5, result.MemPct) // avg of 25..34
assert.Equal(t, 500.0, result.DiskTotal)
assert.Equal(t, 250.0, result.DiskUsed)
assert.Equal(t, 50.0, result.DiskPct)
assert.Equal(t, 4.5, result.NetworkSent)
assert.Equal(t, 9.0, result.NetworkRecv)
assert.Equal(t, [2]uint64{4500, 9000}, result.Bandwidth)
}
// --- Container Stats Tests ---
func TestAverageContainerStatsSlice_Empty(t *testing.T) {
result := records.AverageContainerStatsSlice(nil)
assert.Equal(t, []container.Stats{}, result)
result = records.AverageContainerStatsSlice([][]container.Stats{})
assert.Equal(t, []container.Stats{}, result)
}
func TestAverageContainerStatsSlice_SingleRecord(t *testing.T) {
input := [][]container.Stats{
{
{Name: "nginx", Cpu: 5.0, Mem: 128.0, Bandwidth: [2]uint64{1000, 2000}},
},
}
result := records.AverageContainerStatsSlice(input)
require.Len(t, result, 1)
assert.Equal(t, "nginx", result[0].Name)
assert.Equal(t, 5.0, result[0].Cpu)
assert.Equal(t, 128.0, result[0].Mem)
assert.Equal(t, [2]uint64{1000, 2000}, result[0].Bandwidth)
}
func TestAverageContainerStatsSlice_BasicAveraging(t *testing.T) {
input := [][]container.Stats{
{
{Name: "nginx", Cpu: 10.0, Mem: 100.0, Bandwidth: [2]uint64{1000, 2000}},
{Name: "redis", Cpu: 5.0, Mem: 64.0, Bandwidth: [2]uint64{500, 1000}},
},
{
{Name: "nginx", Cpu: 20.0, Mem: 200.0, Bandwidth: [2]uint64{3000, 4000}},
{Name: "redis", Cpu: 15.0, Mem: 128.0, Bandwidth: [2]uint64{1500, 2000}},
},
}
result := records.AverageContainerStatsSlice(input)
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
require.Len(t, result, 2)
assert.Equal(t, "nginx", result[0].Name)
assert.Equal(t, 15.0, result[0].Cpu)
assert.Equal(t, 150.0, result[0].Mem)
assert.Equal(t, [2]uint64{2000, 3000}, result[0].Bandwidth)
assert.Equal(t, "redis", result[1].Name)
assert.Equal(t, 10.0, result[1].Cpu)
assert.Equal(t, 96.0, result[1].Mem)
assert.Equal(t, [2]uint64{1000, 1500}, result[1].Bandwidth)
}
// Tests containers that appear in some records but not all.
func TestAverageContainerStatsSlice_ContainerAppearsInSomeRecords(t *testing.T) {
input := [][]container.Stats{
{
{Name: "nginx", Cpu: 10.0, Mem: 100.0},
{Name: "redis", Cpu: 5.0, Mem: 64.0},
},
{
{Name: "nginx", Cpu: 20.0, Mem: 200.0},
// redis not present
},
}
result := records.AverageContainerStatsSlice(input)
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
require.Len(t, result, 2)
assert.Equal(t, "nginx", result[0].Name)
assert.Equal(t, 15.0, result[0].Cpu)
assert.Equal(t, 150.0, result[0].Mem)
// redis: sum / count where count = total records (2), not records containing redis
assert.Equal(t, "redis", result[1].Name)
assert.Equal(t, 2.5, result[1].Cpu)
assert.Equal(t, 32.0, result[1].Mem)
}
// Tests backward compatibility with deprecated NetworkSent/NetworkRecv (MB) when Bandwidth is zero.
func TestAverageContainerStatsSlice_DeprecatedNetworkFields(t *testing.T) {
input := [][]container.Stats{
{
{Name: "nginx", Cpu: 10.0, Mem: 100.0, NetworkSent: 1.0, NetworkRecv: 2.0}, // 1 MB, 2 MB
},
{
{Name: "nginx", Cpu: 20.0, Mem: 200.0, NetworkSent: 3.0, NetworkRecv: 4.0}, // 3 MB, 4 MB
},
}
result := records.AverageContainerStatsSlice(input)
require.Len(t, result, 1)
assert.Equal(t, "nginx", result[0].Name)
// avg sent = (1*1048576 + 3*1048576) / 2 = 2*1048576
assert.Equal(t, uint64(2*1048576), result[0].Bandwidth[0])
// avg recv = (2*1048576 + 4*1048576) / 2 = 3*1048576
assert.Equal(t, uint64(3*1048576), result[0].Bandwidth[1])
}
// Tests that when Bandwidth is set, deprecated NetworkSent/NetworkRecv are ignored.
func TestAverageContainerStatsSlice_MixedBandwidthAndDeprecated(t *testing.T) {
input := [][]container.Stats{
{
{Name: "nginx", Cpu: 10.0, Mem: 100.0, Bandwidth: [2]uint64{5000, 6000}, NetworkSent: 99.0, NetworkRecv: 99.0},
},
{
{Name: "nginx", Cpu: 20.0, Mem: 200.0, Bandwidth: [2]uint64{7000, 8000}},
},
}
result := records.AverageContainerStatsSlice(input)
require.Len(t, result, 1)
assert.Equal(t, uint64(6000), result[0].Bandwidth[0])
assert.Equal(t, uint64(7000), result[0].Bandwidth[1])
}
func TestAverageContainerStatsSlice_ThreeRecords(t *testing.T) {
input := [][]container.Stats{
{{Name: "app", Cpu: 1.0, Mem: 100.0}},
{{Name: "app", Cpu: 2.0, Mem: 200.0}},
{{Name: "app", Cpu: 3.0, Mem: 300.0}},
}
result := records.AverageContainerStatsSlice(input)
require.Len(t, result, 1)
assert.Equal(t, 2.0, result[0].Cpu)
assert.Equal(t, 200.0, result[0].Mem)
}
func TestAverageContainerStatsSlice_ManyContainers(t *testing.T) {
input := [][]container.Stats{
{
{Name: "a", Cpu: 10.0, Mem: 100.0},
{Name: "b", Cpu: 20.0, Mem: 200.0},
{Name: "c", Cpu: 30.0, Mem: 300.0},
{Name: "d", Cpu: 40.0, Mem: 400.0},
},
{
{Name: "a", Cpu: 20.0, Mem: 200.0},
{Name: "b", Cpu: 30.0, Mem: 300.0},
{Name: "c", Cpu: 40.0, Mem: 400.0},
{Name: "d", Cpu: 50.0, Mem: 500.0},
},
}
result := records.AverageContainerStatsSlice(input)
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
require.Len(t, result, 4)
assert.Equal(t, 15.0, result[0].Cpu)
assert.Equal(t, 25.0, result[1].Cpu)
assert.Equal(t, 35.0, result[2].Cpu)
assert.Equal(t, 45.0, result[3].Cpu)
}