Merge pull request #258 from vishh/disk_usage

Adding global filesystem usage information
This commit is contained in:
Victor Marmol 2014-10-01 08:33:25 -07:00
commit 537550a778
8 changed files with 197 additions and 4 deletions

View File

@ -12,6 +12,7 @@ To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above),
``` ```
sudo docker run \ sudo docker run \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:rw \ --volume=/var/run:/var/run:rw \
--volume=/sys:/sys:ro \ --volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \

View File

@ -24,10 +24,11 @@ import (
"code.google.com/p/go.exp/inotify" "code.google.com/p/go.exp/inotify"
"github.com/docker/libcontainer/cgroups" "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/golang/glog"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
"github.com/google/cadvisor/utils" "github.com/google/cadvisor/utils"
) )
@ -40,9 +41,14 @@ type rawContainerHandler struct {
watcher *inotify.Watcher watcher *inotify.Watcher
stopWatcher chan error stopWatcher chan error
watches map[string]struct{} watches map[string]struct{}
fsInfo fs.FsInfo
} }
func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) {
fsInfo, err := fs.NewFsInfo()
if err != nil {
return nil, err
}
return &rawContainerHandler{ return &rawContainerHandler{
name: name, name: name,
cgroup: &cgroups.Cgroup{ cgroup: &cgroups.Cgroup{
@ -53,6 +59,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, mac
machineInfoFactory: machineInfoFactory, machineInfoFactory: machineInfoFactory,
stopWatcher: make(chan error), stopWatcher: make(chan error),
watches: make(map[string]struct{}), watches: make(map[string]struct{}),
fsInfo: fsInfo,
}, nil }, nil
} }
@ -141,11 +148,27 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) {
} }
} }
// Fs.
if self.name == "/" {
spec.HasFilesystem = true
}
return spec, nil return spec, nil
} }
func (self *rawContainerHandler) GetStats() (stats *info.ContainerStats, err error) { func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) {
return libcontainer.GetStatsCgroupOnly(self.cgroup) stats, err := libcontainer.GetStatsCgroupOnly(self.cgroup)
if err != nil {
return nil, err
}
// Get Filesystem information only for the root cgroup.
if self.name == "/" {
stats.Filesystem, err = self.fsInfo.GetFsStats()
if err != nil {
return nil, err
}
}
return stats, nil
} }
// Lists all directories under "path" and outputs the results as children of "parent". // Lists all directories under "path" and outputs the results as children of "parent".
@ -203,7 +226,7 @@ func (self *rawContainerHandler) ListThreads(listType container.ListType) ([]int
} }
func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { 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 { func (self *rawContainerHandler) watchDirectory(dir string, containerName string) error {

84
fs/fs.go Normal file
View File

@ -0,0 +1,84 @@
// +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 (
"strings"
"syscall"
"unsafe"
"github.com/docker/docker/pkg/mount"
"github.com/golang/glog"
)
type partition struct {
mountpoint string
major uint32
minor uint32
}
type FsInfoImpl struct {
partitions map[string]partition
}
func NewFsInfo() (FsInfo, error) {
mounts, err := mount.GetMounts()
if err != nil {
return nil, err
}
partitions := make(map[string]partition, 0)
for _, mount := range mounts {
if !strings.HasPrefix(mount.Fstype, "ext") {
continue
}
// Avoid bind mounts.
if _, ok := partitions[mount.Source]; ok {
continue
}
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 {
fsStat := FsStats{
Device: device,
Major: uint(partition.major),
Minor: uint(partition.minor),
Capacity: total,
Free: free,
}
filesystems = append(filesystems, fsStat)
}
}
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
}

23
fs/statvfs.c Normal file
View File

@ -0,0 +1,23 @@
// +build cgo
#include <sys/statvfs.h>
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;
}

14
fs/types.go Normal file
View File

@ -0,0 +1,14 @@
package fs
type FsStats struct {
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 on the host.
GetFsStats() ([]FsStats, error)
}

View File

@ -17,6 +17,8 @@ package info
import ( import (
"reflect" "reflect"
"time" "time"
"github.com/google/cadvisor/fs"
) )
type CpuSpec struct { type CpuSpec struct {
@ -47,6 +49,8 @@ type ContainerSpec struct {
Memory MemorySpec `json:"memory,omitempty"` Memory MemorySpec `json:"memory,omitempty"`
HasNetwork bool `json:"has_network"` HasNetwork bool `json:"has_network"`
HasFilesystem bool `json:"has_filesystem"`
} }
// Container reference contains enough information to uniquely identify a container // Container reference contains enough information to uniquely identify a container
@ -236,6 +240,8 @@ type ContainerStats struct {
DiskIo DiskIoStats `json:"diskio,omitempty"` DiskIo DiskIoStats `json:"diskio,omitempty"`
Memory *MemoryStats `json:"memory,omitempty"` Memory *MemoryStats `json:"memory,omitempty"`
Network *NetworkStats `json:"network,omitempty"` Network *NetworkStats `json:"network,omitempty"`
// Filesystem statistics
Filesystem []fs.FsStats `json:"filesystem,omitempty"`
} }
// Makes a deep copy of the ContainerStats and returns a pointer to the new // Makes a deep copy of the ContainerStats and returns a pointer to the new

View File

@ -27,6 +27,7 @@ import (
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/google/cadvisor/fs"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
"github.com/google/cadvisor/manager" "github.com/google/cadvisor/manager"
) )
@ -98,6 +99,8 @@ var funcMap = template.FuncMap{
"getMemoryUsagePercent": getMemoryUsagePercent, "getMemoryUsagePercent": getMemoryUsagePercent,
"getHotMemoryPercent": getHotMemoryPercent, "getHotMemoryPercent": getHotMemoryPercent,
"getColdMemoryPercent": getColdMemoryPercent, "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. // 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 CpuAvailable bool
MemoryAvailable bool MemoryAvailable bool
NetworkAvailable bool NetworkAvailable bool
FsAvailable bool
} }
func init() { func init() {
@ -252,6 +256,17 @@ func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats
return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine) 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 { func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
start := time.Now() start := time.Now()
@ -312,6 +327,7 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
CpuAvailable: cont.Spec.HasCpu, CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory, MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork, NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
} }
err = pageTemplate.Execute(w, data) err = pageTemplate.Execute(w, data)
if err != nil { if err != nil {

View File

@ -147,6 +147,32 @@ const containersHtmlTemplate = `
</div> </div>
</div> </div>
{{end}} {{end}}
{{if .FsAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Filesystem</h3>
</div>
<div class="panel-body">
{{with getFsStats .Stats}}
{{range .}}
<div class="row col-sm-12">
<h4>Partition: {{.Device}}</h4>
</div>
<div class="col-sm-9">
<div class="progress">
<div id="memory-usage-chart"></div>
<div class="progress-bar progress-bar-danger" style="width: {{getFsUsagePercent .Capacity .Free}}%">
</div>
</div>
</div>
<div class="col-sm-3">
{{printSize .Capacity}} {{printUnit .Capacity}} ({{getFsUsagePercent .Capacity .Free}}%)
</div>
{{end}}
{{end}}
</div>
</div>
{{end}}
{{if .NetworkAvailable}} {{if .NetworkAvailable}}
<div class="panel panel-primary"> <div class="panel panel-primary">
<div class="panel-heading"> <div class="panel-heading">