mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-07 08:02:08 +00:00
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
143 lines
5.1 KiB
Go
143 lines
5.1 KiB
Go
package nodeagent
|
|
|
|
import (
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
func samplerWithFiles(files map[string]string) *usageSampler {
|
|
return &usageSampler{readFile: func(path string) ([]byte, error) {
|
|
if value, ok := files[path]; ok {
|
|
return []byte(value), nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}}
|
|
}
|
|
|
|
func TestHostUsageSeparatesResidentMemoryAndMachineMemory(t *testing.T) {
|
|
files := map[string]string{
|
|
"/proc/self/status": "VmRSS: 140288 kB\n",
|
|
"/proc/meminfo": "MemTotal: 8388608 kB\nMemAvailable: 6291456 kB\n",
|
|
"/proc/stat": "cpu 100 0 100 800 0 0 0 0 50 0\n",
|
|
}
|
|
s := samplerWithFiles(files)
|
|
var first models.NodeUsage
|
|
s.sample(&first)
|
|
if first.CPUPercent != nil || *first.ResidentMB != 137 || *first.MemoryUsedMB != 2048 || *first.MemoryLimitMB != 8192 || first.MemoryScope != "host" {
|
|
t.Fatalf("unexpected initial snapshot: %+v", first)
|
|
}
|
|
files["/proc/stat"] = "cpu 125 0 125 850 0 0 0 0 100 0\n"
|
|
var next models.NodeUsage
|
|
s.sample(&next)
|
|
if next.CPUScope != "host" || next.CPUPercent == nil || *next.CPUPercent != 50 {
|
|
t.Fatalf("expected 50%% host CPU excluding guest double counting: %+v", next)
|
|
}
|
|
}
|
|
|
|
func TestContainerUsageUsesLimitsAndCPUInterval(t *testing.T) {
|
|
files := map[string]string{
|
|
"/proc/meminfo": "MemTotal: 8388608 kB\nMemAvailable: 6291456 kB\n",
|
|
"/sys/fs/cgroup/memory.current": "143654912",
|
|
"/sys/fs/cgroup/memory.max": "536870912",
|
|
"/sys/fs/cgroup/cpu.stat": "usage_usec 1250000\n",
|
|
"/sys/fs/cgroup/cpu.max": "50000 100000",
|
|
}
|
|
s := samplerWithFiles(files)
|
|
s.previous = cpuReading{scope: "container", used: 1000000, cores: 0.5, at: time.Now().Add(-time.Second)}
|
|
var usage models.NodeUsage
|
|
s.sample(&usage)
|
|
if usage.MemoryScope != "container" || *usage.MemoryUsedMB != 137 || *usage.MemoryLimitMB != 512 || usage.CPUPercent == nil || *usage.CPUPercent < 45 || *usage.CPUPercent > 51 {
|
|
t.Fatalf("expected container 137/512 MiB, approximately 50%% CPU: %+v", usage)
|
|
}
|
|
files["/sys/fs/cgroup/cpu.max"] = "25000 100000"
|
|
var changed models.NodeUsage
|
|
s.sample(&changed)
|
|
if changed.CPUPercent != nil {
|
|
t.Fatal("a changed CPU quota must establish a new baseline")
|
|
}
|
|
}
|
|
|
|
func TestMissingTelemetryIsNotZero(t *testing.T) {
|
|
s := samplerWithFiles(nil)
|
|
var usage models.NodeUsage
|
|
s.sample(&usage)
|
|
if usage.CPUPercent != nil || usage.ResidentMB != nil || usage.MemoryUsedMB != nil || usage.MemoryLimitMB != nil {
|
|
t.Fatalf("missing OS metrics must remain unavailable: %+v", usage)
|
|
}
|
|
}
|
|
|
|
func TestCgroupV1AndNestedV2Memory(t *testing.T) {
|
|
for _, version := range []string{"v1", "v2"} {
|
|
t.Run(version, func(t *testing.T) {
|
|
files := map[string]string{"/proc/meminfo": "MemTotal: 8388608 kB\nMemAvailable: 6291456 kB\n"}
|
|
if version == "v1" {
|
|
files["/sys/fs/cgroup/memory/memory.usage_in_bytes"] = "143654912"
|
|
files["/sys/fs/cgroup/memory/memory.limit_in_bytes"] = "536870912"
|
|
} else {
|
|
files["/proc/self/cgroup"] = "0::/worker\n"
|
|
files["/sys/fs/cgroup/worker/cgroup.procs"] = "1\n"
|
|
files["/sys/fs/cgroup/worker/memory.current"] = "143654912"
|
|
files["/sys/fs/cgroup/worker/memory.max"] = "536870912"
|
|
}
|
|
var usage models.NodeUsage
|
|
samplerWithFiles(files).sample(&usage)
|
|
if usage.MemoryScope != "container" || *usage.MemoryLimitMB != 512 {
|
|
t.Fatalf("unexpected %s memory snapshot: %+v", version, usage)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCPUSetSize(t *testing.T) {
|
|
for raw, want := range map[string]int{"0-3,6,8-9": 7, "0": 1, "": 0, "3-1": 0, "no": 0} {
|
|
if got := cpuSetSize(raw); got != want {
|
|
t.Errorf("cpuSetSize(%q) = %d, want %d", raw, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContainerMemoryDoesNotRequireHostMemory(t *testing.T) {
|
|
files := map[string]string{
|
|
"/sys/fs/cgroup/memory.current": "143654912",
|
|
"/sys/fs/cgroup/memory.max": "536870912",
|
|
}
|
|
var usage models.NodeUsage
|
|
samplerWithFiles(files).sample(&usage)
|
|
if usage.MemoryScope != "container" || usage.MemoryUsedMB == nil || *usage.MemoryUsedMB != 137 || *usage.MemoryLimitMB != 512 {
|
|
t.Fatalf("expected cgroup memory even without host totals: %+v", usage)
|
|
}
|
|
}
|
|
|
|
func TestContainerCPUQuotaEqualToAvailableCPUs(t *testing.T) {
|
|
files := map[string]string{
|
|
"/sys/fs/cgroup/cpu.stat": "usage_usec 1250000\n",
|
|
"/sys/fs/cgroup/cpu.max": strconv.Itoa(runtime.NumCPU()*100000) + " 100000",
|
|
}
|
|
var usage models.NodeUsage
|
|
samplerWithFiles(files).sample(&usage)
|
|
if usage.CPUScope != "container" {
|
|
t.Fatalf("a finite CPU quota should retain container scope: %+v", usage)
|
|
}
|
|
}
|
|
|
|
func TestContainerCPUSetRecognizesHostCPUsOutsideProcessAffinity(t *testing.T) {
|
|
procStat := "cpu 100 0 100 800 0 0 0 0\n"
|
|
for i := 0; i <= runtime.NumCPU(); i++ {
|
|
procStat += "cpu" + strconv.Itoa(i) + " 1 0 1 8 0 0 0 0\n"
|
|
}
|
|
files := map[string]string{
|
|
"/proc/stat": procStat,
|
|
"/sys/fs/cgroup/cpu.stat": "usage_usec 1250000\n",
|
|
"/sys/fs/cgroup/cpu.max": "max 100000",
|
|
"/sys/fs/cgroup/cpuset.cpus.effective": "0-" + strconv.Itoa(runtime.NumCPU()-1),
|
|
}
|
|
if got := samplerWithFiles(files).cpu(); got.scope != "container" || got.cores != float64(runtime.NumCPU()) {
|
|
t.Fatalf("a cpuset smaller than the host must use container scope: %+v", got)
|
|
}
|
|
}
|