From 3bc4e69ba10ffa3484115b6718dc7a4675edca55 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Mon, 4 Dec 2017 10:48:22 -0800 Subject: [PATCH] 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 --- fs/du.go | 9 +++++++ fs/du_unix.go | 46 +++++++++++++++++++++++++++++---- fs/du_windows.go | 27 ++++++++++++++++++++ snapshots/btrfs/btrfs.go | 55 +++++++++++++++++++++++++++++++--------- 4 files changed, 120 insertions(+), 17 deletions(-) diff --git a/fs/du.go b/fs/du.go index 61f439d39..26f533315 100644 --- a/fs/du.go +++ b/fs/du.go @@ -1,5 +1,7 @@ package fs +import "context" + // Usage of disk information type Usage struct { Inodes int64 @@ -11,3 +13,10 @@ type Usage struct { func DiskUsage(roots ...string) (Usage, error) { 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) +} diff --git a/fs/du_unix.go b/fs/du_unix.go index d8654d32f..6328e80f3 100644 --- a/fs/du_unix.go +++ b/fs/du_unix.go @@ -3,17 +3,19 @@ package fs import ( + "context" "os" "path/filepath" "syscall" ) +type inode struct { + // TODO(stevvooe): Can probably reduce memory usage by not tracking + // device, but we can leave this right for now. + dev, ino uint64 +} + func diskUsage(roots ...string) (Usage, error) { - type inode struct { - // TODO(stevvooe): Can probably reduce memory usage by not tracking - // device, but we can leave this right for now. - dev, ino uint64 - } var ( size int64 @@ -45,3 +47,37 @@ func diskUsage(roots ...string) (Usage, error) { Size: size, }, 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 +} diff --git a/fs/du_windows.go b/fs/du_windows.go index 4a0363c06..3f852fc15 100644 --- a/fs/du_windows.go +++ b/fs/du_windows.go @@ -3,6 +3,7 @@ package fs import ( + "context" "os" "path/filepath" ) @@ -31,3 +32,29 @@ func diskUsage(roots ...string) (Usage, error) { Size: size, }, 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 +} diff --git a/snapshots/btrfs/btrfs.go b/snapshots/btrfs/btrfs.go index be18227b6..197cce01f 100644 --- a/snapshots/btrfs/btrfs.go +++ b/snapshots/btrfs/btrfs.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/containerd/btrfs" + "github.com/containerd/containerd/fs" "github.com/containerd/containerd/log" "github.com/containerd/containerd/mount" "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. 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 - // snapshot or shared among other resources. We may find that this is the - // correct value to reoprt but the stability of the implementation is under - // question. - // - // In general, this has impact on the model we choose for reporting usage. - // Ideally, the value should allow aggregration. For overlay, this is - // simple since we can scan the diff directory to get a unique value. This - // breaks down when start looking the behavior when data is shared between - // snapshots, such as that for btrfs. +func (b *snapshotter) usage(ctx context.Context, key string) (snapshots.Usage, error) { + ctx, t, err := b.ms.TransactionContext(ctx, false) + if err != nil { + return snapshots.Usage{}, err + } + id, info, usage, err := storage.GetInfo(ctx, key) + var parentID string + if err == nil && info.Kind == snapshots.KindActive && info.Parent != "" { + parentID, _, _, err = storage.GetInfo(ctx, info.Parent) + + } + 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. @@ -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) { + 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) if err != nil { 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 { return errors.Wrap(err, "failed to commit") }