forked from RepoMirrors/btrfs
implement usage stats
This commit is contained in:
parent
3343324613
commit
6f59d604fc
83
btrfs.go
83
btrfs.go
@ -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) }
|
||||
|
@ -6,6 +6,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
_BTRFS_BLOCK_GROUP_TYPE_MASK = (blockGroupData |
|
||||
blockGroupSystem |
|
||||
blockGroupMetadata)
|
||||
_BTRFS_BLOCK_GROUP_PROFILE_MASK = (blockGroupRaid0 |
|
||||
blockGroupRaid1 |
|
||||
blockGroupRaid5 |
|
||||
|
57
ioctl_h.go
57
ioctl_h.go
@ -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
175
usage.go
Normal 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
66
xattr.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user