k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/cpumanager/topology/topology.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package topology
    18  
    19  import (
    20  	"fmt"
    21  
    22  	cadvisorapi "github.com/google/cadvisor/info/v1"
    23  	"k8s.io/klog/v2"
    24  	"k8s.io/utils/cpuset"
    25  )
    26  
    27  // NUMANodeInfo is a map from NUMANode ID to a list of CPU IDs associated with
    28  // that NUMANode.
    29  type NUMANodeInfo map[int]cpuset.CPUSet
    30  
    31  // CPUDetails is a map from CPU ID to Core ID, Socket ID, and NUMA ID.
    32  type CPUDetails map[int]CPUInfo
    33  
    34  // CPUTopology contains details of node cpu, where :
    35  // CPU  - logical CPU, cadvisor - thread
    36  // Core - physical CPU, cadvisor - Core
    37  // Socket - socket, cadvisor - Socket
    38  // NUMA Node - NUMA cell, cadvisor - Node
    39  type CPUTopology struct {
    40  	NumCPUs      int
    41  	NumCores     int
    42  	NumSockets   int
    43  	NumNUMANodes int
    44  	CPUDetails   CPUDetails
    45  }
    46  
    47  // CPUsPerCore returns the number of logical CPUs are associated with
    48  // each core.
    49  func (topo *CPUTopology) CPUsPerCore() int {
    50  	if topo.NumCores == 0 {
    51  		return 0
    52  	}
    53  	return topo.NumCPUs / topo.NumCores
    54  }
    55  
    56  // CPUsPerSocket returns the number of logical CPUs are associated with
    57  // each socket.
    58  func (topo *CPUTopology) CPUsPerSocket() int {
    59  	if topo.NumSockets == 0 {
    60  		return 0
    61  	}
    62  	return topo.NumCPUs / topo.NumSockets
    63  }
    64  
    65  // CPUCoreID returns the physical core ID which the given logical CPU
    66  // belongs to.
    67  func (topo *CPUTopology) CPUCoreID(cpu int) (int, error) {
    68  	info, ok := topo.CPUDetails[cpu]
    69  	if !ok {
    70  		return -1, fmt.Errorf("unknown CPU ID: %d", cpu)
    71  	}
    72  	return info.CoreID, nil
    73  }
    74  
    75  // CPUCoreID returns the socket ID which the given logical CPU belongs to.
    76  func (topo *CPUTopology) CPUSocketID(cpu int) (int, error) {
    77  	info, ok := topo.CPUDetails[cpu]
    78  	if !ok {
    79  		return -1, fmt.Errorf("unknown CPU ID: %d", cpu)
    80  	}
    81  	return info.SocketID, nil
    82  }
    83  
    84  // CPUCoreID returns the NUMA node ID which the given logical CPU belongs to.
    85  func (topo *CPUTopology) CPUNUMANodeID(cpu int) (int, error) {
    86  	info, ok := topo.CPUDetails[cpu]
    87  	if !ok {
    88  		return -1, fmt.Errorf("unknown CPU ID: %d", cpu)
    89  	}
    90  	return info.NUMANodeID, nil
    91  }
    92  
    93  // CPUInfo contains the NUMA, socket, and core IDs associated with a CPU.
    94  type CPUInfo struct {
    95  	NUMANodeID int
    96  	SocketID   int
    97  	CoreID     int
    98  }
    99  
   100  // KeepOnly returns a new CPUDetails object with only the supplied cpus.
   101  func (d CPUDetails) KeepOnly(cpus cpuset.CPUSet) CPUDetails {
   102  	result := CPUDetails{}
   103  	for cpu, info := range d {
   104  		if cpus.Contains(cpu) {
   105  			result[cpu] = info
   106  		}
   107  	}
   108  	return result
   109  }
   110  
   111  // NUMANodes returns all of the NUMANode IDs associated with the CPUs in this
   112  // CPUDetails.
   113  func (d CPUDetails) NUMANodes() cpuset.CPUSet {
   114  	var numaNodeIDs []int
   115  	for _, info := range d {
   116  		numaNodeIDs = append(numaNodeIDs, info.NUMANodeID)
   117  	}
   118  	return cpuset.New(numaNodeIDs...)
   119  }
   120  
   121  // NUMANodesInSockets returns all of the logical NUMANode IDs associated with
   122  // the given socket IDs in this CPUDetails.
   123  func (d CPUDetails) NUMANodesInSockets(ids ...int) cpuset.CPUSet {
   124  	var numaNodeIDs []int
   125  	for _, id := range ids {
   126  		for _, info := range d {
   127  			if info.SocketID == id {
   128  				numaNodeIDs = append(numaNodeIDs, info.NUMANodeID)
   129  			}
   130  		}
   131  	}
   132  	return cpuset.New(numaNodeIDs...)
   133  }
   134  
   135  // Sockets returns all of the socket IDs associated with the CPUs in this
   136  // CPUDetails.
   137  func (d CPUDetails) Sockets() cpuset.CPUSet {
   138  	var socketIDs []int
   139  	for _, info := range d {
   140  		socketIDs = append(socketIDs, info.SocketID)
   141  	}
   142  	return cpuset.New(socketIDs...)
   143  }
   144  
   145  // CPUsInSockets returns all of the logical CPU IDs associated with the given
   146  // socket IDs in this CPUDetails.
   147  func (d CPUDetails) CPUsInSockets(ids ...int) cpuset.CPUSet {
   148  	var cpuIDs []int
   149  	for _, id := range ids {
   150  		for cpu, info := range d {
   151  			if info.SocketID == id {
   152  				cpuIDs = append(cpuIDs, cpu)
   153  			}
   154  		}
   155  	}
   156  	return cpuset.New(cpuIDs...)
   157  }
   158  
   159  // SocketsInNUMANodes returns all of the logical Socket IDs associated with the
   160  // given NUMANode IDs in this CPUDetails.
   161  func (d CPUDetails) SocketsInNUMANodes(ids ...int) cpuset.CPUSet {
   162  	var socketIDs []int
   163  	for _, id := range ids {
   164  		for _, info := range d {
   165  			if info.NUMANodeID == id {
   166  				socketIDs = append(socketIDs, info.SocketID)
   167  			}
   168  		}
   169  	}
   170  	return cpuset.New(socketIDs...)
   171  }
   172  
   173  // Cores returns all of the core IDs associated with the CPUs in this
   174  // CPUDetails.
   175  func (d CPUDetails) Cores() cpuset.CPUSet {
   176  	var coreIDs []int
   177  	for _, info := range d {
   178  		coreIDs = append(coreIDs, info.CoreID)
   179  	}
   180  	return cpuset.New(coreIDs...)
   181  }
   182  
   183  // CoresInNUMANodes returns all of the core IDs associated with the given
   184  // NUMANode IDs in this CPUDetails.
   185  func (d CPUDetails) CoresInNUMANodes(ids ...int) cpuset.CPUSet {
   186  	var coreIDs []int
   187  	for _, id := range ids {
   188  		for _, info := range d {
   189  			if info.NUMANodeID == id {
   190  				coreIDs = append(coreIDs, info.CoreID)
   191  			}
   192  		}
   193  	}
   194  	return cpuset.New(coreIDs...)
   195  }
   196  
   197  // CoresInSockets returns all of the core IDs associated with the given socket
   198  // IDs in this CPUDetails.
   199  func (d CPUDetails) CoresInSockets(ids ...int) cpuset.CPUSet {
   200  	var coreIDs []int
   201  	for _, id := range ids {
   202  		for _, info := range d {
   203  			if info.SocketID == id {
   204  				coreIDs = append(coreIDs, info.CoreID)
   205  			}
   206  		}
   207  	}
   208  	return cpuset.New(coreIDs...)
   209  }
   210  
   211  // CPUs returns all of the logical CPU IDs in this CPUDetails.
   212  func (d CPUDetails) CPUs() cpuset.CPUSet {
   213  	var cpuIDs []int
   214  	for cpuID := range d {
   215  		cpuIDs = append(cpuIDs, cpuID)
   216  	}
   217  	return cpuset.New(cpuIDs...)
   218  }
   219  
   220  // CPUsInNUMANodes returns all of the logical CPU IDs associated with the given
   221  // NUMANode IDs in this CPUDetails.
   222  func (d CPUDetails) CPUsInNUMANodes(ids ...int) cpuset.CPUSet {
   223  	var cpuIDs []int
   224  	for _, id := range ids {
   225  		for cpu, info := range d {
   226  			if info.NUMANodeID == id {
   227  				cpuIDs = append(cpuIDs, cpu)
   228  			}
   229  		}
   230  	}
   231  	return cpuset.New(cpuIDs...)
   232  }
   233  
   234  // CPUsInCores returns all of the logical CPU IDs associated with the given
   235  // core IDs in this CPUDetails.
   236  func (d CPUDetails) CPUsInCores(ids ...int) cpuset.CPUSet {
   237  	var cpuIDs []int
   238  	for _, id := range ids {
   239  		for cpu, info := range d {
   240  			if info.CoreID == id {
   241  				cpuIDs = append(cpuIDs, cpu)
   242  			}
   243  		}
   244  	}
   245  	return cpuset.New(cpuIDs...)
   246  }
   247  
   248  // Discover returns CPUTopology based on cadvisor node info
   249  func Discover(machineInfo *cadvisorapi.MachineInfo) (*CPUTopology, error) {
   250  	if machineInfo.NumCores == 0 {
   251  		return nil, fmt.Errorf("could not detect number of cpus")
   252  	}
   253  
   254  	CPUDetails := CPUDetails{}
   255  	numPhysicalCores := 0
   256  
   257  	for _, node := range machineInfo.Topology {
   258  		numPhysicalCores += len(node.Cores)
   259  		for _, core := range node.Cores {
   260  			if coreID, err := getUniqueCoreID(core.Threads); err == nil {
   261  				for _, cpu := range core.Threads {
   262  					CPUDetails[cpu] = CPUInfo{
   263  						CoreID:     coreID,
   264  						SocketID:   core.SocketID,
   265  						NUMANodeID: node.Id,
   266  					}
   267  				}
   268  			} else {
   269  				klog.ErrorS(nil, "Could not get unique coreID for socket", "socket", core.SocketID, "core", core.Id, "threads", core.Threads)
   270  				return nil, err
   271  			}
   272  		}
   273  	}
   274  
   275  	return &CPUTopology{
   276  		NumCPUs:      machineInfo.NumCores,
   277  		NumSockets:   machineInfo.NumSockets,
   278  		NumCores:     numPhysicalCores,
   279  		NumNUMANodes: CPUDetails.NUMANodes().Size(),
   280  		CPUDetails:   CPUDetails,
   281  	}, nil
   282  }
   283  
   284  // getUniqueCoreID computes coreId as the lowest cpuID
   285  // for a given Threads []int slice. This will assure that coreID's are
   286  // platform unique (opposite to what cAdvisor reports)
   287  func getUniqueCoreID(threads []int) (coreID int, err error) {
   288  	if len(threads) == 0 {
   289  		return 0, fmt.Errorf("no cpus provided")
   290  	}
   291  
   292  	if len(threads) != cpuset.New(threads...).Size() {
   293  		return 0, fmt.Errorf("cpus provided are not unique")
   294  	}
   295  
   296  	min := threads[0]
   297  	for _, thread := range threads[1:] {
   298  		if thread < min {
   299  			min = thread
   300  		}
   301  	}
   302  
   303  	return min, nil
   304  }