From e5200948f573ee107f60a759d687edc05ec75a4f Mon Sep 17 00:00:00 2001 From: Rohit Jnagal Date: Tue, 23 Sep 2014 06:28:11 +0000 Subject: [PATCH] Add a disk map to machine info. This is read once at start of cAdvisor. We can use this to report machine state as well as return logical name for block devices in UI. Signed-off-by: Rohit Jnagal (github: rjnagal) --- cadvisor.go | 8 ++- client/client_test.go | 10 +++- info/machine.go | 17 ++++++ manager/machine.go | 10 +++- manager/manager.go | 5 +- manager/manager_test.go | 10 +++- utils/sysfs/fakesysfs/fake.go | 64 ++++++++++++++++++++ utils/sysfs/sysfs.go | 108 ++++++++++++++++++++++++++++++++++ 8 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 utils/sysfs/fakesysfs/fake.go create mode 100644 utils/sysfs/sysfs.go diff --git a/cadvisor.go b/cadvisor.go index 7626a591..8a43a687 100644 --- a/cadvisor.go +++ b/cadvisor.go @@ -34,6 +34,7 @@ import ( "github.com/google/cadvisor/manager" "github.com/google/cadvisor/pages" "github.com/google/cadvisor/pages/static" + "github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/validate" ) @@ -65,7 +66,12 @@ func main() { glog.Fatalf("Failed to connect to database: %s", err) } - containerManager, err := manager.New(storageDriver) + sysFs, err := sysfs.NewRealSysFs() + if err != nil { + glog.Fatalf("Failed to create a system interface: %s", err) + } + + containerManager, err := manager.New(storageDriver, sysFs) if err != nil { glog.Fatalf("Failed to create a Container Manager: %s", err) } diff --git a/client/client_test.go b/client/client_test.go index 5b432104..bf4d9f76 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -58,7 +58,7 @@ func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, repl encoder := json.NewEncoder(w) encoder.Encode(replyObj) } else if r.URL.Path == "/api/v1.2/machine" { - fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`) + fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360, "disk_map":["8:0":{"name":"sda","major":8,"minor":0,"size":10737418240}]}`) } else { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "Page not found.") @@ -78,6 +78,14 @@ func TestGetMachineinfo(t *testing.T) { minfo := &info.MachineInfo{ NumCores: 8, MemoryCapacity: 31625871360, + DiskMap: map[string]info.DiskInfo{ + "8:0": info.DiskInfo{ + Name: "sda", + Major: 8, + Minor: 0, + Size: 10737418240, + }, + }, } client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, nil, minfo, t) if err != nil { diff --git a/info/machine.go b/info/machine.go index 0e73a8e0..7c76494b 100644 --- a/info/machine.go +++ b/info/machine.go @@ -22,6 +22,20 @@ type FsInfo struct { Capacity uint64 `json:"capacity"` } +type DiskInfo struct { + // device name + Name string `json:"name"` + + // Major number + Major uint64 `json:"major"` + + // Minor number + Minor uint64 `json:"minor"` + + // Size in bytes + Size uint64 `json:"size"` +} + type MachineInfo struct { // The number of cores in this machine. NumCores int `json:"num_cores"` @@ -31,6 +45,9 @@ type MachineInfo struct { // Filesystems on this machine. Filesystems []FsInfo `json:"filesystems"` + + // Disk map + DiskMap map[string]DiskInfo `json:"disk_map"` } type VersionInfo struct { diff --git a/manager/machine.go b/manager/machine.go index 8cf05f0d..3386dd66 100644 --- a/manager/machine.go +++ b/manager/machine.go @@ -27,12 +27,13 @@ import ( "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/fs" "github.com/google/cadvisor/info" + "github.com/google/cadvisor/utils/sysfs" ) var numCpuRegexp = regexp.MustCompile("processor\\t*: +[0-9]+") var memoryCapacityRegexp = regexp.MustCompile("MemTotal: *([0-9]+) kB") -func getMachineInfo() (*info.MachineInfo, error) { +func getMachineInfo(sysFs sysfs.SysFs) (*info.MachineInfo, error) { // Get the number of CPUs from /proc/cpuinfo. out, err := ioutil.ReadFile("/proc/cpuinfo") if err != nil { @@ -69,10 +70,17 @@ func getMachineInfo() (*info.MachineInfo, error) { return nil, err } + diskMap, err := sysfs.GetBlockDeviceInfo(sysFs) + if err != nil { + return nil, err + } + machineInfo := &info.MachineInfo{ NumCores: numCores, MemoryCapacity: memoryCapacity, + DiskMap: diskMap, } + for _, fs := range filesystems { machineInfo.Filesystems = append(machineInfo.Filesystems, info.FsInfo{fs.Device, fs.Capacity}) } diff --git a/manager/manager.go b/manager/manager.go index 26c4f351..bf9fa871 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -30,6 +30,7 @@ import ( "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/info" "github.com/google/cadvisor/storage" + "github.com/google/cadvisor/utils/sysfs" ) var globalHousekeepingInterval = flag.Duration("global_housekeeping_interval", 1*time.Minute, "Interval between global housekeepings") @@ -64,7 +65,7 @@ type Manager interface { } // New takes a driver and returns a new manager. -func New(driver storage.StorageDriver) (Manager, error) { +func New(driver storage.StorageDriver, sysfs sysfs.SysFs) (Manager, error) { if driver == nil { return nil, fmt.Errorf("nil storage driver!") } @@ -83,7 +84,7 @@ func New(driver storage.StorageDriver) (Manager, error) { cadvisorContainer: selfContainer, } - machineInfo, err := getMachineInfo() + machineInfo, err := getMachineInfo(sysfs) if err != nil { return nil, err } diff --git a/manager/manager_test.go b/manager/manager_test.go index 25712fd1..40d6c783 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -27,12 +27,14 @@ import ( "github.com/google/cadvisor/info" itest "github.com/google/cadvisor/info/test" stest "github.com/google/cadvisor/storage/test" + "github.com/google/cadvisor/utils/sysfs/fakesysfs" ) // TODO(vmarmol): Refactor these tests. func createManagerAndAddContainers( driver *stest.MockStorageDriver, + sysfs *fakesysfs.FakeSysFs, containers []string, f func(*container.MockContainerHandler), t *testing.T, @@ -41,7 +43,7 @@ func createManagerAndAddContainers( driver = &stest.MockStorageDriver{} } container.ClearContainerHandlerFactories() - mif, err := New(driver) + mif, err := New(driver, sysfs) if err != nil { t.Fatal(err) } @@ -81,8 +83,10 @@ func expectManagerWithContainers(containers []string, query *info.ContainerInfoR } driver := &stest.MockStorageDriver{} + sysfs := &fakesysfs.FakeSysFs{} m := createManagerAndAddContainers( driver, + sysfs, containers, func(h *container.MockContainerHandler) { cinfo := infosMap[h.Name] @@ -200,7 +204,7 @@ func TestDockerContainersInfo(t *testing.T) { } func TestNew(t *testing.T) { - manager, err := New(&stest.MockStorageDriver{}) + manager, err := New(&stest.MockStorageDriver{}, &fakesysfs.FakeSysFs{}) if err != nil { t.Fatalf("Expected manager.New to succeed: %s", err) } @@ -210,7 +214,7 @@ func TestNew(t *testing.T) { } func TestNewNilManager(t *testing.T) { - _, err := New(nil) + _, err := New(nil, nil) if err == nil { t.Fatalf("Expected nil manager to return error") } diff --git a/utils/sysfs/fakesysfs/fake.go b/utils/sysfs/fakesysfs/fake.go new file mode 100644 index 00000000..5af797c0 --- /dev/null +++ b/utils/sysfs/fakesysfs/fake.go @@ -0,0 +1,64 @@ +// Copyright 2014 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 fakesysfs + +import ( + "os" + "time" +) + +// If we extend sysfs to support more interfaces, it might be worth making this a mock instead of a fake. +type FileInfo struct { +} + +func (self *FileInfo) Name() string { + return "sda" +} + +func (self *FileInfo) Size() int64 { + return 1234567 +} + +func (self *FileInfo) Mode() os.FileMode { + return 0 +} + +func (self *FileInfo) ModTime() time.Time { + return time.Time{} +} + +func (self *FileInfo) IsDir() bool { + return true +} + +func (self *FileInfo) Sys() interface{} { + return nil +} + +type FakeSysFs struct { + info FileInfo +} + +func (self *FakeSysFs) GetBlockDevices() ([]os.FileInfo, error) { + return []os.FileInfo{&self.info}, nil +} + +func (self *FakeSysFs) GetBlockDeviceSize(name string) (string, error) { + return "1234567", nil +} + +func (self *FakeSysFs) GetBlockDeviceNumbers(name string) (string, error) { + return "8:0\n", nil +} diff --git a/utils/sysfs/sysfs.go b/utils/sysfs/sysfs.go new file mode 100644 index 00000000..01f39b9e --- /dev/null +++ b/utils/sysfs/sysfs.go @@ -0,0 +1,108 @@ +// Copyright 2014 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 ( + "fmt" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + + "github.com/google/cadvisor/info" +) + +const BlockDir = "/sys/block" + +// Abstracts the lowest level calls to sysfs. +type SysFs interface { + // Get directory information for available block devices. + GetBlockDevices() ([]os.FileInfo, error) + // Get Size of a given block device. + GetBlockDeviceSize(string) (string, error) + // Get device major:minor number string. + GetBlockDeviceNumbers(string) (string, error) +} + +type realSysFs struct{} + +func NewRealSysFs() (SysFs, error) { + return &realSysFs{}, nil +} + +func (self *realSysFs) GetBlockDevices() ([]os.FileInfo, error) { + return ioutil.ReadDir(BlockDir) +} + +func (self *realSysFs) GetBlockDeviceNumbers(name string) (string, error) { + dev, err := ioutil.ReadFile(path.Join(BlockDir, name, "/dev")) + if err != nil { + return "", err + } + return string(dev), nil +} + +func (self *realSysFs) GetBlockDeviceSize(name string) (string, error) { + size, err := ioutil.ReadFile(path.Join(BlockDir, name, "/size")) + if err != nil { + return "", err + } + return string(size), nil +} + +// Get information about block devices present on the system. +// Uses the passed in system interface to retrieve the low level OS information. +func GetBlockDeviceInfo(sysfs SysFs) (map[string]info.DiskInfo, error) { + disks, err := sysfs.GetBlockDevices() + if err != nil { + return nil, err + } + + diskMap := make(map[string]info.DiskInfo) + for _, disk := range disks { + name := disk.Name() + // Ignore loopback and ram devices. + if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "ram") { + continue + } + disk_info := info.DiskInfo{ + Name: name, + } + dev, err := sysfs.GetBlockDeviceNumbers(name) + if err != nil { + return nil, err + } + n, err := fmt.Sscanf(dev, "%d:%d", &disk_info.Major, &disk_info.Minor) + if err != nil || n != 2 { + return nil, fmt.Errorf("could not parse device numbers from %s for device %s", dev, name) + } + out, err := sysfs.GetBlockDeviceSize(name) + if err != nil { + return nil, err + } + // Remove trailing newline before conversion. + size, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) + if err != nil { + return nil, err + } + // size is in 512 bytes blocks. + disk_info.Size = size * 512 + + device := fmt.Sprintf("%d:%d", disk_info.Major, disk_info.Minor) + diskMap[device] = disk_info + } + return diskMap, nil +}