Files
kubernetes/pkg/kubelet/cm/cpumanager/topology/topology_test.go
Francesco Romani 0e9b92090c node: cpumgr: stricter precheck for full-pcpus-only
In order to implement the `full-pcpus-only` cpumanager policy option,
we leverage the implementation of the algorithm which picks CPUs.
By design, CPUs are taken from the biggest chunk available (socket
or NUMA zone) to physical cores, down to single cores.

Leveraging this, if the requested CPU count is a multiple of the SMT
level (commonly 2), we're guaranteed that only full physical cores
will be taken.

The hidden assumption here is this holds true by construction iff
the user reserved CPUs (if any) considering full physical CPUs.
IOW, if the user did intentionally or mistakely reserve single threads
which are no core siblings[1], then the simple check we implemented
is not sufficient.

A easy example can probably outline this better. With this setup:

cores: [(0, 4), (1, 5), (2, 6), (3, 8)] (in parens: thread siblings).
SMT level: 2 (each tuple is 2 elements)
Reserved CPUs: 0,1 (explicit pick using `--reserved-cpus`)

A container then requests 6 cpus. full-pcpus-only check: 6 % 2 == 0. Passed.
The CPU allocator will take first full cores, (2,6) and (3,8), and will
then pick the remaining single CPUs. The allocation will succeed, but
it's incorrect.

We can fix this case with a stricter precheck.
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. Doing so, we fall back in the intended behavior, and
by construction all possible CPUs allocation whose number is multiple
of the SMT level are now correct again.

+++

[1] or thread siblings in the linux parlance, in any case:
hyperthread siblings of the same physical core

Signed-off-by: Francesco Romani <fromani@redhat.com>
2023-03-02 16:00:58 +01:00

1103 lines
32 KiB
Go

/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package topology
import (
"reflect"
"testing"
cadvisorapi "github.com/google/cadvisor/info/v1"
"github.com/google/go-cmp/cmp"
"k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
)
func Test_Discover(t *testing.T) {
tests := []struct {
name string
machineInfo cadvisorapi.MachineInfo
want *CPUTopology
wantErr bool
}{
{
name: "FailNumCores",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 0,
},
want: &CPUTopology{},
wantErr: true,
},
{
name: "OneSocketHT",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 8,
NumSockets: 1,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0, 4}},
{SocketID: 0, Id: 1, Threads: []int{1, 5}},
{SocketID: 0, Id: 2, Threads: []int{2, 6}},
{SocketID: 0, Id: 3, Threads: []int{3, 7}},
},
},
},
},
want: &CPUTopology{
NumCPUs: 8,
NumSockets: 1,
NumCores: 4,
NumNUMANodes: 1,
CPUDetails: map[int]CPUInfo{
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
1: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
3: {CoreID: 3, SocketID: 0, NUMANodeID: 0},
4: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
5: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
6: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
7: {CoreID: 3, SocketID: 0, NUMANodeID: 0},
},
},
wantErr: false,
},
{
// dual xeon gold 6230
name: "DualSocketMultiNumaPerSocketHT",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 80,
NumSockets: 2,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0, 40}},
{SocketID: 0, Id: 1, Threads: []int{1, 41}},
{SocketID: 0, Id: 2, Threads: []int{2, 42}},
{SocketID: 0, Id: 8, Threads: []int{3, 43}},
{SocketID: 0, Id: 9, Threads: []int{4, 44}},
{SocketID: 0, Id: 16, Threads: []int{5, 45}},
{SocketID: 0, Id: 17, Threads: []int{6, 46}},
{SocketID: 0, Id: 18, Threads: []int{7, 47}},
{SocketID: 0, Id: 24, Threads: []int{8, 48}},
{SocketID: 0, Id: 25, Threads: []int{9, 49}},
},
},
{Id: 1,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 3, Threads: []int{10, 50}},
{SocketID: 0, Id: 4, Threads: []int{11, 51}},
{SocketID: 0, Id: 10, Threads: []int{12, 52}},
{SocketID: 0, Id: 11, Threads: []int{13, 53}},
{SocketID: 0, Id: 12, Threads: []int{14, 54}},
{SocketID: 0, Id: 19, Threads: []int{15, 55}},
{SocketID: 0, Id: 20, Threads: []int{16, 56}},
{SocketID: 0, Id: 26, Threads: []int{17, 57}},
{SocketID: 0, Id: 27, Threads: []int{18, 58}},
{SocketID: 0, Id: 28, Threads: []int{19, 59}},
},
},
{Id: 2,
Cores: []cadvisorapi.Core{
{SocketID: 1, Id: 0, Threads: []int{20, 60}},
{SocketID: 1, Id: 1, Threads: []int{21, 61}},
{SocketID: 1, Id: 2, Threads: []int{22, 62}},
{SocketID: 1, Id: 8, Threads: []int{23, 63}},
{SocketID: 1, Id: 9, Threads: []int{24, 64}},
{SocketID: 1, Id: 16, Threads: []int{25, 65}},
{SocketID: 1, Id: 17, Threads: []int{26, 66}},
{SocketID: 1, Id: 18, Threads: []int{27, 67}},
{SocketID: 1, Id: 24, Threads: []int{28, 68}},
{SocketID: 1, Id: 25, Threads: []int{29, 69}},
},
},
{Id: 3,
Cores: []cadvisorapi.Core{
{SocketID: 1, Id: 3, Threads: []int{30, 70}},
{SocketID: 1, Id: 4, Threads: []int{31, 71}},
{SocketID: 1, Id: 10, Threads: []int{32, 72}},
{SocketID: 1, Id: 11, Threads: []int{33, 73}},
{SocketID: 1, Id: 12, Threads: []int{34, 74}},
{SocketID: 1, Id: 19, Threads: []int{35, 75}},
{SocketID: 1, Id: 20, Threads: []int{36, 76}},
{SocketID: 1, Id: 26, Threads: []int{37, 77}},
{SocketID: 1, Id: 27, Threads: []int{38, 78}},
{SocketID: 1, Id: 28, Threads: []int{39, 79}},
},
},
},
},
want: &CPUTopology{
NumCPUs: 80,
NumSockets: 2,
NumCores: 40,
NumNUMANodes: 4,
CPUDetails: map[int]CPUInfo{
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
1: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
3: {CoreID: 3, SocketID: 0, NUMANodeID: 0},
4: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
5: {CoreID: 5, SocketID: 0, NUMANodeID: 0},
6: {CoreID: 6, SocketID: 0, NUMANodeID: 0},
7: {CoreID: 7, SocketID: 0, NUMANodeID: 0},
8: {CoreID: 8, SocketID: 0, NUMANodeID: 0},
9: {CoreID: 9, SocketID: 0, NUMANodeID: 0},
10: {CoreID: 10, SocketID: 0, NUMANodeID: 1},
11: {CoreID: 11, SocketID: 0, NUMANodeID: 1},
12: {CoreID: 12, SocketID: 0, NUMANodeID: 1},
13: {CoreID: 13, SocketID: 0, NUMANodeID: 1},
14: {CoreID: 14, SocketID: 0, NUMANodeID: 1},
15: {CoreID: 15, SocketID: 0, NUMANodeID: 1},
16: {CoreID: 16, SocketID: 0, NUMANodeID: 1},
17: {CoreID: 17, SocketID: 0, NUMANodeID: 1},
18: {CoreID: 18, SocketID: 0, NUMANodeID: 1},
19: {CoreID: 19, SocketID: 0, NUMANodeID: 1},
20: {CoreID: 20, SocketID: 1, NUMANodeID: 2},
21: {CoreID: 21, SocketID: 1, NUMANodeID: 2},
22: {CoreID: 22, SocketID: 1, NUMANodeID: 2},
23: {CoreID: 23, SocketID: 1, NUMANodeID: 2},
24: {CoreID: 24, SocketID: 1, NUMANodeID: 2},
25: {CoreID: 25, SocketID: 1, NUMANodeID: 2},
26: {CoreID: 26, SocketID: 1, NUMANodeID: 2},
27: {CoreID: 27, SocketID: 1, NUMANodeID: 2},
28: {CoreID: 28, SocketID: 1, NUMANodeID: 2},
29: {CoreID: 29, SocketID: 1, NUMANodeID: 2},
30: {CoreID: 30, SocketID: 1, NUMANodeID: 3},
31: {CoreID: 31, SocketID: 1, NUMANodeID: 3},
32: {CoreID: 32, SocketID: 1, NUMANodeID: 3},
33: {CoreID: 33, SocketID: 1, NUMANodeID: 3},
34: {CoreID: 34, SocketID: 1, NUMANodeID: 3},
35: {CoreID: 35, SocketID: 1, NUMANodeID: 3},
36: {CoreID: 36, SocketID: 1, NUMANodeID: 3},
37: {CoreID: 37, SocketID: 1, NUMANodeID: 3},
38: {CoreID: 38, SocketID: 1, NUMANodeID: 3},
39: {CoreID: 39, SocketID: 1, NUMANodeID: 3},
40: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
41: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
42: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
43: {CoreID: 3, SocketID: 0, NUMANodeID: 0},
44: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
45: {CoreID: 5, SocketID: 0, NUMANodeID: 0},
46: {CoreID: 6, SocketID: 0, NUMANodeID: 0},
47: {CoreID: 7, SocketID: 0, NUMANodeID: 0},
48: {CoreID: 8, SocketID: 0, NUMANodeID: 0},
49: {CoreID: 9, SocketID: 0, NUMANodeID: 0},
50: {CoreID: 10, SocketID: 0, NUMANodeID: 1},
51: {CoreID: 11, SocketID: 0, NUMANodeID: 1},
52: {CoreID: 12, SocketID: 0, NUMANodeID: 1},
53: {CoreID: 13, SocketID: 0, NUMANodeID: 1},
54: {CoreID: 14, SocketID: 0, NUMANodeID: 1},
55: {CoreID: 15, SocketID: 0, NUMANodeID: 1},
56: {CoreID: 16, SocketID: 0, NUMANodeID: 1},
57: {CoreID: 17, SocketID: 0, NUMANodeID: 1},
58: {CoreID: 18, SocketID: 0, NUMANodeID: 1},
59: {CoreID: 19, SocketID: 0, NUMANodeID: 1},
60: {CoreID: 20, SocketID: 1, NUMANodeID: 2},
61: {CoreID: 21, SocketID: 1, NUMANodeID: 2},
62: {CoreID: 22, SocketID: 1, NUMANodeID: 2},
63: {CoreID: 23, SocketID: 1, NUMANodeID: 2},
64: {CoreID: 24, SocketID: 1, NUMANodeID: 2},
65: {CoreID: 25, SocketID: 1, NUMANodeID: 2},
66: {CoreID: 26, SocketID: 1, NUMANodeID: 2},
67: {CoreID: 27, SocketID: 1, NUMANodeID: 2},
68: {CoreID: 28, SocketID: 1, NUMANodeID: 2},
69: {CoreID: 29, SocketID: 1, NUMANodeID: 2},
70: {CoreID: 30, SocketID: 1, NUMANodeID: 3},
71: {CoreID: 31, SocketID: 1, NUMANodeID: 3},
72: {CoreID: 32, SocketID: 1, NUMANodeID: 3},
73: {CoreID: 33, SocketID: 1, NUMANodeID: 3},
74: {CoreID: 34, SocketID: 1, NUMANodeID: 3},
75: {CoreID: 35, SocketID: 1, NUMANodeID: 3},
76: {CoreID: 36, SocketID: 1, NUMANodeID: 3},
77: {CoreID: 37, SocketID: 1, NUMANodeID: 3},
78: {CoreID: 38, SocketID: 1, NUMANodeID: 3},
79: {CoreID: 39, SocketID: 1, NUMANodeID: 3},
},
},
wantErr: false,
},
{
// FAKE Topology from dual xeon gold 6230
// (see: dual xeon gold 6230).
// We flip NUMA cells and Sockets to exercise the code.
// TODO(fromanirh): replace with a real-world topology
// once we find a suitable one.
// Note: this is a fake topology. Thus, there is not a "correct"
// representation. This one was created following the these concepts:
// 1. be internally consistent (most important rule)
// 2. be as close as possible as existing HW topologies
// 3. if possible, minimize chances wrt existing HW topologies.
name: "DualNumaMultiSocketPerNumaHT",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 80,
NumSockets: 4,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0, 40}},
{SocketID: 0, Id: 1, Threads: []int{1, 41}},
{SocketID: 0, Id: 2, Threads: []int{2, 42}},
{SocketID: 0, Id: 8, Threads: []int{3, 43}},
{SocketID: 0, Id: 9, Threads: []int{4, 44}},
{SocketID: 0, Id: 16, Threads: []int{5, 45}},
{SocketID: 0, Id: 17, Threads: []int{6, 46}},
{SocketID: 0, Id: 18, Threads: []int{7, 47}},
{SocketID: 0, Id: 24, Threads: []int{8, 48}},
{SocketID: 0, Id: 25, Threads: []int{9, 49}},
{SocketID: 1, Id: 3, Threads: []int{10, 50}},
{SocketID: 1, Id: 4, Threads: []int{11, 51}},
{SocketID: 1, Id: 10, Threads: []int{12, 52}},
{SocketID: 1, Id: 11, Threads: []int{13, 53}},
{SocketID: 1, Id: 12, Threads: []int{14, 54}},
{SocketID: 1, Id: 19, Threads: []int{15, 55}},
{SocketID: 1, Id: 20, Threads: []int{16, 56}},
{SocketID: 1, Id: 26, Threads: []int{17, 57}},
{SocketID: 1, Id: 27, Threads: []int{18, 58}},
{SocketID: 1, Id: 28, Threads: []int{19, 59}},
},
},
{Id: 1,
Cores: []cadvisorapi.Core{
{SocketID: 2, Id: 0, Threads: []int{20, 60}},
{SocketID: 2, Id: 1, Threads: []int{21, 61}},
{SocketID: 2, Id: 2, Threads: []int{22, 62}},
{SocketID: 2, Id: 8, Threads: []int{23, 63}},
{SocketID: 2, Id: 9, Threads: []int{24, 64}},
{SocketID: 2, Id: 16, Threads: []int{25, 65}},
{SocketID: 2, Id: 17, Threads: []int{26, 66}},
{SocketID: 2, Id: 18, Threads: []int{27, 67}},
{SocketID: 2, Id: 24, Threads: []int{28, 68}},
{SocketID: 2, Id: 25, Threads: []int{29, 69}},
{SocketID: 3, Id: 3, Threads: []int{30, 70}},
{SocketID: 3, Id: 4, Threads: []int{31, 71}},
{SocketID: 3, Id: 10, Threads: []int{32, 72}},
{SocketID: 3, Id: 11, Threads: []int{33, 73}},
{SocketID: 3, Id: 12, Threads: []int{34, 74}},
{SocketID: 3, Id: 19, Threads: []int{35, 75}},
{SocketID: 3, Id: 20, Threads: []int{36, 76}},
{SocketID: 3, Id: 26, Threads: []int{37, 77}},
{SocketID: 3, Id: 27, Threads: []int{38, 78}},
{SocketID: 3, Id: 28, Threads: []int{39, 79}},
},
},
},
},
want: &CPUTopology{
NumCPUs: 80,
NumSockets: 4,
NumCores: 40,
NumNUMANodes: 2,
CPUDetails: map[int]CPUInfo{
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
1: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
3: {CoreID: 3, SocketID: 0, NUMANodeID: 0},
4: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
5: {CoreID: 5, SocketID: 0, NUMANodeID: 0},
6: {CoreID: 6, SocketID: 0, NUMANodeID: 0},
7: {CoreID: 7, SocketID: 0, NUMANodeID: 0},
8: {CoreID: 8, SocketID: 0, NUMANodeID: 0},
9: {CoreID: 9, SocketID: 0, NUMANodeID: 0},
10: {CoreID: 10, SocketID: 1, NUMANodeID: 0},
11: {CoreID: 11, SocketID: 1, NUMANodeID: 0},
12: {CoreID: 12, SocketID: 1, NUMANodeID: 0},
13: {CoreID: 13, SocketID: 1, NUMANodeID: 0},
14: {CoreID: 14, SocketID: 1, NUMANodeID: 0},
15: {CoreID: 15, SocketID: 1, NUMANodeID: 0},
16: {CoreID: 16, SocketID: 1, NUMANodeID: 0},
17: {CoreID: 17, SocketID: 1, NUMANodeID: 0},
18: {CoreID: 18, SocketID: 1, NUMANodeID: 0},
19: {CoreID: 19, SocketID: 1, NUMANodeID: 0},
20: {CoreID: 20, SocketID: 2, NUMANodeID: 1},
21: {CoreID: 21, SocketID: 2, NUMANodeID: 1},
22: {CoreID: 22, SocketID: 2, NUMANodeID: 1},
23: {CoreID: 23, SocketID: 2, NUMANodeID: 1},
24: {CoreID: 24, SocketID: 2, NUMANodeID: 1},
25: {CoreID: 25, SocketID: 2, NUMANodeID: 1},
26: {CoreID: 26, SocketID: 2, NUMANodeID: 1},
27: {CoreID: 27, SocketID: 2, NUMANodeID: 1},
28: {CoreID: 28, SocketID: 2, NUMANodeID: 1},
29: {CoreID: 29, SocketID: 2, NUMANodeID: 1},
30: {CoreID: 30, SocketID: 3, NUMANodeID: 1},
31: {CoreID: 31, SocketID: 3, NUMANodeID: 1},
32: {CoreID: 32, SocketID: 3, NUMANodeID: 1},
33: {CoreID: 33, SocketID: 3, NUMANodeID: 1},
34: {CoreID: 34, SocketID: 3, NUMANodeID: 1},
35: {CoreID: 35, SocketID: 3, NUMANodeID: 1},
36: {CoreID: 36, SocketID: 3, NUMANodeID: 1},
37: {CoreID: 37, SocketID: 3, NUMANodeID: 1},
38: {CoreID: 38, SocketID: 3, NUMANodeID: 1},
39: {CoreID: 39, SocketID: 3, NUMANodeID: 1},
40: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
41: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
42: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
43: {CoreID: 3, SocketID: 0, NUMANodeID: 0},
44: {CoreID: 4, SocketID: 0, NUMANodeID: 0},
45: {CoreID: 5, SocketID: 0, NUMANodeID: 0},
46: {CoreID: 6, SocketID: 0, NUMANodeID: 0},
47: {CoreID: 7, SocketID: 0, NUMANodeID: 0},
48: {CoreID: 8, SocketID: 0, NUMANodeID: 0},
49: {CoreID: 9, SocketID: 0, NUMANodeID: 0},
50: {CoreID: 10, SocketID: 1, NUMANodeID: 0},
51: {CoreID: 11, SocketID: 1, NUMANodeID: 0},
52: {CoreID: 12, SocketID: 1, NUMANodeID: 0},
53: {CoreID: 13, SocketID: 1, NUMANodeID: 0},
54: {CoreID: 14, SocketID: 1, NUMANodeID: 0},
55: {CoreID: 15, SocketID: 1, NUMANodeID: 0},
56: {CoreID: 16, SocketID: 1, NUMANodeID: 0},
57: {CoreID: 17, SocketID: 1, NUMANodeID: 0},
58: {CoreID: 18, SocketID: 1, NUMANodeID: 0},
59: {CoreID: 19, SocketID: 1, NUMANodeID: 0},
60: {CoreID: 20, SocketID: 2, NUMANodeID: 1},
61: {CoreID: 21, SocketID: 2, NUMANodeID: 1},
62: {CoreID: 22, SocketID: 2, NUMANodeID: 1},
63: {CoreID: 23, SocketID: 2, NUMANodeID: 1},
64: {CoreID: 24, SocketID: 2, NUMANodeID: 1},
65: {CoreID: 25, SocketID: 2, NUMANodeID: 1},
66: {CoreID: 26, SocketID: 2, NUMANodeID: 1},
67: {CoreID: 27, SocketID: 2, NUMANodeID: 1},
68: {CoreID: 28, SocketID: 2, NUMANodeID: 1},
69: {CoreID: 29, SocketID: 2, NUMANodeID: 1},
70: {CoreID: 30, SocketID: 3, NUMANodeID: 1},
71: {CoreID: 31, SocketID: 3, NUMANodeID: 1},
72: {CoreID: 32, SocketID: 3, NUMANodeID: 1},
73: {CoreID: 33, SocketID: 3, NUMANodeID: 1},
74: {CoreID: 34, SocketID: 3, NUMANodeID: 1},
75: {CoreID: 35, SocketID: 3, NUMANodeID: 1},
76: {CoreID: 36, SocketID: 3, NUMANodeID: 1},
77: {CoreID: 37, SocketID: 3, NUMANodeID: 1},
78: {CoreID: 38, SocketID: 3, NUMANodeID: 1},
79: {CoreID: 39, SocketID: 3, NUMANodeID: 1},
},
},
wantErr: false,
},
{
name: "DualSocketNoHT",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 4,
NumSockets: 2,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0}},
{SocketID: 0, Id: 2, Threads: []int{2}},
},
},
{Id: 1,
Cores: []cadvisorapi.Core{
{SocketID: 1, Id: 1, Threads: []int{1}},
{SocketID: 1, Id: 3, Threads: []int{3}},
},
},
},
},
want: &CPUTopology{
NumCPUs: 4,
NumSockets: 2,
NumCores: 4,
NumNUMANodes: 2,
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},
},
},
wantErr: false,
},
{
name: "DualSocketHT - non unique Core'ID's",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 12,
NumSockets: 2,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0, 6}},
{SocketID: 0, Id: 1, Threads: []int{1, 7}},
{SocketID: 0, Id: 2, Threads: []int{2, 8}},
},
},
{Id: 1,
Cores: []cadvisorapi.Core{
{SocketID: 1, Id: 0, Threads: []int{3, 9}},
{SocketID: 1, Id: 1, Threads: []int{4, 10}},
{SocketID: 1, Id: 2, Threads: []int{5, 11}},
},
},
},
},
want: &CPUTopology{
NumCPUs: 12,
NumSockets: 2,
NumCores: 6,
NumNUMANodes: 2,
CPUDetails: map[int]CPUInfo{
0: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
1: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
2: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
3: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
4: {CoreID: 4, SocketID: 1, NUMANodeID: 1},
5: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
6: {CoreID: 0, SocketID: 0, NUMANodeID: 0},
7: {CoreID: 1, SocketID: 0, NUMANodeID: 0},
8: {CoreID: 2, SocketID: 0, NUMANodeID: 0},
9: {CoreID: 3, SocketID: 1, NUMANodeID: 1},
10: {CoreID: 4, SocketID: 1, NUMANodeID: 1},
11: {CoreID: 5, SocketID: 1, NUMANodeID: 1},
},
},
wantErr: false,
},
{
name: "OneSocketHT fail",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 8,
NumSockets: 1,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0, 4}},
{SocketID: 0, Id: 1, Threads: []int{1, 5}},
{SocketID: 0, Id: 2, Threads: []int{2, 2}}, // Wrong case - should fail here
{SocketID: 0, Id: 3, Threads: []int{3, 7}},
},
},
},
},
want: &CPUTopology{},
wantErr: true,
},
{
name: "OneSocketHT fail",
machineInfo: cadvisorapi.MachineInfo{
NumCores: 8,
NumSockets: 1,
Topology: []cadvisorapi.Node{
{Id: 0,
Cores: []cadvisorapi.Core{
{SocketID: 0, Id: 0, Threads: []int{0, 4}},
{SocketID: 0, Id: 1, Threads: []int{1, 5}},
{SocketID: 0, Id: 2, Threads: []int{2, 6}},
{SocketID: 0, Id: 3, Threads: []int{}}, // Wrong case - should fail here
},
},
},
},
want: &CPUTopology{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Discover(&tt.machineInfo)
if err != nil {
if tt.wantErr {
t.Logf("Discover() expected error = %v", err)
} else {
t.Errorf("Discover() error = %v, wantErr %v", err, tt.wantErr)
}
return
}
if diff := cmp.Diff(got, tt.want); diff != "" {
t.Errorf("Discover() = %v, want %v diff=%s", got, tt.want, diff)
}
})
}
}
func TestCPUDetailsKeepOnly(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {},
1: {},
2: {},
}
tests := []struct {
name string
cpus cpuset.CPUSet
want CPUDetails
}{{
name: "cpus is in CPUDetails.",
cpus: cpuset.New(0, 1),
want: map[int]CPUInfo{
0: {},
1: {},
},
}, {
name: "cpus is not in CPUDetails.",
cpus: cpuset.New(3),
want: CPUDetails{},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.KeepOnly(tt.cpus)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("KeepOnly() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsNUMANodes(t *testing.T) {
tests := []struct {
name string
details CPUDetails
want cpuset.CPUSet
}{{
name: "Get CPUset of NUMANode IDs",
details: map[int]CPUInfo{
0: {NUMANodeID: 0},
1: {NUMANodeID: 0},
2: {NUMANodeID: 1},
3: {NUMANodeID: 1},
},
want: cpuset.New(0, 1),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.details.NUMANodes()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NUMANodes() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsNUMANodesInSockets(t *testing.T) {
var details1 CPUDetails
details1 = map[int]CPUInfo{
0: {SocketID: 0, NUMANodeID: 0},
1: {SocketID: 1, NUMANodeID: 0},
2: {SocketID: 2, NUMANodeID: 1},
3: {SocketID: 3, NUMANodeID: 1},
}
// poorly designed mainboards
var details2 CPUDetails
details2 = map[int]CPUInfo{
0: {SocketID: 0, NUMANodeID: 0},
1: {SocketID: 0, NUMANodeID: 1},
2: {SocketID: 1, NUMANodeID: 2},
3: {SocketID: 1, NUMANodeID: 3},
}
tests := []struct {
name string
details CPUDetails
ids []int
want cpuset.CPUSet
}{{
name: "Socket IDs is in CPUDetails.",
details: details1,
ids: []int{0, 1, 2},
want: cpuset.New(0, 1),
}, {
name: "Socket IDs is not in CPUDetails.",
details: details1,
ids: []int{4},
want: cpuset.New(),
}, {
name: "Socket IDs is in CPUDetails. (poorly designed mainboards)",
details: details2,
ids: []int{0},
want: cpuset.New(0, 1),
}, {
name: "Socket IDs is not in CPUDetails. (poorly designed mainboards)",
details: details2,
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.details.NUMANodesInSockets(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NUMANodesInSockets() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsSockets(t *testing.T) {
tests := []struct {
name string
details CPUDetails
want cpuset.CPUSet
}{{
name: "Get CPUset of Socket IDs",
details: map[int]CPUInfo{
0: {SocketID: 0},
1: {SocketID: 0},
2: {SocketID: 1},
3: {SocketID: 1},
},
want: cpuset.New(0, 1),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.details.Sockets()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Sockets() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCPUsInSockets(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {SocketID: 0},
1: {SocketID: 0},
2: {SocketID: 1},
3: {SocketID: 2},
}
tests := []struct {
name string
ids []int
want cpuset.CPUSet
}{{
name: "Socket IDs is in CPUDetails.",
ids: []int{0, 1},
want: cpuset.New(0, 1, 2),
}, {
name: "Socket IDs is not in CPUDetails.",
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.CPUsInSockets(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CPUsInSockets() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsSocketsInNUMANodes(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {NUMANodeID: 0, SocketID: 0},
1: {NUMANodeID: 0, SocketID: 1},
2: {NUMANodeID: 1, SocketID: 2},
3: {NUMANodeID: 2, SocketID: 3},
}
tests := []struct {
name string
ids []int
want cpuset.CPUSet
}{{
name: "NUMANodes IDs is in CPUDetails.",
ids: []int{0, 1},
want: cpuset.New(0, 1, 2),
}, {
name: "NUMANodes IDs is not in CPUDetails.",
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.SocketsInNUMANodes(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("SocketsInNUMANodes() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCores(t *testing.T) {
tests := []struct {
name string
details CPUDetails
want cpuset.CPUSet
}{{
name: "Get CPUset of Cores",
details: map[int]CPUInfo{
0: {CoreID: 0},
1: {CoreID: 0},
2: {CoreID: 1},
3: {CoreID: 1},
},
want: cpuset.New(0, 1),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.details.Cores()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Cores() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCoresInNUMANodes(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {NUMANodeID: 0, CoreID: 0},
1: {NUMANodeID: 0, CoreID: 1},
2: {NUMANodeID: 1, CoreID: 2},
3: {NUMANodeID: 2, CoreID: 3},
}
tests := []struct {
name string
ids []int
want cpuset.CPUSet
}{{
name: "NUMANodes IDs is in CPUDetails.",
ids: []int{0, 1},
want: cpuset.New(0, 1, 2),
}, {
name: "NUMANodes IDs is not in CPUDetails.",
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.CoresInNUMANodes(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CoresInNUMANodes() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCoresInSockets(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {SocketID: 0, CoreID: 0},
1: {SocketID: 0, CoreID: 1},
2: {SocketID: 1, CoreID: 2},
3: {SocketID: 2, CoreID: 3},
}
tests := []struct {
name string
ids []int
want cpuset.CPUSet
}{{
name: "Socket IDs is in CPUDetails.",
ids: []int{0, 1},
want: cpuset.New(0, 1, 2),
}, {
name: "Socket IDs is not in CPUDetails.",
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.CoresInSockets(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CoresInSockets() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCPUs(t *testing.T) {
tests := []struct {
name string
details CPUDetails
want cpuset.CPUSet
}{{
name: "Get CPUset of CPUs",
details: map[int]CPUInfo{
0: {},
1: {},
},
want: cpuset.New(0, 1),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.details.CPUs()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CPUs() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCPUsInNUMANodes(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {NUMANodeID: 0},
1: {NUMANodeID: 0},
2: {NUMANodeID: 1},
3: {NUMANodeID: 2},
}
tests := []struct {
name string
ids []int
want cpuset.CPUSet
}{{
name: "NUMANode IDs is in CPUDetails.",
ids: []int{0, 1},
want: cpuset.New(0, 1, 2),
}, {
name: "NUMANode IDs is not in CPUDetails.",
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.CPUsInNUMANodes(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CPUsInNUMANodes() = %v, want %v", got, tt.want)
}
})
}
}
func TestCPUDetailsCPUsInCores(t *testing.T) {
var details CPUDetails
details = map[int]CPUInfo{
0: {CoreID: 0},
1: {CoreID: 0},
2: {CoreID: 1},
3: {CoreID: 2},
}
tests := []struct {
name string
ids []int
want cpuset.CPUSet
}{{
name: "Core IDs is in CPUDetails.",
ids: []int{0, 1},
want: cpuset.New(0, 1, 2),
}, {
name: "Core IDs is not in CPUDetails.",
ids: []int{3},
want: cpuset.New(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := details.CPUsInCores(tt.ids...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CPUsInCores() = %v, want %v", got, tt.want)
}
})
}
}
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)
}
})
}
}