From d252816706a3226dbc055616df91f2e1793a0459 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 17 Nov 2021 16:11:08 +0000 Subject: [PATCH] vfs: add vfs/stats remote control to show statistics - fixes #5816 --- vfs/rc.go | 48 ++++++++++++++++++++++++++++++++++++++ vfs/rc_test.go | 12 ++++++++++ vfs/vfs.go | 27 +++++++++++++++++++++ vfs/vfscache/cache.go | 24 +++++++++++++++++++ vfs/vfscache/cache_test.go | 12 ++++++++++ 5 files changed, 123 insertions(+) diff --git a/vfs/rc.go b/vfs/rc.go index 3450773f0..1326817c3 100644 --- a/vfs/rc.go +++ b/vfs/rc.go @@ -389,3 +389,51 @@ func rcList(ctx context.Context, in rc.Params) (out rc.Params, err error) { out["vfses"] = names return out, nil } + +func init() { + rc.Add(rc.Call{ + Path: "vfs/stats", + Title: "Stats for a VFS.", + Help: ` +This returns stats for the selected VFS. + + { + // Status of the disk cache - only present if --vfs-cache-mode > off + "diskCache": { + "bytesUsed": 0, + "erroredFiles": 0, + "files": 0, + "hashType": 1, + "outOfSpace": false, + "path": "/home/user/.cache/rclone/vfs/local/mnt/a", + "pathMeta": "/home/user/.cache/rclone/vfsMeta/local/mnt/a", + "uploadsInProgress": 0, + "uploadsQueued": 0 + }, + "fs": "/mnt/a", + "inUse": 1, + // Status of the in memory metadata cache + "metadataCache": { + "dirs": 1, + "files": 0 + }, + // Options as returned by options/get + "opt": { + "CacheMaxAge": 3600000000000, + // ... + "WriteWait": 1000000000 + } + } + +` + getVFSHelp, + Fn: rcStats, + }) +} + +func rcStats(ctx context.Context, in rc.Params) (out rc.Params, err error) { + vfs, err := getVFS(in) + if err != nil { + return nil, err + } + return vfs.Stats(), nil +} diff --git a/vfs/rc_test.go b/vfs/rc_test.go index 096ce5768..102355deb 100644 --- a/vfs/rc_test.go +++ b/vfs/rc_test.go @@ -119,3 +119,15 @@ func TestRcList(t *testing.T) { }, }, out) } + +func TestRcStats(t *testing.T) { + r, vfs, cleanup, call := rcNewRun(t, "vfs/stats") + defer cleanup() + out, err := call.Fn(context.Background(), nil) + require.NoError(t, err) + assert.Equal(t, fs.ConfigString(r.Fremote), out["fs"]) + assert.Equal(t, int32(1), out["inUse"]) + assert.Equal(t, 0, out["metadataCache"].(rc.Params)["files"]) + assert.Equal(t, 1, out["metadataCache"].(rc.Params)["dirs"]) + assert.Equal(t, vfs.Opt, out["opt"].(vfscommon.Options)) +} diff --git a/vfs/vfs.go b/vfs/vfs.go index 640edd246..d220d015e 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -36,6 +36,7 @@ import ( "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/cache" "github.com/rclone/rclone/fs/log" + "github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/vfs/vfscache" "github.com/rclone/rclone/vfs/vfscommon" @@ -241,6 +242,32 @@ func New(f fs.Fs, opt *vfscommon.Options) *VFS { return vfs } +// Stats returns info about the VFS +func (vfs *VFS) Stats() (out rc.Params) { + out = make(rc.Params) + out["fs"] = fs.ConfigString(vfs.f) + out["opt"] = vfs.Opt + out["inUse"] = atomic.LoadInt32(&vfs.inUse) + + var ( + dirs int + files int + ) + vfs.root.walk(func(d *Dir) { + dirs++ + files += len(d.items) + }) + inf := make(rc.Params) + out["metadataCache"] = inf + inf["dirs"] = dirs + inf["files"] = files + + if vfs.cache != nil { + out["diskCache"] = vfs.cache.Stats() + } + return out +} + // Return the number of active cache entries and a VFS if any are in // the cache. func activeCacheEntries() (vfs *VFS, count int) { diff --git a/vfs/vfscache/cache.go b/vfs/vfscache/cache.go index 88d1f2f24..50c32bfc2 100644 --- a/vfs/vfscache/cache.go +++ b/vfs/vfscache/cache.go @@ -21,6 +21,7 @@ import ( "github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/operations" + "github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/lib/encoder" "github.com/rclone/rclone/lib/file" "github.com/rclone/rclone/vfs/vfscache/writeback" @@ -145,6 +146,29 @@ func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVir return c, nil } +// Stats returns info about the Cache +func (c *Cache) Stats() (out rc.Params) { + out = make(rc.Params) + // read only - no locking needed to read these + out["path"] = c.root + out["pathMeta"] = c.metaRoot + out["hashType"] = c.hashType + + uploadsInProgress, uploadsQueued := c.writeback.Stats() + out["uploadsInProgress"] = uploadsInProgress + out["uploadsQueued"] = uploadsQueued + + c.mu.Lock() + defer c.mu.Unlock() + + out["files"] = len(c.item) + out["erroredFiles"] = len(c.errItems) + out["bytesUsed"] = c.used + out["outOfSpace"] = c.outOfSpace + + return out +} + // createDir creates a directory path, along with any necessary parents func createDir(dir string) error { return file.MkdirAll(dir, 0700) diff --git a/vfs/vfscache/cache_test.go b/vfs/vfscache/cache_test.go index 8df997d14..6755dcee4 100644 --- a/vfs/vfscache/cache_test.go +++ b/vfs/vfscache/cache_test.go @@ -701,3 +701,15 @@ func TestCacheDump(t *testing.T) { out = c.Dump() assert.Equal(t, "Cache{\n}\n", out) } + +func TestCacheStats(t *testing.T) { + _, c, cleanup := newTestCache(t) + defer cleanup() + + out := c.Stats() + assert.Equal(t, int64(0), out["bytesUsed"]) + assert.Equal(t, 0, out["erroredFiles"]) + assert.Equal(t, 0, out["files"]) + assert.Equal(t, 0, out["uploadsInProgress"]) + assert.Equal(t, 0, out["uploadsQueued"]) +}