Merge pull request #384 from rjnagal/cpu
Add cache info to machine topology
This commit is contained in:
commit
aadbe8d986
@ -27,11 +27,22 @@ type Node struct {
|
|||||||
// Per-node memory
|
// Per-node memory
|
||||||
Memory uint64 `json:"memory"`
|
Memory uint64 `json:"memory"`
|
||||||
Cores []Core `json:"cores"`
|
Cores []Core `json:"cores"`
|
||||||
|
Caches []Cache `json:"caches"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Core struct {
|
type Core struct {
|
||||||
Id int `json:"core_id"`
|
Id int `json:"core_id"`
|
||||||
Threads []int `json:"thread_ids"`
|
Threads []int `json:"thread_ids"`
|
||||||
|
Caches []Cache `json:"caches"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
// Size of memory cache in bytes.
|
||||||
|
Size uint64 `json:"size"`
|
||||||
|
// Type of memory cache: data, instruction, or unified.
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Level (distance from cpus) in a multi-level cache hierarchy.
|
||||||
|
Level int `json:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Node) FindCore(id int) (bool, int) {
|
func (self *Node) FindCore(id int) (bool, int) {
|
||||||
@ -60,6 +71,16 @@ func (self *Node) AddThread(thread int, core int) {
|
|||||||
self.Cores[coreIdx].Threads = append(self.Cores[coreIdx].Threads, thread)
|
self.Cores[coreIdx].Threads = append(self.Cores[coreIdx].Threads, thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Node) AddNodeCache(c Cache) {
|
||||||
|
self.Caches = append(self.Caches, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Node) AddPerCoreCache(c Cache) {
|
||||||
|
for idx, _ := range self.Cores {
|
||||||
|
self.Cores[idx].Caches = append(self.Cores[idx].Caches, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type DiskInfo struct {
|
type DiskInfo struct {
|
||||||
// device name
|
// device name
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -128,7 +128,7 @@ func addNode(nodes *[]info.Node, id int) (int, error) {
|
|||||||
return idx, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTopology(cpuinfo string) ([]info.Node, int, error) {
|
func getTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
|
||||||
nodes := []info.Node{}
|
nodes := []info.Node{}
|
||||||
numCores := 0
|
numCores := 0
|
||||||
lastThread := -1
|
lastThread := -1
|
||||||
@ -177,6 +177,29 @@ func getTopology(cpuinfo string) ([]info.Node, int, error) {
|
|||||||
if numCores < 1 {
|
if numCores < 1 {
|
||||||
return nil, numCores, fmt.Errorf("could not detect any cores")
|
return nil, numCores, fmt.Errorf("could not detect any cores")
|
||||||
}
|
}
|
||||||
|
for idx, node := range nodes {
|
||||||
|
caches, err := sysfs.GetCacheInfo(sysFs, node.Cores[0].Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, fmt.Errorf("failed to get cache information for node %d: %v", node.Id, err)
|
||||||
|
}
|
||||||
|
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
|
return nodes, numCores, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +235,7 @@ func getMachineInfo(sysFs sysfs.SysFs) (*info.MachineInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
topology, numCores, err := getTopology(string(cpuinfo))
|
topology, numCores, err := getTopology(sysFs, string(cpuinfo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
|
"github.com/google/cadvisor/utils/sysfs"
|
||||||
|
"github.com/google/cadvisor/utils/sysfs/fakesysfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTopology(t *testing.T) {
|
func TestTopology(t *testing.T) {
|
||||||
@ -28,7 +30,15 @@ func TestTopology(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to read input test file %s", testfile)
|
t.Fatalf("unable to read input test file %s", testfile)
|
||||||
}
|
}
|
||||||
topology, numCores, err := getTopology(string(testcpuinfo))
|
sysFs := &fakesysfs.FakeSysFs{}
|
||||||
|
c := sysfs.CacheInfo{
|
||||||
|
Size: 32 * 1024,
|
||||||
|
Type: "unified",
|
||||||
|
Level: 1,
|
||||||
|
Cpus: 2,
|
||||||
|
}
|
||||||
|
sysFs.SetCacheInfo(c)
|
||||||
|
topology, numCores, err := getTopology(sysFs, string(testcpuinfo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to get topology for sample cpuinfo %s", string(testcpuinfo))
|
t.Errorf("failed to get topology for sample cpuinfo %s", string(testcpuinfo))
|
||||||
}
|
}
|
||||||
@ -40,12 +50,18 @@ func TestTopology(t *testing.T) {
|
|||||||
numNodes := 2
|
numNodes := 2
|
||||||
numCoresPerNode := 3
|
numCoresPerNode := 3
|
||||||
numThreads := 2
|
numThreads := 2
|
||||||
|
cache := info.Cache{
|
||||||
|
Size: 32 * 1024,
|
||||||
|
Type: "unified",
|
||||||
|
Level: 1,
|
||||||
|
}
|
||||||
for i := 0; i < numNodes; i++ {
|
for i := 0; i < numNodes; i++ {
|
||||||
node := info.Node{Id: i}
|
node := info.Node{Id: i}
|
||||||
// Copy over Memory from result. TODO(rjnagal): Use memory from fake.
|
// Copy over Memory from result. TODO(rjnagal): Use memory from fake.
|
||||||
node.Memory = topology[i].Memory
|
node.Memory = topology[i].Memory
|
||||||
for j := 0; j < numCoresPerNode; j++ {
|
for j := 0; j < numCoresPerNode; j++ {
|
||||||
core := info.Core{Id: i*numCoresPerNode + j}
|
core := info.Core{Id: i*numCoresPerNode + j}
|
||||||
|
core.Caches = append(core.Caches, cache)
|
||||||
for k := 0; k < numThreads; k++ {
|
for k := 0; k < numThreads; k++ {
|
||||||
core.Threads = append(core.Threads, k*numCoresPerNode*numNodes+core.Id)
|
core.Threads = append(core.Threads, k*numCoresPerNode*numNodes+core.Id)
|
||||||
}
|
}
|
||||||
@ -60,13 +76,27 @@ func TestTopology(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologyWithSimpleCpuinfo(t *testing.T) {
|
func TestTopologyWithSimpleCpuinfo(t *testing.T) {
|
||||||
topology, numCores, err := getTopology("processor\t: 0\n")
|
sysFs := &fakesysfs.FakeSysFs{}
|
||||||
|
c := sysfs.CacheInfo{
|
||||||
|
Size: 32 * 1024,
|
||||||
|
Type: "unified",
|
||||||
|
Level: 1,
|
||||||
|
Cpus: 1,
|
||||||
|
}
|
||||||
|
sysFs.SetCacheInfo(c)
|
||||||
|
topology, numCores, err := getTopology(sysFs, "processor\t: 0\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected cpuinfo with no topology data to succeed.")
|
t.Errorf("Expected cpuinfo with no topology data to succeed.")
|
||||||
}
|
}
|
||||||
node := info.Node{Id: 0}
|
node := info.Node{Id: 0}
|
||||||
core := info.Core{Id: 0}
|
core := info.Core{Id: 0}
|
||||||
core.Threads = append(core.Threads, 0)
|
core.Threads = append(core.Threads, 0)
|
||||||
|
cache := info.Cache{
|
||||||
|
Size: 32 * 1024,
|
||||||
|
Type: "unified",
|
||||||
|
Level: 1,
|
||||||
|
}
|
||||||
|
core.Caches = append(core.Caches, cache)
|
||||||
node.Cores = append(node.Cores, core)
|
node.Cores = append(node.Cores, core)
|
||||||
// Copy over Memory from result. TODO(rjnagal): Use memory from fake.
|
// Copy over Memory from result. TODO(rjnagal): Use memory from fake.
|
||||||
node.Memory = topology[0].Memory
|
node.Memory = topology[0].Memory
|
||||||
@ -80,7 +110,7 @@ func TestTopologyWithSimpleCpuinfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTopologyEmptyCpuinfo(t *testing.T) {
|
func TestTopologyEmptyCpuinfo(t *testing.T) {
|
||||||
_, _, err := getTopology("")
|
_, _, err := getTopology(&fakesysfs.FakeSysFs{}, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected empty cpuinfo to fail.")
|
t.Errorf("Expected empty cpuinfo to fail.")
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,17 @@ package fakesysfs
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/utils/sysfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we extend sysfs to support more interfaces, it might be worth making this a mock instead of a fake.
|
// If we extend sysfs to support more interfaces, it might be worth making this a mock instead of a fake.
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
|
EntryName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileInfo) Name() string {
|
func (self *FileInfo) Name() string {
|
||||||
return "sda"
|
return self.EntryName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FileInfo) Size() int64 {
|
func (self *FileInfo) Size() int64 {
|
||||||
@ -49,9 +52,11 @@ func (self *FileInfo) Sys() interface{} {
|
|||||||
|
|
||||||
type FakeSysFs struct {
|
type FakeSysFs struct {
|
||||||
info FileInfo
|
info FileInfo
|
||||||
|
cache sysfs.CacheInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FakeSysFs) GetBlockDevices() ([]os.FileInfo, error) {
|
func (self *FakeSysFs) GetBlockDevices() ([]os.FileInfo, error) {
|
||||||
|
self.info.EntryName = "sda"
|
||||||
return []os.FileInfo{&self.info}, nil
|
return []os.FileInfo{&self.info}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,3 +67,16 @@ func (self *FakeSysFs) GetBlockDeviceSize(name string) (string, error) {
|
|||||||
func (self *FakeSysFs) GetBlockDeviceNumbers(name string) (string, error) {
|
func (self *FakeSysFs) GetBlockDeviceNumbers(name string) (string, error) {
|
||||||
return "8:0\n", nil
|
return "8:0\n", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *FakeSysFs) GetCaches(id int) ([]os.FileInfo, error) {
|
||||||
|
self.info.EntryName = "index0"
|
||||||
|
return []os.FileInfo{&self.info}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FakeSysFs) GetCacheInfo(cpu int, cache string) (sysfs.CacheInfo, error) {
|
||||||
|
return self.cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FakeSysFs) SetCacheInfo(cache sysfs.CacheInfo) {
|
||||||
|
self.cache = cache
|
||||||
|
}
|
||||||
|
@ -25,7 +25,10 @@ import (
|
|||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
)
|
)
|
||||||
|
|
||||||
const BlockDir = "/sys/block"
|
const (
|
||||||
|
blockDir = "/sys/block"
|
||||||
|
cacheDir = "/sys/devices/system/cpu/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
// Abstracts the lowest level calls to sysfs.
|
// Abstracts the lowest level calls to sysfs.
|
||||||
type SysFs interface {
|
type SysFs interface {
|
||||||
@ -35,6 +38,11 @@ type SysFs interface {
|
|||||||
GetBlockDeviceSize(string) (string, error)
|
GetBlockDeviceSize(string) (string, error)
|
||||||
// Get device major:minor number string.
|
// Get device major:minor number string.
|
||||||
GetBlockDeviceNumbers(string) (string, error)
|
GetBlockDeviceNumbers(string) (string, error)
|
||||||
|
|
||||||
|
// Get directory information for available caches accessible to given cpu.
|
||||||
|
GetCaches(id int) ([]os.FileInfo, error)
|
||||||
|
// Get information for a cache accessible from the given cpu.
|
||||||
|
GetCacheInfo(cpu int, cache string) (CacheInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type realSysFs struct{}
|
type realSysFs struct{}
|
||||||
@ -44,11 +52,11 @@ func NewRealSysFs() (SysFs, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *realSysFs) GetBlockDevices() ([]os.FileInfo, error) {
|
func (self *realSysFs) GetBlockDevices() ([]os.FileInfo, error) {
|
||||||
return ioutil.ReadDir(BlockDir)
|
return ioutil.ReadDir(blockDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *realSysFs) GetBlockDeviceNumbers(name string) (string, error) {
|
func (self *realSysFs) GetBlockDeviceNumbers(name string) (string, error) {
|
||||||
dev, err := ioutil.ReadFile(path.Join(BlockDir, name, "/dev"))
|
dev, err := ioutil.ReadFile(path.Join(blockDir, name, "/dev"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -56,13 +64,59 @@ func (self *realSysFs) GetBlockDeviceNumbers(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *realSysFs) GetBlockDeviceSize(name string) (string, error) {
|
func (self *realSysFs) GetBlockDeviceSize(name string) (string, error) {
|
||||||
size, err := ioutil.ReadFile(path.Join(BlockDir, name, "/size"))
|
size, err := ioutil.ReadFile(path.Join(blockDir, name, "/size"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(size), nil
|
return string(size), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *realSysFs) GetCaches(id int) ([]os.FileInfo, error) {
|
||||||
|
cpuPath := fmt.Sprintf("%s%d/cache", cacheDir, id)
|
||||||
|
return ioutil.ReadDir(cpuPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *realSysFs) GetCacheInfo(id int, name string) (CacheInfo, error) {
|
||||||
|
cachePath := fmt.Sprintf("%s%d/cache/%s", cacheDir, id, name)
|
||||||
|
out, err := ioutil.ReadFile(path.Join(cachePath, "/size"))
|
||||||
|
if err != nil {
|
||||||
|
return CacheInfo{}, err
|
||||||
|
}
|
||||||
|
var size uint64
|
||||||
|
n, err := fmt.Sscanf(string(out), "%dK", &size)
|
||||||
|
if err != nil || n != 1 {
|
||||||
|
return CacheInfo{}, err
|
||||||
|
}
|
||||||
|
// convert to bytes
|
||||||
|
size = size * 1024
|
||||||
|
out, err = ioutil.ReadFile(path.Join(cachePath, "/level"))
|
||||||
|
if err != nil {
|
||||||
|
return CacheInfo{}, err
|
||||||
|
}
|
||||||
|
var level int
|
||||||
|
n, err = fmt.Sscanf(string(out), "%d", &level)
|
||||||
|
if err != nil || n != 1 {
|
||||||
|
return CacheInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err = ioutil.ReadFile(path.Join(cachePath, "/type"))
|
||||||
|
if err != nil {
|
||||||
|
return CacheInfo{}, err
|
||||||
|
}
|
||||||
|
cacheType := strings.TrimSpace(string(out))
|
||||||
|
out, err = ioutil.ReadFile(path.Join(cachePath, "/shared_cpu_list"))
|
||||||
|
if err != nil {
|
||||||
|
return CacheInfo{}, err
|
||||||
|
}
|
||||||
|
cpus := strings.Split(string(out), ",")
|
||||||
|
return CacheInfo{
|
||||||
|
Size: size,
|
||||||
|
Level: level,
|
||||||
|
Type: cacheType,
|
||||||
|
Cpus: len(cpus),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
func GetBlockDeviceInfo(sysfs SysFs) (map[string]info.DiskInfo, error) {
|
func GetBlockDeviceInfo(sysfs SysFs) (map[string]info.DiskInfo, error) {
|
||||||
@ -106,3 +160,34 @@ func GetBlockDeviceInfo(sysfs SysFs) (map[string]info.DiskInfo, error) {
|
|||||||
}
|
}
|
||||||
return diskMap, nil
|
return diskMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CacheInfo struct {
|
||||||
|
// size in bytes
|
||||||
|
Size uint64
|
||||||
|
// cache type - instruction, data, unified
|
||||||
|
Type string
|
||||||
|
// distance from cpus in a multi-level hierarchy
|
||||||
|
Level int
|
||||||
|
// number of cpus that can access this cache.
|
||||||
|
Cpus int
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCacheInfo(sysfs SysFs, id int) ([]CacheInfo, error) {
|
||||||
|
caches, err := sysfs.GetCaches(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := []CacheInfo{}
|
||||||
|
for _, cache := range caches {
|
||||||
|
if !strings.HasPrefix(cache.Name(), "index") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cacheInfo, err := sysfs.GetCacheInfo(id, cache.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info = append(info, cacheInfo)
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user