335 lines
9.2 KiB
Go
335 lines
9.2 KiB
Go
// ABOUTME: Tests for the summary accumulator that tracks metrics across a run.
|
|
// ABOUTME: Validates stats computation (peak/avg/P95), process peak tracking, and edge cases.
|
|
package summary
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"edp.buildth.ing/DevFW-CICD/forgejo-runner-resource-collector/internal/metrics"
|
|
)
|
|
|
|
func TestAccumulator_NoSamples(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
result := acc.Summarize()
|
|
if result != nil {
|
|
t.Errorf("expected nil summary for no samples, got %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_SingleSample(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{TotalPercent: 42.5},
|
|
Memory: metrics.MemoryMetrics{
|
|
UsedBytes: 1000,
|
|
UsedPercent: 50.0,
|
|
},
|
|
})
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// With a single sample, peak=avg=p95
|
|
if s.CPUTotal.Peak != 42.5 {
|
|
t.Errorf("CPU peak: got %f, want 42.5", s.CPUTotal.Peak)
|
|
}
|
|
if s.CPUTotal.Avg != 42.5 {
|
|
t.Errorf("CPU avg: got %f, want 42.5", s.CPUTotal.Avg)
|
|
}
|
|
if s.CPUTotal.P95 != 42.5 {
|
|
t.Errorf("CPU p95: got %f, want 42.5", s.CPUTotal.P95)
|
|
}
|
|
if s.MemUsedBytes.Peak != 1000 {
|
|
t.Errorf("MemUsedBytes peak: got %f, want 1000", s.MemUsedBytes.Peak)
|
|
}
|
|
if s.MemUsedPercent.Peak != 50.0 {
|
|
t.Errorf("MemUsedPercent peak: got %f, want 50.0", s.MemUsedPercent.Peak)
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_Stats(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
cpuValues := []float64{10, 20, 30, 40, 50}
|
|
for i, v := range cpuValues {
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, i, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{TotalPercent: v},
|
|
Memory: metrics.MemoryMetrics{
|
|
UsedBytes: uint64(v * 100),
|
|
UsedPercent: v,
|
|
},
|
|
})
|
|
}
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// Peak = max = 50
|
|
if s.CPUTotal.Peak != 50 {
|
|
t.Errorf("CPU peak: got %f, want 50", s.CPUTotal.Peak)
|
|
}
|
|
// Avg = (10+20+30+40+50)/5 = 30
|
|
if s.CPUTotal.Avg != 30 {
|
|
t.Errorf("CPU avg: got %f, want 30", s.CPUTotal.Avg)
|
|
}
|
|
// P95: sorted=[10,20,30,40,50], index=int(4*0.95)=int(3.8)=3, value=40
|
|
if s.CPUTotal.P95 != 40 {
|
|
t.Errorf("CPU p95: got %f, want 40", s.CPUTotal.P95)
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_P95_LargerDataset(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
// 20 values: 1, 2, 3, ..., 20
|
|
for i := 1; i <= 20; i++ {
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, i, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{TotalPercent: float64(i)},
|
|
Memory: metrics.MemoryMetrics{},
|
|
})
|
|
}
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// P95: sorted=[1..20], index=int(19*0.95)=int(18.05)=18, value=19
|
|
if s.CPUTotal.P95 != 19 {
|
|
t.Errorf("CPU p95: got %f, want 19", s.CPUTotal.P95)
|
|
}
|
|
// Avg = (1+2+...+20)/20 = 210/20 = 10.5
|
|
if s.CPUTotal.Avg != 10.5 {
|
|
t.Errorf("CPU avg: got %f, want 10.5", s.CPUTotal.Avg)
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_MemoryStats(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
memBytes := []uint64{1000, 2000, 3000, 4000, 5000}
|
|
memPercent := []float64{10, 20, 30, 40, 50}
|
|
|
|
for i := range memBytes {
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, i, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{TotalPercent: 0},
|
|
Memory: metrics.MemoryMetrics{
|
|
UsedBytes: memBytes[i],
|
|
UsedPercent: memPercent[i],
|
|
},
|
|
})
|
|
}
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// MemUsedBytes: peak=5000, avg=3000, p95=4000
|
|
if s.MemUsedBytes.Peak != 5000 {
|
|
t.Errorf("MemUsedBytes peak: got %f, want 5000", s.MemUsedBytes.Peak)
|
|
}
|
|
if s.MemUsedBytes.Avg != 3000 {
|
|
t.Errorf("MemUsedBytes avg: got %f, want 3000", s.MemUsedBytes.Avg)
|
|
}
|
|
if s.MemUsedBytes.P95 != 4000 {
|
|
t.Errorf("MemUsedBytes p95: got %f, want 4000", s.MemUsedBytes.P95)
|
|
}
|
|
|
|
// MemUsedPercent: peak=50, avg=30, p95=40
|
|
if s.MemUsedPercent.Peak != 50 {
|
|
t.Errorf("MemUsedPercent peak: got %f, want 50", s.MemUsedPercent.Peak)
|
|
}
|
|
if s.MemUsedPercent.Avg != 30 {
|
|
t.Errorf("MemUsedPercent avg: got %f, want 30", s.MemUsedPercent.Avg)
|
|
}
|
|
if s.MemUsedPercent.P95 != 40 {
|
|
t.Errorf("MemUsedPercent p95: got %f, want 40", s.MemUsedPercent.P95)
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_ProcessPeaks(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
|
|
// Same PID across two samples; peaks should be retained
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
TopCPU: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "a", CPUPercent: 10, MemRSS: 100},
|
|
},
|
|
TopMemory: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "a", CPUPercent: 10, MemRSS: 100},
|
|
},
|
|
})
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, 1, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
TopCPU: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "a", CPUPercent: 20, MemRSS: 50},
|
|
},
|
|
TopMemory: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "a", CPUPercent: 5, MemRSS: 200},
|
|
},
|
|
})
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// Should find PID 1 with peak CPU=20, peak mem=200
|
|
found := false
|
|
for _, p := range s.TopCPUProcesses {
|
|
if p.PID == 1 {
|
|
found = true
|
|
if p.PeakCPU != 20 {
|
|
t.Errorf("PeakCPU: got %f, want 20", p.PeakCPU)
|
|
}
|
|
if p.PeakMem != 200 {
|
|
t.Errorf("PeakMem: got %d, want 200", p.PeakMem)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("PID 1 not found in TopCPUProcesses")
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_ProcessPeaks_TopN(t *testing.T) {
|
|
acc := NewAccumulator(2) // Only top 2
|
|
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
TopCPU: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "low", CPUPercent: 10, MemRSS: 100},
|
|
{PID: 2, Name: "mid", CPUPercent: 50, MemRSS: 500},
|
|
{PID: 3, Name: "high", CPUPercent: 90, MemRSS: 900},
|
|
},
|
|
TopMemory: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "low", CPUPercent: 10, MemRSS: 100},
|
|
{PID: 2, Name: "mid", CPUPercent: 50, MemRSS: 500},
|
|
{PID: 3, Name: "high", CPUPercent: 90, MemRSS: 900},
|
|
},
|
|
})
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// TopCPUProcesses should have at most 2 entries, sorted by PeakCPU descending
|
|
if len(s.TopCPUProcesses) != 2 {
|
|
t.Fatalf("TopCPUProcesses length: got %d, want 2", len(s.TopCPUProcesses))
|
|
}
|
|
if s.TopCPUProcesses[0].PeakCPU != 90 {
|
|
t.Errorf("TopCPU[0] PeakCPU: got %f, want 90", s.TopCPUProcesses[0].PeakCPU)
|
|
}
|
|
if s.TopCPUProcesses[1].PeakCPU != 50 {
|
|
t.Errorf("TopCPU[1] PeakCPU: got %f, want 50", s.TopCPUProcesses[1].PeakCPU)
|
|
}
|
|
|
|
// TopMemProcesses should have at most 2 entries, sorted by PeakMem descending
|
|
if len(s.TopMemProcesses) != 2 {
|
|
t.Fatalf("TopMemProcesses length: got %d, want 2", len(s.TopMemProcesses))
|
|
}
|
|
if s.TopMemProcesses[0].PeakMem != 900 {
|
|
t.Errorf("TopMem[0] PeakMem: got %d, want 900", s.TopMemProcesses[0].PeakMem)
|
|
}
|
|
if s.TopMemProcesses[1].PeakMem != 500 {
|
|
t.Errorf("TopMem[1] PeakMem: got %d, want 500", s.TopMemProcesses[1].PeakMem)
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_ProcessPeaks_Dedup(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
|
|
// A process appears in both TopCPU and TopMemory
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
TopCPU: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "proc", CPUPercent: 80, MemRSS: 100},
|
|
},
|
|
TopMemory: []metrics.ProcessMetrics{
|
|
{PID: 1, Name: "proc", CPUPercent: 30, MemRSS: 500},
|
|
},
|
|
})
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
// The internal process map should have merged the peaks
|
|
// PeakCPU should be 80 (from TopCPU), PeakMem should be 500 (from TopMemory)
|
|
for _, p := range s.TopCPUProcesses {
|
|
if p.PID == 1 {
|
|
if p.PeakCPU != 80 {
|
|
t.Errorf("PeakCPU: got %f, want 80", p.PeakCPU)
|
|
}
|
|
if p.PeakMem != 500 {
|
|
t.Errorf("PeakMem: got %d, want 500", p.PeakMem)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_SampleCount(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
if acc.SampleCount() != 0 {
|
|
t.Errorf("initial SampleCount: got %d, want 0", acc.SampleCount())
|
|
}
|
|
|
|
for i := 0; i < 3; i++ {
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, i, 0, time.UTC),
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
})
|
|
}
|
|
|
|
if acc.SampleCount() != 3 {
|
|
t.Errorf("SampleCount after 3 adds: got %d, want 3", acc.SampleCount())
|
|
}
|
|
}
|
|
|
|
func TestAccumulator_Duration(t *testing.T) {
|
|
acc := NewAccumulator(5)
|
|
start := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(2025, 1, 1, 0, 1, 0, 0, time.UTC) // 60 seconds later
|
|
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: start,
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
})
|
|
acc.Add(&metrics.SystemMetrics{
|
|
Timestamp: end,
|
|
CPU: metrics.CPUMetrics{},
|
|
Memory: metrics.MemoryMetrics{},
|
|
})
|
|
|
|
s := acc.Summarize()
|
|
if s == nil {
|
|
t.Fatal("expected non-nil summary")
|
|
}
|
|
|
|
if !s.StartTime.Equal(start) {
|
|
t.Errorf("StartTime: got %v, want %v", s.StartTime, start)
|
|
}
|
|
if s.DurationSeconds != 60 {
|
|
t.Errorf("DurationSeconds: got %f, want 60", s.DurationSeconds)
|
|
}
|
|
}
|