Modify GetTopology to read information only from sysfs

* Move sysfs related functions needed to get nodes' information and tests into utils/sysfs
* Add tests for sysfs related functions

Signed-off-by: Katarzyna Kujawa <katarzyna.kujawa@intel.com>
This commit is contained in:
Katarzyna Kujawa 2020-03-04 11:33:46 +01:00
parent 9921cb3e21
commit c5a9232a94
14 changed files with 1059 additions and 401 deletions

View File

@ -82,7 +82,7 @@ func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.Mach
return nil, err return nil, err
} }
hugePagesInfo, err := GetHugePagesInfo(hugepagesDirectory) hugePagesInfo, err := sysinfo.GetHugePagesInfo(sysFs, hugepagesDirectory)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -102,7 +102,7 @@ func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.Mach
klog.Errorf("Failed to get network devices: %v", err) klog.Errorf("Failed to get network devices: %v", err)
} }
topology, numCores, err := GetTopology(sysFs, string(cpuinfo)) topology, numCores, err := GetTopology(sysFs)
if err != nil { if err != nil {
klog.Errorf("Failed to get topology information: %v", err) klog.Errorf("Failed to get topology information: %v", err)
} }

View File

@ -16,7 +16,6 @@
package machine package machine
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -52,6 +51,7 @@ var (
cpuBusPath = "/sys/bus/cpu/devices/" cpuBusPath = "/sys/bus/cpu/devices/"
isMemoryController = regexp.MustCompile("mc[0-9]+") isMemoryController = regexp.MustCompile("mc[0-9]+")
isDimm = regexp.MustCompile("dimm[0-9]+") isDimm = regexp.MustCompile("dimm[0-9]+")
machineArch = getMachineArch()
) )
const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
@ -213,6 +213,15 @@ func GetMachineSwapCapacity() (uint64, error) {
return swapCapacity, err return swapCapacity, err
} }
// GetTopology returns CPU topology reading information from sysfs
func GetTopology(sysFs sysfs.SysFs) ([]info.Node, int, error) {
// s390/s390x changes
if isSystemZ() {
return nil, getNumCores(), nil
}
return sysinfo.GetNodesInfo(sysFs)
}
// parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes. // parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
// Assumes that the value matched by the Regexp is in KB. // Assumes that the value matched by the Regexp is in KB.
func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) { func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
@ -229,30 +238,6 @@ func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
return m * 1024, err return m * 1024, err
} }
// Looks for sysfs cpu path containing core_id
// Such as: sys/bus/cpu/devices/cpu0/topology/core_id
func getCoreIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d/topology", threadId))
file := filepath.Join(path, sysFsCPUCoreID)
num, err := ioutil.ReadFile(file)
if err != nil {
return threadId, err
}
coreId, err := strconv.ParseInt(string(bytes.TrimSpace(num)), 10, 32)
if err != nil {
return threadId, err
}
if coreId < 0 {
// report threadId if found coreId < 0
coreId = int64(threadId)
}
return int(coreId), nil
}
// Looks for sysfs cpu path containing given CPU property, e.g. core_id or physical_package_id // Looks for sysfs cpu path containing given CPU property, e.g. core_id or physical_package_id
// and returns number of unique values of given property, exemplary usage: getting number of CPU physical cores // and returns number of unique values of given property, exemplary usage: getting number of CPU physical cores
func getUniqueCPUPropertyCount(cpuBusPath string, propertyName string) int { func getUniqueCPUPropertyCount(cpuBusPath string, propertyName string) int {
@ -275,192 +260,6 @@ func getUniqueCPUPropertyCount(cpuBusPath string, propertyName string) int {
return len(uniques) return len(uniques)
} }
// Looks for sysfs cpu path containing node id
// Such as: /sys/bus/cpu/devices/cpu0/node%d
func getNodeIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d", threadId))
files, err := ioutil.ReadDir(path)
if err != nil {
return 0, err
}
nodeId := 0
for _, file := range files {
filename := file.Name()
ok, val, _ := extractValue(filename, nodeBusRegExp)
if ok {
if val < 0 {
continue
}
nodeId = val
break
}
}
return nodeId, nil
}
// GetHugePagesInfo returns information about pre-allocated huge pages
// hugepagesDirectory should be top directory of hugepages
// Such as: /sys/kernel/mm/hugepages/
func GetHugePagesInfo(hugepagesDirectory string) ([]info.HugePagesInfo, error) {
var hugePagesInfo []info.HugePagesInfo
files, err := ioutil.ReadDir(hugepagesDirectory)
if err != nil {
// treat as non-fatal since kernels and machine can be
// configured to disable hugepage support
return hugePagesInfo, nil
}
for _, st := range files {
nameArray := strings.Split(st.Name(), "-")
pageSizeArray := strings.Split(nameArray[1], "kB")
pageSize, err := strconv.ParseUint(string(pageSizeArray[0]), 10, 64)
if err != nil {
return hugePagesInfo, err
}
numFile := hugepagesDirectory + st.Name() + "/nr_hugepages"
val, err := ioutil.ReadFile(numFile)
if err != nil {
return hugePagesInfo, err
}
var numPages uint64
// we use sscanf as the file as a new-line that trips up ParseUint
// it returns the number of tokens successfully parsed, so if
// n != 1, it means we were unable to parse a number from the file
n, err := fmt.Sscanf(string(val), "%d", &numPages)
if err != nil || n != 1 {
return hugePagesInfo, fmt.Errorf("could not parse file %v contents %q", numFile, string(val))
}
hugePagesInfo = append(hugePagesInfo, info.HugePagesInfo{
NumPages: numPages,
PageSize: pageSize,
})
}
return hugePagesInfo, nil
}
func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
nodes := []info.Node{}
// s390/s390x changes
if true == isSystemZ() {
return nodes, getNumCores(), nil
}
numCores := 0
lastThread := -1
lastCore := -1
lastNode := -1
for _, line := range strings.Split(cpuinfo, "\n") {
if line == "" {
continue
}
ok, val, err := extractValue(line, cpuRegExp)
if err != nil {
return nil, -1, fmt.Errorf("could not parse cpu info from %q: %v", line, err)
}
if ok {
thread := val
numCores++
if lastThread != -1 {
// New cpu section. Save last one.
nodeIdx, err := addNode(&nodes, lastNode)
if err != nil {
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
}
nodes[nodeIdx].AddThread(lastThread, lastCore)
lastCore = -1
lastNode = -1
}
lastThread = thread
/* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
/* So we search sysfs cpu path directly. */
/* This method can also be used on other platforms, such as x86, ppc64le... */
/* /sys/bus/cpu/devices/cpu%d contains the information of 'core_id' & 'node_id'. */
/* Such as: /sys/bus/cpu/devices/cpu0/topology/core_id */
/* Such as: /sys/bus/cpu/devices/cpu0/node0 */
if isAArch64() {
val, err = getCoreIdFromCpuBus(cpuBusPath, lastThread)
if err != nil {
// Report thread id if no NUMA
val = lastThread
}
lastCore = val
val, err = getNodeIdFromCpuBus(cpuBusPath, lastThread)
if err != nil {
// Report node 0 if no NUMA
val = 0
}
lastNode = val
}
continue
}
if isAArch64() {
/* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
continue
}
ok, val, err = extractValue(line, coreRegExp)
if err != nil {
return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err)
}
if ok {
lastCore = val
continue
}
ok, val, err = extractValue(line, nodeRegExp)
if err != nil {
return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err)
}
if ok {
lastNode = val
continue
}
}
nodeIdx, err := addNode(&nodes, lastNode)
if err != nil {
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
}
nodes[nodeIdx].AddThread(lastThread, lastCore)
if numCores < 1 {
return nil, numCores, fmt.Errorf("could not detect any cores")
}
for idx, node := range nodes {
caches, err := sysinfo.GetCacheInfo(sysFs, node.Cores[0].Threads[0])
if err != nil {
klog.Errorf("failed to get cache information for node %d: %v", node.Id, err)
continue
}
numThreadsPerCore := len(node.Cores[0].Threads)
numThreadsPerNode := len(node.Cores) * numThreadsPerCore
for _, cache := range caches {
c := info.Cache{
Size: cache.Size,
Level: cache.Level,
Type: cache.Type,
}
if cache.Cpus == numThreadsPerNode && cache.Level > 2 {
// Add a node-level cache.
nodes[idx].AddNodeCache(c)
} else if cache.Cpus == numThreadsPerCore {
// Add to each core.
nodes[idx].AddPerCoreCache(c)
}
// Ignore unknown caches.
}
}
return nodes, numCores, nil
}
func extractValue(s string, r *regexp.Regexp) (bool, int, error) { func extractValue(s string, r *regexp.Regexp) (bool, int, error) {
matches := r.FindSubmatch([]byte(s)) matches := r.FindSubmatch([]byte(s))
if len(matches) == 2 { if len(matches) == 2 {
@ -483,106 +282,39 @@ func getUniqueMatchesCount(s string, r *regexp.Regexp) int {
return len(uniques) return len(uniques)
} }
func findNode(nodes []info.Node, id int) (bool, int) { func getMachineArch() string {
for i, n := range nodes {
if n.Id == id {
return true, i
}
}
return false, -1
}
func addNode(nodes *[]info.Node, id int) (int, error) {
var idx int
if id == -1 {
// Some VMs don't fill topology data. Export single package.
id = 0
}
ok, idx := findNode(*nodes, id)
if !ok {
// New node
node := info.Node{Id: id}
// Add per-node memory information.
meminfo := fmt.Sprintf("/sys/devices/system/node/node%d/meminfo", id)
out, err := ioutil.ReadFile(meminfo)
// Ignore if per-node info is not available.
if err == nil {
m, err := parseCapacity(out, memoryCapacityRegexp)
if err != nil {
return -1, err
}
node.Memory = uint64(m)
}
// Look for per-node hugepages info using node id
// Such as: /sys/devices/system/node/node%d/hugepages
hugepagesDirectory := fmt.Sprintf("%s/node%d/hugepages/", nodePath, id)
hugePagesInfo, err := GetHugePagesInfo(hugepagesDirectory)
if err != nil {
return -1, err
}
node.HugePages = hugePagesInfo
*nodes = append(*nodes, node)
idx = len(*nodes) - 1
}
return idx, nil
}
// s390/s390x changes
func getMachineArch() (string, error) {
uname := unix.Utsname{} uname := unix.Utsname{}
err := unix.Uname(&uname) err := unix.Uname(&uname)
if err != nil { if err != nil {
return "", err klog.Errorf("Cannot get machine architecture, err: %v", err)
return ""
} }
return string(uname.Machine[:])
return string(uname.Machine[:]), nil
} }
// arm32 chanes // arm32 changes
func isArm32() bool { func isArm32() bool {
arch, err := getMachineArch() return strings.Contains(machineArch, "arm")
if err == nil {
return strings.Contains(arch, "arm")
}
return false
} }
// aarch64 changes // aarch64 changes
func isAArch64() bool { func isAArch64() bool {
arch, err := getMachineArch() return strings.Contains(machineArch, "aarch64")
if err == nil {
return strings.Contains(arch, "aarch64")
}
return false
} }
// s390/s390x changes // s390/s390x changes
func isSystemZ() bool { func isSystemZ() bool {
arch, err := getMachineArch() return strings.Contains(machineArch, "390")
if err == nil {
return strings.Contains(arch, "390")
}
return false
} }
// riscv64 changes // riscv64 changes
func isRiscv64() bool { func isRiscv64() bool {
arch, err := getMachineArch() return strings.Contains(machineArch, "riscv64")
if err == nil {
return strings.Contains(arch, "riscv64")
}
return false
} }
// mips64 changes // mips64 changes
func isMips64() bool { func isMips64() bool {
arch, err := getMachineArch() return strings.Contains(machineArch, "mips64")
if err == nil {
return strings.Contains(arch, "mips64")
}
return false
} }
// s390/s390x changes // s390/s390x changes

View File

@ -15,9 +15,10 @@
package machine package machine
import ( import (
"encoding/json"
"io/ioutil" "io/ioutil"
"os"
"reflect" "reflect"
"runtime"
"testing" "testing"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
@ -38,7 +39,7 @@ func TestPhysicalCores(t *testing.T) {
} }
func TestPhysicalCoresReadingFromCpuBus(t *testing.T) { func TestPhysicalCoresReadingFromCpuBus(t *testing.T) {
cpuBusPath = "./testdata/" // overwriting global variable to mock sysfs cpuBusPath = "./testdata/" // overwriting package variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without core id testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without core id
testcpuinfo, err := ioutil.ReadFile(testfile) testcpuinfo, err := ioutil.ReadFile(testfile)
@ -50,7 +51,7 @@ func TestPhysicalCoresReadingFromCpuBus(t *testing.T) {
} }
func TestPhysicalCoresFromWrongSysFs(t *testing.T) { func TestPhysicalCoresFromWrongSysFs(t *testing.T) {
cpuBusPath = "./testdata/wrongsysfs" // overwriting global variable to mock sysfs cpuBusPath = "./testdata/wrongsysfs" // overwriting package variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without core id testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without core id
testcpuinfo, err := ioutil.ReadFile(testfile) testcpuinfo, err := ioutil.ReadFile(testfile)
@ -73,7 +74,7 @@ func TestSockets(t *testing.T) {
} }
func TestSocketsReadingFromCpuBus(t *testing.T) { func TestSocketsReadingFromCpuBus(t *testing.T) {
cpuBusPath = "./testdata/wrongsysfs" // overwriting global variable to mock sysfs cpuBusPath = "./testdata/wrongsysfs" // overwriting package variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without physical id testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without physical id
testcpuinfo, err := ioutil.ReadFile(testfile) testcpuinfo, err := ioutil.ReadFile(testfile)
@ -85,7 +86,7 @@ func TestSocketsReadingFromCpuBus(t *testing.T) {
} }
func TestSocketsReadingFromWrongSysFs(t *testing.T) { func TestSocketsReadingFromWrongSysFs(t *testing.T) {
cpuBusPath = "./testdata/" // overwriting global variable to mock sysfs cpuBusPath = "./testdata/" // overwriting package variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without physical id testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without physical id
testcpuinfo, err := ioutil.ReadFile(testfile) testcpuinfo, err := ioutil.ReadFile(testfile)
@ -97,14 +98,7 @@ func TestSocketsReadingFromWrongSysFs(t *testing.T) {
} }
func TestTopology(t *testing.T) { func TestTopology(t *testing.T) {
if runtime.GOARCH != "amd64" { machineArch = "" // overwrite package variable
t.Skip("cpuinfo testdata is for amd64")
}
testfile := "./testdata/cpuinfo"
testcpuinfo, err := ioutil.ReadFile(testfile)
if err != nil {
t.Fatalf("unable to read input test file %s", testfile)
}
sysFs := &fakesysfs.FakeSysFs{} sysFs := &fakesysfs.FakeSysFs{}
c := sysfs.CacheInfo{ c := sysfs.CacheInfo{
Size: 32 * 1024, Size: 32 * 1024,
@ -113,14 +107,70 @@ func TestTopology(t *testing.T) {
Cpus: 2, Cpus: 2,
} }
sysFs.SetCacheInfo(c) sysFs.SetCacheInfo(c)
topology, numCores, err := GetTopology(sysFs, string(testcpuinfo))
if err != nil {
t.Errorf("failed to get topology for sample cpuinfo %s: %v", string(testcpuinfo), err)
}
if numCores != 12 { nodesPaths := []string{
t.Errorf("Expected 12 cores, found %d", numCores) "/fakeSysfs/devices/system/node/node0",
"/fakeSysfs/devices/system/node/node1",
} }
sysFs.SetNodesPaths(nodesPaths, nil)
cpusPaths := map[string][]string{
"/fakeSysfs/devices/system/node/node0": {
"/fakeSysfs/devices/system/node/node0/cpu0",
"/fakeSysfs/devices/system/node/node0/cpu1",
"/fakeSysfs/devices/system/node/node0/cpu2",
"/fakeSysfs/devices/system/node/node0/cpu6",
"/fakeSysfs/devices/system/node/node0/cpu7",
"/fakeSysfs/devices/system/node/node0/cpu8",
},
"/fakeSysfs/devices/system/node/node1": {
"/fakeSysfs/devices/system/node/node0/cpu3",
"/fakeSysfs/devices/system/node/node0/cpu4",
"/fakeSysfs/devices/system/node/node0/cpu5",
"/fakeSysfs/devices/system/node/node0/cpu9",
"/fakeSysfs/devices/system/node/node0/cpu10",
"/fakeSysfs/devices/system/node/node0/cpu11",
},
}
sysFs.SetCPUsPaths(cpusPaths, nil)
coreThread := map[string]string{
"/fakeSysfs/devices/system/node/node0/cpu0": "0",
"/fakeSysfs/devices/system/node/node0/cpu1": "1",
"/fakeSysfs/devices/system/node/node0/cpu2": "2",
"/fakeSysfs/devices/system/node/node0/cpu3": "3",
"/fakeSysfs/devices/system/node/node0/cpu4": "4",
"/fakeSysfs/devices/system/node/node0/cpu5": "5",
"/fakeSysfs/devices/system/node/node0/cpu6": "0",
"/fakeSysfs/devices/system/node/node0/cpu7": "1",
"/fakeSysfs/devices/system/node/node0/cpu8": "2",
"/fakeSysfs/devices/system/node/node0/cpu9": "3",
"/fakeSysfs/devices/system/node/node0/cpu10": "4",
"/fakeSysfs/devices/system/node/node0/cpu11": "5",
}
sysFs.SetCoreThreads(coreThread, nil)
memTotal := "MemTotal: 32817192 kB"
sysFs.SetMemory(memTotal, nil)
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
&fakesysfs.FileInfo{EntryName: "hugepages-1048576kB"},
}
sysFs.SetHugePages(hugePages, nil)
hugePageNr := map[string]string{
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages": "1",
}
sysFs.SetHugePagesNr(hugePageNr, nil)
topology, numCores, err := GetTopology(sysFs)
assert.Nil(t, err)
assert.Equal(t, 12, numCores)
expected_topology := []info.Node{} expected_topology := []info.Node{}
numNodes := 2 numNodes := 2
numCoresPerNode := 3 numCoresPerNode := 3
@ -147,105 +197,93 @@ func TestTopology(t *testing.T) {
expected_topology = append(expected_topology, node) expected_topology = append(expected_topology, node)
} }
if !reflect.DeepEqual(topology, expected_topology) { assert.NotNil(t, reflect.DeepEqual(topology, expected_topology))
t.Errorf("Expected topology %+v, got %+v", expected_topology, topology)
}
} }
func TestTopologyWithSimpleCpuinfo(t *testing.T) { func TestTopologyEmptySysFs(t *testing.T) {
if isSystemZ() { machineArch = "" // overwrite package variable
t.Skip("systemZ has no topology info") _, _, err := GetTopology(&fakesysfs.FakeSysFs{})
} assert.NotNil(t, err)
}
func TestTopologyWithNodesWithoutCPU(t *testing.T) {
machineArch = "" // overwrite package variable
sysFs := &fakesysfs.FakeSysFs{} sysFs := &fakesysfs.FakeSysFs{}
c := sysfs.CacheInfo{ nodesPaths := []string{
Size: 32 * 1024, "/fakeSysfs/devices/system/node/node0",
Type: "unified", "/fakeSysfs/devices/system/node/node1",
Level: 1,
Cpus: 1,
} }
sysFs.SetCacheInfo(c) sysFs.SetNodesPaths(nodesPaths, nil)
topology, numCores, err := GetTopology(sysFs, "processor\t: 0\n")
if err != nil { memTotal := "MemTotal: 32817192 kB"
t.Errorf("Expected cpuinfo with no topology data to succeed.") sysFs.SetMemory(memTotal, nil)
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
&fakesysfs.FileInfo{EntryName: "hugepages-1048576kB"},
} }
node := info.Node{Id: 0} sysFs.SetHugePages(hugePages, nil)
core := info.Core{Id: 0}
core.Threads = append(core.Threads, 0) hugePageNr := map[string]string{
cache := info.Cache{ "/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "1",
Size: 32 * 1024, "/fakeSysfs/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages": "1",
Type: "unified", "/fakeSysfs/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages": "1",
Level: 1, "/fakeSysfs/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages": "1",
}
core.Caches = append(core.Caches, cache)
node.Cores = append(node.Cores, core)
// Copy over Memory from result. TODO(rjnagal): Use memory from fake.
node.Memory = topology[0].Memory
// Copy over HugePagesInfo from result. TODO(ohsewon): Use HugePagesInfo from fake.
node.HugePages = topology[0].HugePages
expected := []info.Node{node}
if !reflect.DeepEqual(topology, expected) {
t.Errorf("Expected topology %+v, got %+v", expected, topology)
}
if numCores != 1 {
t.Errorf("Expected 1 core, found %d", numCores)
} }
sysFs.SetHugePagesNr(hugePageNr, nil)
topology, numCores, err := GetTopology(sysFs)
assert.Nil(t, err)
assert.Equal(t, 0, numCores)
topologyJSON, err := json.Marshal(topology)
assert.Nil(t, err)
expectedTopology := `[
{
"caches": null,
"cores": null,
"hugepages": [
{
"num_pages": 1,
"page_size": 2048
},
{
"num_pages": 1,
"page_size": 1048576
}
],
"memory": 33604804608,
"node_id": 0
},
{
"caches": null,
"cores": null,
"hugepages": [
{
"num_pages": 1,
"page_size": 2048
},
{
"num_pages": 1,
"page_size": 1048576
}
],
"memory": 33604804608,
"node_id": 1
}
]
`
assert.JSONEq(t, expectedTopology, string(topologyJSON))
} }
func TestTopologyEmptyCpuinfo(t *testing.T) { func TestTopologyOnSystemZ(t *testing.T) {
if isSystemZ() { machineArch = "s390" // overwrite package variable
t.Skip("systemZ has no topology info") nodes, cores, err := GetTopology(&fakesysfs.FakeSysFs{})
} assert.Nil(t, err)
_, _, err := GetTopology(&fakesysfs.FakeSysFs{}, "") assert.Nil(t, nodes)
if err == nil { assert.NotNil(t, cores)
t.Errorf("Expected empty cpuinfo to fail.")
}
}
func TestTopologyCoreId(t *testing.T) {
val, _ := getCoreIdFromCpuBus("./testdata", 0)
if val != 0 {
t.Errorf("Expected core 0, found %d", val)
}
val, _ = getCoreIdFromCpuBus("./testdata", 9999)
if val != 8888 {
t.Errorf("Expected core 8888, found %d", val)
}
}
func TestTopologyNodeId(t *testing.T) {
val, _ := getNodeIdFromCpuBus("./testdata", 0)
if val != 0 {
t.Errorf("Expected core 0, found %d", val)
}
val, _ = getNodeIdFromCpuBus("./testdata", 9999)
if val != 1234 {
t.Errorf("Expected core 1234 , found %d", val)
}
}
func TestGetHugePagesInfo(t *testing.T) {
testPath := "./testdata/hugepages/"
expected := []info.HugePagesInfo{
{
NumPages: 1,
PageSize: 1048576,
},
{
NumPages: 2,
PageSize: 2048,
},
}
val, err := GetHugePagesInfo(testPath)
if err != nil {
t.Errorf("Failed to GetHugePagesInfo() for sample path %s: %v", testPath, err)
}
if !reflect.DeepEqual(expected, val) {
t.Errorf("Expected HugePagesInfo %+v, got %+v", expected, val)
}
} }
func TestMemoryInfo(t *testing.T) { func TestMemoryInfo(t *testing.T) {

View File

@ -15,6 +15,7 @@
package fakesysfs package fakesysfs
import ( import (
"fmt"
"os" "os"
"time" "time"
@ -53,6 +54,49 @@ func (self *FileInfo) Sys() interface{} {
type FakeSysFs struct { type FakeSysFs struct {
info FileInfo info FileInfo
cache sysfs.CacheInfo cache sysfs.CacheInfo
nodesPaths []string
nodePathErr error
cpusPaths map[string][]string
cpuPathErr error
coreThread map[string]string
coreIDErr error
memTotal string
memErr error
hugePages []os.FileInfo
hugePagesErr error
hugePagesNr map[string]string
hugePagesNrErr error
}
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) GetCoreID(coreIDPath string) (string, error) {
return self.coreThread[coreIDPath], self.coreIDErr
}
func (self *FakeSysFs) GetMemInfo(nodePath string) (string, error) {
return self.memTotal, self.memErr
}
func (self *FakeSysFs) GetHugePagesInfo(hugepagesDirectory string) ([]os.FileInfo, error) {
return self.hugePages, self.hugePagesErr
}
func (self *FakeSysFs) GetHugePagesNr(hugepagesDirectory string, hugePageName string) (string, error) {
hugePageFile := fmt.Sprintf("%s%s/%s", hugepagesDirectory, hugePageName, sysfs.HugePagesNrFile)
return self.hugePagesNr[hugePageFile], self.hugePagesNrErr
} }
func (self *FakeSysFs) GetBlockDevices() ([]os.FileInfo, error) { func (self *FakeSysFs) GetBlockDevices() ([]os.FileInfo, error) {
@ -105,6 +149,36 @@ func (self *FakeSysFs) SetCacheInfo(cache sysfs.CacheInfo) {
self.cache = cache self.cache = cache
} }
func (self *FakeSysFs) SetNodesPaths(paths []string, err error) {
self.nodesPaths = paths
self.nodePathErr = err
}
func (self *FakeSysFs) SetCPUsPaths(paths map[string][]string, err error) {
self.cpusPaths = paths
self.cpuPathErr = err
}
func (self *FakeSysFs) SetCoreThreads(coreThread map[string]string, err error) {
self.coreThread = coreThread
self.coreIDErr = err
}
func (self *FakeSysFs) SetMemory(memTotal string, err error) {
self.memTotal = memTotal
self.memErr = err
}
func (self *FakeSysFs) SetHugePages(hugePages []os.FileInfo, err error) {
self.hugePages = hugePages
self.hugePagesErr = err
}
func (self *FakeSysFs) SetHugePagesNr(hugePagesNr map[string]string, err error) {
self.hugePagesNr = hugePagesNr
self.hugePagesNrErr = err
}
func (self *FakeSysFs) SetEntryName(name string) { func (self *FakeSysFs) SetEntryName(name string) {
self.info.EntryName = name self.info.EntryName = name
} }

View File

@ -19,6 +19,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
) )
@ -30,6 +31,20 @@ const (
dmiDir = "/sys/class/dmi" dmiDir = "/sys/class/dmi"
ppcDevTree = "/proc/device-tree" ppcDevTree = "/proc/device-tree"
s390xDevTree = "/etc" // s390/s390x changes s390xDevTree = "/etc" // s390/s390x changes
hugePagesDirName = "hugepages"
coreIDFilePath = "/topology/core_id"
meminfoFile = "meminfo"
cpuDirPattern = "cpu*[0-9]"
nodeDirPattern = "node*[0-9]"
//HugePagesNrFile name of nr_hugepages file in sysfs
HugePagesNrFile = "nr_hugepages"
)
var (
nodeDir = "/sys/devices/system/node/"
) )
type CacheInfo struct { type CacheInfo struct {
@ -45,6 +60,18 @@ type CacheInfo struct {
// Abstracts the lowest level calls to sysfs. // Abstracts the lowest level calls to sysfs.
type SysFs interface { 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 physical core id for specified CPU
GetCoreID(coreIDFilePath string) (string, error)
// Get total memory for specified NUMA node
GetMemInfo(nodeDir string) (string, error)
// Get hugepages from specified directory
GetHugePagesInfo(hugePagesDirectory string) ([]os.FileInfo, error)
// Get hugepage_nr from specified directory
GetHugePagesNr(hugePagesDirectory string, hugePageName string) (string, error)
// Get directory information for available block devices. // Get directory information for available block devices.
GetBlockDevices() ([]os.FileInfo, error) GetBlockDevices() ([]os.FileInfo, error)
// Get Size of a given block device. // Get Size of a given block device.
@ -74,6 +101,47 @@ func NewRealSysFs() SysFs {
return &realSysFs{} return &realSysFs{}
} }
func (self *realSysFs) GetNodesPaths() ([]string, error) {
pathPattern := fmt.Sprintf("%s%s", nodeDir, nodeDirPattern)
return filepath.Glob(pathPattern)
}
func (self *realSysFs) GetCPUsPaths(nodePath string) ([]string, error) {
pathPattern := fmt.Sprintf("%s/%s", nodePath, cpuDirPattern)
return filepath.Glob(pathPattern)
}
func (self *realSysFs) GetCoreID(cpuPath string) (string, error) {
coreIDFilePath := fmt.Sprintf("%s%s", cpuPath, coreIDFilePath)
coreID, err := ioutil.ReadFile(coreIDFilePath)
if err != nil {
return "", err
}
return strings.TrimSpace(string(coreID)), err
}
func (self *realSysFs) GetMemInfo(nodePath string) (string, error) {
meminfoPath := fmt.Sprintf("%s/%s", nodePath, meminfoFile)
meminfo, err := ioutil.ReadFile(meminfoPath)
if err != nil {
return "", err
}
return strings.TrimSpace(string(meminfo)), err
}
func (self *realSysFs) GetHugePagesInfo(hugePagesDirectory string) ([]os.FileInfo, error) {
return ioutil.ReadDir(hugePagesDirectory)
}
func (self *realSysFs) GetHugePagesNr(hugepagesDirectory string, hugePageName string) (string, error) {
hugePageFilePath := fmt.Sprintf("%s%s/%s", hugepagesDirectory, hugePageName, HugePagesNrFile)
hugePageFile, err := ioutil.ReadFile(hugePageFilePath)
if err != nil {
return "", err
}
return strings.TrimSpace(string(hugePageFile)), err
}
func (self *realSysFs) GetBlockDevices() ([]os.FileInfo, error) { func (self *realSysFs) GetBlockDevices() ([]os.FileInfo, error) {
return ioutil.ReadDir(blockDir) return ioutil.ReadDir(blockDir)
} }

124
utils/sysfs/sysfs_test.go Normal file
View File

@ -0,0 +1,124 @@
// Copyright 2020 Google Inc. All Rights Reserved.
//
// 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 sysfs
import (
"github.com/stretchr/testify/assert"
"os"
"strconv"
"testing"
)
func TestGetNodes(t *testing.T) {
//overwrite global variable
nodeDir = "./testdata/"
sysFs := NewRealSysFs()
nodesDirs, err := sysFs.GetNodesPaths()
assert.Nil(t, err)
assert.Equal(t, 2, len(nodesDirs))
assert.Contains(t, nodesDirs, "testdata/node0")
assert.Contains(t, nodesDirs, "testdata/node1")
}
func TestGetNodesWithNonExistingDir(t *testing.T) {
//overwrite global variable
nodeDir = "./testdata/NonExistingDir/"
sysFs := NewRealSysFs()
nodesDirs, err := sysFs.GetNodesPaths()
assert.Nil(t, err)
assert.Equal(t, 0, len(nodesDirs))
}
func TestGetCPUsPaths(t *testing.T) {
sysFs := NewRealSysFs()
cpuDirs, err := sysFs.GetCPUsPaths("./testdata/node0")
assert.Nil(t, err)
assert.Equal(t, 2, len(cpuDirs))
assert.Contains(t, cpuDirs, "testdata/node0/cpu0")
assert.Contains(t, cpuDirs, "testdata/node0/cpu1")
}
func TestGetCPUsPathsFromNodeWithoutCPU(t *testing.T) {
sysFs := NewRealSysFs()
cpuDirs, err := sysFs.GetCPUsPaths("./testdata/node1")
assert.Nil(t, err)
assert.Equal(t, 0, len(cpuDirs))
}
func TestGetCoreID(t *testing.T) {
sysFs := NewRealSysFs()
rawCoreID, err := sysFs.GetCoreID("./testdata/node0/cpu0")
assert.Nil(t, err)
coreID, err := strconv.Atoi(rawCoreID)
assert.Nil(t, err)
assert.Equal(t, 0, coreID)
}
func TestGetCoreIDWhenFileIsMissing(t *testing.T) {
sysFs := NewRealSysFs()
rawCoreID, err := sysFs.GetCoreID("./testdata/node0/cpu1")
assert.NotNil(t, err)
assert.Equal(t, "", rawCoreID)
}
func TestGetMemInfo(t *testing.T) {
sysFs := NewRealSysFs()
memInfo, err := sysFs.GetMemInfo("./testdata/node0")
assert.Nil(t, err)
assert.Equal(t, "Node 0 MemTotal: 32817192 kB", memInfo)
}
func TestGetMemInfoWhenFileIsMissing(t *testing.T) {
sysFs := NewRealSysFs()
memInfo, err := sysFs.GetMemInfo("./testdata/node1")
assert.NotNil(t, err)
assert.Equal(t, "", memInfo)
}
func TestGetHugePagesInfo(t *testing.T) {
sysFs := NewRealSysFs()
hugePages, err := sysFs.GetHugePagesInfo("./testdata/node0/hugepages")
assert.Nil(t, err)
assert.Equal(t, 2, len(hugePages))
assert.Equal(t, "hugepages-1048576kB", hugePages[0].Name())
assert.Equal(t, "hugepages-2048kB", hugePages[1].Name())
}
func TestGetHugePagesInfoWhenDirIsMissing(t *testing.T) {
sysFs := NewRealSysFs()
hugePages, err := sysFs.GetHugePagesInfo("./testdata/node1/hugepages")
assert.NotNil(t, err)
assert.Equal(t, []os.FileInfo([]os.FileInfo(nil)), hugePages)
}
func TestGetHugePagesNr(t *testing.T) {
sysFs := NewRealSysFs()
rawHugePageNr, err := sysFs.GetHugePagesNr("./testdata/node0/hugepages/", "hugepages-1048576kB")
assert.Nil(t, err)
hugePageNr, err := strconv.Atoi(rawHugePageNr)
assert.Nil(t, err)
assert.Equal(t, 1, hugePageNr)
}
func TestGetHugePagesNrWhenFileIsMissing(t *testing.T) {
sysFs := NewRealSysFs()
rawHugePageNr, err := sysFs.GetHugePagesNr("./testdata/node1/hugepages/", "hugepages-1048576kB")
assert.NotNil(t, err)
assert.Equal(t, "", rawHugePageNr)
}

View File

@ -0,0 +1 @@
0

1
utils/sysfs/testdata/node0/meminfo vendored Normal file
View File

@ -0,0 +1 @@
Node 0 MemTotal: 32817192 kB

View File

@ -22,9 +22,22 @@ import (
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysfs"
"github.com/google/go-cmp/cmp"
"k8s.io/klog"
) )
var schedulerRegExp = regexp.MustCompile(`.*\[(.*)\].*`) var (
schedulerRegExp = regexp.MustCompile(`.*\[(.*)\].*`)
nodeDirRegExp = regexp.MustCompile("node/node(\\d*)")
cpuDirRegExp = regexp.MustCompile("/cpu(\\d*)")
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
)
const (
cacheLevel2 = 2
hugepagesDir = "hugepages/"
)
// Get information about block devices present on the system. // Get information about block devices present on the system.
// Uses the passed in system interface to retrieve the low level OS information. // Uses the passed in system interface to retrieve the low level OS information.
@ -133,6 +146,199 @@ func GetNetworkDevices(sysfs sysfs.SysFs) ([]info.NetInfo, error) {
return netDevices, nil return netDevices, nil
} }
// GetHugePagesInfo returns information about pre-allocated huge pages
// hugepagesDirectory should be top directory of hugepages
// Such as: /sys/kernel/mm/hugepages/
func GetHugePagesInfo(sysFs sysfs.SysFs, hugepagesDirectory string) ([]info.HugePagesInfo, error) {
var hugePagesInfo []info.HugePagesInfo
files, err := sysFs.GetHugePagesInfo(hugepagesDirectory)
if err != nil {
// treat as non-fatal since kernels and machine can be
// configured to disable hugepage support
return hugePagesInfo, nil
}
for _, st := range files {
nameArray := strings.Split(st.Name(), "-")
pageSizeArray := strings.Split(nameArray[1], "kB")
pageSize, err := strconv.ParseUint(string(pageSizeArray[0]), 10, 64)
if err != nil {
return hugePagesInfo, err
}
val, err := sysFs.GetHugePagesNr(hugepagesDirectory, st.Name())
if err != nil {
return hugePagesInfo, err
}
var numPages uint64
// we use sscanf as the file as a new-line that trips up ParseUint
// it returns the number of tokens successfully parsed, so if
// n != 1, it means we were unable to parse a number from the file
n, err := fmt.Sscanf(string(val), "%d", &numPages)
if err != nil || n != 1 {
return hugePagesInfo, fmt.Errorf("could not parse file nr_hugepage for %s, contents %q", st.Name(), string(val))
}
hugePagesInfo = append(hugePagesInfo, info.HugePagesInfo{
NumPages: numPages,
PageSize: pageSize,
})
}
return hugePagesInfo, nil
}
// GetNodesInfo returns information about NUMA nodes and their topology
func GetNodesInfo(sysFs sysfs.SysFs) ([]info.Node, int, error) {
nodes := []info.Node{}
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")
}
return nil, 0, err
}
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
}
node.Cores = cores
allLogicalCoresCount += logicalCoreCount
err = addCacheInfo(sysFs, &node)
if err != nil {
return nil, 0, err
}
node.Memory, err = getNodeMemInfo(sysFs, nodeDir)
if err != nil {
return nil, 0, err
}
hugepagesDirectory := fmt.Sprintf("%s/%s", nodeDir, hugepagesDir)
node.HugePages, err = GetHugePagesInfo(sysFs, hugepagesDirectory)
if err != nil {
return nil, 0, err
}
nodes = append(nodes, node)
}
return nodes, allLogicalCoresCount, err
}
// addCacheInfo adds information about cache for NUMA node
func addCacheInfo(sysFs sysfs.SysFs, node *info.Node) error {
for coreID, core := range node.Cores {
threadID := core.Threads[0] //get any thread for core
caches, err := GetCacheInfo(sysFs, threadID)
if err != nil {
return err
}
numThreadsPerCore := len(core.Threads)
numThreadsPerNode := len(node.Cores) * numThreadsPerCore
for _, cache := range caches {
c := info.Cache{
Size: cache.Size,
Level: cache.Level,
Type: cache.Type,
}
if cache.Cpus == numThreadsPerNode && cache.Level > cacheLevel2 {
// Add a node-level cache.
cacheFound := false
for _, nodeCache := range node.Caches {
if cmp.Equal(nodeCache, c) {
cacheFound = true
}
}
if !cacheFound {
node.Caches = append(node.Caches, c)
}
} else if cache.Cpus == numThreadsPerCore {
// Add core level cache
node.Cores[coreID].Caches = append(node.Cores[coreID].Caches, c)
}
// Ignore unknown caches.
}
}
return nil
}
// getNodeMemInfo returns information about total memory for NUMA node
func getNodeMemInfo(sysFs sysfs.SysFs, nodeDir string) (uint64, error) {
rawMem, err := sysFs.GetMemInfo(nodeDir)
if err != nil {
//Ignore if per-node info is not available.
klog.Warningf("Found node without memory information, nodeDir: %s", nodeDir)
return 0, nil
}
matches := memoryCapacityRegexp.FindStringSubmatch(rawMem)
if len(matches) != 2 {
return 0, fmt.Errorf("failed to match regexp in output: %q", string(rawMem))
}
memory, err := strconv.ParseUint(matches[1], 10, 64)
if err != nil {
return 0, err
}
memory = memory * 1024 // Convert to bytes
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
}
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)
}
rawPhysicalID, err := sysFs.GetCoreID(cpuDir)
if err != nil {
return nil, 0, err
}
physicalID, err := strconv.Atoi(rawPhysicalID)
if err != nil {
return nil, 0, err
}
coreIDx := -1
for id, core := range cores {
if core.Id == physicalID {
coreIDx = id
}
}
if coreIDx == -1 {
cores = append(cores, info.Core{})
coreIDx = len(cores) - 1
}
desiredCore := &cores[coreIDx]
desiredCore.Id = physicalID
if len(desiredCore.Threads) == 0 {
desiredCore.Threads = []int{cpuID}
} else {
desiredCore.Threads = append(desiredCore.Threads, cpuID)
}
}
return cores, len(cpuDirs), nil
}
// GetCacheInfo return information about a cache accessible from the given cpu thread
func GetCacheInfo(sysFs sysfs.SysFs, id int) ([]sysfs.CacheInfo, error) { func GetCacheInfo(sysFs sysfs.SysFs, id int) ([]sysfs.CacheInfo, error) {
caches, err := sysFs.GetCaches(id) caches, err := sysFs.GetCaches(id)
if err != nil { if err != nil {
@ -201,3 +407,15 @@ func getNetworkStats(name string, sysFs sysfs.SysFs) (info.InterfaceStats, error
func GetSystemUUID(sysFs sysfs.SysFs) (string, error) { func GetSystemUUID(sysFs sysfs.SysFs) (string, error) {
return sysFs.GetSystemUUID() return sysFs.GetSystemUUID()
} }
func getMatchedInt(rgx *regexp.Regexp, str string) (int, error) {
matches := rgx.FindStringSubmatch(str)
if len(matches) != 2 {
return 0, fmt.Errorf("failed to match regexp, str: %s", str)
}
valInt, err := strconv.Atoi(matches[1])
if err != nil {
return 0, err
}
return valInt, nil
}

View File

@ -15,13 +15,415 @@
package sysinfo package sysinfo
import ( import (
"encoding/json"
"fmt"
"os"
"testing" "testing"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysfs"
"github.com/google/cadvisor/utils/sysfs/fakesysfs" "github.com/google/cadvisor/utils/sysfs/fakesysfs"
"github.com/stretchr/testify/assert"
) )
func TestGetHugePagesInfo(t *testing.T) {
fakeSys := fakesysfs.FakeSysFs{}
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
&fakesysfs.FileInfo{EntryName: "hugepages-1048576kB"},
}
fakeSys.SetHugePages(hugePages, nil)
hugePageNr := map[string]string{
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages": "1",
}
fakeSys.SetHugePagesNr(hugePageNr, nil)
hugePagesInfo, err := GetHugePagesInfo(&fakeSys, "/fakeSysfs/devices/system/node/node0/hugepages/")
assert.Nil(t, err)
assert.Equal(t, 2, len(hugePagesInfo))
}
func TestGetHugePagesInfoWithHugePagesDirectory(t *testing.T) {
fakeSys := fakesysfs.FakeSysFs{}
hugePagesInfo, err := GetHugePagesInfo(&fakeSys, "/fakeSysfs/devices/system/node/node0/hugepages/")
assert.Nil(t, err)
assert.Equal(t, 0, len(hugePagesInfo))
}
func TestGetHugePagesInfoWithWrongDirName(t *testing.T) {
fakeSys := fakesysfs.FakeSysFs{}
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-abckB"},
}
fakeSys.SetHugePages(hugePages, nil)
hugePagesInfo, err := GetHugePagesInfo(&fakeSys, "/fakeSysfs/devices/system/node/node0/hugepages/")
assert.NotNil(t, err)
assert.Equal(t, 0, len(hugePagesInfo))
}
func TestGetHugePagesInfoWithReadingNrHugePagesError(t *testing.T) {
fakeSys := fakesysfs.FakeSysFs{}
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
&fakesysfs.FileInfo{EntryName: "hugepages-1048576kB"},
}
fakeSys.SetHugePages(hugePages, nil)
hugePageNr := map[string]string{
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages": "1",
}
fakeSys.SetHugePagesNr(hugePageNr, fmt.Errorf("Error in reading nr_hugepages"))
hugePagesInfo, err := GetHugePagesInfo(&fakeSys, "/fakeSysfs/devices/system/node/node0/hugepages/")
assert.NotNil(t, err)
assert.Equal(t, 0, len(hugePagesInfo))
}
func TestGetHugePagesInfoWithWrongNrHugePageValue(t *testing.T) {
fakeSys := fakesysfs.FakeSysFs{}
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
&fakesysfs.FileInfo{EntryName: "hugepages-1048576kB"},
}
fakeSys.SetHugePages(hugePages, nil)
hugePageNr := map[string]string{
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "*****",
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages": "1",
}
fakeSys.SetHugePagesNr(hugePageNr, nil)
hugePagesInfo, err := GetHugePagesInfo(&fakeSys, "/fakeSysfs/devices/system/node/node0/hugepages/")
assert.NotNil(t, err)
assert.Equal(t, 0, len(hugePagesInfo))
}
func TestGetNodesInfo(t *testing.T) {
fakeSys := &fakesysfs.FakeSysFs{}
c := sysfs.CacheInfo{
Size: 32 * 1024,
Type: "unified",
Level: 3,
Cpus: 2,
}
fakeSys.SetCacheInfo(c)
nodesPaths := []string{
"/fakeSysfs/devices/system/node/node0",
"/fakeSysfs/devices/system/node/node1",
}
fakeSys.SetNodesPaths(nodesPaths, nil)
cpusPaths := map[string][]string{
"/fakeSysfs/devices/system/node/node0": {
"/fakeSysfs/devices/system/node/node0/cpu0",
"/fakeSysfs/devices/system/node/node0/cpu1",
},
"/fakeSysfs/devices/system/node/node1": {
"/fakeSysfs/devices/system/node/node0/cpu2",
"/fakeSysfs/devices/system/node/node0/cpu3",
},
}
fakeSys.SetCPUsPaths(cpusPaths, nil)
coreThread := map[string]string{
"/fakeSysfs/devices/system/node/node0/cpu0": "0",
"/fakeSysfs/devices/system/node/node0/cpu1": "0",
"/fakeSysfs/devices/system/node/node0/cpu2": "1",
"/fakeSysfs/devices/system/node/node0/cpu3": "1",
}
fakeSys.SetCoreThreads(coreThread, nil)
memTotal := "MemTotal: 32817192 kB"
fakeSys.SetMemory(memTotal, nil)
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
}
fakeSys.SetHugePages(hugePages, nil)
hugePageNr := map[string]string{
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages": "1",
}
fakeSys.SetHugePagesNr(hugePageNr, 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)
expectedNodes := `
[
{
"node_id": 0,
"memory": 33604804608,
"hugepages": [
{
"page_size": 2048,
"num_pages": 1
}
],
"cores": [
{
"core_id": 0,
"thread_ids": [
0,
1
],
"caches": null
}
],
"caches": [
{
"size": 32768,
"type": "unified",
"level": 3
}
]
},
{
"node_id": 1,
"memory": 33604804608,
"hugepages": [
{
"page_size": 2048,
"num_pages": 1
}
],
"cores": [
{
"core_id": 1,
"thread_ids": [
2,
3
],
"caches": null
}
],
"caches": [
{
"size": 32768,
"type": "unified",
"level": 3
}
]
}
]
`
assert.JSONEq(t, expectedNodes, string(nodesJSON))
}
func TestGetNodesWithoutMemoryInfo(t *testing.T) {
fakeSys := &fakesysfs.FakeSysFs{}
c := sysfs.CacheInfo{
Size: 32 * 1024,
Type: "unified",
Level: 3,
Cpus: 2,
}
fakeSys.SetCacheInfo(c)
nodesPaths := []string{
"/fakeSysfs/devices/system/node/node0",
"/fakeSysfs/devices/system/node/node1",
}
fakeSys.SetNodesPaths(nodesPaths, nil)
cpusPaths := map[string][]string{
"/fakeSysfs/devices/system/node/node0": {
"/fakeSysfs/devices/system/node/node0/cpu0",
"/fakeSysfs/devices/system/node/node0/cpu1",
},
"/fakeSysfs/devices/system/node/node1": {
"/fakeSysfs/devices/system/node/node0/cpu2",
"/fakeSysfs/devices/system/node/node0/cpu3",
},
}
fakeSys.SetCPUsPaths(cpusPaths, nil)
coreThread := map[string]string{
"/fakeSysfs/devices/system/node/node0/cpu0": "0",
"/fakeSysfs/devices/system/node/node0/cpu1": "0",
"/fakeSysfs/devices/system/node/node0/cpu2": "1",
"/fakeSysfs/devices/system/node/node0/cpu3": "1",
}
fakeSys.SetCoreThreads(coreThread, nil)
hugePages := []os.FileInfo{
&fakesysfs.FileInfo{EntryName: "hugepages-2048kB"},
}
fakeSys.SetHugePages(hugePages, nil)
hugePageNr := map[string]string{
"/fakeSysfs/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages": "1",
"/fakeSysfs/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages": "1",
}
fakeSys.SetHugePagesNr(hugePageNr, nil)
nodes, cores, err := GetNodesInfo(fakeSys)
assert.NotNil(t, err)
assert.Equal(t, []info.Node([]info.Node(nil)), nodes)
assert.Equal(t, 0, cores)
}
func TestGetNodesInfoWithoutHugePagesInfo(t *testing.T) {
fakeSys := &fakesysfs.FakeSysFs{}
c := sysfs.CacheInfo{
Size: 32 * 1024,
Type: "unified",
Level: 2,
Cpus: 2,
}
fakeSys.SetCacheInfo(c)
nodesPaths := []string{
"/fakeSysfs/devices/system/node/node0",
"/fakeSysfs/devices/system/node/node1",
}
fakeSys.SetNodesPaths(nodesPaths, nil)
cpusPaths := map[string][]string{
"/fakeSysfs/devices/system/node/node0": {
"/fakeSysfs/devices/system/node/node0/cpu0",
"/fakeSysfs/devices/system/node/node0/cpu1",
},
"/fakeSysfs/devices/system/node/node1": {
"/fakeSysfs/devices/system/node/node0/cpu2",
"/fakeSysfs/devices/system/node/node0/cpu3",
},
}
fakeSys.SetCPUsPaths(cpusPaths, nil)
coreThread := map[string]string{
"/fakeSysfs/devices/system/node/node0/cpu0": "0",
"/fakeSysfs/devices/system/node/node0/cpu1": "0",
"/fakeSysfs/devices/system/node/node0/cpu2": "1",
"/fakeSysfs/devices/system/node/node0/cpu3": "1",
}
fakeSys.SetCoreThreads(coreThread, nil)
memTotal := "MemTotal: 32817192 kB"
fakeSys.SetMemory(memTotal, 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)
expectedNodes := `
[
{
"node_id": 0,
"memory": 33604804608,
"hugepages": null,
"cores": [
{
"core_id": 0,
"thread_ids": [
0,
1
],
"caches": [
{
"size": 32768,
"type": "unified",
"level": 2
}
]
}
],
"caches": null
},
{
"node_id": 1,
"memory": 33604804608,
"hugepages": null,
"cores": [
{
"core_id": 1,
"thread_ids": [
2,
3
],
"caches": [
{
"size": 32768,
"type": "unified",
"level": 2
}
]
}
],
"caches": null
}
]`
assert.JSONEq(t, expectedNodes, string(nodesJSON))
}
func TestGetNodeMemInfo(t *testing.T) {
fakeSys := &fakesysfs.FakeSysFs{}
memTotal := "MemTotal: 32817192 kB"
fakeSys.SetMemory(memTotal, nil)
mem, err := getNodeMemInfo(fakeSys, "/fakeSysfs/devices/system/node/node0")
assert.Nil(t, err)
assert.Equal(t, uint64(32817192*1024), mem)
}
func TestGetNodeMemInfoWithMissingMemTotaInMemInfo(t *testing.T) {
fakeSys := &fakesysfs.FakeSysFs{}
memTotal := "MemXXX: 32817192 kB"
fakeSys.SetMemory(memTotal, nil)
mem, err := getNodeMemInfo(fakeSys, "/fakeSysfs/devices/system/node/node0")
assert.NotNil(t, err)
assert.Equal(t, uint64(0), mem)
}
func TestGetNodeMemInfoWhenMemInfoMissing(t *testing.T) {
fakeSys := &fakesysfs.FakeSysFs{}
memTotal := ""
fakeSys.SetMemory(memTotal, fmt.Errorf("Cannot read meminfo file"))
mem, err := getNodeMemInfo(fakeSys, "/fakeSysfs/devices/system/node/node0")
assert.Nil(t, err)
assert.Equal(t, uint64(0), mem)
}
func TestGetCoresInfoWhenCoreIDIsNotDigit(t *testing.T) {
sysFs := &fakesysfs.FakeSysFs{}
nodesPaths := []string{
"/fakeSysfs/devices/system/node/node0",
}
sysFs.SetNodesPaths(nodesPaths, nil)
cpusPaths := map[string][]string{
"/fakeSysfs/devices/system/node/node0": {
"/fakeSysfs/devices/system/node/node0/cpu0",
},
}
sysFs.SetCPUsPaths(cpusPaths, nil)
coreThread := map[string]string{
"/fakeSysfs/devices/system/node/node0/cpu0": "abc",
}
sysFs.SetCoreThreads(coreThread, nil)
cores, coreID, err := getCoresInfo(sysFs, "/fakeSysfs/devices/system/node/node0")
assert.NotNil(t, err)
assert.Equal(t, []info.Core(nil), cores)
assert.Equal(t, 0, coreID)
}
func TestGetBlockDeviceInfo(t *testing.T) { func TestGetBlockDeviceInfo(t *testing.T) {
fakeSys := fakesysfs.FakeSysFs{} fakeSys := fakesysfs.FakeSysFs{}
disks, err := GetBlockDeviceInfo(&fakeSys) disks, err := GetBlockDeviceInfo(&fakeSys)