Merge pull request #258 from vishh/disk_usage
Adding global filesystem usage information
This commit is contained in:
commit
537550a778
@ -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 \
|
||||||
|
@ -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
84
fs/fs.go
Normal 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
23
fs/statvfs.c
Normal 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
14
fs/types.go
Normal 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)
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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">
|
||||||
|
Loading…
Reference in New Issue
Block a user