Skip to content

Commit

Permalink
Merge pull request #2414 from katarzyna-z/kk-machine-api-cores-sockets
Browse files Browse the repository at this point in the history
Extending machine API by number of physical cpu cores and cpu sockets
  • Loading branch information
dashpole authored Mar 6, 2020
2 parents c470c61 + a589057 commit 7152b60
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 21 deletions.
6 changes: 6 additions & 0 deletions info/v1/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ type MachineInfo struct {
// The number of cores in this machine.
NumCores int `json:"num_cores"`

// The number of physical cores in this machine.
NumPhysicalCores int `json:"num_physical_cores"`

// The number of cpu sockets in this machine.
NumSockets int `json:"num_sockets"`

// Maximum clock speed for the cores, in KHz.
CpuFrequency uint64 `json:"cpu_frequency_khz"`

Expand Down
28 changes: 15 additions & 13 deletions machine/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,21 @@ func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.Mach
instanceID := realCloudInfo.GetInstanceID()

machineInfo := &info.MachineInfo{
NumCores: numCores,
CpuFrequency: clockSpeed,
MemoryCapacity: memoryCapacity,
HugePages: hugePagesInfo,
DiskMap: diskMap,
NetworkDevices: netDevices,
Topology: topology,
MachineID: getInfoFromFiles(filepath.Join(rootFs, *machineIdFilePath)),
SystemUUID: systemUUID,
BootID: getInfoFromFiles(filepath.Join(rootFs, *bootIdFilePath)),
CloudProvider: cloudProvider,
InstanceType: instanceType,
InstanceID: instanceID,
NumCores: numCores,
NumPhysicalCores: GetPhysicalCores(cpuinfo),
NumSockets: GetSockets(cpuinfo),
CpuFrequency: clockSpeed,
MemoryCapacity: memoryCapacity,
HugePages: hugePagesInfo,
DiskMap: diskMap,
NetworkDevices: netDevices,
Topology: topology,
MachineID: getInfoFromFiles(filepath.Join(rootFs, *machineIdFilePath)),
SystemUUID: systemUUID,
BootID: getInfoFromFiles(filepath.Join(rootFs, *bootIdFilePath)),
CloudProvider: cloudProvider,
InstanceType: instanceType,
InstanceID: instanceID,
}

for i := range filesystems {
Expand Down
80 changes: 72 additions & 8 deletions machine/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,50 @@ import (

var (
cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`)
coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`)
nodeRegExp = regexp.MustCompile(`^physical id\s*:\s*([0-9]+)$`)
coreRegExp = regexp.MustCompile(`(?m)^core id\s*:\s*([0-9]+)$`)
nodeRegExp = regexp.MustCompile(`(?m)^physical id\s*:\s*([0-9]+)$`)
nodeBusRegExp = regexp.MustCompile(`^node([0-9]+)$`)
// Power systems have a different format so cater for both
cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
swapCapacityRegexp = regexp.MustCompile(`SwapTotal:\s*([0-9]+) kB`)

cpuBusPath = "/sys/bus/cpu/devices/"
)

const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
const cpuBusPath = "/sys/bus/cpu/devices/"
const nodePath = "/sys/devices/system/node"
const sysFsCPUCoreID = "core_id"
const sysFsCPUPhysicalPackageID = "physical_package_id"
const sysFsCPUTopology = "topology"

// GetPhysicalCores returns number of CPU cores reading /proc/cpuinfo file or if needed information from sysfs cpu path
func GetPhysicalCores(procInfo []byte) int {
numCores := getUniqueMatchesCount(string(procInfo), coreRegExp)
if numCores == 0 {
// read number of cores from /sys/bus/cpu/devices/cpu*/topology/core_id to deal with processors
// for which 'core id' is not available in /proc/cpuinfo
numCores = getUniqueCPUPropertyCount(cpuBusPath, sysFsCPUCoreID)
}
if numCores == 0 {
klog.Errorf("Cannot read number of physical cores correctly, number of cores set to %d", numCores)
}
return numCores
}

// GetSockets returns number of CPU sockets reading /proc/cpuinfo file or if needed information from sysfs cpu path
func GetSockets(procInfo []byte) int {
numSocket := getUniqueMatchesCount(string(procInfo), nodeRegExp)
if numSocket == 0 {
// read number of sockets from /sys/bus/cpu/devices/cpu*/topology/physical_package_id to deal with processors
// for which 'physical id' is not available in /proc/cpuinfo
numSocket = getUniqueCPUPropertyCount(cpuBusPath, sysFsCPUPhysicalPackageID)
}
if numSocket == 0 {
klog.Errorf("Cannot read number of sockets correctly, number of sockets set to %d", numSocket)
}
return numSocket
}

// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
func GetClockSpeed(procInfo []byte) (uint64, error) {
Expand Down Expand Up @@ -132,11 +164,11 @@ func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
return m * 1024, err
}

/* Look for sysfs cpu path containing core_id */
/* Such as: sys/bus/cpu/devices/cpu0/topology/core_id */
// 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, "core_id")
file := filepath.Join(path, sysFsCPUCoreID)

num, err := ioutil.ReadFile(file)
if err != nil {
Expand All @@ -156,8 +188,30 @@ func getCoreIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
return int(coreId), nil
}

/* Look for sysfs cpu path containing node id */
/* Such as: /sys/bus/cpu/devices/cpu0/node%d */
// 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
func getUniqueCPUPropertyCount(cpuBusPath string, propertyName string) int {
pathPattern := cpuBusPath + "cpu*[0-9]"
sysCPUPaths, err := filepath.Glob(pathPattern)
if err != nil {
klog.Errorf("Cannot find files matching pattern (pathPattern: %s), number of unique %s set to 0", pathPattern, propertyName)
return 0
}
uniques := make(map[string]bool)
for _, sysCPUPath := range sysCPUPaths {
propertyPath := filepath.Join(sysCPUPath, sysFsCPUTopology, propertyName)
propertyVal, err := ioutil.ReadFile(propertyPath)
if err != nil {
klog.Errorf("Cannot open %s, number of unique %s set to 0", propertyPath, propertyName)
return 0
}
uniques[string(propertyVal)] = true
}
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))

Expand Down Expand Up @@ -354,6 +408,16 @@ func extractValue(s string, r *regexp.Regexp) (bool, int, error) {
return false, -1, nil
}

// getUniqueMatchesCount returns number of unique matches in given argument using provided regular expression
func getUniqueMatchesCount(s string, r *regexp.Regexp) int {
matches := r.FindAllString(s, -1)
uniques := make(map[string]bool)
for _, match := range matches {
uniques[match] = true
}
return len(uniques)
}

func findNode(nodes []info.Node, id int) (bool, int) {
for i, n := range nodes {
if n.Id == id {
Expand Down
1 change: 1 addition & 0 deletions machine/testdata/cpu0/topology/physical_package_id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
1 change: 1 addition & 0 deletions machine/testdata/cpu9999/topology/physical_package_id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
43 changes: 43 additions & 0 deletions machine/testdata/cpuinfo_arm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
processor : 0
model name : ARMv7 Processor rev 4 (v7l)
BogoMIPS : 76.80
Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 4

processor : 1
model name : ARMv7 Processor rev 4 (v7l)
BogoMIPS : 76.80
Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 4

processor : 2
model name : ARMv7 Processor rev 4 (v7l)
BogoMIPS : 76.80
Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 4

processor : 3
model name : ARMv7 Processor rev 4 (v7l)
BogoMIPS : 76.80
Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 4

Hardware : BCM2835
Revision : 0000
Serial : 00000000d0a71bfd
Empty file.
71 changes: 71 additions & 0 deletions machine/topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,79 @@ import (
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/sysfs"
"github.com/google/cadvisor/utils/sysfs/fakesysfs"
"github.com/stretchr/testify/assert"
)

func TestPhysicalCores(t *testing.T) {
testfile := "./testdata/cpuinfo"

testcpuinfo, err := ioutil.ReadFile(testfile)
assert.Nil(t, err)
assert.NotNil(t, testcpuinfo)

numPhysicalCores := GetPhysicalCores(testcpuinfo)
assert.Equal(t, 6, numPhysicalCores)
}

func TestPhysicalCoresReadingFromCpuBus(t *testing.T) {
cpuBusPath = "./testdata/" // overwriting global variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without core id

testcpuinfo, err := ioutil.ReadFile(testfile)
assert.Nil(t, err)
assert.NotNil(t, testcpuinfo)

numPhysicalCores := GetPhysicalCores(testcpuinfo)
assert.Equal(t, 2, numPhysicalCores)
}

func TestPhysicalCoresFromWrongSysFs(t *testing.T) {
cpuBusPath = "./testdata/wrongsysfs" // overwriting global variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without core id

testcpuinfo, err := ioutil.ReadFile(testfile)
assert.Nil(t, err)
assert.NotNil(t, testcpuinfo)

numPhysicalCores := GetPhysicalCores(testcpuinfo)
assert.Equal(t, 0, numPhysicalCores)
}

func TestSockets(t *testing.T) {
testfile := "./testdata/cpuinfo"

testcpuinfo, err := ioutil.ReadFile(testfile)
assert.Nil(t, err)
assert.NotNil(t, testcpuinfo)

numSockets := GetSockets(testcpuinfo)
assert.Equal(t, 2, numSockets)
}

func TestSocketsReadingFromCpuBus(t *testing.T) {
cpuBusPath = "./testdata/wrongsysfs" // overwriting global variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without physical id

testcpuinfo, err := ioutil.ReadFile(testfile)
assert.Nil(t, err)
assert.NotNil(t, testcpuinfo)

numSockets := GetSockets(testcpuinfo)
assert.Equal(t, 0, numSockets)
}

func TestSocketsReadingFromWrongSysFs(t *testing.T) {
cpuBusPath = "./testdata/" // overwriting global variable to mock sysfs
testfile := "./testdata/cpuinfo_arm" // mock cpuinfo without physical id

testcpuinfo, err := ioutil.ReadFile(testfile)
assert.Nil(t, err)
assert.NotNil(t, testcpuinfo)

numSockets := GetSockets(testcpuinfo)
assert.Equal(t, 1, numSockets)
}

func TestTopology(t *testing.T) {
if runtime.GOARCH != "amd64" {
t.Skip("cpuinfo testdata is for amd64")
Expand Down

0 comments on commit 7152b60

Please sign in to comment.