From 242439c9d702834d502f88ea04c7e9180139c7f7 Mon Sep 17 00:00:00 2001 From: Szymon Scharmach Date: Tue, 22 Aug 2017 23:47:55 -0700 Subject: [PATCH] Add topology helper and tests to cpumanager. --- pkg/kubelet/cm/cpumanager/topology/BUILD | 18 +- .../cm/cpumanager/topology/topology.go | 169 ++++++++++++++++++ .../cm/cpumanager/topology/topology_test.go | 123 +++++++++++++ 3 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 pkg/kubelet/cm/cpumanager/topology/topology.go create mode 100644 pkg/kubelet/cm/cpumanager/topology/topology_test.go diff --git a/pkg/kubelet/cm/cpumanager/topology/BUILD b/pkg/kubelet/cm/cpumanager/topology/BUILD index df7277afab5..a2a34307d71 100644 --- a/pkg/kubelet/cm/cpumanager/topology/BUILD +++ b/pkg/kubelet/cm/cpumanager/topology/BUILD @@ -1,9 +1,16 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = ["doc.go"], + srcs = [ + "doc.go", + "topology.go", + ], visibility = ["//visibility:public"], + deps = [ + "//pkg/kubelet/cm/cpuset:go_default_library", + "//vendor/github.com/google/cadvisor/info/v1:go_default_library", + ], ) filegroup( @@ -19,3 +26,10 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_test( + name = "go_default_test", + srcs = ["topology_test.go"], + library = ":go_default_library", + deps = ["//vendor/github.com/google/cadvisor/info/v1:go_default_library"], +) diff --git a/pkg/kubelet/cm/cpumanager/topology/topology.go b/pkg/kubelet/cm/cpumanager/topology/topology.go new file mode 100644 index 00000000000..a03cddbad8a --- /dev/null +++ b/pkg/kubelet/cm/cpumanager/topology/topology.go @@ -0,0 +1,169 @@ +/* +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 ( + "fmt" + + cadvisorapi "github.com/google/cadvisor/info/v1" + "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" +) + +// CPUDetails is a map from CPU ID to Core ID and Socket ID. +type CPUDetails map[int]CPUInfo + +// CPUTopology contains details of node cpu, where : +// CPU - logical CPU, cadvisor - thread +// Core - physical CPU, cadvisor - Core +// Socket - socket, cadvisor - Node +type CPUTopology struct { + NumCPUs int + NumCores int + NumSockets int + CPUDetails CPUDetails +} + +// CPUsPerCore returns the number of logical CPUs are associated with +// each core. +func (topo *CPUTopology) CPUsPerCore() int { + if topo.NumCores == 0 { + return 0 + } + return topo.NumCPUs / topo.NumCores +} + +// CPUsPerSocket returns the number of logical CPUs are associated with +// each socket. +func (topo *CPUTopology) CPUsPerSocket() int { + if topo.NumSockets == 0 { + return 0 + } + return topo.NumCPUs / topo.NumSockets +} + +// CPUInfo contains the socket and core IDs associated with a CPU. +type CPUInfo struct { + SocketID int + CoreID int +} + +// KeepOnly returns a new CPUDetails object with only the supplied cpus. +func (d CPUDetails) KeepOnly(cpus cpuset.CPUSet) CPUDetails { + result := CPUDetails{} + for cpu, info := range d { + if cpus.Contains(cpu) { + result[cpu] = info + } + } + return result +} + +// Sockets returns all of the socket IDs associated with the CPUs in this +// CPUDetails. +func (d CPUDetails) Sockets() cpuset.CPUSet { + b := cpuset.NewBuilder() + for _, info := range d { + b.Add(info.SocketID) + } + return b.Result() +} + +// CPUsInSocket returns all of the logical CPU IDs associated with the +// given socket ID in this CPUDetails. +func (d CPUDetails) CPUsInSocket(id int) cpuset.CPUSet { + b := cpuset.NewBuilder() + for cpu, info := range d { + if info.SocketID == id { + b.Add(cpu) + } + } + return b.Result() +} + +// Cores returns all of the core IDs associated with the CPUs in this +// CPUDetails. +func (d CPUDetails) Cores() cpuset.CPUSet { + b := cpuset.NewBuilder() + for _, info := range d { + b.Add(info.CoreID) + } + return b.Result() +} + +// CoresInSocket returns all of the core IDs associated with the given +// socket ID in this CPUDetails. +func (d CPUDetails) CoresInSocket(id int) cpuset.CPUSet { + b := cpuset.NewBuilder() + for _, info := range d { + if info.SocketID == id { + b.Add(info.CoreID) + } + } + return b.Result() +} + +// CPUs returns all of the logical CPU IDs in this CPUDetails. +func (d CPUDetails) CPUs() cpuset.CPUSet { + b := cpuset.NewBuilder() + for cpuID := range d { + b.Add(cpuID) + } + return b.Result() +} + +// CPUsInCore returns all of the logical CPU IDs associated with the +// given core ID in this CPUDetails. +func (d CPUDetails) CPUsInCore(id int) cpuset.CPUSet { + b := cpuset.NewBuilder() + for cpu, info := range d { + if info.CoreID == id { + b.Add(cpu) + } + } + return b.Result() +} + +// Discover returns CPUTopology based on cadvisor node info +func Discover(machineInfo *cadvisorapi.MachineInfo) (*CPUTopology, error) { + + if machineInfo.NumCores == 0 { + return nil, fmt.Errorf("could not detect number of cpus") + } + + CPUDetails := CPUDetails{} + + numCPUs := machineInfo.NumCores + numPhysicalCores := 0 + for _, socket := range machineInfo.Topology { + numPhysicalCores += len(socket.Cores) + for _, core := range socket.Cores { + for _, cpu := range core.Threads { + CPUDetails[cpu] = CPUInfo{ + CoreID: core.Id, + SocketID: socket.Id, + } + } + } + } + + return &CPUTopology{ + NumCPUs: numCPUs, + NumSockets: len(machineInfo.Topology), + NumCores: numPhysicalCores, + CPUDetails: CPUDetails, + }, nil +} diff --git a/pkg/kubelet/cm/cpumanager/topology/topology_test.go b/pkg/kubelet/cm/cpumanager/topology/topology_test.go new file mode 100644 index 00000000000..ef7e83fd100 --- /dev/null +++ b/pkg/kubelet/cm/cpumanager/topology/topology_test.go @@ -0,0 +1,123 @@ +/* +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" +) + +func Test_Discover(t *testing.T) { + + tests := []struct { + name string + args *cadvisorapi.MachineInfo + want *CPUTopology + wantErr bool + }{ + { + name: "FailNumCores", + args: &cadvisorapi.MachineInfo{ + NumCores: 0, + }, + want: &CPUTopology{}, + wantErr: true, + }, + { + name: "OneSocketHT", + args: &cadvisorapi.MachineInfo{ + NumCores: 8, + Topology: []cadvisorapi.Node{ + {Id: 0, + Cores: []cadvisorapi.Core{ + {Id: 0, Threads: []int{0, 4}}, + {Id: 1, Threads: []int{1, 5}}, + {Id: 2, Threads: []int{2, 6}}, + {Id: 3, Threads: []int{3, 7}}, + }, + }, + }, + }, + want: &CPUTopology{ + NumCPUs: 8, + NumSockets: 1, + NumCores: 4, + CPUDetails: map[int]CPUInfo{ + 0: {CoreID: 0, SocketID: 0}, + 1: {CoreID: 1, SocketID: 0}, + 2: {CoreID: 2, SocketID: 0}, + 3: {CoreID: 3, SocketID: 0}, + 4: {CoreID: 0, SocketID: 0}, + 5: {CoreID: 1, SocketID: 0}, + 6: {CoreID: 2, SocketID: 0}, + 7: {CoreID: 3, SocketID: 0}, + }, + }, + wantErr: false, + }, + { + name: "DualSocketNoHT", + args: &cadvisorapi.MachineInfo{ + NumCores: 4, + Topology: []cadvisorapi.Node{ + {Id: 0, + Cores: []cadvisorapi.Core{ + {Id: 0, Threads: []int{0}}, + {Id: 2, Threads: []int{2}}, + }, + }, + {Id: 1, + Cores: []cadvisorapi.Core{ + {Id: 1, Threads: []int{1}}, + {Id: 3, Threads: []int{3}}, + }, + }, + }, + }, + want: &CPUTopology{ + NumCPUs: 4, + NumSockets: 2, + NumCores: 4, + CPUDetails: map[int]CPUInfo{ + 0: {CoreID: 0, SocketID: 0}, + 1: {CoreID: 1, SocketID: 1}, + 2: {CoreID: 2, SocketID: 0}, + 3: {CoreID: 3, SocketID: 1}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Discover(tt.args) + 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 !reflect.DeepEqual(got, tt.want) { + t.Errorf("Discover() = %v, want %v", got, tt.want) + } + }) + } +}