From b8ed0bd0e3431bb2eb472801ccaaca42d370e272 Mon Sep 17 00:00:00 2001 From: Abin Shahab Date: Tue, 14 Oct 2014 23:10:22 +0000 Subject: [PATCH] Mounted partitions space usage metrics per container This computes the space usage for mounted partitions. It takes in a list of mounted partitions from containerHints and computes the device's disk usage(so each mount must be a separate partition). This is useful for users who mount partitions on containers and store most of the container's persistent data on those partitions. --- container/raw/container_hints.go | 10 ++++-- container/raw/container_hints_test.go | 19 +++++++++++ container/raw/handler.go | 49 +++++++++++++++++++++------ fs/fs.go | 30 ++++++++++------ fs/types.go | 3 ++ 5 files changed, 87 insertions(+), 24 deletions(-) diff --git a/container/raw/container_hints.go b/container/raw/container_hints.go index b1ea21c1..e63d1441 100644 --- a/container/raw/container_hints.go +++ b/container/raw/container_hints.go @@ -32,8 +32,14 @@ type containerHints struct { } type containerHint struct { - FullName string `json:"full_path,omitempty"` - NetworkInterface *networkInterface `json:"network_interface,omitempty"` + FullName string `json:"full_path,omitempty"` + NetworkInterface *networkInterface `json:"network_interface,omitempty"` + Mounts []mount `json:"mounts,omitempty"` +} + +type mount struct { + HostDir string `json:"host-dir,omitempty"` + ContainerDir string `json:"container-dir,omitempty"` } type networkInterface struct { diff --git a/container/raw/container_hints_test.go b/container/raw/container_hints_test.go index f885f6ac..8ba35837 100644 --- a/container/raw/container_hints_test.go +++ b/container/raw/container_hints_test.go @@ -15,6 +15,25 @@ func TestGetContainerHintsFromFile(t *testing.T) { cHints.AllHosts[0].NetworkInterface.VethChild != "eth1" { t.Errorf("Cannot find network interface in %s", cHints) } + + var mountDirs [5]string + for i, mountDir := range cHints.AllHosts[0].Mounts { + mountDirs[i] = mountDir.HostDir + } + + correctMountDirs := [...]string{ + "/var/run/nm-sdc1", + "/var/run/nm-sdb3", + "/var/run/nm-sda3", + "/var/run/netns/root", + "/var/run/openvswitch/db.sock", + } + + for i, mountDir := range cHints.AllHosts[0].Mounts { + if correctMountDirs[i] != mountDir.HostDir { + t.Errorf("Cannot find mount %s in %s", mountDir.HostDir, cHints) + } + } } func TestFileNotExist(t *testing.T) { diff --git a/container/raw/handler.go b/container/raw/handler.go index 007a0fc9..c8eee8e8 100644 --- a/container/raw/handler.go +++ b/container/raw/handler.go @@ -45,6 +45,7 @@ type rawContainerHandler struct { watches map[string]struct{} fsInfo fs.FsInfo networkInterface *networkInterface + mounts []mount } func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { @@ -57,9 +58,11 @@ func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, mac return nil, err } var networkInterface *networkInterface + var mounts []mount for _, container := range cHints.AllHosts { if name == container.FullName { networkInterface = container.NetworkInterface + mounts = container.Mounts break } } @@ -74,7 +77,8 @@ func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, mac stopWatcher: make(chan error), watches: make(map[string]struct{}), fsInfo: fsInfo, - networkInterface: networkInterface, + networkInterface: networkInterface, + mounts: mounts, }, nil } @@ -164,7 +168,7 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { } // Fs. - if self.name == "/" { + if self.name == "/" || self.mounts != nil { spec.HasFilesystem = true } @@ -175,6 +179,34 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { return spec, nil } +func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error { + + // Get Filesystem information only for the root cgroup. + if self.name == "/" { + filesystems, err := self.fsInfo.GetGlobalFsInfo() + if err != nil { + return err + } + for _, fs := range filesystems { + stats.Filesystem = append(stats.Filesystem, info.FsStats{fs.Device, fs.Capacity, fs.Capacity - fs.Free}) + } + } else if self.mounts != nil { + var mountSet map[string]bool + mountSet = make(map[string]bool) + for _, mount := range self.mounts { + mountSet[mount.HostDir] = true + } + filesystems, err := self.fsInfo.GetFsInfoForPath(mountSet) + if err != nil { + return err + } + for _, fs := range filesystems { + stats.Filesystem = append(stats.Filesystem, info.FsStats{fs.Device, fs.Capacity, fs.Capacity - fs.Free}) + } + } + return nil +} + func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { state := dockerlibcontainer.State{} if self.networkInterface != nil { @@ -191,15 +223,10 @@ func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { if err != nil { return nil, err } - // Get Filesystem information only for the root cgroup. - if self.name == "/" { - filesystems, err := self.fsInfo.GetGlobalFsInfo() - if err != nil { - return nil, err - } - for _, fs := range filesystems { - stats.Filesystem = append(stats.Filesystem, info.FsStats{fs.Device, fs.Capacity, fs.Capacity - fs.Free}) - } + + err = self.getFsStats(stats) + if err != nil { + return nil, err } return stats, nil diff --git a/fs/fs.go b/fs/fs.go index b93b519a..b1830ba9 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -50,25 +50,33 @@ func NewFsInfo() (FsInfo, error) { return &RealFsInfo{partitions}, nil } -func (self *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) { +func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]bool) ([]Fs, error) { filesystems := make([]Fs, 0) + deviceSet := make(map[string]bool) for device, partition := range self.partitions { - total, free, err := getVfsStats(partition.mountpoint) - if err != nil { - glog.Errorf("Statvfs failed. Error: %v", err) - } else { - deviceInfo := DeviceInfo{ - Device: device, - Major: uint(partition.major), - Minor: uint(partition.minor), + if mountSet == nil || mountSet[partition.mountpoint] == true { + total, free, err := getVfsStats(partition.mountpoint) + if err != nil { + glog.Errorf("Statvfs failed. Error: %v", err) + } else if deviceSet[device] == false { + deviceSet[device] = true + deviceInfo := DeviceInfo{ + Device: device, + Major: uint(partition.major), + Minor: uint(partition.minor), + } + fs := Fs{deviceInfo, total, free} + filesystems = append(filesystems, fs) } - fs := Fs{deviceInfo, total, free} - filesystems = append(filesystems, fs) } } return filesystems, nil } +func (self *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) { + return self.GetFsInfoForPath(nil) +} + func major(devNumber uint64) uint { return uint((devNumber >> 8) & 0xfff) } diff --git a/fs/types.go b/fs/types.go index 14592aff..c27fe99a 100644 --- a/fs/types.go +++ b/fs/types.go @@ -16,6 +16,9 @@ type FsInfo interface { // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems on the host. GetGlobalFsInfo() ([]Fs, error) + // Returns capacity and free space, in bytes, of the set of mounts passed. + GetFsInfoForPath(mountSet map[string]bool) ([]Fs, error) + // Returns number of bytes occupied by 'dir'. GetDirUsage(dir string) (uint64, error)