1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-26 07:28:20 +08:00

Merge pull request #5010 from ipfs/feat/diskusage2

Efficient "repo stat" (DiskUsage) and "--size-only" flag
This commit is contained in:
Whyrusleeping
2018-07-16 16:50:41 +02:00
committed by GitHub
4 changed files with 99 additions and 66 deletions

View File

@ -150,14 +150,20 @@ var repoStatCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Get stats for the currently used repo.",
ShortDescription: `
'ipfs repo stat' is a plumbing command that will scan the local
set of stored objects and print repo statistics. It outputs to stdout:
'ipfs repo stat' provides information about the local set of
stored objects. It outputs:
RepoSize int Size in bytes that the repo is currently taking.
StorageMax string Maximum datastore size (from configuration)
NumObjects int Number of objects in the local repo.
RepoPath string The path to the repo being currently used.
RepoSize int Size in bytes that the repo is currently taking.
Version string The repo version.
`,
},
Options: []cmdkit.Option{
cmdkit.BoolOption("size-only", "Only report RepoSize and StorageMax."),
cmdkit.BoolOption("human", "Output sizes in MiB."),
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
n, err := GetNode(env)
if err != nil {
@ -165,18 +171,28 @@ Version string The repo version.
return
}
stat, err := corerepo.RepoStat(n, req.Context)
sizeOnly, _ := req.Options["size-only"].(bool)
if sizeOnly {
sizeStat, err := corerepo.RepoSize(req.Context, n)
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
cmds.EmitOnce(res, &corerepo.Stat{
SizeStat: sizeStat,
})
return
}
stat, err := corerepo.RepoStat(req.Context, n)
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
cmds.EmitOnce(res, stat)
cmds.EmitOnce(res, &stat)
},
Options: []cmdkit.Option{
cmdkit.BoolOption("human", "Output RepoSize in MiB."),
},
Type: corerepo.Stat{},
Type: &corerepo.Stat{},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
stat, ok := v.(*corerepo.Stat)
@ -184,31 +200,34 @@ Version string The repo version.
return e.TypeErr(stat, v)
}
human, _ := req.Options["human"].(bool)
wtr := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
defer wtr.Flush()
fmt.Fprintf(wtr, "NumObjects:\t%d\n", stat.NumObjects)
sizeInMiB := stat.RepoSize / (1024 * 1024)
if human && sizeInMiB > 0 {
fmt.Fprintf(wtr, "RepoSize (MiB):\t%d\n", sizeInMiB)
} else {
fmt.Fprintf(wtr, "RepoSize:\t%d\n", stat.RepoSize)
}
if stat.StorageMax != corerepo.NoLimit {
maxSizeInMiB := stat.StorageMax / (1024 * 1024)
if human && maxSizeInMiB > 0 {
fmt.Fprintf(wtr, "StorageMax (MiB):\t%d\n", maxSizeInMiB)
human, _ := req.Options["human"].(bool)
sizeOnly, _ := req.Options["size-only"].(bool)
printSize := func(name string, size uint64) {
sizeInMiB := size / (1024 * 1024)
if human && sizeInMiB > 0 {
fmt.Fprintf(wtr, "%s (MiB):\t%d\n", name, sizeInMiB)
} else {
fmt.Fprintf(wtr, "StorageMax:\t%d\n", stat.StorageMax)
fmt.Fprintf(wtr, "%s:\t%d\n", name, size)
}
}
fmt.Fprintf(wtr, "RepoPath:\t%s\n", stat.RepoPath)
fmt.Fprintf(wtr, "Version:\t%s\n", stat.Version)
wtr.Flush()
if !sizeOnly {
fmt.Fprintf(wtr, "NumObjects:\t%d\n", stat.NumObjects)
}
printSize("RepoSize", stat.RepoSize)
printSize("StorageMax", stat.StorageMax)
if !sizeOnly {
fmt.Fprintf(wtr, "RepoPath:\t%s\n", stat.RepoPath)
fmt.Fprintf(wtr, "Version:\t%s\n", stat.Version)
}
return nil
}),
},
}

View File

@ -5,34 +5,40 @@ import (
"math"
context "context"
"github.com/ipfs/go-ipfs/core"
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
)
type Stat struct {
NumObjects uint64
// SizeStat wraps information about the repository size and its limit.
type SizeStat struct {
RepoSize uint64 // size in bytes
StorageMax uint64 // size in bytes
}
// Stat wraps information about the objects stored on disk.
type Stat struct {
SizeStat
NumObjects uint64
RepoPath string
Version string
StorageMax uint64 // size in bytes
}
// NoLimit represents the value for unlimited storage
const NoLimit uint64 = math.MaxUint64
func RepoStat(n *core.IpfsNode, ctx context.Context) (*Stat, error) {
r := n.Repo
usage, err := r.GetStorageUsage()
// RepoStat returns a *Stat object with all the fields set.
func RepoStat(ctx context.Context, n *core.IpfsNode) (Stat, error) {
sizeStat, err := RepoSize(ctx, n)
if err != nil {
return nil, err
return Stat{}, err
}
allKeys, err := n.Blockstore.AllKeysChan(ctx)
if err != nil {
return nil, err
return Stat{}, err
}
count := uint64(0)
@ -42,27 +48,44 @@ func RepoStat(n *core.IpfsNode, ctx context.Context) (*Stat, error) {
path, err := fsrepo.BestKnownPath()
if err != nil {
return nil, err
return Stat{}, err
}
return Stat{
SizeStat: SizeStat{
RepoSize: sizeStat.RepoSize,
StorageMax: sizeStat.StorageMax,
},
NumObjects: count,
RepoPath: path,
Version: fmt.Sprintf("fs-repo@%d", fsrepo.RepoVersion),
}, nil
}
// RepoSize returns a *Stat object with the RepoSize and StorageMax fields set.
func RepoSize(ctx context.Context, n *core.IpfsNode) (SizeStat, error) {
r := n.Repo
cfg, err := r.Config()
if err != nil {
return nil, err
return SizeStat{}, err
}
usage, err := r.GetStorageUsage()
if err != nil {
return SizeStat{}, err
}
storageMax := NoLimit
if cfg.Datastore.StorageMax != "" {
storageMax, err = humanize.ParseBytes(cfg.Datastore.StorageMax)
if err != nil {
return nil, err
return SizeStat{}, err
}
}
return &Stat{
NumObjects: count,
return SizeStat{
RepoSize: usage,
RepoPath: path,
Version: fmt.Sprintf("fs-repo@%d", fsrepo.RepoVersion),
StorageMax: storageMax,
}, nil
}

View File

@ -27,6 +27,7 @@ import (
ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr"
lockfile "gx/ipfs/QmYzCZUe9CBDkyPNPcRNqXQK8KKhtUfXvc88PkFujAEJPe/go-fs-lock"
logging "gx/ipfs/QmcVVHfdyv15GVPk7NrxdWjh2hLVccXnoD8j2tyQShiXJb/go-log"
ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
)
// LockFile is the filename of the repo lock, relative to config dir
@ -674,29 +675,7 @@ func (r *FSRepo) Datastore() repo.Datastore {
// GetStorageUsage computes the storage space taken by the repo in bytes
func (r *FSRepo) GetStorageUsage() (uint64, error) {
pth, err := config.PathRoot()
if err != nil {
return 0, err
}
pth, err = filepath.EvalSymlinks(pth)
if err != nil {
log.Debugf("filepath.EvalSymlinks error: %s", err)
return 0, err
}
var du uint64
err = filepath.Walk(pth, func(p string, f os.FileInfo, err error) error {
if err != nil {
log.Debugf("filepath.Walk error: %s", err)
return nil
}
if f != nil {
du += uint64(f.Size())
}
return nil
})
return du, err
return ds.DiskUsage(r.Datastore())
}
func (r *FSRepo) SwarmKey() ([]byte, error) {

View File

@ -245,6 +245,18 @@ test_expect_success "repo stats are updated correctly" '
test $(get_field_num "RepoSize" repo-stats-2) -ge $(get_field_num "RepoSize" repo-stats)
'
test_expect_success "'ipfs repo stat --size-only' succeeds" '
ipfs repo stat --size-only > repo-stats-size-only
'
test_expect_success "repo stats came out correct for --size-only" '
grep "RepoSize" repo-stats-size-only &&
grep "StorageMax" repo-stats-size-only &&
grep -v "RepoPath" repo-stats-size-only &&
grep -v "NumObjects" repo-stats-size-only &&
grep -v "Version" repo-stats-size-only
'
test_expect_success "'ipfs repo version' succeeds" '
ipfs repo version > repo-version
'