mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #114114 from ffromani/full-pcpus-stricter-precheck-issue113537
node: cpumgr: stricter pre-check for the policy option full-pcpus-only
This commit is contained in:
commit
efe20f6c9b
@ -45,11 +45,15 @@ const (
|
||||
|
||||
// SMTAlignmentError represents an error due to SMT alignment
|
||||
type SMTAlignmentError struct {
|
||||
RequestedCPUs int
|
||||
CpusPerCore int
|
||||
RequestedCPUs int
|
||||
CpusPerCore int
|
||||
AvailablePhysicalCPUs int
|
||||
}
|
||||
|
||||
func (e SMTAlignmentError) Error() string {
|
||||
if e.AvailablePhysicalCPUs > 0 {
|
||||
return fmt.Sprintf("SMT Alignment Error: not enough free physical CPUs: available physical CPUs = %d, requested CPUs = %d, CPUs per core = %d", e.AvailablePhysicalCPUs, e.RequestedCPUs, e.CpusPerCore)
|
||||
}
|
||||
return fmt.Sprintf("SMT Alignment Error: requested %d cpus not multiple cpus per core = %d", e.RequestedCPUs, e.CpusPerCore)
|
||||
}
|
||||
|
||||
@ -100,7 +104,13 @@ type staticPolicy struct {
|
||||
// cpu socket topology
|
||||
topology *topology.CPUTopology
|
||||
// set of CPUs that is not available for exclusive assignment
|
||||
reserved cpuset.CPUSet
|
||||
reservedCPUs cpuset.CPUSet
|
||||
// Superset of reservedCPUs. It includes not just the reservedCPUs themselves,
|
||||
// but also any siblings of those reservedCPUs on the same physical die.
|
||||
// NOTE: If the reserved set includes full physical CPUs from the beginning
|
||||
// (e.g. only reserved pairs of core siblings) this set is expected to be
|
||||
// identical to the reserved set.
|
||||
reservedPhysicalCPUs cpuset.CPUSet
|
||||
// topology manager reference to get container Topology affinity
|
||||
affinity topologymanager.Store
|
||||
// set of CPUs to reuse across allocations in a pod
|
||||
@ -152,8 +162,18 @@ func NewStaticPolicy(topology *topology.CPUTopology, numReservedCPUs int, reserv
|
||||
return nil, err
|
||||
}
|
||||
|
||||
klog.InfoS("Reserved CPUs not available for exclusive assignment", "reservedSize", reserved.Size(), "reserved", reserved)
|
||||
policy.reserved = reserved
|
||||
var reservedPhysicalCPUs cpuset.CPUSet
|
||||
for _, cpu := range reserved.UnsortedList() {
|
||||
core, err := topology.CPUCoreID(cpu)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[cpumanager] unable to build the reserved physical CPUs from the reserved set: %w", err)
|
||||
}
|
||||
reservedPhysicalCPUs = reservedPhysicalCPUs.Union(topology.CPUDetails.CPUsInCores(core))
|
||||
}
|
||||
|
||||
klog.InfoS("Reserved CPUs not available for exclusive assignment", "reservedSize", reserved.Size(), "reserved", reserved, "reservedPhysicalCPUs", reservedPhysicalCPUs)
|
||||
policy.reservedCPUs = reserved
|
||||
policy.reservedPhysicalCPUs = reservedPhysicalCPUs
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
@ -189,9 +209,9 @@ func (p *staticPolicy) validateState(s state.State) error {
|
||||
// 1. Check if the reserved cpuset is not part of default cpuset because:
|
||||
// - kube/system reserved have changed (increased) - may lead to some containers not being able to start
|
||||
// - user tampered with file
|
||||
if !p.reserved.Intersection(tmpDefaultCPUset).Equals(p.reserved) {
|
||||
if !p.reservedCPUs.Intersection(tmpDefaultCPUset).Equals(p.reservedCPUs) {
|
||||
return fmt.Errorf("not all reserved cpus: \"%s\" are present in defaultCpuSet: \"%s\"",
|
||||
p.reserved.String(), tmpDefaultCPUset.String())
|
||||
p.reservedCPUs.String(), tmpDefaultCPUset.String())
|
||||
}
|
||||
|
||||
// 2. Check if state for static policy is consistent
|
||||
@ -230,12 +250,16 @@ func (p *staticPolicy) validateState(s state.State) error {
|
||||
|
||||
// GetAllocatableCPUs returns the total set of CPUs available for allocation.
|
||||
func (p *staticPolicy) GetAllocatableCPUs(s state.State) cpuset.CPUSet {
|
||||
return p.topology.CPUDetails.CPUs().Difference(p.reserved)
|
||||
return p.topology.CPUDetails.CPUs().Difference(p.reservedCPUs)
|
||||
}
|
||||
|
||||
// GetAvailableCPUs returns the set of unassigned CPUs minus the reserved set.
|
||||
func (p *staticPolicy) GetAvailableCPUs(s state.State) cpuset.CPUSet {
|
||||
return s.GetDefaultCPUSet().Difference(p.reserved)
|
||||
return s.GetDefaultCPUSet().Difference(p.reservedCPUs)
|
||||
}
|
||||
|
||||
func (p *staticPolicy) GetAvailablePhysicalCPUs(s state.State) cpuset.CPUSet {
|
||||
return s.GetDefaultCPUSet().Difference(p.reservedPhysicalCPUs)
|
||||
}
|
||||
|
||||
func (p *staticPolicy) updateCPUsToReuse(pod *v1.Pod, container *v1.Container, cset cpuset.CPUSet) {
|
||||
@ -278,19 +302,36 @@ func (p *staticPolicy) Allocate(s state.State, pod *v1.Pod, container *v1.Contai
|
||||
}
|
||||
}()
|
||||
|
||||
if p.options.FullPhysicalCPUsOnly && ((numCPUs % p.topology.CPUsPerCore()) != 0) {
|
||||
// Since CPU Manager has been enabled requesting strict SMT alignment, it means a guaranteed pod can only be admitted
|
||||
// if the CPU requested is a multiple of the number of virtual cpus per physical cores.
|
||||
// In case CPU request is not a multiple of the number of virtual cpus per physical cores the Pod will be put
|
||||
// in Failed state, with SMTAlignmentError as reason. Since the allocation happens in terms of physical cores
|
||||
// and the scheduler is responsible for ensuring that the workload goes to a node that has enough CPUs,
|
||||
// the pod would be placed on a node where there are enough physical cores available to be allocated.
|
||||
// Just like the behaviour in case of static policy, takeByTopology will try to first allocate CPUs from the same socket
|
||||
// and only in case the request cannot be sattisfied on a single socket, CPU allocation is done for a workload to occupy all
|
||||
// CPUs on a physical core. Allocation of individual threads would never have to occur.
|
||||
return SMTAlignmentError{
|
||||
RequestedCPUs: numCPUs,
|
||||
CpusPerCore: p.topology.CPUsPerCore(),
|
||||
if p.options.FullPhysicalCPUsOnly {
|
||||
CPUsPerCore := p.topology.CPUsPerCore()
|
||||
if (numCPUs % CPUsPerCore) != 0 {
|
||||
// Since CPU Manager has been enabled requesting strict SMT alignment, it means a guaranteed pod can only be admitted
|
||||
// if the CPU requested is a multiple of the number of virtual cpus per physical cores.
|
||||
// In case CPU request is not a multiple of the number of virtual cpus per physical cores the Pod will be put
|
||||
// in Failed state, with SMTAlignmentError as reason. Since the allocation happens in terms of physical cores
|
||||
// and the scheduler is responsible for ensuring that the workload goes to a node that has enough CPUs,
|
||||
// the pod would be placed on a node where there are enough physical cores available to be allocated.
|
||||
// Just like the behaviour in case of static policy, takeByTopology will try to first allocate CPUs from the same socket
|
||||
// and only in case the request cannot be sattisfied on a single socket, CPU allocation is done for a workload to occupy all
|
||||
// CPUs on a physical core. Allocation of individual threads would never have to occur.
|
||||
return SMTAlignmentError{
|
||||
RequestedCPUs: numCPUs,
|
||||
CpusPerCore: CPUsPerCore,
|
||||
}
|
||||
}
|
||||
|
||||
availablePhysicalCPUs := p.GetAvailablePhysicalCPUs(s).Size()
|
||||
|
||||
// It's legal to reserve CPUs which are not core siblings. In this case the CPU allocator can descend to single cores
|
||||
// when picking CPUs. This will void the guarantee of FullPhysicalCPUsOnly. To prevent this, we need to additionally consider
|
||||
// all the core siblings of the reserved CPUs as unavailable when computing the free CPUs, before to start the actual allocation.
|
||||
// This way, by construction all possible CPUs allocation whose number is multiple of the SMT level are now correct again.
|
||||
if numCPUs > availablePhysicalCPUs {
|
||||
return SMTAlignmentError{
|
||||
RequestedCPUs: numCPUs,
|
||||
CpusPerCore: CPUsPerCore,
|
||||
AvailablePhysicalCPUs: availablePhysicalCPUs,
|
||||
}
|
||||
}
|
||||
}
|
||||
if cpuset, ok := s.GetCPUSet(string(pod.UID), container.Name); ok {
|
||||
|
@ -36,6 +36,7 @@ type staticPolicyTest struct {
|
||||
description string
|
||||
topo *topology.CPUTopology
|
||||
numReservedCPUs int
|
||||
reservedCPUs *cpuset.CPUSet
|
||||
podUID string
|
||||
options map[string]string
|
||||
containerName string
|
||||
@ -196,17 +197,6 @@ func TestStaticPolicyAdd(t *testing.T) {
|
||||
// So we will permutate the options to ensure this holds true.
|
||||
|
||||
optionsInsensitiveTestCases := []staticPolicyTest{
|
||||
{
|
||||
description: "GuPodSingleCore, SingleSocketHT, ExpectError",
|
||||
topo: topoSingleSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{},
|
||||
stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7),
|
||||
pod: makePod("fakePod", "fakeContainer2", "8000m", "8000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, SingleSocketHT, ExpectAllocOneCore",
|
||||
topo: topoSingleSocketHT,
|
||||
@ -222,21 +212,6 @@ func TestStaticPolicyAdd(t *testing.T) {
|
||||
expCPUAlloc: true,
|
||||
expCSet: cpuset.New(1, 5),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, SingleSocketHT, ExpectSameAllocation",
|
||||
topo: topoSingleSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer3": cpuset.New(2, 3, 6, 7),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(0, 1, 4, 5),
|
||||
pod: makePod("fakePod", "fakeContainer3", "4000m", "4000m"),
|
||||
expErr: nil,
|
||||
expCPUAlloc: true,
|
||||
expCSet: cpuset.New(2, 3, 6, 7),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, DualSocketHT, ExpectAllocOneSocket",
|
||||
topo: topoDualSocketHT,
|
||||
@ -334,36 +309,6 @@ func TestStaticPolicyAdd(t *testing.T) {
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, SingleSocketHT, NoAllocExpectError",
|
||||
topo: topoSingleSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer100": cpuset.New(1, 2, 3, 4, 5, 6),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(0, 7),
|
||||
pod: makePod("fakePod", "fakeContainer5", "2000m", "2000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, DualSocketHT, NoAllocExpectError",
|
||||
topo: topoDualSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer100": cpuset.New(1, 2, 3),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(0, 4, 5, 6, 7, 8, 9, 10, 11),
|
||||
pod: makePod("fakePod", "fakeContainer5", "10000m", "10000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
// All the CPUs from Socket 0 are available. Some CPUs from each
|
||||
// Socket have been already assigned.
|
||||
@ -416,23 +361,6 @@ func TestStaticPolicyAdd(t *testing.T) {
|
||||
expCPUAlloc: true,
|
||||
expCSet: largeTopoSock1CPUSet.Union(cpuset.New(10, 34, 22, 47)),
|
||||
},
|
||||
{
|
||||
// Only 7 CPUs are available.
|
||||
// Pod requests 76 cores.
|
||||
// Error is expected since available CPUs are less than the request.
|
||||
description: "GuPodMultipleCores, topoQuadSocketFourWayHT, NoAlloc",
|
||||
topo: topoQuadSocketFourWayHT,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer100": largeTopoCPUSet.Difference(cpuset.New(10, 11, 53, 37, 55, 67, 52)),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(10, 11, 53, 37, 55, 67, 52),
|
||||
pod: makePod("fakePod", "fakeContainer5", "76000m", "76000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
}
|
||||
|
||||
// testcases for the default behaviour of the policy.
|
||||
@ -464,6 +392,79 @@ func TestStaticPolicyAdd(t *testing.T) {
|
||||
expCPUAlloc: true,
|
||||
expCSet: cpuset.New(10, 11, 53, 67, 52),
|
||||
},
|
||||
{
|
||||
description: "GuPodSingleCore, SingleSocketHT, ExpectError",
|
||||
topo: topoSingleSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{},
|
||||
stDefaultCPUSet: cpuset.New(0, 1, 2, 3, 4, 5, 6, 7),
|
||||
pod: makePod("fakePod", "fakeContainer2", "8000m", "8000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, SingleSocketHT, ExpectSameAllocation",
|
||||
topo: topoSingleSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer3": cpuset.New(2, 3, 6, 7),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(0, 1, 4, 5),
|
||||
pod: makePod("fakePod", "fakeContainer3", "4000m", "4000m"),
|
||||
expErr: nil,
|
||||
expCPUAlloc: true,
|
||||
expCSet: cpuset.New(2, 3, 6, 7),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, DualSocketHT, NoAllocExpectError",
|
||||
topo: topoDualSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer100": cpuset.New(1, 2, 3),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(0, 4, 5, 6, 7, 8, 9, 10, 11),
|
||||
pod: makePod("fakePod", "fakeContainer5", "10000m", "10000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodMultipleCores, SingleSocketHT, NoAllocExpectError",
|
||||
topo: topoSingleSocketHT,
|
||||
numReservedCPUs: 1,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer100": cpuset.New(1, 2, 3, 4, 5, 6),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(0, 7),
|
||||
pod: makePod("fakePod", "fakeContainer5", "2000m", "2000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
// Only 7 CPUs are available.
|
||||
// Pod requests 76 cores.
|
||||
// Error is expected since available CPUs are less than the request.
|
||||
description: "GuPodMultipleCores, topoQuadSocketFourWayHT, NoAlloc",
|
||||
topo: topoQuadSocketFourWayHT,
|
||||
stAssignments: state.ContainerCPUAssignments{
|
||||
"fakePod": map[string]cpuset.CPUSet{
|
||||
"fakeContainer100": largeTopoCPUSet.Difference(cpuset.New(10, 11, 53, 37, 55, 67, 52)),
|
||||
},
|
||||
},
|
||||
stDefaultCPUSet: cpuset.New(10, 11, 53, 37, 55, 67, 52),
|
||||
pod: makePod("fakePod", "fakeContainer5", "76000m", "76000m"),
|
||||
expErr: fmt.Errorf("not enough cpus available to satisfy request"),
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
}
|
||||
|
||||
// testcases for the FullPCPUsOnlyOption
|
||||
@ -497,6 +498,50 @@ func TestStaticPolicyAdd(t *testing.T) {
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodManyCores, topoDualSocketHT, ExpectDoNotAllocPartialCPU",
|
||||
topo: topoDualSocketHT,
|
||||
options: map[string]string{
|
||||
FullPCPUsOnlyOption: "true",
|
||||
},
|
||||
numReservedCPUs: 2,
|
||||
reservedCPUs: newCPUSetPtr(1, 6),
|
||||
stAssignments: state.ContainerCPUAssignments{},
|
||||
stDefaultCPUSet: cpuset.New(0, 2, 3, 4, 5, 7, 8, 9, 10, 11),
|
||||
pod: makePod("fakePod", "fakeContainerBug113537_1", "10000m", "10000m"),
|
||||
expErr: SMTAlignmentError{RequestedCPUs: 10, CpusPerCore: 2, AvailablePhysicalCPUs: 8},
|
||||
expCPUAlloc: false,
|
||||
expCSet: cpuset.New(),
|
||||
},
|
||||
{
|
||||
description: "GuPodManyCores, topoDualSocketHT, AutoReserve, ExpectAllocAllCPUs",
|
||||
topo: topoDualSocketHT,
|
||||
options: map[string]string{
|
||||
FullPCPUsOnlyOption: "true",
|
||||
},
|
||||
numReservedCPUs: 2,
|
||||
stAssignments: state.ContainerCPUAssignments{},
|
||||
stDefaultCPUSet: cpuset.New(1, 2, 3, 4, 5, 7, 8, 9, 10, 11),
|
||||
pod: makePod("fakePod", "fakeContainerBug113537_2", "10000m", "10000m"),
|
||||
expErr: nil,
|
||||
expCPUAlloc: true,
|
||||
expCSet: cpuset.New(1, 2, 3, 4, 5, 7, 8, 9, 10, 11),
|
||||
},
|
||||
{
|
||||
description: "GuPodManyCores, topoDualSocketHT, ExpectAllocAllCPUs",
|
||||
topo: topoDualSocketHT,
|
||||
options: map[string]string{
|
||||
FullPCPUsOnlyOption: "true",
|
||||
},
|
||||
numReservedCPUs: 2,
|
||||
reservedCPUs: newCPUSetPtr(0, 6),
|
||||
stAssignments: state.ContainerCPUAssignments{},
|
||||
stDefaultCPUSet: cpuset.New(1, 2, 3, 4, 5, 7, 8, 9, 10, 11),
|
||||
pod: makePod("fakePod", "fakeContainerBug113537_2", "10000m", "10000m"),
|
||||
expErr: nil,
|
||||
expCPUAlloc: true,
|
||||
expCSet: cpuset.New(1, 2, 3, 4, 5, 7, 8, 9, 10, 11),
|
||||
},
|
||||
}
|
||||
newNUMAAffinity := func(bits ...int) bitmask.BitMask {
|
||||
affinity, _ := bitmask.NewBitMask(bits...)
|
||||
@ -565,7 +610,11 @@ func runStaticPolicyTestCase(t *testing.T, testCase staticPolicyTest) {
|
||||
if testCase.topologyHint != nil {
|
||||
tm = topologymanager.NewFakeManagerWithHint(testCase.topologyHint)
|
||||
}
|
||||
policy, _ := NewStaticPolicy(testCase.topo, testCase.numReservedCPUs, cpuset.New(), tm, testCase.options)
|
||||
cpus := cpuset.New()
|
||||
if testCase.reservedCPUs != nil {
|
||||
cpus = testCase.reservedCPUs.Clone()
|
||||
}
|
||||
policy, _ := NewStaticPolicy(testCase.topo, testCase.numReservedCPUs, cpus, tm, testCase.options)
|
||||
|
||||
st := &mockState{
|
||||
assignments: testCase.stAssignments,
|
||||
@ -1093,3 +1142,8 @@ func TestStaticPolicyOptions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newCPUSetPtr(cpus ...int) *cpuset.CPUSet {
|
||||
ret := cpuset.New(cpus...)
|
||||
return &ret
|
||||
}
|
||||
|
@ -62,6 +62,34 @@ func (topo *CPUTopology) CPUsPerSocket() int {
|
||||
return topo.NumCPUs / topo.NumSockets
|
||||
}
|
||||
|
||||
// CPUCoreID returns the physical core ID which the given logical CPU
|
||||
// belongs to.
|
||||
func (topo *CPUTopology) CPUCoreID(cpu int) (int, error) {
|
||||
info, ok := topo.CPUDetails[cpu]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("unknown CPU ID: %d", cpu)
|
||||
}
|
||||
return info.CoreID, nil
|
||||
}
|
||||
|
||||
// CPUCoreID returns the socket ID which the given logical CPU belongs to.
|
||||
func (topo *CPUTopology) CPUSocketID(cpu int) (int, error) {
|
||||
info, ok := topo.CPUDetails[cpu]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("unknown CPU ID: %d", cpu)
|
||||
}
|
||||
return info.SocketID, nil
|
||||
}
|
||||
|
||||
// CPUCoreID returns the NUMA node ID which the given logical CPU belongs to.
|
||||
func (topo *CPUTopology) CPUNUMANodeID(cpu int) (int, error) {
|
||||
info, ok := topo.CPUDetails[cpu]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("unknown CPU ID: %d", cpu)
|
||||
}
|
||||
return info.NUMANodeID, nil
|
||||
}
|
||||
|
||||
// CPUInfo contains the NUMA, socket, and core IDs associated with a CPU.
|
||||
type CPUInfo struct {
|
||||
NUMANodeID int
|
||||
|
@ -923,3 +923,180 @@ func TestCPUDetailsCPUsInCores(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCPUCoreID(t *testing.T) {
|
||||
topoDualSocketHT := &CPUTopology{
|
||||
NumCPUs: 12,
|
||||
NumSockets: 2,
|
||||
NumCores: 6,
|
||||
CPUDetails: map[int]CPUInfo{
|
||||
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
|
||||
1: {CoreID: 1, SocketID: 1, NUMANodeID: 1},
|
||||
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
|
||||
3: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
|
||||
4: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
|
||||
5: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
|
||||
6: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
|
||||
7: {CoreID: 1, SocketID: 1, NUMANodeID: 1},
|
||||
8: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
|
||||
9: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
|
||||
10: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
|
||||
11: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
topo *CPUTopology
|
||||
id int
|
||||
want int
|
||||
wantErr bool
|
||||
}{{
|
||||
name: "Known Core ID",
|
||||
topo: topoDualSocketHT,
|
||||
id: 2,
|
||||
want: 2,
|
||||
}, {
|
||||
name: "Known Core ID (core sibling).",
|
||||
topo: topoDualSocketHT,
|
||||
id: 8,
|
||||
want: 2,
|
||||
}, {
|
||||
name: "Unknown Core ID.",
|
||||
topo: topoDualSocketHT,
|
||||
id: -2,
|
||||
want: -1,
|
||||
wantErr: true,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.topo.CPUCoreID(tt.id)
|
||||
gotErr := (err != nil)
|
||||
if gotErr != tt.wantErr {
|
||||
t.Errorf("CPUCoreID() returned err %v, want %v", gotErr, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CPUCoreID() returned %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCPUSocketID(t *testing.T) {
|
||||
topoDualSocketHT := &CPUTopology{
|
||||
NumCPUs: 12,
|
||||
NumSockets: 2,
|
||||
NumCores: 6,
|
||||
CPUDetails: map[int]CPUInfo{
|
||||
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
|
||||
1: {CoreID: 1, SocketID: 1, NUMANodeID: 1},
|
||||
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
|
||||
3: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
|
||||
4: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
|
||||
5: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
|
||||
6: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
|
||||
7: {CoreID: 1, SocketID: 1, NUMANodeID: 1},
|
||||
8: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
|
||||
9: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
|
||||
10: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
|
||||
11: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
topo *CPUTopology
|
||||
id int
|
||||
want int
|
||||
wantErr bool
|
||||
}{{
|
||||
name: "Known Core ID",
|
||||
topo: topoDualSocketHT,
|
||||
id: 3,
|
||||
want: 1,
|
||||
}, {
|
||||
name: "Known Core ID (core sibling).",
|
||||
topo: topoDualSocketHT,
|
||||
id: 9,
|
||||
want: 1,
|
||||
}, {
|
||||
name: "Unknown Core ID.",
|
||||
topo: topoDualSocketHT,
|
||||
id: 1000,
|
||||
want: -1,
|
||||
wantErr: true,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.topo.CPUSocketID(tt.id)
|
||||
gotErr := (err != nil)
|
||||
if gotErr != tt.wantErr {
|
||||
t.Errorf("CPUSocketID() returned err %v, want %v", gotErr, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CPUSocketID() returned %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCPUNUMANodeID(t *testing.T) {
|
||||
topoDualSocketHT := &CPUTopology{
|
||||
NumCPUs: 12,
|
||||
NumSockets: 2,
|
||||
NumCores: 6,
|
||||
CPUDetails: map[int]CPUInfo{
|
||||
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
|
||||
1: {CoreID: 1, SocketID: 1, NUMANodeID: 1},
|
||||
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
|
||||
3: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
|
||||
4: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
|
||||
5: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
|
||||
6: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
|
||||
7: {CoreID: 1, SocketID: 1, NUMANodeID: 1},
|
||||
8: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
|
||||
9: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
|
||||
10: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
|
||||
11: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
topo *CPUTopology
|
||||
id int
|
||||
want int
|
||||
wantErr bool
|
||||
}{{
|
||||
name: "Known Core ID",
|
||||
topo: topoDualSocketHT,
|
||||
id: 0,
|
||||
want: 0,
|
||||
}, {
|
||||
name: "Known Core ID (core sibling).",
|
||||
topo: topoDualSocketHT,
|
||||
id: 6,
|
||||
want: 0,
|
||||
}, {
|
||||
name: "Unknown Core ID.",
|
||||
topo: topoDualSocketHT,
|
||||
id: 1000,
|
||||
want: -1,
|
||||
wantErr: true,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.topo.CPUNUMANodeID(tt.id)
|
||||
gotErr := (err != nil)
|
||||
if gotErr != tt.wantErr {
|
||||
t.Errorf("CPUSocketID() returned err %v, want %v", gotErr, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CPUSocketID() returned %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user