From 946b18f789f5f6866609ffc7cceead36c196464a Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Sat, 27 Sep 2014 00:31:28 +0000 Subject: [PATCH 1/3] Adding filesystem usage information to MachineInfo. --- container/raw/handler.go | 21 ++++++++++--- fs/fs.go | 66 ++++++++++++++++++++++++++++++++++++++++ fs/statvfs.c | 23 ++++++++++++++ fs/types.go | 12 ++++++++ info/container.go | 4 +++ 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 fs/fs.go create mode 100644 fs/statvfs.c create mode 100644 fs/types.go diff --git a/container/raw/handler.go b/container/raw/handler.go index d51eaa8a..cc1e7002 100644 --- a/container/raw/handler.go +++ b/container/raw/handler.go @@ -24,10 +24,11 @@ import ( "code.google.com/p/go.exp/inotify" "github.com/docker/libcontainer/cgroups" - "github.com/docker/libcontainer/cgroups/fs" + cgroup_fs "github.com/docker/libcontainer/cgroups/fs" "github.com/golang/glog" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/fs" "github.com/google/cadvisor/info" "github.com/google/cadvisor/utils" ) @@ -40,6 +41,7 @@ type rawContainerHandler struct { watcher *inotify.Watcher stopWatcher chan error watches map[string]struct{} + fsInfo fs.FsInfo } func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { @@ -53,6 +55,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, mac machineInfoFactory: machineInfoFactory, stopWatcher: make(chan error), watches: make(map[string]struct{}), + fsInfo: fs.NewFsInfo(), }, nil } @@ -144,8 +147,18 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { return spec, nil } -func (self *rawContainerHandler) GetStats() (stats *info.ContainerStats, err error) { - return libcontainer.GetStatsCgroupOnly(self.cgroup) +func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { + stats, err := libcontainer.GetStatsCgroupOnly(self.cgroup) + if err != nil { + return nil, err + } + // Get Filesystem information + fsStats, err := self.fsInfo.GetFsStats(self.name) + if err != nil { + return nil, err + } + stats.FsStats = fsStats + return stats, nil } // Lists all directories under "path" and outputs the results as children of "parent". @@ -203,7 +216,7 @@ func (self *rawContainerHandler) ListThreads(listType container.ListType) ([]int } func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { - return fs.GetPids(self.cgroup) + return cgroup_fs.GetPids(self.cgroup) } func (self *rawContainerHandler) watchDirectory(dir string, containerName string) error { diff --git a/fs/fs.go b/fs/fs.go new file mode 100644 index 00000000..b99bd75c --- /dev/null +++ b/fs/fs.go @@ -0,0 +1,66 @@ +// +build linux +// +// Provides Filesystem Stats +package fs + +/* + extern int getBytesFree(const char *path, unsigned long long *bytes); + extern int getBytesTotal(const char *path, unsigned long long *bytes); +*/ +import "C" + +import ( + "syscall" + "unsafe" + + "github.com/docker/docker/pkg/mount" + "github.com/golang/glog" +) + +const EXT_SUPER_MAGIC = 0xEF53 + +type FsInfoImpl struct{} + +func NewFsInfo() FsInfo { + return &FsInfoImpl{} +} + +func (*FsInfoImpl) GetFsStats(containerName string) ([]FsStat, error) { + filesystems := make([]FsStat, 0) + if containerName != "/" { + return filesystems, nil + } + mounts, err := mount.GetMounts() + if err != nil { + return nil, err + } + for _, mount := range mounts { + if !strings.HasPrefix("ext", mount.FsType) || mount.Mountpoint != mount.Root { + continue + } + total, free, err := getVfsStats(mount.Mountpoint) + if err != nil { + glog.Errorf("Statvfs failed. Error: %v", err) + } else { + glog.V(1).Infof("%s is an ext partition at %s. Total: %d, Free: %d", mount.Source, mount.Mountpoint, total, free) + filesystems = append(filesystems, FsStat{mount.Source, total, free}) + } + } + return filesystems, nil +} + +func getVfsStats(path string) (total uint64, free uint64, err error) { + _p0, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, 0, err + } + res, err := C.getBytesFree((*C.char)(unsafe.Pointer(_p0)), (*_Ctype_ulonglong)(unsafe.Pointer(&free))) + if res != 0 { + return 0, 0, err + } + res, err = C.getBytesTotal((*C.char)(unsafe.Pointer(_p0)), (*_Ctype_ulonglong)(unsafe.Pointer(&total))) + if res != 0 { + return 0, 0, err + } + return total, free, nil +} diff --git a/fs/statvfs.c b/fs/statvfs.c new file mode 100644 index 00000000..6961df4f --- /dev/null +++ b/fs/statvfs.c @@ -0,0 +1,23 @@ +// +build cgo + +#include + +int getBytesFree(const char *path, unsigned long long *bytes) { + struct statvfs buf; + int res; + if ((res = statvfs(path, &buf)) && res != 0) { + return -1; + } + *bytes = buf.f_frsize * buf.f_bfree; + return 0; +} + +int getBytesTotal(const char *path, unsigned long long *bytes) { + struct statvfs buf; + int res; + if ((res = statvfs(path, &buf)) && res != 0) { + return -1; + } + *bytes = buf.f_frsize * buf.f_blocks; + return 0; +} diff --git a/fs/types.go b/fs/types.go new file mode 100644 index 00000000..e527993e --- /dev/null +++ b/fs/types.go @@ -0,0 +1,12 @@ +package fs + +type FsStat struct { + Name string `json:"name"` + Capacity uint64 `json:"capacity"` + Free uint64 `json:"free"` +} + +type FsInfo interface { + // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems used by container 'containerName'. + GetFsStats(containerName string) ([]FsStat, error) +} diff --git a/info/container.go b/info/container.go index 649b069f..dbfb698f 100644 --- a/info/container.go +++ b/info/container.go @@ -17,6 +17,8 @@ package info import ( "reflect" "time" + + "github.com/google/cadvisor/fs" ) type CpuSpec struct { @@ -236,6 +238,8 @@ type ContainerStats struct { DiskIo DiskIoStats `json:"diskio,omitempty"` Memory *MemoryStats `json:"memory,omitempty"` Network *NetworkStats `json:"network,omitempty"` + // Filesystem statistics + FsStats []fs.FsStat `json:"fs_stats,omitempty"` } // Makes a deep copy of the ContainerStats and returns a pointer to the new From b9e70f0240db11e2e9c039cb4dd956df93c680e5 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Mon, 29 Sep 2014 23:21:13 +0000 Subject: [PATCH 2/3] Filesystem stats are now per container. As of now, fs stats are reported only for the root cgroup. To make cadvisor detect all the disks, the rootfs of host needs to mounted inside cadvisor. --- README.md | 1 + container/raw/handler.go | 20 +++++++++++++------- deploy/Dockerfile | 1 + fs/fs.go | 27 ++++++++++++++++++--------- fs/types.go | 8 +++++--- info/container.go | 6 ++++-- 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 1d865fae..a19f1a21 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above), ``` sudo docker run \ + --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:rw \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ diff --git a/container/raw/handler.go b/container/raw/handler.go index cc1e7002..6d6fd7a9 100644 --- a/container/raw/handler.go +++ b/container/raw/handler.go @@ -41,7 +41,7 @@ type rawContainerHandler struct { watcher *inotify.Watcher stopWatcher chan error watches map[string]struct{} - fsInfo fs.FsInfo + fsInfo fs.FsInfo } func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { @@ -55,7 +55,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, mac machineInfoFactory: machineInfoFactory, stopWatcher: make(chan error), watches: make(map[string]struct{}), - fsInfo: fs.NewFsInfo(), + fsInfo: fs.NewFsInfo(), }, nil } @@ -144,6 +144,10 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { } } + // Fs. + if self.name == "/" { + spec.HasFs = true + } return spec, nil } @@ -152,12 +156,14 @@ func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { if err != nil { return nil, err } - // Get Filesystem information - fsStats, err := self.fsInfo.GetFsStats(self.name) - if err != nil { - return nil, err + // Get Filesystem information only for the root cgroup. + if self.name == "/" { + stats.Fs, err = self.fsInfo.GetFsStats() + if err != nil { + return nil, err + } } - stats.FsStats = fsStats + return stats, nil } diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 1c1b2d28..5702ab63 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -3,6 +3,7 @@ MAINTAINER dengnan@google.com vmarmol@google.com vishnuk@google.com # Grab cadvisor from the staging directory. ADD cadvisor /usr/bin/cadvisor +RUN mkdir /rootfs EXPOSE 8080 ENTRYPOINT ["/usr/bin/cadvisor"] diff --git a/fs/fs.go b/fs/fs.go index b99bd75c..a5bd2331 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -10,6 +10,7 @@ package fs import "C" import ( + "strings" "syscall" "unsafe" @@ -17,33 +18,41 @@ import ( "github.com/golang/glog" ) -const EXT_SUPER_MAGIC = 0xEF53 - type FsInfoImpl struct{} func NewFsInfo() FsInfo { return &FsInfoImpl{} } -func (*FsInfoImpl) GetFsStats(containerName string) ([]FsStat, error) { +func (*FsInfoImpl) GetFsStats() ([]FsStat, error) { filesystems := make([]FsStat, 0) - if containerName != "/" { - return filesystems, nil - } mounts, err := mount.GetMounts() if err != nil { return nil, err } + processedPartitions := make(map[string]bool, 0) for _, mount := range mounts { - if !strings.HasPrefix("ext", mount.FsType) || mount.Mountpoint != mount.Root { + if !strings.HasPrefix(mount.Fstype, "ext") { + continue + } + // Avoid bind mounts. + if _, ok := processedPartitions[mount.Source]; ok { continue } total, free, err := getVfsStats(mount.Mountpoint) if err != nil { glog.Errorf("Statvfs failed. Error: %v", err) } else { - glog.V(1).Infof("%s is an ext partition at %s. Total: %d, Free: %d", mount.Source, mount.Mountpoint, total, free) - filesystems = append(filesystems, FsStat{mount.Source, total, free}) + glog.V(1).Infof("%s is an %s partition at %s. Total: %d, Free: %d", mount.Source, mount.Fstype, mount.Mountpoint, total, free) + fsStat := FsStat{ + Device: mount.Source, + Major: uint(mount.Major), + Minor: uint(mount.Minor), + Capacity: total, + Free: free, + } + filesystems = append(filesystems, fsStat) + processedPartitions[mount.Source] = true } } return filesystems, nil diff --git a/fs/types.go b/fs/types.go index e527993e..78294b85 100644 --- a/fs/types.go +++ b/fs/types.go @@ -1,12 +1,14 @@ package fs type FsStat struct { - Name string `json:"name"` + Device string `json:"device,omitempty"` + Major uint `json:"major"` + Minor uint `json:"minor"` Capacity uint64 `json:"capacity"` Free uint64 `json:"free"` } type FsInfo interface { - // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems used by container 'containerName'. - GetFsStats(containerName string) ([]FsStat, error) + // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems on the host. + GetFsStats() ([]FsStat, error) } diff --git a/info/container.go b/info/container.go index dbfb698f..83197b39 100644 --- a/info/container.go +++ b/info/container.go @@ -17,7 +17,7 @@ package info import ( "reflect" "time" - + "github.com/google/cadvisor/fs" ) @@ -49,6 +49,8 @@ type ContainerSpec struct { Memory MemorySpec `json:"memory,omitempty"` HasNetwork bool `json:"has_network"` + + HasFs bool `json:"has_fs"` } // Container reference contains enough information to uniquely identify a container @@ -239,7 +241,7 @@ type ContainerStats struct { Memory *MemoryStats `json:"memory,omitempty"` Network *NetworkStats `json:"network,omitempty"` // Filesystem statistics - FsStats []fs.FsStat `json:"fs_stats,omitempty"` + Fs []fs.FsStat `json:"fs,omitempty"` } // Makes a deep copy of the ContainerStats and returns a pointer to the new From c21ff1f16635e317947b79f1c230fc9b02575891 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Tue, 30 Sep 2014 23:38:34 +0000 Subject: [PATCH 3/3] Adding a disk usage progress bar. --- container/raw/handler.go | 10 +++++++--- deploy/Dockerfile | 1 - fs/fs.go | 39 ++++++++++++++++++++++++--------------- fs/types.go | 4 ++-- info/container.go | 4 ++-- pages/containers.go | 16 ++++++++++++++++ pages/containers_html.go | 26 ++++++++++++++++++++++++++ 7 files changed, 77 insertions(+), 23 deletions(-) diff --git a/container/raw/handler.go b/container/raw/handler.go index 6d6fd7a9..6441f652 100644 --- a/container/raw/handler.go +++ b/container/raw/handler.go @@ -45,6 +45,10 @@ type rawContainerHandler struct { } func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { + fsInfo, err := fs.NewFsInfo() + if err != nil { + return nil, err + } return &rawContainerHandler{ name: name, cgroup: &cgroups.Cgroup{ @@ -55,7 +59,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, mac machineInfoFactory: machineInfoFactory, stopWatcher: make(chan error), watches: make(map[string]struct{}), - fsInfo: fs.NewFsInfo(), + fsInfo: fsInfo, }, nil } @@ -146,7 +150,7 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { // Fs. if self.name == "/" { - spec.HasFs = true + spec.HasFilesystem = true } return spec, nil } @@ -158,7 +162,7 @@ func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) { } // Get Filesystem information only for the root cgroup. if self.name == "/" { - stats.Fs, err = self.fsInfo.GetFsStats() + stats.Filesystem, err = self.fsInfo.GetFsStats() if err != nil { return nil, err } diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 5702ab63..1c1b2d28 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -3,7 +3,6 @@ MAINTAINER dengnan@google.com vmarmol@google.com vishnuk@google.com # Grab cadvisor from the staging directory. ADD cadvisor /usr/bin/cadvisor -RUN mkdir /rootfs EXPOSE 8080 ENTRYPOINT ["/usr/bin/cadvisor"] diff --git a/fs/fs.go b/fs/fs.go index a5bd2331..4309e0db 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -18,41 +18,50 @@ import ( "github.com/golang/glog" ) -type FsInfoImpl struct{} - -func NewFsInfo() FsInfo { - return &FsInfoImpl{} +type partition struct { + mountpoint string + major uint32 + minor uint32 } -func (*FsInfoImpl) GetFsStats() ([]FsStat, error) { - filesystems := make([]FsStat, 0) +type FsInfoImpl struct { + partitions map[string]partition +} + +func NewFsInfo() (FsInfo, error) { mounts, err := mount.GetMounts() if err != nil { return nil, err } - processedPartitions := make(map[string]bool, 0) + partitions := make(map[string]partition, 0) for _, mount := range mounts { if !strings.HasPrefix(mount.Fstype, "ext") { continue } // Avoid bind mounts. - if _, ok := processedPartitions[mount.Source]; ok { + if _, ok := partitions[mount.Source]; ok { continue } - total, free, err := getVfsStats(mount.Mountpoint) + partitions[mount.Source] = partition{mount.Mountpoint, uint32(mount.Major), uint32(mount.Minor)} + } + return &FsInfoImpl{partitions}, nil +} + +func (self *FsInfoImpl) GetFsStats() ([]FsStats, error) { + filesystems := make([]FsStats, 0) + for device, partition := range self.partitions { + total, free, err := getVfsStats(partition.mountpoint) if err != nil { glog.Errorf("Statvfs failed. Error: %v", err) } else { - glog.V(1).Infof("%s is an %s partition at %s. Total: %d, Free: %d", mount.Source, mount.Fstype, mount.Mountpoint, total, free) - fsStat := FsStat{ - Device: mount.Source, - Major: uint(mount.Major), - Minor: uint(mount.Minor), + fsStat := FsStats{ + Device: device, + Major: uint(partition.major), + Minor: uint(partition.minor), Capacity: total, Free: free, } filesystems = append(filesystems, fsStat) - processedPartitions[mount.Source] = true } } return filesystems, nil diff --git a/fs/types.go b/fs/types.go index 78294b85..4fabdbc2 100644 --- a/fs/types.go +++ b/fs/types.go @@ -1,6 +1,6 @@ package fs -type FsStat struct { +type FsStats struct { Device string `json:"device,omitempty"` Major uint `json:"major"` Minor uint `json:"minor"` @@ -10,5 +10,5 @@ type FsStat struct { type FsInfo interface { // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems on the host. - GetFsStats() ([]FsStat, error) + GetFsStats() ([]FsStats, error) } diff --git a/info/container.go b/info/container.go index 83197b39..22c31e78 100644 --- a/info/container.go +++ b/info/container.go @@ -50,7 +50,7 @@ type ContainerSpec struct { HasNetwork bool `json:"has_network"` - HasFs bool `json:"has_fs"` + HasFilesystem bool `json:"has_filesystem"` } // Container reference contains enough information to uniquely identify a container @@ -241,7 +241,7 @@ type ContainerStats struct { Memory *MemoryStats `json:"memory,omitempty"` Network *NetworkStats `json:"network,omitempty"` // Filesystem statistics - Fs []fs.FsStat `json:"fs,omitempty"` + Filesystem []fs.FsStats `json:"filesystem,omitempty"` } // Makes a deep copy of the ContainerStats and returns a pointer to the new diff --git a/pages/containers.go b/pages/containers.go index eca961aa..18b86ccd 100644 --- a/pages/containers.go +++ b/pages/containers.go @@ -27,6 +27,7 @@ import ( "time" "github.com/golang/glog" + "github.com/google/cadvisor/fs" "github.com/google/cadvisor/info" "github.com/google/cadvisor/manager" ) @@ -98,6 +99,8 @@ var funcMap = template.FuncMap{ "getMemoryUsagePercent": getMemoryUsagePercent, "getHotMemoryPercent": getHotMemoryPercent, "getColdMemoryPercent": getColdMemoryPercent, + "getFsStats": getFsStats, + "getFsUsagePercent": getFsUsagePercent, } // TODO(vmarmol): Consider housekeeping Spec too so we can show changes through time. We probably don't need it ever second though. @@ -115,6 +118,7 @@ type pageData struct { CpuAvailable bool MemoryAvailable bool NetworkAvailable bool + FsAvailable bool } func init() { @@ -252,6 +256,17 @@ func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine) } +func getFsStats(stats []*info.ContainerStats) []fs.FsStats { + if len(stats) == 0 { + return []fs.FsStats{} + } + return stats[len(stats)-1].Filesystem +} + +func getFsUsagePercent(capacity, free uint64) uint64 { + return uint64((float64(capacity-free) / float64(capacity)) * 100) +} + func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error { start := time.Now() @@ -312,6 +327,7 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) CpuAvailable: cont.Spec.HasCpu, MemoryAvailable: cont.Spec.HasMemory, NetworkAvailable: cont.Spec.HasNetwork, + FsAvailable: cont.Spec.HasFilesystem, } err = pageTemplate.Execute(w, data) if err != nil { diff --git a/pages/containers_html.go b/pages/containers_html.go index c883790e..71c87bf2 100644 --- a/pages/containers_html.go +++ b/pages/containers_html.go @@ -147,6 +147,32 @@ const containersHtmlTemplate = ` {{end}} + {{if .FsAvailable}} +
+
+

Filesystem

+
+
+ {{with getFsStats .Stats}} + {{range .}} +
+

Partition: {{.Device}}

+
+
+
+
+
+
+
+
+
+ {{printSize .Capacity}} {{printUnit .Capacity}} ({{getFsUsagePercent .Capacity .Free}}%) +
+ {{end}} + {{end}} +
+
+ {{end}} {{if .NetworkAvailable}}