Skip to content

Commit

Permalink
add subcommand summary (#3381)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexilee authored Apr 3, 2023
1 parent 4932d99 commit 6b7a267
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 6 deletions.
2 changes: 1 addition & 1 deletion cmd/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func clone(ctx *cli.Context) error {
progress := utils.NewProgress(false)
defer progress.Done()
bar := progress.AddCountBar("Cloning entries", 0)
if errno := readProgress(f, func(count uint64, total uint64) {
if _, errno := readProgress(f, func(count uint64, total uint64) {
bar.SetTotal(int64(total))
bar.SetCurrent(int64(count))
}); errno != 0 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func info(ctx *cli.Context) error {
if err != nil {
logger.Fatalf("write message: %s", err)
}
if errno := readProgress(f, func(count, size uint64) {
if _, errno := readProgress(f, func(count, size uint64) {
dspin.SetCurrent(int64(count), int64(size))
}); errno != 0 {
logger.Errorf("failed to get info: %s", syscall.Errno(errno))
Expand Down
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func Main(args []string) error {
cmdSync(),
cmdDebug(),
cmdClone(),
cmdSummary(),
},
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/rmr.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func rmr(ctx *cli.Context) error {
if err != nil {
logger.Fatalf("write message: %s", err)
}
if errno := readProgress(f, func(count, bytes uint64) {
if _, errno := readProgress(f, func(count, bytes uint64) {
spin.SetCurrent(int64(count))
}); errno != 0 {
logger.Fatalf("RMR %s: %s", path, errno)
Expand Down
194 changes: 194 additions & 0 deletions cmd/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* JuiceFS, Copyright 2023 Juicedata, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cmd

import (
"encoding/json"
"fmt"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"

"github.com/dustin/go-humanize"
"github.com/juicedata/juicefs/pkg/meta"
"github.com/juicedata/juicefs/pkg/utils"
"github.com/juicedata/juicefs/pkg/vfs"
"github.com/urfave/cli/v2"
)

func cmdSummary() *cli.Command {
return &cli.Command{
Name: "summary",
Action: summary,
Category: "INSPECTOR",
Usage: "Show tree summary of a directory",
ArgsUsage: "PATH",
Description: `
It is used to show tree summary of target directory.
Examples:
# Show with path
$ juicefs summary /mnt/jfs/foo
# Show max depth of 5
$ juicefs summary --depth 5 /mnt/jfs/foo
# Show top 20 entries
$ juicefs summary --entries 20 /mnt/jfs/foo
# Show accurate result
$ juicefs summary --strict /mnt/jfs/foo
`,
Flags: []cli.Flag{
&cli.UintFlag{
Name: "depth",
Aliases: []string{"d"},
Value: 2,
Usage: "depth of tree to show (zero means only show root)",
},
&cli.UintFlag{
Name: "entries",
Aliases: []string{"e"},
Value: 10,
Usage: "show top N entries (sort by size)",
},
&cli.BoolFlag{
Name: "strict",
Usage: "show accurate summary, including directories and files (may be slow)",
},
&cli.BoolFlag{
Name: "csv",
Usage: "print summary in csv format",
},
},
}
}

func summary(ctx *cli.Context) error {
setup(ctx, 1)
if runtime.GOOS == "windows" {
logger.Infof("Windows is not supported")
return nil
}
var strict uint8
if ctx.Bool("strict") {
strict = 1
}
depth := ctx.Uint("depth")
if depth > 10 {
logger.Warn("depth should be less than 11")
depth = 10
}
topN := ctx.Uint("entries")
if topN > 100 {
logger.Warn("entries should be less than 101")
topN = 100
}

csv := ctx.Bool("csv")
progress := utils.NewProgress(csv)
path := ctx.Args().Get(0)
dspin := progress.AddDoubleSpinner(path)
dpath, err := filepath.Abs(path)
if err != nil {
logger.Fatalf("abs of %s: %s", path, err)
}
inode, err := utils.GetFileInode(dpath)
if err != nil {
logger.Fatalf("lookup inode for %s: %s", path, err)
}
if inode < uint64(meta.RootInode) {
logger.Fatalf("inode number shouldn't be less than %d", meta.RootInode)
}
f, err := openController(dpath)
if err != nil {
logger.Fatalf("open controller: %s", err)
}
headerLen := uint32(8)
contentLen := uint32(8 + 1 + 1 + 1)
wb := utils.NewBuffer(headerLen + contentLen)
wb.Put32(meta.OpSummary)
wb.Put32(contentLen)
wb.Put64(inode)
wb.Put8(uint8(depth))
wb.Put8(uint8(topN))
wb.Put8(strict)
_, err = f.Write(wb.Bytes())
if err != nil {
logger.Fatalf("write message: %s", err)
}
data, errno := readProgress(f, func(count, size uint64) {
dspin.SetCurrent(int64(count), int64(size))
})
if errno == syscall.EINVAL {
logger.Fatalf("summary is not supported, please upgrade and mount again")
}
if errno != 0 {
logger.Errorf("failed to get info: %s", syscall.Errno(errno))
}
dspin.Done()
progress.Done()

var resp vfs.SummaryReponse
err = json.Unmarshal(data, &resp)
_ = f.Close()
if err == nil && resp.Errno != 0 {
err = resp.Errno
}
if err != nil {
logger.Fatalf("summary: %s", err)
}
results := [][]string{{"PATH", "SIZE", "DIRS", "FILES"}}
renderTree(&results, &resp.Tree, csv)
if csv {
printCSVResult(results)
} else {
printResult(results, 0, false)
}
return nil
}

func printCSVResult(results [][]string) {
for _, r := range results {
fmt.Println(strings.Join(r, ","))
}
}

func renderTree(results *[][]string, tree *meta.TreeSummary, csv bool) {
if tree == nil {
return
}
var size string
if csv {
size = strconv.FormatUint(tree.Size, 10)
} else {
size = humanize.IBytes(uint64(tree.Size))
}

result := []string{
tree.Path,
size,
strconv.FormatUint(tree.Dirs, 10),
strconv.FormatUint(tree.Files, 10),
}
*results = append(*results, result)
for _, child := range tree.Children {
renderTree(results, child, csv)
}
}
14 changes: 11 additions & 3 deletions cmd/warmup.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ func readControl(cf *os.File, resp []byte) int {
}
}

func readProgress(cf *os.File, showProgress func(uint64, uint64)) (errno syscall.Errno) {
var resp = make([]byte, 1024)
func readProgress(cf *os.File, showProgress func(uint64, uint64)) (data []byte, errno syscall.Errno) {
var resp = make([]byte, 2<<16)
END:
for {
n := readControl(cf, resp)
Expand All @@ -101,6 +101,14 @@ END:
} else if off+17 <= n && resp[off] == meta.CPROGRESS {
showProgress(binary.BigEndian.Uint64(resp[off+1:off+9]), binary.BigEndian.Uint64(resp[off+9:off+17]))
off += 17
} else if off+5 < n && resp[off] == meta.CDATA {
size := binary.BigEndian.Uint32(resp[off+1 : off+5])
if off+5+int(size) > n {
logger.Errorf("Bad response off %d n %d: %v", off, n, resp)
break
}
data = append(data, resp[off+5:off+5+int(size)]...)
break END
} else {
logger.Errorf("Bad response off %d n %d: %v", off, n, resp)
break
Expand Down Expand Up @@ -134,7 +142,7 @@ func sendCommand(cf *os.File, batch []string, threads uint, background bool, dsp
logger.Infof("Warm-up cache for %d paths in background", len(batch))
return
}
if errno := readProgress(cf, func(count, bytes uint64) {
if _, errno := readProgress(cf, func(count, bytes uint64) {
dspin.SetCurrent(int64(count), int64(bytes))
}); errno != 0 {
logger.Fatalf("Warm up failed: %s", errno)
Expand Down
17 changes: 17 additions & 0 deletions pkg/meta/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const (
InfoV2 = 1005
// Clone is a message to clone a file or dir from another.
Clone = 1006
// OpSummary is a message to get tree summary of directories.
OpSummary = 1007
)

const (
Expand Down Expand Up @@ -111,6 +113,7 @@ type internalNode struct {

// Type of control messages
const CPROGRESS = 0xFE // 16 bytes: progress increment
const CDATA = 0xFF // 4 bytes: data length

// MsgCallback is a callback for messages from meta service.
type MsgCallback func(...interface{}) error
Expand Down Expand Up @@ -230,6 +233,18 @@ type Summary struct {
Dirs uint64
}

type TreeSummary struct {
Inode Ino
Path string
Type uint8
Size uint64
Files uint64
Dirs uint64
Children []*TreeSummary `json:",omitempty"`

parent *TreeSummary `json:"-"`
}

type SessionInfo struct {
Version string
HostName string
Expand Down Expand Up @@ -403,6 +418,8 @@ type Meta interface {
GetSummary(ctx Context, inode Ino, summary *Summary, recursive bool) syscall.Errno
// Get summary of a node; for a directory it will use recorded dirStats
FastGetSummary(ctx Context, inode Ino, summary *Summary, recursive bool) syscall.Errno
// GetTreeSummary returns a summary in tree structure
GetTreeSummary(ctx Context, root *TreeSummary, depth, topN uint8, strict bool) syscall.Errno
// Clone a file or directory
Clone(ctx Context, srcIno, dstParentIno Ino, dstName string, cmode uint8, cumask uint16, count, total *uint64) syscall.Errno
// GetPaths returns all paths of an inode
Expand Down
Loading

0 comments on commit 6b7a267

Please sign in to comment.