Skip to content

Commit

Permalink
cmd/compact: add new compact cmd, used for specific path (#4337)
Browse files Browse the repository at this point in the history
Signed-off-by: jiefeng <[email protected]>
  • Loading branch information
jiefenghuang authored Jan 15, 2024
1 parent 0b79ca7 commit b1b6d29
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 20 deletions.
33 changes: 22 additions & 11 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,37 @@ ltmain.sh
*.rej
.deps
.dirstamp
.vscode
.idea
fstests/secfs.test
fstests/flock
!fstests/Makefile
jfs
/juicefs
/juicefs.ceph
/juicefs.exe
/juicefs.lite
dist/
*.rdb
.release-env
*.so
libjfs.h
.DS_Store
docs/node_modules
cmd/cmd
*.dump
*.out
.hypothesis
__pycache__
/node_modules

# os
.DS_Store

# ide
.vscode
.idea

# lang
__pycache__

# temp
pkg/meta/badger
*.dump
*.out

# gen
/juicefs
/juicefs.ceph
/juicefs.exe
/juicefs.lite
dist/
118 changes: 118 additions & 0 deletions cmd/compact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* JuiceFS, Copyright 2024 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 (
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func createTestFile(path string, size int, partCnt int) error {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
if err != nil {
return err
}
defer file.Close()

content := []byte(strings.Repeat("a", size/partCnt))
for i := 0; i < partCnt; i++ {
if _, err = file.Write(content); err != nil {
return err
}
if err = file.Sync(); err != nil {
return err
}
}
return nil
}

type testDir struct {
path string
fileCnt int
fileSize int
filePart int
}

func initForCompactTest(mountDir string, dirs map[string]testDir) {
for _, d := range dirs {
dirPath := filepath.Join(mountDir, d.path)

err := os.MkdirAll(dirPath, 0755)
if err != nil {
panic(err)
}

for i := 0; i < d.fileCnt; i++ {
if err := createTestFile(filepath.Join(dirPath, fmt.Sprintf("%d", i)), d.fileSize, d.filePart); err != nil {
panic(err)
}
}
}
}

func TestCompact(t *testing.T) {
logger.Level = logrus.DebugLevel
var bucket string
mountTemp(t, &bucket, []string{"--trash-days=0"}, nil)
defer umountTemp(t)

dirs := map[string]testDir{
"d1/d11": {
path: "d1/d11",
fileCnt: 10,
fileSize: 10,
filePart: 2,
},
"d1": {
path: "d1",
fileCnt: 20,
fileSize: 10,
filePart: 5,
},
"d2": {
path: "d2",
fileCnt: 5,
fileSize: 20,
filePart: 4,
},
}
initForCompactTest(testMountPoint, dirs)
dataDir := filepath.Join(bucket, testVolume, "chunks")

sumChunks := 0
for _, d := range dirs {
sumChunks += d.fileCnt * d.filePart
}

chunkCnt := getFileCount(dataDir)
assert.Equal(t, sumChunks, chunkCnt)

for _, d := range dirs {
err := Main([]string{"", "compact", filepath.Join(testMountPoint, d.path)})
assert.Nil(t, err)

chunkCnt = getFileCount(dataDir)
sumChunks -= d.fileCnt * (d.filePart - 1)
assert.Equal(t, sumChunks, chunkCnt)
}
}
131 changes: 131 additions & 0 deletions cmd/compact_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//go:build !windows
// +build !windows

/*
* JuiceFS, Copyright 2024 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 (
"fmt"
"math"
"path/filepath"
"syscall"

"github.com/juicedata/juicefs/pkg/meta"
"github.com/juicedata/juicefs/pkg/utils"
"github.com/urfave/cli/v2"
)

func cmdCompact() *cli.Command {
return &cli.Command{
Name: "compact",
Action: compact,
Category: "TOOL",
Usage: "Trigger compaction of chunks",
ArgsUsage: "PATH...",
Description: `
Examples:
# compact with path
$ juicefs compact /mnt/jfs/foo
`,
Flags: []cli.Flag{
&cli.UintFlag{
Name: "threads",
Aliases: []string{"p"},
Value: 10,
Usage: "compact concurrency",
},
},
}
}

func compact(ctx *cli.Context) error {
setup(ctx, 1)

coCnt := ctx.Int("threads")
if coCnt <= 0 {
logger.Warn("threads should be > 0")
coCnt = 1
} else if coCnt >= math.MaxUint16 {
logger.Warn("threads should be < MaxUint16")
coCnt = math.MaxUint16
}

paths := ctx.Args().Slice()
for i := 0; i < len(paths); i++ {
path, err := filepath.Abs(paths[i])
if err != nil {
logger.Fatalf("get absolute path of %s error: %v", paths[i], err)
}

inodeNo, err := utils.GetFileInode(path)
if err != nil {
logger.Errorf("lookup inode for %s error: %v", path, err)
continue
}
inode := meta.Ino(inodeNo)

if !inode.IsValid() {
logger.Fatalf("inode numbe %d not valid", inode)
}

if err = doCompact(inode, path, uint16(coCnt)); err != nil {
logger.Error(err)
}
}
return nil
}

func doCompact(inode meta.Ino, path string, coCnt uint16) error {
f, err := openController(path)
if err != nil {
return fmt.Errorf("open control file for [%d:%s]: %w", inode, path, err)
}
defer f.Close()

headerLen, bodyLen := uint32(8), uint32(8+2)
wb := utils.NewBuffer(headerLen + bodyLen)
wb.Put32(meta.CompactPath)
wb.Put32(bodyLen)
wb.Put64(uint64(inode))
wb.Put16(coCnt)

_, err = f.Write(wb.Bytes())
if err != nil {
logger.Fatalf("write message: %s", err)
}

progress := utils.NewProgress(false)
bar := progress.AddCountBar("Compacted chunks", 0)
_, errno := readProgress(f, func(totalChunks, currChunks uint64) {
bar.SetTotal(int64(totalChunks))
bar.SetCurrent(int64(currChunks))
})

bar.Done()
progress.Done()

if errno == syscall.EINVAL {
logger.Fatalf("compact is not supported, please upgrade and mount again")
}
if errno != 0 {
return fmt.Errorf("compact [%d:%s] error: %s", inode, path, errno)
}

logger.Infof("compact [%d:%s] success.", inode, path)
return nil
}
38 changes: 38 additions & 0 deletions cmd/compact_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//go:build windows
// +build windows

/*
* JuiceFS, Copyright 2024 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 (
"github.com/urfave/cli/v2"
)

func cmdCompact() *cli.Command {
return &cli.Command{
Name: "compact",
Action: compact,
Category: "TOOL",
Usage: "Trigger compaction of chunks, not supported for Windows",
}
}

func compact(ctx *cli.Context) error {
logger.Warnf("not supported for Windows.")
return nil
}
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func Main(args []string) error {
cmdDebug(),
cmdClone(),
cmdSummary(),
cmdCompact(),
},
}

Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ require (
github.com/redis/go-redis/v9 v9.0.2
github.com/sirupsen/logrus v1.9.0
github.com/smartystreets/goconvey v1.7.2
github.com/stretchr/testify v1.8.4
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2
github.com/tencentyun/cos-go-sdk-v5 v0.7.45
github.com/tikv/client-go/v2 v2.0.4
Expand Down Expand Up @@ -112,6 +113,7 @@ require (
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/siphash v1.2.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
Expand Down Expand Up @@ -172,7 +174,7 @@ require (
github.com/klauspost/reedsolomon v1.9.11 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/dns v1.1.41 // indirect
Expand All @@ -198,6 +200,7 @@ require (
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 // indirect
github.com/pingcap/kvproto v0.0.0-20221129023506-621ec37aac7a // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
Expand Down Expand Up @@ -246,6 +249,7 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
xorm.io/builder v0.3.7 // indirect
)

Expand Down
Loading

0 comments on commit b1b6d29

Please sign in to comment.