Pulse/internal/dockeragent/agent_cpu_test.go
2026-03-27 23:50:41 +00:00

326 lines
8.1 KiB
Go

package dockeragent
import (
"testing"
"time"
containertypes "github.com/moby/moby/api/types/container"
"github.com/rs/zerolog"
)
func TestCalculateContainerCPUPercent(t *testing.T) {
logger := zerolog.Nop()
t.Run("manual delta across two calls", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: make(map[string]cpuSample),
cpuCount: 2,
}
// First call: stores sample, returns 0
stats1 := containertypes.StatsResponse{
Read: time.Now().Add(-time.Second),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 100},
SystemUsage: 1000,
OnlineCPUs: 2,
},
}
got := agent.calculateContainerCPUPercent("container-123456", stats1)
if got != 0 {
t.Fatalf("first call: expected 0, got %f", got)
}
if _, ok := agent.prevContainerCPU["container-123456"]; !ok {
t.Fatal("expected sample to be stored after first call")
}
// Second call: computes delta from stored sample
stats2 := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 2000,
OnlineCPUs: 2,
},
}
got = agent.calculateContainerCPUPercent("container-123456", stats2)
if got <= 0 {
t.Fatalf("second call: expected percent > 0, got %f", got)
}
})
t.Run("first manual sample returns zero", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: make(map[string]cpuSample),
}
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 100},
SystemUsage: 1000,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 100},
SystemUsage: 1000,
},
}
got := agent.calculateContainerCPUPercent("container-123456", stats)
if got != 0 {
t.Fatalf("expected 0, got %f", got)
}
if _, ok := agent.prevContainerCPU["container-123456"]; !ok {
t.Fatal("expected sample to be stored")
}
})
t.Run("manual system delta uses previous sample", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 100,
systemUsage: 1000,
onlineCPUs: 2,
read: time.Now().Add(-time.Second),
},
},
}
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 2000,
OnlineCPUs: 0,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 2000,
},
}
got := agent.calculateContainerCPUPercent("container-123456", stats)
if got <= 0 {
t.Fatalf("expected percent > 0, got %f", got)
}
})
t.Run("manual time delta fallback", func(t *testing.T) {
agent := &Agent{
logger: logger,
cpuCount: 4,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 100,
systemUsage: 1000,
onlineCPUs: 0,
read: time.Now().Add(-2 * time.Second),
},
},
}
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1000,
OnlineCPUs: 0,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1000,
},
}
got := agent.calculateContainerCPUPercent("container-123456", stats)
if got <= 0 {
t.Fatalf("expected percent > 0, got %f", got)
}
})
t.Run("counter reset uses current total", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 200,
systemUsage: 1000,
onlineCPUs: 2,
read: time.Now().Add(-time.Second),
},
},
}
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 50},
SystemUsage: 1200,
OnlineCPUs: 2,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 50},
SystemUsage: 1200,
},
}
got := agent.calculateContainerCPUPercent("container-123456", stats)
if got <= 0 {
t.Fatalf("expected percent > 0, got %f", got)
}
})
t.Run("no online CPUs returns zero", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 100,
systemUsage: 1000,
onlineCPUs: 0,
read: time.Now().Add(-time.Second),
},
},
}
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1100,
OnlineCPUs: 0,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1100,
},
}
if got := agent.calculateContainerCPUPercent("container-123456", stats); got != 0 {
t.Fatalf("expected 0, got %f", got)
}
})
t.Run("elapsed non-positive returns zero", func(t *testing.T) {
now := time.Now()
agent := &Agent{
logger: logger,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 100,
systemUsage: 1000,
onlineCPUs: 2,
read: now,
},
},
}
stats := containertypes.StatsResponse{
Read: now,
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 900,
OnlineCPUs: 2,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 900,
},
}
if got := agent.calculateContainerCPUPercent("container-123456", stats); got != 0 {
t.Fatalf("expected 0, got %f", got)
}
})
t.Run("total delta zero returns zero", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 200,
systemUsage: 1000,
onlineCPUs: 2,
read: time.Now().Add(-time.Second),
},
},
}
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1100,
OnlineCPUs: 2,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1100,
},
}
if got := agent.calculateContainerCPUPercent("container-123456", stats); got != 0 {
t.Fatalf("expected 0, got %f", got)
}
})
t.Run("no valid delta returns zero", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: map[string]cpuSample{
"container-123456": {
totalUsage: 100,
systemUsage: 1000,
onlineCPUs: 0,
read: time.Time{},
},
},
}
stats := containertypes.StatsResponse{
Read: time.Time{},
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 1000,
OnlineCPUs: 0,
},
PreCPUStats: containertypes.CPUStats{},
}
got := agent.calculateContainerCPUPercent("container-123456", stats)
if got != 0 {
t.Fatalf("expected 0, got %f", got)
}
})
t.Run("ignores stale precpu stats", func(t *testing.T) {
agent := &Agent{
logger: logger,
prevContainerCPU: make(map[string]cpuSample),
cpuCount: 2,
}
// Even with valid-looking PreCPUStats, we use manual tracking.
// First call stores sample and returns 0.
stats := containertypes.StatsResponse{
Read: time.Now(),
CPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 200},
SystemUsage: 2000,
OnlineCPUs: 2,
},
PreCPUStats: containertypes.CPUStats{
CPUUsage: containertypes.CPUUsage{TotalUsage: 100},
SystemUsage: 1000,
},
}
got := agent.calculateContainerCPUPercent("container-123456", stats)
if got != 0 {
t.Fatalf("expected 0 on first call (manual tracking), got %f", got)
}
})
}