cadvisor/fs/fs.go
Abin Shahab e9d6289964 Added /proc/diskstats
Read disk io information from /proc/diskstats.
This will allow the user who provides partition container hints to get partition-specific io (blkio provides io for the container, but at the disk device level).
2014-10-19 23:36:08 +00:00

186 lines
4.7 KiB
Go

// +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 (
"bufio"
"fmt"
"os/exec"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/docker/docker/pkg/mount"
"github.com/golang/glog"
)
type partition struct {
mountpoint string
major uint
minor uint
}
type RealFsInfo 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, uint(mount.Major), uint(mount.Minor)}
}
return &RealFsInfo{partitions}, nil
}
func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) {
filesystems := make([]Fs, 0)
deviceSet := make(map[string]struct{})
diskStatsMap, err := getDiskStatsMap("/proc/diskstats")
if err != nil {
return nil, err
}
for device, partition := range self.partitions {
_, hasMount := mountSet[partition.mountpoint]
_, hasDevice := deviceSet[device]
if mountSet == nil || hasMount && !hasDevice {
total, free, err := getVfsStats(partition.mountpoint)
if err != nil {
glog.Errorf("Statvfs failed. Error: %v", err)
} else {
deviceSet[device] = struct{}{}
deviceInfo := DeviceInfo{
Device: device,
Major: uint(partition.major),
Minor: uint(partition.minor),
}
fs := Fs{deviceInfo, total, free, diskStatsMap[device]}
filesystems = append(filesystems, fs)
}
}
}
return filesystems, nil
}
func getDiskStatsMap(diskStatsFile string) (map[string]DiskStats, error) {
file, err := os.Open(diskStatsFile)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
diskStatsMap := make(map[string]DiskStats)
partitionRegex, _ := regexp.Compile("sd[a-z]\\d")
for scanner.Scan() {
line :=scanner.Text()
words := strings.Fields(line)
if !partitionRegex.MatchString(words[2]) {
continue
}
// 8 50 sdd2 40 0 280 223 7 0 22 108 0 330 330
deviceName := "/dev/" + words[2]
wordLength := len(words)
var stats = make([]uint64,wordLength)
var error error
for i := 3; i < wordLength; i++ {
stats[i], error = strconv.ParseUint(words[i], 10, 64)
if error != nil {
return nil, error
}
}
diskStats := DiskStats {
ReadsCompleted: stats[3],
ReadsMerged: stats[4],
SectorsRead: stats[5],
ReadTime: stats[6],
WritesCompleted:stats[7],
WritesMerged: stats[8],
SectorsWritten: stats[9],
WriteTime: stats[10],
IOInProgress: stats[11],
IOTime: stats[12],
WeightedIOTime: stats[13],
}
diskStatsMap[deviceName] = diskStats
}
return diskStatsMap, nil
}
func (self *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) {
return self.GetFsInfoForPath(nil)
}
func major(devNumber uint64) uint {
return uint((devNumber >> 8) & 0xfff)
}
func minor(devNumber uint64) uint {
return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00))
}
func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
var buf syscall.Stat_t
err := syscall.Stat(dir, &buf)
if err != nil {
return nil, fmt.Errorf("stat failed on %s with error: %s", dir, err)
}
major := major(buf.Dev)
minor := minor(buf.Dev)
for device, partition := range self.partitions {
if partition.major == major && partition.minor == minor {
return &DeviceInfo{device, major, minor}, nil
}
}
return nil, fmt.Errorf("could not find device with major: %d, minor: %d in cached partitions map", major, minor)
}
func (self *RealFsInfo) GetDirUsage(dir string) (uint64, error) {
out, err := exec.Command("du", "-s", dir).CombinedOutput()
if err != nil {
return 0, fmt.Errorf("du command failed on %s with output %s - %s", dir, out, err)
}
usageInKb, err := strconv.ParseUint(strings.Fields(string(out))[0], 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse 'du' output %s - %s", out, err)
}
return usageInKb * 1024, 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
}