Fix number of logical CPU cores when nodes are not available
Add reading CPU topology without nodes Add test for reading CPU topology without nodes Signed-off-by: Katarzyna Kujawa <katarzyna.kujawa@intel.com>
This commit is contained in:
parent
4273303203
commit
6536a6c35e
@ -206,6 +206,104 @@ func TestTopologyEmptySysFs(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestTopologyWithoutNodes(t *testing.T) {
|
||||
machineArch = "" // overwrite package variable
|
||||
sysFs := &fakesysfs.FakeSysFs{}
|
||||
|
||||
c := sysfs.CacheInfo{
|
||||
Size: 32 * 1024,
|
||||
Type: "unified",
|
||||
Level: 0,
|
||||
Cpus: 2,
|
||||
}
|
||||
sysFs.SetCacheInfo(c)
|
||||
|
||||
nodesPaths := []string{}
|
||||
sysFs.SetNodesPaths(nodesPaths, nil)
|
||||
|
||||
cpusPaths := map[string][]string{
|
||||
"/sys/devices/system/cpu": {
|
||||
"/sys/devices/system/cpu/cpu0",
|
||||
"/sys/devices/system/cpu/cpu1",
|
||||
"/sys/devices/system/cpu/cpu2",
|
||||
"/sys/devices/system/cpu/cpu3",
|
||||
},
|
||||
}
|
||||
sysFs.SetCPUsPaths(cpusPaths, nil)
|
||||
|
||||
coreThread := map[string]string{
|
||||
"/sys/devices/system/cpu/cpu0": "0",
|
||||
"/sys/devices/system/cpu/cpu1": "1",
|
||||
"/sys/devices/system/cpu/cpu2": "0",
|
||||
"/sys/devices/system/cpu/cpu3": "1",
|
||||
}
|
||||
sysFs.SetCoreThreads(coreThread, nil)
|
||||
|
||||
physicalPackageIDs := map[string]string{
|
||||
"/sys/devices/system/cpu/cpu0": "0",
|
||||
"/sys/devices/system/cpu/cpu1": "1",
|
||||
"/sys/devices/system/cpu/cpu2": "0",
|
||||
"/sys/devices/system/cpu/cpu3": "1",
|
||||
}
|
||||
sysFs.SetPhysicalPackageIDs(physicalPackageIDs, nil)
|
||||
|
||||
topology, numCores, err := GetTopology(sysFs)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(topology))
|
||||
assert.Equal(t, 4, numCores)
|
||||
|
||||
topologyJSON, err := json.Marshal(topology)
|
||||
assert.Nil(t, err)
|
||||
|
||||
expectedTopology := `[
|
||||
{
|
||||
"node_id":0,
|
||||
"memory":0,
|
||||
"hugepages":null,
|
||||
"cores":[
|
||||
{
|
||||
"core_id":0,
|
||||
"thread_ids":[
|
||||
0,
|
||||
2
|
||||
],
|
||||
"caches":[
|
||||
{
|
||||
"size":32768,
|
||||
"type":"unified",
|
||||
"level":0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"caches":null
|
||||
},
|
||||
{
|
||||
"node_id":1,
|
||||
"memory":0,
|
||||
"hugepages":null,
|
||||
"cores":[
|
||||
{
|
||||
"core_id":1,
|
||||
"thread_ids":[
|
||||
1,
|
||||
3
|
||||
],
|
||||
"caches":[
|
||||
{
|
||||
"size":32768,
|
||||
"type":"unified",
|
||||
"level":0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"caches":null
|
||||
}
|
||||
]`
|
||||
assert.JSONEq(t, expectedTopology, string(topologyJSON))
|
||||
}
|
||||
|
||||
func TestTopologyWithNodesWithoutCPU(t *testing.T) {
|
||||
machineArch = "" // overwrite package variable
|
||||
sysFs := &fakesysfs.FakeSysFs{}
|
||||
|
@ -64,6 +64,9 @@ type FakeSysFs struct {
|
||||
coreThread map[string]string
|
||||
coreIDErr error
|
||||
|
||||
physicalPackageIDs map[string]string
|
||||
physicalPackageIDErr error
|
||||
|
||||
memTotal string
|
||||
memErr error
|
||||
|
||||
@ -78,14 +81,18 @@ func (self *FakeSysFs) GetNodesPaths() ([]string, error) {
|
||||
return self.nodesPaths, self.nodePathErr
|
||||
}
|
||||
|
||||
func (self *FakeSysFs) GetCPUsPaths(nodePath string) ([]string, error) {
|
||||
return self.cpusPaths[nodePath], self.cpuPathErr
|
||||
func (self *FakeSysFs) GetCPUsPaths(cpusPath string) ([]string, error) {
|
||||
return self.cpusPaths[cpusPath], self.cpuPathErr
|
||||
}
|
||||
|
||||
func (self *FakeSysFs) GetCoreID(coreIDPath string) (string, error) {
|
||||
return self.coreThread[coreIDPath], self.coreIDErr
|
||||
}
|
||||
|
||||
func (self *FakeSysFs) GetCPUPhysicalPackageID(cpuPath string) (string, error) {
|
||||
return self.physicalPackageIDs[cpuPath], self.physicalPackageIDErr
|
||||
}
|
||||
|
||||
func (self *FakeSysFs) GetMemInfo(nodePath string) (string, error) {
|
||||
return self.memTotal, self.memErr
|
||||
}
|
||||
@ -164,6 +171,11 @@ func (self *FakeSysFs) SetCoreThreads(coreThread map[string]string, err error) {
|
||||
self.coreIDErr = err
|
||||
}
|
||||
|
||||
func (self *FakeSysFs) SetPhysicalPackageIDs(physicalPackageIDs map[string]string, err error) {
|
||||
self.physicalPackageIDs = physicalPackageIDs
|
||||
self.physicalPackageIDErr = err
|
||||
}
|
||||
|
||||
func (self *FakeSysFs) SetMemory(memTotal string, err error) {
|
||||
self.memTotal = memTotal
|
||||
self.memErr = err
|
||||
|
@ -32,8 +32,9 @@ const (
|
||||
ppcDevTree = "/proc/device-tree"
|
||||
s390xDevTree = "/etc" // s390/s390x changes
|
||||
|
||||
coreIDFilePath = "/topology/core_id"
|
||||
meminfoFile = "meminfo"
|
||||
coreIDFilePath = "/topology/core_id"
|
||||
packageIDFilePath = "/topology/physical_package_id"
|
||||
meminfoFile = "meminfo"
|
||||
|
||||
cpuDirPattern = "cpu*[0-9]"
|
||||
nodeDirPattern = "node*[0-9]"
|
||||
@ -61,10 +62,12 @@ type CacheInfo struct {
|
||||
type SysFs interface {
|
||||
// Get NUMA nodes paths
|
||||
GetNodesPaths() ([]string, error)
|
||||
// Get paths to CPU assigned for specified NUMA node
|
||||
GetCPUsPaths(nodePath string) ([]string, error)
|
||||
// Get paths to CPUs in provided directory e.g. /sys/devices/system/node/node0 or /sys/devices/system/cpu
|
||||
GetCPUsPaths(cpusPath string) ([]string, error)
|
||||
// Get physical core id for specified CPU
|
||||
GetCoreID(coreIDFilePath string) (string, error)
|
||||
// Get physical package id for specified CPU
|
||||
GetCPUPhysicalPackageID(cpuPath string) (string, error)
|
||||
// Get total memory for specified NUMA node
|
||||
GetMemInfo(nodeDir string) (string, error)
|
||||
// Get hugepages from specified directory
|
||||
@ -105,8 +108,8 @@ func (self *realSysFs) GetNodesPaths() ([]string, error) {
|
||||
return filepath.Glob(pathPattern)
|
||||
}
|
||||
|
||||
func (self *realSysFs) GetCPUsPaths(nodePath string) ([]string, error) {
|
||||
pathPattern := fmt.Sprintf("%s/%s", nodePath, cpuDirPattern)
|
||||
func (self *realSysFs) GetCPUsPaths(cpusPath string) ([]string, error) {
|
||||
pathPattern := fmt.Sprintf("%s/%s", cpusPath, cpuDirPattern)
|
||||
return filepath.Glob(pathPattern)
|
||||
}
|
||||
|
||||
@ -119,6 +122,15 @@ func (self *realSysFs) GetCoreID(cpuPath string) (string, error) {
|
||||
return strings.TrimSpace(string(coreID)), err
|
||||
}
|
||||
|
||||
func (self *realSysFs) GetCPUPhysicalPackageID(cpuPath string) (string, error) {
|
||||
packageIDFilePath := fmt.Sprintf("%s%s", cpuPath, packageIDFilePath)
|
||||
packageID, err := ioutil.ReadFile(packageIDFilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(packageID)), err
|
||||
}
|
||||
|
||||
func (self *realSysFs) GetMemInfo(nodePath string) (string, error) {
|
||||
meminfoPath := fmt.Sprintf("%s/%s", nodePath, meminfoFile)
|
||||
meminfo, err := ioutil.ReadFile(meminfoPath)
|
||||
|
@ -30,8 +30,10 @@ import (
|
||||
var (
|
||||
schedulerRegExp = regexp.MustCompile(`.*\[(.*)\].*`)
|
||||
nodeDirRegExp = regexp.MustCompile("node/node(\\d*)")
|
||||
cpuDirRegExp = regexp.MustCompile("/cpu(\\d*)")
|
||||
cpuDirRegExp = regexp.MustCompile("/cpu(\\d+)")
|
||||
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
|
||||
|
||||
cpusPath = "/sys/devices/system/cpu"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -193,25 +195,31 @@ func GetNodesInfo(sysFs sysfs.SysFs) ([]info.Node, int, error) {
|
||||
allLogicalCoresCount := 0
|
||||
|
||||
nodesDirs, err := sysFs.GetNodesPaths()
|
||||
if err != nil || len(nodesDirs) == 0 {
|
||||
if len(nodesDirs) == 0 && err == nil {
|
||||
//sysFs.GetNodesPaths uses filePath.Glob which does not return any error if pattern does not match anything
|
||||
err = fmt.Errorf("Any path to specific node is not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(nodesDirs) == 0 {
|
||||
klog.Warningf("Nodes topology is not available, providing CPU topology")
|
||||
return getCPUTopology(sysFs)
|
||||
}
|
||||
|
||||
for _, nodeDir := range nodesDirs {
|
||||
id, err := getMatchedInt(nodeDirRegExp, nodeDir)
|
||||
node := info.Node{Id: id}
|
||||
|
||||
cores, logicalCoreCount, err := getCoresInfo(sysFs, nodeDir)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
cpuDirs, err := sysFs.GetCPUsPaths(nodeDir)
|
||||
if len(cpuDirs) == 0 {
|
||||
klog.Warningf("Found node without any CPU, nodeDir: %s, number of cpuDirs %d, err: %v", nodeDir, len(cpuDirs), err)
|
||||
} else {
|
||||
cores, err := getCoresInfo(sysFs, cpuDirs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
node.Cores = cores
|
||||
}
|
||||
node.Cores = cores
|
||||
|
||||
allLogicalCoresCount += logicalCoreCount
|
||||
allLogicalCoresCount += len(cpuDirs)
|
||||
err = addCacheInfo(sysFs, &node)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@ -233,6 +241,62 @@ func GetNodesInfo(sysFs sysfs.SysFs) ([]info.Node, int, error) {
|
||||
return nodes, allLogicalCoresCount, err
|
||||
}
|
||||
|
||||
func getCPUTopology(sysFs sysfs.SysFs) ([]info.Node, int, error) {
|
||||
nodes := []info.Node{}
|
||||
|
||||
cpusPaths, err := sysFs.GetCPUsPaths(cpusPath)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
cpusCount := len(cpusPaths)
|
||||
|
||||
if cpusCount == 0 {
|
||||
err = fmt.Errorf("Any CPU is not available, cpusPath: %s", cpusPath)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
cpusByPhysicalPackageID, err := getCpusByPhysicalPackageID(sysFs, cpusPaths)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
for physicalPackageID, cpus := range cpusByPhysicalPackageID {
|
||||
node := info.Node{Id: physicalPackageID}
|
||||
|
||||
cores, err := getCoresInfo(sysFs, cpus)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
node.Cores = cores
|
||||
|
||||
err = addCacheInfo(sysFs, &node)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
return nodes, cpusCount, nil
|
||||
}
|
||||
|
||||
func getCpusByPhysicalPackageID(sysFs sysfs.SysFs, cpusPaths []string) (map[int][]string, error) {
|
||||
cpuPathsByPhysicalPackageID := make(map[int][]string, 0)
|
||||
for _, cpuPath := range cpusPaths {
|
||||
|
||||
rawPhysicalPackageID, _ := sysFs.GetCPUPhysicalPackageID(cpuPath)
|
||||
physicalPackageID, err := strconv.Atoi(rawPhysicalPackageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := cpuPathsByPhysicalPackageID[physicalPackageID]; !ok {
|
||||
cpuPathsByPhysicalPackageID[physicalPackageID] = make([]string, 0)
|
||||
}
|
||||
|
||||
cpuPathsByPhysicalPackageID[physicalPackageID] = append(cpuPathsByPhysicalPackageID[physicalPackageID], cpuPath)
|
||||
}
|
||||
return cpuPathsByPhysicalPackageID, nil
|
||||
}
|
||||
|
||||
// addCacheInfo adds information about cache for NUMA node
|
||||
func addCacheInfo(sysFs sysfs.SysFs, node *info.Node) error {
|
||||
for coreID, core := range node.Cores {
|
||||
@ -292,28 +356,22 @@ func getNodeMemInfo(sysFs sysfs.SysFs, nodeDir string) (uint64, error) {
|
||||
return uint64(memory), nil
|
||||
}
|
||||
|
||||
// getCoresInfo retruns infromation about physical and logical cores assigned to NUMA node
|
||||
func getCoresInfo(sysFs sysfs.SysFs, nodeDir string) ([]info.Core, int, error) {
|
||||
cpuDirs, err := sysFs.GetCPUsPaths(nodeDir)
|
||||
if err != nil || len(cpuDirs) == 0 {
|
||||
klog.Warningf("Found node without any CPU, nodeDir: %s, number of cpuDirs %d, err: %v", nodeDir, len(cpuDirs), err)
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// getCoresInfo retruns infromation about physical cores
|
||||
func getCoresInfo(sysFs sysfs.SysFs, cpuDirs []string) ([]info.Core, error) {
|
||||
cores := make([]info.Core, 0, len(cpuDirs))
|
||||
for _, cpuDir := range cpuDirs {
|
||||
cpuID, err := getMatchedInt(cpuDirRegExp, cpuDir)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("Unexpected format of CPU directory, cpuDirRegExp %s, cpuDir: %s", cpuDirRegExp, cpuDir)
|
||||
return nil, fmt.Errorf("Unexpected format of CPU directory, cpuDirRegExp %s, cpuDir: %s", cpuDirRegExp, cpuDir)
|
||||
}
|
||||
|
||||
rawPhysicalID, err := sysFs.GetCoreID(cpuDir)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
physicalID, err := strconv.Atoi(rawPhysicalID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coreIDx := -1
|
||||
@ -335,7 +393,7 @@ func getCoresInfo(sysFs sysfs.SysFs, nodeDir string) ([]info.Core, int, error) {
|
||||
desiredCore.Threads = append(desiredCore.Threads, cpuID)
|
||||
}
|
||||
}
|
||||
return cores, len(cpuDirs), nil
|
||||
return cores, nil
|
||||
}
|
||||
|
||||
// GetCacheInfo return information about a cache accessible from the given cpu thread
|
||||
|
@ -369,6 +369,104 @@ func TestGetNodesInfoWithoutHugePagesInfo(t *testing.T) {
|
||||
assert.JSONEq(t, expectedNodes, string(nodesJSON))
|
||||
}
|
||||
|
||||
func TestGetNodesInfoWithoutNodes(t *testing.T) {
|
||||
fakeSys := &fakesysfs.FakeSysFs{}
|
||||
|
||||
c := sysfs.CacheInfo{
|
||||
Size: 32 * 1024,
|
||||
Type: "unified",
|
||||
Level: 1,
|
||||
Cpus: 2,
|
||||
}
|
||||
fakeSys.SetCacheInfo(c)
|
||||
|
||||
nodesPaths := []string{}
|
||||
fakeSys.SetNodesPaths(nodesPaths, nil)
|
||||
|
||||
cpusPaths := map[string][]string{
|
||||
cpusPath: {
|
||||
cpusPath + "/cpu0",
|
||||
cpusPath + "/cpu1",
|
||||
cpusPath + "/cpu2",
|
||||
cpusPath + "/cpu3",
|
||||
},
|
||||
}
|
||||
fakeSys.SetCPUsPaths(cpusPaths, nil)
|
||||
|
||||
coreThread := map[string]string{
|
||||
cpusPath + "/cpu0": "0",
|
||||
cpusPath + "/cpu1": "0",
|
||||
cpusPath + "/cpu2": "1",
|
||||
cpusPath + "/cpu3": "1",
|
||||
}
|
||||
fakeSys.SetCoreThreads(coreThread, nil)
|
||||
|
||||
physicalPackageIDs := map[string]string{
|
||||
"/sys/devices/system/cpu/cpu0": "0",
|
||||
"/sys/devices/system/cpu/cpu1": "0",
|
||||
"/sys/devices/system/cpu/cpu2": "1",
|
||||
"/sys/devices/system/cpu/cpu3": "1",
|
||||
}
|
||||
fakeSys.SetPhysicalPackageIDs(physicalPackageIDs, nil)
|
||||
|
||||
nodes, cores, err := GetNodesInfo(fakeSys)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(nodes))
|
||||
assert.Equal(t, 4, cores)
|
||||
|
||||
nodesJSON, err := json.Marshal(nodes)
|
||||
assert.Nil(t, err)
|
||||
fmt.Println(string(nodesJSON))
|
||||
|
||||
expectedNodes := `[
|
||||
{
|
||||
"node_id":0,
|
||||
"memory":0,
|
||||
"hugepages":null,
|
||||
"cores":[
|
||||
{
|
||||
"core_id":0,
|
||||
"thread_ids":[
|
||||
0,
|
||||
1
|
||||
],
|
||||
"caches":[
|
||||
{
|
||||
"size":32768,
|
||||
"type":"unified",
|
||||
"level":1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"caches":null
|
||||
},
|
||||
{
|
||||
"node_id":1,
|
||||
"memory":0,
|
||||
"hugepages":null,
|
||||
"cores":[
|
||||
{
|
||||
"core_id":1,
|
||||
"thread_ids":[
|
||||
2,
|
||||
3
|
||||
],
|
||||
"caches":[
|
||||
{
|
||||
"size":32768,
|
||||
"type":"unified",
|
||||
"level":1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"caches":null
|
||||
}
|
||||
]`
|
||||
assert.JSONEq(t, expectedNodes, string(nodesJSON))
|
||||
}
|
||||
|
||||
func TestGetNodeMemInfo(t *testing.T) {
|
||||
fakeSys := &fakesysfs.FakeSysFs{}
|
||||
memTotal := "MemTotal: 32817192 kB"
|
||||
@ -418,10 +516,9 @@ func TestGetCoresInfoWhenCoreIDIsNotDigit(t *testing.T) {
|
||||
}
|
||||
sysFs.SetCoreThreads(coreThread, nil)
|
||||
|
||||
cores, coreID, err := getCoresInfo(sysFs, "/fakeSysfs/devices/system/node/node0")
|
||||
cores, err := getCoresInfo(sysFs, []string{"/fakeSysfs/devices/system/node/node0/cpu0"})
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, []info.Core(nil), cores)
|
||||
assert.Equal(t, 0, coreID)
|
||||
}
|
||||
|
||||
func TestGetBlockDeviceInfo(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user