implement usage stats

This commit is contained in:
Denys Smirnov 2016-10-06 14:26:05 +03:00
parent 3343324613
commit 6f59d604fc
5 changed files with 310 additions and 74 deletions

View File

@ -1,7 +1,6 @@
package btrfs
import (
"bytes"
"fmt"
"github.com/dennwc/btrfs/ioctl"
"io"
@ -12,8 +11,6 @@ import (
const SuperMagic = 0x9123683E
const xattrPrefix = "btrfs."
func Open(path string, ro bool) (*FS, error) {
if ok, err := IsSubVolume(path); err != nil {
return nil, err
@ -60,16 +57,16 @@ type Info struct {
func (f *FS) Info() (out Info, err error) {
var arg btrfs_ioctl_fs_info_args
if err = ioctl.Do(f.f, _BTRFS_IOC_FS_INFO, &arg); err != nil {
return
}
out = Info{
MaxID: arg.max_id,
NumDevices: arg.num_devices,
FSID: arg.fsid,
NodeSize: arg.nodesize,
SectorSize: arg.sectorsize,
CloneAlignment: arg.clone_alignment,
arg, err = iocFsInfo(f.f)
if err == nil {
out = Info{
MaxID: arg.max_id,
NumDevices: arg.num_devices,
FSID: arg.fsid,
NodeSize: arg.nodesize,
SectorSize: arg.sectorsize,
CloneAlignment: arg.clone_alignment,
}
}
return
}
@ -160,10 +157,10 @@ func (f *FS) SetFlags(flags SubvolFlags) error {
}
func (f *FS) Sync() (err error) {
if err = ioctl.Do(f.f, _BTRFS_IOC_START_SYNC, nil); err != nil {
if err = ioctl.Ioctl(f.f, _BTRFS_IOC_START_SYNC, 0); err != nil {
return
}
return ioctl.Do(f.f, _BTRFS_IOC_WAIT_SYNC, nil)
return ioctl.Ioctl(f.f, _BTRFS_IOC_WAIT_SYNC, 0)
}
func (f *FS) CreateSubVolume(name string) error {
@ -221,58 +218,4 @@ func (f *FS) ListSubvolumes(filter func(Subvolume) bool) ([]Subvolume, error) {
return out, nil
}
type Compression string
const (
CompressionNone = Compression("")
LZO = Compression("lzo")
ZLIB = Compression("zlib")
)
const xattrCompression = xattrPrefix + "compression"
func SetCompression(path string, v Compression) error {
var value []byte
if v != CompressionNone {
var err error
value, err = syscall.ByteSliceFromString(string(v))
if err != nil {
return err
}
}
err := syscall.Setxattr(path, xattrCompression, value, 0)
if err != nil {
return &os.PathError{Op: "setxattr", Path: path, Err: err}
}
return nil
}
func GetCompression(path string) (Compression, error) {
var buf []byte
for {
sz, err := syscall.Getxattr(path, xattrCompression, nil)
if err == syscall.ENODATA || sz == 0 {
return CompressionNone, nil
} else if err != nil {
return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err}
}
if cap(buf) < sz {
buf = make([]byte, sz)
} else {
buf = buf[:sz]
}
sz, err = syscall.Getxattr(path, xattrCompression, buf)
if err == syscall.ENODATA {
return CompressionNone, nil
} else if err == syscall.ERANGE {
// xattr changed by someone else, and is larger than our current buffer
continue
} else if err != nil {
return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err}
}
buf = buf[:sz]
break
}
buf = bytes.TrimSuffix(buf, []byte{0})
return Compression(buf), nil
}
func (f *FS) Usage() (UsageInfo, error) { return spaceUsage(f.f) }

View File

@ -6,6 +6,9 @@ import (
)
const (
_BTRFS_BLOCK_GROUP_TYPE_MASK = (blockGroupData |
blockGroupSystem |
blockGroupMetadata)
_BTRFS_BLOCK_GROUP_PROFILE_MASK = (blockGroupRaid0 |
blockGroupRaid1 |
blockGroupRaid5 |

View File

@ -678,8 +678,49 @@ func iocDefaultSubvol(f *os.File, out *uint64) error {
return ioctl.Do(f, _BTRFS_IOC_DEFAULT_SUBVOL, out)
}
func iocSpaceInfo(f *os.File, out *btrfs_ioctl_space_args) error {
return ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, out)
type spaceInfo struct {
Flags uint64
TotalBytes uint64
UsedBytes uint64
}
func iocSpaceInfo(f *os.File) ([]spaceInfo, error) {
arg := &btrfs_ioctl_space_args{}
if err := ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, arg); err != nil {
return nil, err
}
n := arg.total_spaces
if n == 0 {
return nil, nil
}
const (
argSize = unsafe.Sizeof(btrfs_ioctl_space_args{})
infoSize = unsafe.Sizeof(btrfs_ioctl_space_info{})
)
buf := make([]byte, argSize+uintptr(n)*infoSize)
basePtr := unsafe.Pointer(&buf[0])
arg = (*btrfs_ioctl_space_args)(basePtr)
arg.space_slots = n
if err := ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, arg); err != nil {
return nil, err
} else if arg.total_spaces == 0 {
return nil, nil
}
if n > arg.total_spaces {
n = arg.total_spaces
}
out := make([]spaceInfo, n)
ptr := uintptr(basePtr) + argSize
for i := 0; i < int(n); i++ {
info := (*btrfs_ioctl_space_info)(unsafe.Pointer(ptr))
out[i] = spaceInfo{
Flags: info.flags,
TotalBytes: info.total_bytes,
UsedBytes: info.used_bytes,
}
ptr += infoSize
}
return out, nil
}
func iocStartSync(f *os.File, out *uint64) error {
@ -712,8 +753,16 @@ func iocScrubProgress(f *os.File, out *btrfs_ioctl_scrub_args) error {
return ioctl.Do(f, _BTRFS_IOC_SCRUB_PROGRESS, out)
}
func iocDevInfo(f *os.File, out *btrfs_ioctl_dev_info_args) error {
return ioctl.Do(f, _BTRFS_IOC_DEV_INFO, out)
func iocFsInfo(f *os.File) (out btrfs_ioctl_fs_info_args, err error) {
err = ioctl.Do(f, _BTRFS_IOC_FS_INFO, &out)
return
}
func iocDevInfo(f *os.File, devid uint64, uuid UUID) (out btrfs_ioctl_dev_info_args, err error) {
out.devid = devid
out.uuid = uuid
err = ioctl.Do(f, _BTRFS_IOC_DEV_INFO, &out)
return
}
func iocBalanceCtl(f *os.File, out *int32) error {

175
usage.go Normal file
View File

@ -0,0 +1,175 @@
package btrfs
import (
"os"
"sort"
"syscall"
)
func cmpChunkBlockGroup(f1, f2 uint64) int {
var mask uint64
if (f1 & _BTRFS_BLOCK_GROUP_TYPE_MASK) ==
(f2 & _BTRFS_BLOCK_GROUP_TYPE_MASK) {
mask = _BTRFS_BLOCK_GROUP_PROFILE_MASK
} else if f2&blockGroupSystem != 0 {
return -1
} else if f1&blockGroupSystem != 0 {
return +1
} else {
mask = _BTRFS_BLOCK_GROUP_TYPE_MASK
}
if (f1 & mask) > (f2 & mask) {
return +1
} else if (f1 & mask) < (f2 & mask) {
return -1
} else {
return 0
}
}
type spaceInfoByBlockGroup []spaceInfo
func (a spaceInfoByBlockGroup) Len() int { return len(a) }
func (a spaceInfoByBlockGroup) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a spaceInfoByBlockGroup) Less(i, j int) bool {
return cmpChunkBlockGroup(a[i].Flags, a[j].Flags) < 0
}
type UsageInfo struct {
Total uint64
TotalUnused uint64
TotalUsed uint64
TotalChunks uint64
FreeEstimated uint64
FreeMin uint64
LogicalDataChunks uint64
RawDataChunks uint64
RawDataUsed uint64
LogicalMetaChunks uint64
RawMetaChunks uint64
RawMetaUsed uint64
SystemUsed uint64
SystemChunks uint64
DataRatio float64
MetadataRatio float64
GlobalReserve uint64
GlobalReserveUsed uint64
}
const minUnallocatedThreshold = 16 * 1024 * 1024
func spaceUsage(f *os.File) (UsageInfo, error) {
info, err := iocFsInfo(f)
if err != nil {
return UsageInfo{}, err
}
var u UsageInfo
for i := uint64(0); i <= info.max_id; i++ {
dev, err := iocDevInfo(f, i, UUID{})
if err == syscall.ENODEV {
continue
} else if err != nil {
return UsageInfo{}, err
}
u.Total += dev.total_bytes
}
spaces, err := iocSpaceInfo(f)
if err != nil {
return UsageInfo{}, err
}
sort.Sort(spaceInfoByBlockGroup(spaces))
var (
maxDataRatio int = 1
mixed bool
)
for _, s := range spaces {
ratio := 1
switch {
case s.Flags&blockGroupRaid0 != 0:
ratio = 1
case s.Flags&blockGroupRaid1 != 0:
ratio = 2
case s.Flags&blockGroupRaid5 != 0:
ratio = 0
case s.Flags&blockGroupRaid6 != 0:
ratio = 0
case s.Flags&blockGroupDup != 0:
ratio = 2
case s.Flags&blockGroupRaid10 != 0:
ratio = 2
}
if ratio > maxDataRatio {
maxDataRatio = ratio
}
if s.Flags&spaceInfoGlobalRsv != 0 {
u.GlobalReserve = s.TotalBytes
u.GlobalReserveUsed = s.UsedBytes
}
if s.Flags&(blockGroupData|blockGroupMetadata) == (blockGroupData | blockGroupMetadata) {
mixed = true
}
if s.Flags&blockGroupData != 0 {
u.RawDataUsed += s.UsedBytes * uint64(ratio)
u.RawDataChunks += s.TotalBytes * uint64(ratio)
u.LogicalDataChunks += s.TotalBytes
}
if s.Flags&blockGroupMetadata != 0 {
u.RawMetaUsed += s.UsedBytes * uint64(ratio)
u.RawMetaChunks += s.TotalBytes * uint64(ratio)
u.LogicalMetaChunks += s.TotalBytes
}
if s.Flags&blockGroupSystem != 0 {
u.SystemUsed += s.UsedBytes * uint64(ratio)
u.SystemChunks += s.TotalBytes * uint64(ratio)
}
}
u.TotalChunks = u.RawDataChunks + u.SystemChunks
u.TotalUsed = u.RawDataUsed + u.SystemUsed
if !mixed {
u.TotalChunks += u.RawMetaChunks
u.TotalUsed += u.RawMetaUsed
}
u.TotalUnused = u.Total - u.TotalChunks
u.DataRatio = float64(u.RawDataChunks) / float64(u.LogicalDataChunks)
if mixed {
u.MetadataRatio = u.DataRatio
} else {
u.MetadataRatio = float64(u.RawMetaChunks) / float64(u.LogicalMetaChunks)
}
// We're able to fill at least DATA for the unused space
//
// With mixed raid levels, this gives a rough estimate but more
// accurate than just counting the logical free space
// (l_data_chunks - l_data_used)
//
// In non-mixed case there's no difference.
u.FreeEstimated = uint64(float64(u.RawDataChunks-u.RawDataUsed) / u.DataRatio)
// For mixed-bg the metadata are left out in calculations thus global
// reserve would be lost. Part of it could be permanently allocated,
// we have to subtract the used bytes so we don't go under zero free.
if mixed {
u.FreeEstimated -= u.GlobalReserve - u.GlobalReserveUsed
}
u.FreeMin = u.FreeEstimated
// Chop unallocatable space
// FIXME: must be applied per device
if u.TotalUnused >= minUnallocatedThreshold {
u.FreeEstimated += uint64(float64(u.TotalUnused) / u.DataRatio)
// Match the calculation of 'df', use the highest raid ratio
u.FreeMin += u.TotalUnused / uint64(maxDataRatio)
}
return u, nil
}

66
xattr.go Normal file
View File

@ -0,0 +1,66 @@
package btrfs
import (
"bytes"
"os"
"syscall"
)
const (
xattrPrefix = "btrfs."
xattrCompression = xattrPrefix + "compression"
)
type Compression string
const (
CompressionNone = Compression("")
LZO = Compression("lzo")
ZLIB = Compression("zlib")
)
func SetCompression(path string, v Compression) error {
var value []byte
if v != CompressionNone {
var err error
value, err = syscall.ByteSliceFromString(string(v))
if err != nil {
return err
}
}
err := syscall.Setxattr(path, xattrCompression, value, 0)
if err != nil {
return &os.PathError{Op: "setxattr", Path: path, Err: err}
}
return nil
}
func GetCompression(path string) (Compression, error) {
var buf []byte
for {
sz, err := syscall.Getxattr(path, xattrCompression, nil)
if err == syscall.ENODATA || sz == 0 {
return CompressionNone, nil
} else if err != nil {
return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err}
}
if cap(buf) < sz {
buf = make([]byte, sz)
} else {
buf = buf[:sz]
}
sz, err = syscall.Getxattr(path, xattrCompression, buf)
if err == syscall.ENODATA {
return CompressionNone, nil
} else if err == syscall.ERANGE {
// xattr changed by someone else, and is larger than our current buffer
continue
} else if err != nil {
return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err}
}
buf = buf[:sz]
break
}
buf = bytes.TrimSuffix(buf, []byte{0})
return Compression(buf), nil
}