From da01bccd368d112669f50fa9a4b69bf9fcb8679e Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Thu, 21 Mar 2024 23:57:37 +0000 Subject: [PATCH] katautils: check number of cores on the system intead of go runtime We used to utilize go runtime's "NumCPUs()", which will give the number of cores available to the Go runtime, which may be a subset of physical cores if the shim is started from within a cpuset. From the function's description: "NumCPU returns the number of logical CPUs usable by the current process." As an example, if containerd is run from within a smaller CPUset, the maximum size of a pod will be dictated by this CPUset, instead of what will be available on the rest of the system. Since the shim will be moved into its own cgroup that may have a different CPUset, let's stick with checking physical cores. This also aligns with what we have documented for maxVCPU handling. In the event we fail to read /proc/cpuinfo, let's use the goruntime. Fixes: #9327 Signed-off-by: Eric Ernst --- src/runtime/pkg/katautils/config.go | 24 +++++- src/runtime/pkg/katautils/config_test.go | 93 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/runtime/pkg/katautils/config.go b/src/runtime/pkg/katautils/config.go index 695002eaaa..a786bb2da1 100644 --- a/src/runtime/pkg/katautils/config.go +++ b/src/runtime/pkg/katautils/config.go @@ -381,12 +381,32 @@ func (h hypervisor) GetEntropySource() string { return h.EntropySource } +var procCPUInfo = "/proc/cpuinfo" + +func getHostCPUs() uint32 { + cpuInfo, err := os.ReadFile(procCPUInfo) + if err != nil { + kataUtilsLogger.Warn("unable to read /proc/cpuinfo to determine cpu count - using go runtime value instead") + return uint32(goruntime.NumCPU()) + } + + cores := 0 + lines := strings.Split(string(cpuInfo), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "processor") { + cores++ + } + } + + return uint32(cores) +} + // Current cpu number should not larger than defaultMaxVCPUs() func getCurrentCpuNum() uint32 { var cpu uint32 h := hypervisor{} - cpu = uint32(goruntime.NumCPU()) + cpu = getHostCPUs() if cpu > h.defaultMaxVCPUs() { cpu = h.defaultMaxVCPUs() } @@ -408,7 +428,7 @@ func (h hypervisor) defaultVCPUs() float32 { } func (h hypervisor) defaultMaxVCPUs() uint32 { - numcpus := uint32(goruntime.NumCPU()) + numcpus := getHostCPUs() maxvcpus := govmm.MaxVCPUs() reqVCPUs := h.DefaultMaxVCPUs diff --git a/src/runtime/pkg/katautils/config_test.go b/src/runtime/pkg/katautils/config_test.go index 3ecb1517e5..f29cb288c4 100644 --- a/src/runtime/pkg/katautils/config_test.go +++ b/src/runtime/pkg/katautils/config_test.go @@ -1802,3 +1802,96 @@ func TestUpdateRuntimeConfigHypervisor(t *testing.T) { } } } + +func TestGetHostCPUs(t *testing.T) { + testCases := []struct { + input string + expected uint32 + }{ + { + input: `processor : 0 +processor : 1 +vendor_id : GenuineAmzing +cpu family : 6 +model : 60 +model name : whatever + ... +processor : 1 +vendor_id : Genuine +cpu family : 6 +model : 60 +model name : Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz +... + `, + expected: 3, + }, + { + + input: `processor : 0 +processor : 1 +BogoMIPS : 48.00 +Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint +CPU implementer : 0x00 +CPU architecture: 8 +CPU variant : 0x0 +CPU part : 0x000 +CPU revision : 00 +processor : 2 +vendor_id : GenuineAmzing +processor : 3 +cpu family : 6 +processor : 4 +model : 60 +processor : 5 +cpu family : 6 +model : 60 + + +model name : Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz +... + `, + expected: 6, + }, + } + + for _, tc := range testCases { + // Create tmp file for mocking cpuinfo + file, err := os.CreateTemp("", "cpuinfo") + procCPUInfo = file.Name() + if err != nil { + t.Fatalf("Error creating temp file: %v", err) + } + defer os.Remove(file.Name()) + + if _, err := file.WriteString(tc.input); err != nil { + t.Fatalf("Error writing to temp file: %v", err) + } + + if err := file.Close(); err != nil { + t.Fatalf("Error closing temp file: %v", err) + } + + parsed := getHostCPUs() + + if parsed != tc.expected { + t.Errorf("Expected number of cores: %d, got: %d", tc.expected, parsed) + } + } +} + +func TestGetHostCPUsNoProc(t *testing.T) { + + // override logger so we can check the output when calling function under test: + logBuf := &bytes.Buffer{} + kataUtilsLogger.Logger.Out = logBuf + + // make sure we fail to read: + procCPUInfo = "/i/am/not/here" + + getHostCPUs() + + expectedLog := "unable to read /proc/cpuinfo to determine cpu count - using go runtime value instead" + actualLog := logBuf.String() + assert.Contains(t, actualLog, expectedLog, "Expected log message not found") + +}