diff --git a/core/commands/repo.go b/core/commands/repo.go index 3b634e6311c..e4ac8807d64 100644 --- a/core/commands/repo.go +++ b/core/commands/repo.go @@ -3,11 +3,10 @@ package commands import ( "bytes" "fmt" - "io" - cmds "github.com/ipfs/go-ipfs/commands" corerepo "github.com/ipfs/go-ipfs/core/corerepo" u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util" + "io" ) var RepoCmd = &cmds.Command{ @@ -19,7 +18,8 @@ var RepoCmd = &cmds.Command{ }, Subcommands: map[string]*cmds.Command{ - "gc": repoGcCmd, + "gc": repoGcCmd, + "stat": repoStatCmd, }, } @@ -95,3 +95,60 @@ order to reclaim hard disk space. }, }, } + +var repoStatCmd = &cmds.Command{ + Helptext: cmds.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: +NumObjects int number of objects in the local repo +RepoSize int size in bytes that the repo is currently taking +RepoPath string the path to the repo being currently used +`, + }, + Run: func(req cmds.Request, res cmds.Response) { + n, err := req.InvocContext().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + stat, err := corerepo.RepoStat(n, req.Context()) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + res.SetOutput(stat) + }, + Options: []cmds.Option{ + cmds.BoolOption("human", "Output RepoSize in MiB."), + }, + Type: corerepo.Stat{}, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + stat, ok := res.Output().(*corerepo.Stat) + if !ok { + return nil, u.ErrCast() + } + + human, _, err := res.Request().Option("human").Bool() + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "NumObjects \t %d\n", stat.NumObjects) + sizeInMiB := stat.RepoSize / (1024 * 1024) + if human && sizeInMiB > 0 { + fmt.Fprintf(buf, "RepoSize (MiB) \t %d\n", sizeInMiB) + } else { + fmt.Fprintf(buf, "RepoSize \t %d\n", stat.RepoSize) + } + fmt.Fprintf(buf, "RepoPath \t %s\n", stat.RepoPath) + + return buf, nil + }, + }, +} diff --git a/core/corerepo/stat.go b/core/corerepo/stat.go new file mode 100644 index 00000000000..65abf2efd42 --- /dev/null +++ b/core/corerepo/stat.go @@ -0,0 +1,43 @@ +package corerepo + +import ( + "github.com/ipfs/go-ipfs/core" + fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo" + context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context" +) + +type Stat struct { + NumObjects uint64 + RepoSize uint64 // size in bytes + RepoPath string +} + +func RepoStat(n *core.IpfsNode, ctx context.Context) (*Stat, error) { + r := n.Repo + + usage, err := r.GetStorageUsage() + if err != nil { + return nil, err + } + + allKeys, err := n.Blockstore.AllKeysChan(ctx) + if err != nil { + return nil, err + } + + count := uint64(0) + for range allKeys { + count++ + } + + path, err := fsrepo.BestKnownPath() + if err != nil { + return nil, err + } + + return &Stat{ + NumObjects: count, + RepoSize: usage, + RepoPath: path, + }, nil +} diff --git a/test/sharness/t0080-repo.sh b/test/sharness/t0080-repo.sh index 01ef79b0ab3..cbe4e8dcf2d 100755 --- a/test/sharness/t0080-repo.sh +++ b/test/sharness/t0080-repo.sh @@ -219,6 +219,31 @@ test_expect_success "'ipfs refs --unique --recursive (bigger)'" ' test_sort_cmp expected actual || test_fsh cat refs_output ' +get_field_num() { + field=$1 + file=$2 + num=$(grep "$field" "$file" | awk '{ print $2 }') + echo $num +} + +test_expect_success "'ipfs repo stat' succeeds" ' + ipfs repo stat > repo-stats +' +test_expect_success "repo stats came out correct" ' + grep "RepoPath" repo-stats && + grep "RepoSize" repo-stats && + grep "NumObjects" repo-stats +' + +test_expect_success "'ipfs repo stat' after adding a file" ' + ipfs add repo-stats && + ipfs repo stat > repo-stats-2 +' + +test_expect_success "repo stats are updated correctly" ' + test $(get_field_num "RepoSize" repo-stats-2) -ge $(get_field_num "RepoSize" repo-stats) +' + test_kill_ipfs_daemon test_done