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 <eric_ernst@apple.com>
This commit is contained in:
Eric Ernst 2024-03-21 23:57:37 +00:00
parent c5c229b330
commit da01bccd36
2 changed files with 115 additions and 2 deletions

View File

@ -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

View File

@ -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")
}