mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-15 01:07:32 +00:00
387 lines
9.7 KiB
Go
387 lines
9.7 KiB
Go
package dockeragent
|
|
|
|
import (
|
|
"math"
|
|
"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)
|
|
}
|
|
})
|
|
|
|
t.Run("podman uses wall clock delta", func(t *testing.T) {
|
|
now := time.Now()
|
|
agent := &Agent{
|
|
logger: logger,
|
|
runtime: RuntimePodman,
|
|
prevContainerCPU: map[string]cpuSample{
|
|
"container-123456": {
|
|
totalUsage: 1_000_000_000,
|
|
systemUsage: 1_000,
|
|
onlineCPUs: 16,
|
|
read: now.Add(-time.Second),
|
|
},
|
|
},
|
|
}
|
|
|
|
stats := containertypes.StatsResponse{
|
|
Read: now,
|
|
CPUStats: containertypes.CPUStats{
|
|
CPUUsage: containertypes.CPUUsage{TotalUsage: 1_250_000_000},
|
|
SystemUsage: 2_000,
|
|
OnlineCPUs: 16,
|
|
},
|
|
}
|
|
|
|
got := agent.calculateContainerCPUPercent("container-123456", stats)
|
|
if math.Abs(got-25.0) > 0.01 {
|
|
t.Fatalf("expected podman cpu percent near 25.0, got %f", got)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDecodeContainerStatsPayloadExtractsPodmanCPU(t *testing.T) {
|
|
payload := []byte(`{
|
|
"read":"2026-04-09T12:00:00Z",
|
|
"cpu_stats":{
|
|
"cpu_usage":{"total_usage":123456789},
|
|
"system_cpu_usage":987654321,
|
|
"online_cpus":16,
|
|
"cpu":0.32,
|
|
"throttling_data":{}
|
|
},
|
|
"precpu_stats":{},
|
|
"memory_stats":{"usage":1000,"limit":2000,"stats":{"cache":100}},
|
|
"blkio_stats":{"io_service_bytes_recursive":[]}
|
|
}`)
|
|
|
|
stats, podmanCPU, err := decodeContainerStatsPayload(payload)
|
|
if err != nil {
|
|
t.Fatalf("unexpected decode error: %v", err)
|
|
}
|
|
if stats.CPUStats.CPUUsage.TotalUsage != 123456789 {
|
|
t.Fatalf("expected total usage 123456789, got %d", stats.CPUStats.CPUUsage.TotalUsage)
|
|
}
|
|
if podmanCPU == nil {
|
|
t.Fatal("expected podman cpu percent to be extracted")
|
|
}
|
|
if math.Abs(*podmanCPU-0.32) > 0.000001 {
|
|
t.Fatalf("expected podman cpu percent 0.32, got %f", *podmanCPU)
|
|
}
|
|
}
|