Implement btrfs usage

Implements btrfs usage using a double walking diff and
counting the result. Walking gives the most accurate
count and includes inode usage.

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
Derek McGowan 2017-12-04 10:48:22 -08:00
parent f6df9f6632
commit 3bc4e69ba1
No known key found for this signature in database
GPG Key ID: F58C5D0A4405ACDB
4 changed files with 120 additions and 17 deletions

View File

@ -1,5 +1,7 @@
package fs package fs
import "context"
// Usage of disk information // Usage of disk information
type Usage struct { type Usage struct {
Inodes int64 Inodes int64
@ -11,3 +13,10 @@ type Usage struct {
func DiskUsage(roots ...string) (Usage, error) { func DiskUsage(roots ...string) (Usage, error) {
return diskUsage(roots...) return diskUsage(roots...)
} }
// DiffUsage counts the numbers of inodes and disk usage in the
// diff between the 2 directories. The first path is intended
// as the base directory and the second as the changed directory.
func DiffUsage(ctx context.Context, a, b string) (Usage, error) {
return diffUsage(ctx, a, b)
}

View File

@ -3,18 +3,20 @@
package fs package fs
import ( import (
"context"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
) )
func diskUsage(roots ...string) (Usage, error) {
type inode struct { type inode struct {
// TODO(stevvooe): Can probably reduce memory usage by not tracking // TODO(stevvooe): Can probably reduce memory usage by not tracking
// device, but we can leave this right for now. // device, but we can leave this right for now.
dev, ino uint64 dev, ino uint64
} }
func diskUsage(roots ...string) (Usage, error) {
var ( var (
size int64 size int64
inodes = map[inode]struct{}{} // expensive! inodes = map[inode]struct{}{} // expensive!
@ -45,3 +47,37 @@ func diskUsage(roots ...string) (Usage, error) {
Size: size, Size: size,
}, nil }, nil
} }
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
var (
size int64
inodes = map[inode]struct{}{} // expensive!
)
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if kind == ChangeKindAdd || kind == ChangeKindModify {
stat := fi.Sys().(*syscall.Stat_t)
inoKey := inode{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
if _, ok := inodes[inoKey]; !ok {
inodes[inoKey] = struct{}{}
size += fi.Size()
}
return nil
}
return nil
}); err != nil {
return Usage{}, err
}
return Usage{
Inodes: int64(len(inodes)),
Size: size,
}, nil
}

View File

@ -3,6 +3,7 @@
package fs package fs
import ( import (
"context"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -31,3 +32,29 @@ func diskUsage(roots ...string) (Usage, error) {
Size: size, Size: size,
}, nil }, nil
} }
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
var (
size int64
)
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if kind == ChangeKindAdd || kind == ChangeKindModify {
size += fi.Size()
return nil
}
return nil
}); err != nil {
return Usage{}, err
}
return Usage{
Size: size,
}, nil
}

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/containerd/btrfs" "github.com/containerd/btrfs"
"github.com/containerd/containerd/fs"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
@ -128,18 +129,43 @@ func (b *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpath
// Usage retrieves the disk usage of the top-level snapshot. // Usage retrieves the disk usage of the top-level snapshot.
func (b *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { func (b *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
panic("not implemented") return b.usage(ctx, key)
}
// TODO(stevvooe): Btrfs has a quota model where data can be exclusive to a func (b *snapshotter) usage(ctx context.Context, key string) (snapshots.Usage, error) {
// snapshot or shared among other resources. We may find that this is the ctx, t, err := b.ms.TransactionContext(ctx, false)
// correct value to reoprt but the stability of the implementation is under if err != nil {
// question. return snapshots.Usage{}, err
// }
// In general, this has impact on the model we choose for reporting usage. id, info, usage, err := storage.GetInfo(ctx, key)
// Ideally, the value should allow aggregration. For overlay, this is var parentID string
// simple since we can scan the diff directory to get a unique value. This if err == nil && info.Kind == snapshots.KindActive && info.Parent != "" {
// breaks down when start looking the behavior when data is shared between parentID, _, _, err = storage.GetInfo(ctx, info.Parent)
// snapshots, such as that for btrfs.
}
t.Rollback() // transaction no longer needed at this point.
if err != nil {
return snapshots.Usage{}, err
}
if info.Kind == snapshots.KindActive {
var du fs.Usage
p := filepath.Join(b.root, "active", id)
if parentID != "" {
du, err = fs.DiffUsage(ctx, filepath.Join(b.root, "snapshots", parentID), p)
} else {
du, err = fs.DiskUsage(p)
}
if err != nil {
// TODO(stevvooe): Consider not reporting an error in this case.
return snapshots.Usage{}, err
}
usage = snapshots.Usage(du)
}
return usage, nil
} }
// Walk the committed snapshots. // Walk the committed snapshots.
@ -238,6 +264,11 @@ func (b *snapshotter) mounts(dir string, s storage.Snapshot) ([]mount.Mount, err
} }
func (b *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (err error) { func (b *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (err error) {
usage, err := b.usage(ctx, key)
if err != nil {
return errors.Wrap(err, "failed to compute usage")
}
ctx, t, err := b.ms.TransactionContext(ctx, true) ctx, t, err := b.ms.TransactionContext(ctx, true)
if err != nil { if err != nil {
return err return err
@ -250,7 +281,7 @@ func (b *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
} }
}() }()
id, err := storage.CommitActive(ctx, key, name, snapshots.Usage{}, opts...) // TODO(stevvooe): Resolve a usage value for btrfs id, err := storage.CommitActive(ctx, key, name, usage, opts...) // TODO(stevvooe): Resolve a usage value for btrfs
if err != nil { if err != nil {
return errors.Wrap(err, "failed to commit") return errors.Wrap(err, "failed to commit")
} }