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).
186 lines
4.7 KiB
Go
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
|
|
}
|