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:
Katarzyna Kujawa 2020-04-06 09:54:45 +02:00
parent 4273303203
commit 6536a6c35e
5 changed files with 310 additions and 33 deletions

View File

@ -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{}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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) {