Repository: juicedata/juicefs
Pull request #3037 (2022-12-01 03:50:49)

CI: run mutation test in parallel

github-actions[bot]: <!-- add-pr-comment:add-pr-comment -->

*Mutate Test Report For Array
 
Report detail: https://github.com/juicedata/juicefs/actions/runs/3589391354
Usage: https://github.com/juicedata/juicefs/blob/main/.github/scripts/how_to_use_mutate_test.md
github-actions[bot]: <!-- add-pr-comment:add-pr-comment -->

*Mutate Test Report For Array
 
Report detail: https://github.com/juicedata/juicefs/actions/runs/3589991564
Usage: https://github.com/juicedata/juicefs/blob/main/.github/scripts/how_to_use_mutate_test.md
github-actions[bot]: <!-- add-pr-comment:add-pr-comment -->

*Mutate Test Report For Array
 
Report detail: https://github.com/juicedata/juicefs/actions/runs/3590170969
Usage: https://github.com/juicedata/juicefs/blob/main/.github/scripts/how_to_use_mutate_test.md

Patch:
diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml
index d6be7929..f4b7bc84 100644
--- a/.github/actions/build/action.yml
+++ b/.github/actions/build/action.yml
@@ -37,14 +37,14 @@ runs:
     - name: Build linux target
       shell: bash
       run: |
+        echo "start to build ${{inputs.target}}"
         if [ "${{inputs.target}}" == "juicefs.fdb" ]; then
-          echo "build juicefs.fdb"
           wget -q https://github.com/apple/foundationdb/releases/download/6.3.23/foundationdb-clients_6.3.23-1_amd64.deb
           sudo dpkg -i foundationdb-clients_6.3.23-1_amd64.deb
           make ${{inputs.target}}
           mv juicefs.fdb juicefs
         else
-          echo "build juicefs"
           make ${{inputs.target}}
         fi
+        echo "build ${{inputs.target}} succeed"
 
diff --git a/.github/scripts/check_coverage.py b/.github/scripts/check_coverage.py
index fa54944e..b7189117 100644
--- a/.github/scripts/check_coverage.py
+++ b/.github/scripts/check_coverage.py
@@ -9,7 +9,7 @@ def is_mutation_in_coverage(original_file, changed_file, coverage_file):
 	changed = open(changed_file, 'r').readlines()
 	for i in range( min(len(original), len(changed)) ):
 		if original[i] != changed[i]:
-			print(f'line {i+1} is different')
+			# print(f'line {i+1} is different')
 			if (i+1) not in coverage:
 				# print(f'line {i+1} is not in coverage')
 				return False
@@ -39,7 +39,7 @@ if __name__ == '__main__':
 	original_file = os.environ['MUTATE_ORIGINAL']
 	changed_file = os.environ['MUTATE_CHANGED']
 	coverage_file = os.environ['COVERAGE_FILE']
-	print(f'MUTATE_ORIGINAL={original_file} MUTATE_CHANGED={changed_file} COVERAGE_FILE={coverage_file} python3 ../../scripts/check_coverage.py')
+	# print(f'MUTATE_ORIGINAL={original_file} MUTATE_CHANGED={changed_file} COVERAGE_FILE={coverage_file} python3 ../../scripts/check_coverage.py')
 	r = is_mutation_in_coverage(original_file, changed_file, coverage_file)
 	if r:
 		exit(0)
diff --git a/.github/scripts/parse_job_total.py b/.github/scripts/parse_job_total.py
new file mode 100644
index 00000000..cd74bf6d
--- /dev/null
+++ b/.github/scripts/parse_job_total.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+import os
+import re
+import sys
+
+
+def parse_test_jobs(test_file_path):
+    with open(test_file_path) as f:
+        lines = f.readlines()
+        for line in lines:
+            g = re.search('^//mutate_test_job_number:\s*(.+)', line.strip())
+            if g:
+                return int(g.group(1))
+                
+    return 0
+
+if __name__ == '__main__':
+    test_file_path = os.environ['TEST_FILE_NAME']
+    if not test_file_path:
+        print('test file name is empty', file=sys.stderr)
+        exit(1)
+    print(parse_test_jobs(test_file_path))
+    
\ No newline at end of file
diff --git a/.github/workflows/fsrand.yml b/.github/workflows/fsrand.yml
index 9c79f444..c51539ba 100644
--- a/.github/workflows/fsrand.yml
+++ b/.github/workflows/fsrand.yml
@@ -131,6 +131,6 @@ jobs:
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
           
       - name: Setup upterm session
-        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
         timeout-minutes: 60
         uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/gateway.yml b/.github/workflows/gateway.yml
index 38109b01..71b12666 100644
--- a/.github/workflows/gateway.yml
+++ b/.github/workflows/gateway.yml
@@ -15,7 +15,12 @@ on:
       - 'docs/**'
       - '**.md'
   workflow_dispatch:
-
+    inputs:
+      debug:
+        type: boolean
+        description: "Run the build with tmate debugging enabled"
+        required: false
+        default: false
 jobs:
   gateway:
     strategy:
@@ -201,6 +206,6 @@ jobs:
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
 
       - name: Setup upterm session
-        if: ${{ failure() }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
         timeout-minutes: 1
         uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/load.yml b/.github/workflows/load.yml
index cebde80a..5af9c429 100644
--- a/.github/workflows/load.yml
+++ b/.github/workflows/load.yml
@@ -3,6 +3,12 @@ on:
   schedule:
     - cron:  '0 19 * * *'
   workflow_dispatch:
+    inputs:
+      debug:
+        type: boolean
+        description: "Run the build with tmate debugging enabled"
+        required: false
+        default: false
 
 jobs:
   load:
@@ -163,6 +169,6 @@ jobs:
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
           
       - name: Setup upterm session
-        if: ${{ failure() }}
-        timeout-minutes: 20
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
+        timeout-minutes: 60
         uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/version_compatible_hypo.yml b/.github/workflows/version_compatible_hypo.yml
index 9ae634ef..d2c42de8 100644
--- a/.github/workflows/version_compatible_hypo.yml
+++ b/.github/workflows/version_compatible_hypo.yml
@@ -145,6 +145,6 @@ jobs:
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
 
       - name: Setup upterm session
-        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
         timeout-minutes: 1
         uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/cmd/info.go b/cmd/info.go
index ce9c1501..e667b802 100644
--- a/cmd/info.go
+++ b/cmd/info.go
@@ -27,6 +27,7 @@ import (
 
 	"github.com/juicedata/juicefs/pkg/meta"
 	"github.com/juicedata/juicefs/pkg/utils"
+	"github.com/juicedata/juicefs/pkg/vfs"
 	"github.com/urfave/cli/v2"
 )
 
@@ -108,7 +109,7 @@ func info(ctx *cli.Context) error {
 		}
 
 		wb := utils.NewBuffer(8 + 10)
-		wb.Put32(meta.Info)
+		wb.Put32(meta.InfoV2)
 		wb.Put32(10)
 		wb.Put64(inode)
 		wb.Put8(recursive)
@@ -117,42 +118,165 @@ func info(ctx *cli.Context) error {
 		if err != nil {
 			logger.Fatalf("write message: %s", err)
 		}
-		data := make([]byte, 4)
-		n := readControl(f, data)
-		if n == 1 && data[0] == byte(syscall.EINVAL&0xff) {
-			logger.Fatalf("info is not supported, please upgrade and mount again")
+		var resp vfs.InfoResponse
+		err = resp.Decode(f)
+		_ = f.Close()
+		if err == syscall.EINVAL {
+			legacyInfo(d, path, inode, recursive, raw)
+			continue
 		}
-		r := utils.ReadBuffer(data)
-		size := r.Get32()
-		data = make([]byte, size)
-		n, err = f.Read(data)
+
 		if err != nil {
 			logger.Fatalf("read info: %s", err)
 		}
+
+		if resp.Failed {
+			logger.Fatalf("failed to get info: %s", resp.Reason)
+		}
+
 		fmt.Println(path, ":")
-		resp := string(data[:n])
-		var p int
-		if p = strings.Index(resp, "chunks:\n"); p > 0 {
-			p += 8
-			raw = 1 // legacy clients always return chunks
-		} else if p = strings.Index(resp, "objects:\n"); p > 0 {
-			p += 9
+		fmt.Printf("  inode: %d\n", resp.Ino)
+		fmt.Printf("  files: %d\n", resp.Summary.Files)
+		fmt.Printf("   dirs: %d\n", resp.Summary.Dirs)
+		fmt.Printf(" length: %s\n", utils.FormatBytes(resp.Summary.Length))
+		fmt.Printf("   size: %s\n", utils.FormatBytes(resp.Summary.Size))
+		switch len(resp.Paths) {
+		case 0:
+			fmt.Printf("   path: %s\n", "unknown")
+		case 1:
+			fmt.Printf("   path: %s\n", resp.Paths[0])
+		default:
+			fmt.Printf("  paths:\n")
+			for _, p := range resp.Paths {
+				fmt.Printf("\t%s\n", p)
+			}
 		}
-		if p <= 0 {
-			fmt.Println(resp)
-		} else {
-			fmt.Println(resp[:p-1])
-			if len(resp[p:]) > 0 {
-				printChunks(resp[p:], raw == 1)
+		if len(resp.Chunks) > 0 {
+			fmt.Println(" chunks:")
+			results := make([][]string, 0, 1+len(resp.Chunks))
+			results = append(results, []string{"chunkIndex", "sliceId", "size", "offset", "length"})
+			for _, c := range resp.Chunks {
+				results = append(results, []string{
+					strconv.FormatUint(c.ChunkIndex, 10),
+					strconv.FormatUint(c.Id, 10),
+					strconv.FormatUint(uint64(c.Size), 10),
+					strconv.FormatUint(uint64(c.Off), 10),
+					strconv.FormatUint(uint64(c.Len), 10),
+				})
 			}
+			printResult(results, -1, false)
+		}
+		if len(resp.Objects) > 0 {
+			fmt.Println(" objects:")
+			results := make([][]string, 0, 1+len(resp.Objects))
+			results = append(results, []string{"chunkIndex", "objectName", "size", "offset", "length"})
+			for _, o := range resp.Objects {
+				results = append(results, []string{
+					strconv.FormatUint(o.ChunkIndex, 10),
+					o.Key,
+					strconv.FormatUint(uint64(o.Size), 10),
+					strconv.FormatUint(uint64(o.Off), 10),
+					strconv.FormatUint(uint64(o.Len), 10),
+				})
+			}
+			printResult(results, 1, false)
+		}
+		if len(resp.FLocks) > 0 {
+			fmt.Println(" flocks:")
+			results := make([][]string, 0, 1+len(resp.FLocks))
+			results = append(results, []string{"Sid", "Owner", "Type"})
+			for _, l := range resp.FLocks {
+				results = append(results, []string{
+					strconv.FormatUint(l.Sid, 10),
+					strconv.FormatUint(l.Owner, 10),
+					l.Type,
+				})
+			}
+			printResult(results, 0, false)
+		}
+		if len(resp.PLocks) > 0 {
+			fmt.Println(" plocks:")
+			results := make([][]string, 0, 1+len(resp.PLocks))
+			results = append(results, []string{"Sid", "Owner", "Type", "Pid", "Start", "End"})
+			for _, l := range resp.PLocks {
+				results = append(results, []string{
+					strconv.FormatUint(l.Sid, 10),
+					strconv.FormatUint(l.Owner, 10),
+					ltypeToString(l.Type),
+					strconv.FormatUint(uint64(l.Pid), 10),
+					strconv.FormatUint(l.Start, 10),
+					strconv.FormatUint(l.End, 10),
+				})
+			}
+			printResult(results, 0, false)
 		}
-		_ = f.Close()
 	}
 
 	return nil
 }
 
-func printChunks(resp string, raw bool) {
+func ltypeToString(t uint32) string {
+	switch t {
+	case meta.F_RDLCK:
+		return "R"
+	case meta.F_WRLCK:
+		return "W"
+	default:
+		return "UNKNOWN"
+	}
+}
+
+func legacyInfo(d, path string, inode uint64, recursive, raw uint8) {
+	f := openController(d)
+	defer f.Close()
+	if f == nil {
+		logger.Errorf("%s is not inside JuiceFS", path)
+		// continue to next path
+		return
+	}
+
+	wb := utils.NewBuffer(8 + 10)
+	wb.Put32(meta.LegacyInfo)
+	wb.Put32(10)
+	wb.Put64(inode)
+	wb.Put8(recursive)
+	wb.Put8(raw)
+	_, err := f.Write(wb.Bytes())
+	if err != nil {
+		logger.Fatalf("write message: %s", err)
+	}
+	data := make([]byte, 4)
+	n := readControl(f, data)
+	if n == 1 && data[0] == byte(syscall.EINVAL&0xff) {
+		logger.Fatalf("info is not supported, please upgrade and mount again")
+	}
+	r := utils.ReadBuffer(data)
+	size := r.Get32()
+	data = make([]byte, size)
+	n, err = f.Read(data)
+	if err != nil {
+		logger.Fatalf("read info: %s", err)
+	}
+	fmt.Println(path, ":")
+	resp := string(data[:n])
+	var p int
+	if p = strings.Index(resp, "chunks:\n"); p > 0 {
+		p += 8
+		raw = 1 // legacy clients always return chunks
+	} else if p = strings.Index(resp, "objects:\n"); p > 0 {
+		p += 9
+	}
+	if p <= 0 {
+		fmt.Println(resp)
+	} else {
+		fmt.Println(resp[:p-1])
+		if len(resp[p:]) > 0 {
+			legacyPrintChunks(resp[p:], raw == 1)
+		}
+	}
+}
+
+func legacyPrintChunks(resp string, raw bool) {
 	cs := strings.Split(resp, "\n")
 	result := make([][]string, len(cs))
 	result[0] = []string{"chunkIndex", "objectName", "size", "offset", "length"}
diff --git a/docs/en/administration/destroy.md b/docs/en/administration/destroy.md
index 438a7861..e9a6c899 100644
--- a/docs/en/administration/destroy.md
+++ b/docs/en/administration/destroy.md
@@ -1,8 +1,7 @@
 ---
-sidebar_label: How to destroy a file system
+title: How to destroy a file system
 sidebar_position: 8
 ---
-# How to destroy a file system
 
 JuiceFS client provides the `destroy` command to completely destroy a file system, which will result in
 
diff --git a/docs/en/administration/fault_diagnosis_and_analysis.md b/docs/en/administration/fault_diagnosis_and_analysis.md
index fa79ce89..c51afc9f 100644
--- a/docs/en/administration/fault_diagnosis_and_analysis.md
+++ b/docs/en/administration/fault_diagnosis_and_analysis.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: Fault Diagnosis and Analysis
+title: Troubleshooting Methods
 sidebar_position: 5
 slug: /fault_diagnosis_and_analysis
 ---
@@ -7,8 +7,6 @@ slug: /fault_diagnosis_and_analysis
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# Fault Diagnosis and Analysis
-
 ## Client log
 
 JuiceFS client will output logs for troubleshooting while running. The level of logs in terms of fatality follows DEBUG < INFO < WARNING < ERROR < FATAL. Since DEBUG logs are not printed by default, you need to explicitly enable it if needed, e.g. by adding the `--debug` option when running the JuiceFS client.
diff --git a/docs/en/administration/metadata_dump_load.md b/docs/en/administration/metadata_dump_load.md
index 5ee09f4c..be23c7e2 100644
--- a/docs/en/administration/metadata_dump_load.md
+++ b/docs/en/administration/metadata_dump_load.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Metadata Backup & Recovery
+title: Metadata Backup & Recovery
 sidebar_position: 2
 slug: /metadata_dump_load
 ---
-# Metadata Backup & Recovery
 
 :::tip
 - JuiceFS v0.15.2 started to support manual backup, recovery and inter-engine migration of metadata.
diff --git a/docs/en/administration/monitoring.md b/docs/en/administration/monitoring.md
index 1fdb54a2..282c1f97 100644
--- a/docs/en/administration/monitoring.md
+++ b/docs/en/administration/monitoring.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: Monitoring and Data Visualization
+title: Monitoring and Data Visualization
 sidebar_position: 3
 ---
 
-# Monitoring and Data Visualization
-
 As a distributed file system hosting massive data storage, it is important for users to directly view the status changes of the entire system in terms of capacity, files, CPU load, disk IO, cache, etc. JuiceFS provides real-time status data externally through the Prometheus-oriented API to achieve the visualization of JuiceFS monitoring with ease, and you only need to expose it to your own Prometheus Server to visualize time series data with tools like Grafana.
 
 ## Get started
diff --git a/docs/en/administration/status_check_and_maintenance.md b/docs/en/administration/status_check_and_maintenance.md
index be48adeb..b9099c49 100644
--- a/docs/en/administration/status_check_and_maintenance.md
+++ b/docs/en/administration/status_check_and_maintenance.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: Status Check & Maintenance
+title: Status Check & Maintenance
 sidebar_position: 4
 ---
 
-# Status Check & Maintenance
-
 Any kind of storage system needs to be checked and maintained regularly after it is put into use, so that potential problems can be found and fixed as early as possible to ensure reliable operation of the file system and the integrity and consistency of the stored data.
 
 JuiceFS provides a series of tools to check and maintain the file system, not only to help us understand the basic information of the file system and its operation status, but also to help us detect and fix potential problems more easily.
diff --git a/docs/en/administration/sync_accounts_between_multiple_hosts.md b/docs/en/administration/sync_accounts_between_multiple_hosts.md
index b383a71b..64ec3b1a 100644
--- a/docs/en/administration/sync_accounts_between_multiple_hosts.md
+++ b/docs/en/administration/sync_accounts_between_multiple_hosts.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: Sync Accounts between Multiple Hosts
+title: Sync Accounts between Multiple Hosts
 sidebar_position: 7
 slug: /sync_accounts_between_multiple_hosts
 ---
 
-# Sync Accounts between Multiple Hosts
-
 JuiceFS supports Unix file permission (but not POSIX extended ACL), you can manage permissions by directory or file granularity, just like a local file system.
 
 To provide users with an intuitive and consistent permission management experience (e.g. the files accessible by user A on host X should be accessible by the same user on host Y), the same user who wants to access JuiceFS should have the same UID and GID on all hosts.
diff --git a/docs/en/administration/troubleshooting.md b/docs/en/administration/troubleshooting.md
index d7ea2852..eed3967b 100644
--- a/docs/en/administration/troubleshooting.md
+++ b/docs/en/administration/troubleshooting.md
@@ -137,3 +137,12 @@ Compiling JuiceFS requires GCC 5.4 and above, this error may occur when using lo
 /go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
 /go/pkg/tool/linux_amd64/compile: signal: killed
 ```
+
+If glibc version is different between build environment and runtime, you may see below error:
+
+```
+$ juicefs
+juicefs: /lib/aarch64-linux-gnu/libc.so.6: version 'GLIBC_2.28' not found (required by juicefs)
+```
+
+This requires you to re-compile JuiceFS Client in your runtime host environment. Most Linux distributions comes with glibc by default, you can check its version with `ldd --version`.
diff --git a/docs/en/benchmark/_performance_tuning.md b/docs/en/benchmark/_performance_tuning.md
index 1411c6c9..0c60b426 100644
--- a/docs/en/benchmark/_performance_tuning.md
+++ b/docs/en/benchmark/_performance_tuning.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: Performance Tuning
+title: Performance Tuning
 sidebar_position: 5
 ---
-# Performance Tuning
\ No newline at end of file
+# Performance Tuning
diff --git a/docs/en/benchmark/benchmark.md b/docs/en/benchmark/benchmark.md
index 0b0e2af9..d42571dd 100644
--- a/docs/en/benchmark/benchmark.md
+++ b/docs/en/benchmark/benchmark.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Performance Benchmark
+title: Performance Benchmark
 sidebar_position: 1
 slug: .
 ---
-# Performance Benchmark
 
 ## Basic benchmark
 
diff --git a/docs/en/benchmark/fio.md b/docs/en/benchmark/fio.md
index 7913de42..86711fcc 100644
--- a/docs/en/benchmark/fio.md
+++ b/docs/en/benchmark/fio.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Benchmark with fio
+title: Benchmark with fio
 sidebar_position: 7
 slug: /fio
 ---
-# Benchmark with fio
 
 :::tip
 Trash is enabled in JuiceFS v1.0+ by default. As a result, temporary files are created and deleted in the file system during the benchmark, and these files will be eventually dumped into a directory named `.trash`. To avoid storage space being occupied by `.trash`, you can run command `juicefs config META-URL --trash-days 0` to disable Trash before benchmark. See [trash](../security/trash.md) for details.
diff --git a/docs/en/benchmark/metadata_engines_benchmark.md b/docs/en/benchmark/metadata_engines_benchmark.md
index 24dd95dc..7e65dd5c 100644
--- a/docs/en/benchmark/metadata_engines_benchmark.md
+++ b/docs/en/benchmark/metadata_engines_benchmark.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: Metadata Engines Benchmark
+title: Metadata Engines Benchmark
 sidebar_position: 6
 slug: /metadata_engines_benchmark
 ---
 
-# Metadata Engines Benchmark
-
 Conclusion first:
 
 - For pure metadata operations, MySQL costs about 2~4x times of Redis; TiKV has similar performance to MySQL, and in most cases it costs a bit less; etcd costs about 1.5x times of TiKV.
diff --git a/docs/en/benchmark/operations_profiling.md b/docs/en/benchmark/operations_profiling.md
index 4df1688d..4eacb84c 100644
--- a/docs/en/benchmark/operations_profiling.md
+++ b/docs/en/benchmark/operations_profiling.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Operations Profiling
+title: Operations Profiling
 sidebar_position: 3
 slug: /operations_profiling
 ---
-# Operations Profiling
 
 ## Introduction
 
diff --git a/docs/en/benchmark/performance_evaluation_guide.md b/docs/en/benchmark/performance_evaluation_guide.md
index 00f44bfa..ec3d5988 100644
--- a/docs/en/benchmark/performance_evaluation_guide.md
+++ b/docs/en/benchmark/performance_evaluation_guide.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: Performance Evaluation Guide
+title: Performance Evaluation Guide
 sidebar_position: 2
 slug: /performance_evaluation_guide
 ---
 
-# JuiceFS Performance Evaluation Guide
-
 Before starting performance testing, it is a good idea to write down a general description of usage scenario, including:
 
 1. What is the application for? For example, Apache Spark, PyTorch, or a program you developed yourself
diff --git a/docs/en/benchmark/stats_watcher.md b/docs/en/benchmark/stats_watcher.md
index 97ae0b9a..e1b576bd 100644
--- a/docs/en/benchmark/stats_watcher.md
+++ b/docs/en/benchmark/stats_watcher.md
@@ -1,11 +1,10 @@
 ---
-sidebar_label: Performance Statistics Watcher
+title: Performance Statistics Monitor
 sidebar_position: 4
 slug: /stats_watcher
 ---
-# JuiceFS Performance Statistics Monitor
 
-JuiceFS exposes a lot of [Promethues metrics](../administration/monitoring.md) for monitoring system internal performance. However, when diagnosing performance issues in practice, users may need a more real-time monitoring tool to know what is actually going on within a certain time range. Thus, we provide a command `stats` to print metrics every second, just like what the Linux command `dstat` does. The output is like:
+JuiceFS exposes a lot of [Prometheus metrics](../administration/monitoring.md) for monitoring system internal performance. However, when diagnosing performance issues in practice, users may need a more real-time monitoring tool to know what is actually going on within a certain time range. Thus, we provide a command `stats` to print metrics every second, just like what the Linux command `dstat` does. The output is like:
 
 ![stats_watcher](../images/juicefs_stats_watcher.png)
 
diff --git a/docs/en/community/_roadmap.md b/docs/en/community/_roadmap.md
index 8cd3ef1e..01dae092 100644
--- a/docs/en/community/_roadmap.md
+++ b/docs/en/community/_roadmap.md
@@ -1,5 +1,4 @@
 ---
-sidebar_label: Roadmap
+title: Roadmap
 sidebar_position: 3
 ---
-# Roadmap
\ No newline at end of file
diff --git a/docs/en/community/adopters.md b/docs/en/community/adopters.md
index 2f811bee..b2c2596c 100644
--- a/docs/en/community/adopters.md
+++ b/docs/en/community/adopters.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: Adopters
+title: Adopters
 sidebar_position: 1
 slug: /adopters
 ---
 
-# JuiceFS Adopters
-
 | Company/Team                                                                            | Environment | Use Cases                   | User Story                                                                                                                                                                                          |
 |-----------------------------------------------------------------------------------------|-------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | [Xiaomi](https://www.mi.com/global)                                                     | Production  | AI                          |                                                                                                                                                                                                     |
diff --git a/docs/en/community/articles.md b/docs/en/community/articles.md
index fbb3cc85..ee2ec1ab 100644
--- a/docs/en/community/articles.md
+++ b/docs/en/community/articles.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: User Articles
+title: User Articles
 sidebar_position: 2
 slug: /articles
 ---
 
-# User Article Collection
-
 JuiceFS is widely applicable to various data storage and sharing scenarios. JuiceFS application cases from all over the world are summarized here. All community users are welcome to maintain this list.
 
 ## AI, Machine Learning and Deep Learning
diff --git a/docs/en/community/integrations.md b/docs/en/community/integrations.md
index ab0582e8..fbf9c85d 100644
--- a/docs/en/community/integrations.md
+++ b/docs/en/community/integrations.md
@@ -23,4 +23,4 @@ slug: /integrations
 
 ## DevOps
 
-- [Terraform Provider for JuiceFS](https://github.com/toowoxx/terraform-provider-juicefs) by Toowoxx IT GmbH, an IT service company from Germany
\ No newline at end of file
+- [Terraform Provider for JuiceFS](https://github.com/toowoxx/terraform-provider-juicefs) by Toowoxx IT GmbH, an IT service company from Germany
diff --git a/docs/en/community/usage_tracking.md b/docs/en/community/usage_tracking.md
index 38cfc848..55a65e3d 100644
--- a/docs/en/community/usage_tracking.md
+++ b/docs/en/community/usage_tracking.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: Usage Tracking
+title: Usage Tracking
 sidebar_position: 4
 ---
 
-# Usage Tracking
-
 JuiceFS by default collects and reports **anonymous** usage data. It only collects core metrics (e.g. version number, file system size), no user or any sensitive data will be collected. You could review related code [here](https://github.com/juicedata/juicefs/blob/main/pkg/usage/usage.go).
 
 These data help us understand how the community is using this project. You could disable reporting easily by command line option `--no-usage-report`:
diff --git a/docs/en/deployment/_share_via_nfs.md b/docs/en/deployment/_share_via_nfs.md
index ff2d980a..fc9851a4 100644
--- a/docs/en/deployment/_share_via_nfs.md
+++ b/docs/en/deployment/_share_via_nfs.md
@@ -1,5 +1,4 @@
 ---
-sidebar_label: Deploy JuiceFS with NFS
+title: Deploy JuiceFS with NFS
 sidebar_position: 5
 ---
-# Deploy JuiceFS with NFS
\ No newline at end of file
diff --git a/docs/en/deployment/_share_via_smb.md b/docs/en/deployment/_share_via_smb.md
index 13a68127..04f3680d 100644
--- a/docs/en/deployment/_share_via_smb.md
+++ b/docs/en/deployment/_share_via_smb.md
@@ -1,5 +1,4 @@
 ---
-sidebar_label: Deploy JuiceFS with SMB
+title: Deploy JuiceFS with SMB
 sidebar_position: 6
 ---
-# Deploy JuiceFS with SMB
\ No newline at end of file
diff --git a/docs/en/deployment/hadoop_java_sdk.md b/docs/en/deployment/hadoop_java_sdk.md
index da9a5db7..458e45f4 100644
--- a/docs/en/deployment/hadoop_java_sdk.md
+++ b/docs/en/deployment/hadoop_java_sdk.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: Use JuiceFS on Hadoop Ecosystem
+title: Use JuiceFS on Hadoop Ecosystem
 sidebar_position: 5
 slug: /hadoop_java_sdk
 ---
@@ -7,8 +7,6 @@ slug: /hadoop_java_sdk
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# Use JuiceFS on Hadoop Ecosystem
-
 JuiceFS provides [Hadoop-compatible FileSystem](https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/filesystem/introduction.html) by Hadoop Java SDK. Various applications in the Hadoop ecosystem can smoothly use JuiceFS to store data without changing the code.
 
 ## Requirements
diff --git a/docs/en/deployment/how_to_use_on_kubernetes.md b/docs/en/deployment/how_to_use_on_kubernetes.md
index a1572ffb..8bc90f62 100644
--- a/docs/en/deployment/how_to_use_on_kubernetes.md
+++ b/docs/en/deployment/how_to_use_on_kubernetes.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: Use JuiceFS on Kubernetes
+title: Use JuiceFS on Kubernetes
 sidebar_position: 4
 slug: /how_to_use_on_kubernetes
 ---
@@ -13,7 +13,7 @@ If you simply need to use JuiceFS inside Kubernetes pods, without any special re
 1. Install and mount JuiceFS on all Kubernetes worker nodes, [Automated Deployment](./automation.md) is recommended for this type of work.
 1. Use `hostPath` volume inside pod definition, and mount a JuiceFS sub-directory to container:
 
-   ```yaml {8-15}
+   ```yaml {8-16}
    apiVersion: v1
    kind: Pod
    metadata:
@@ -34,8 +34,8 @@ If you simply need to use JuiceFS inside Kubernetes pods, without any special re
 
 In comparison to using JuiceFS CSI Driver, `hostPath` is a much more simple practice, and easier to debug when things go wrong, but notice that:
 
+* For ease of management, generally all pods use the same host mount point. Lack of isolation may lead to data security issues, and obviously, you won't be able to adjust JuiceFS mount parameters separately for each application. Please evaluate carefully.
 * All worker nodes should mount JuiceFS in advance, so when adding a new node to the cluster, JuiceFS needs to be installed and mounted during the initialization process, otherwise the new node does not have a JuiceFS mount point, and the container will not be created.
-* For the convenience of management, all containers generally use the same host mount point. Lack of isolation may lead to data security issues. Please evaluate carefully.
 * The system resources (such as CPU, memory, etc.) occupied by the JuiceFS mounting process on the host are not controlled by Kubernetes, and may occupy too many host resources. You can consider using [`system-reserved`](https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#system-reserved) to properly adjust the system resource reservation of Kubernetes, to reserve more resources for the JuiceFS mount process.
 * If the JuiceFS mount process on the host exits unexpectedly, the application pod will not be able to access the mount point normally. In this case, the JuiceFS file system needs to be remounted and the application pod must be rebuilt. However, JuiceFS CSI Driver solves this problem well by providing the [Automatic Mount Point Recovery](https://juicefs.com/docs/csi/recover-failed-mountpoint) mechanism.
 * If you're using Docker as Kubernetes container runtime, it's best to start JuiceFS mount prior to Docker in startup order, to avoid containers being created before JuiceFS is properly mounted. For systemd, you can use below unit file to manually control startup order:
diff --git a/docs/en/deployment/juicefs_on_docker.md b/docs/en/deployment/juicefs_on_docker.md
index 4448515b..0586b98e 100644
--- a/docs/en/deployment/juicefs_on_docker.md
+++ b/docs/en/deployment/juicefs_on_docker.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on Docker
+title: Use JuiceFS on Docker
 sidebar_position: 3
 slug: /juicefs_on_docker
 ---
-# Use JuiceFS on Docker
 
 There are several common ways to use JuiceFS as Docker persistent storage.
 
diff --git a/docs/en/deployment/s3_gateway.md b/docs/en/deployment/s3_gateway.md
index 4312fe1a..8c6dc19f 100644
--- a/docs/en/deployment/s3_gateway.md
+++ b/docs/en/deployment/s3_gateway.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: Deploy JuiceFS S3 Gateway
+title: Deploy JuiceFS S3 Gateway
 sidebar_position: 6
 slug: /s3_gateway
 ---
 
-# Deploy JuiceFS S3 Gateway
-
 JuiceFS has introduced S3 gateway since v0.11. The feature is implemented based on the [MinIO S3 Gateway](https://docs.min.io/docs/minio-gateway-for-s3.html). It provides an S3-compatible RESTful API for files on JuiceFS, enabling the management of files stored on JuiceFS with tools such as s3cmd, AWS CLI, and MinIO Client (mc) in cases where mounting is not convenient. In addition, S3 gateway also provides a web-based file manager that allows users to manage files in web browsers.
 
 Since JuiceFS stores files in chunks, the files cannot be accessed directly through the interfaces of the underlying object storage. The S3 gateway accesses the underlying object storage in a similar way, shown in the following architecture diagram.
diff --git a/docs/en/development/contributing_guide.md b/docs/en/development/contributing_guide.md
index 1d8bbfa1..ccfe10c6 100644
--- a/docs/en/development/contributing_guide.md
+++ b/docs/en/development/contributing_guide.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: Contributing Guide
+title: Contributing Guide
 sidebar_position: 1
 ---
 
-# Contributing to JuiceFS
-
 ## Guidelines
 
 - Before starting work on a feature or bug fix, please search GitHub or reach out to us via GitHub, Slack etc. The purpose of this step is make sure no one else is already working on it and we'll ask you to open a GitHub issue if necessary.
diff --git a/docs/en/development/data_structures.md b/docs/en/development/data_structures.md
index d863f611..7d52af9c 100644
--- a/docs/en/development/data_structures.md
+++ b/docs/en/development/data_structures.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Internals
+title: Internals
 sidebar_position: 4
 slug: /internals
 ---
-# JuiceFS Internals
 
 ## 1. Introduction
 
diff --git a/docs/en/getting-started/installation.md b/docs/en/getting-started/installation.md
index 96379d5e..357e54f2 100644
--- a/docs/en/getting-started/installation.md
+++ b/docs/en/getting-started/installation.md
@@ -1,12 +1,10 @@
 ---
-sidebar_label: Installation
+title: Installation
 sidebar_position: 1
 slug: /installation
 pagination_prev: introduction/comparison/juicefs_vs_s3ql
 ---
 
-# Installation
-
 JuiceFS has good cross-platform capability and supports running on all kinds of operating systems of almost all major architectures, including and not limited to Linux, macOS, Windows, etc.
 
 The JuiceFS client has only one binary file, you can download the pre-compiled version to unzip it and use it directly, or you can compile it manually with the source code.
diff --git a/docs/en/guide/cache_management.md b/docs/en/guide/cache_management.md
index f29f02c3..8a315713 100644
--- a/docs/en/guide/cache_management.md
+++ b/docs/en/guide/cache_management.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Cache
+title: Cache
 sidebar_position: 3
 slug: /cache_management
 ---
-# Cache
 
 For a file system driven by a combination of object storage and database, cache is an important medium for interacting efficiently between the local client and the remote service. Read and write data can be loaded into the cache in advance or asynchronously, and then the client uploads to or prefetches from the remote service in the background. The use of caching technology can significantly reduce the latency of storage operations and increase data throughput compared to interacting with remote services directly.
 
diff --git a/docs/en/guide/how_to_set_up_metadata_engine.md b/docs/en/guide/how_to_set_up_metadata_engine.md
index 3d627709..33973b98 100644
--- a/docs/en/guide/how_to_set_up_metadata_engine.md
+++ b/docs/en/guide/how_to_set_up_metadata_engine.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: How to Set Up Metadata Engine
+title: How to Set Up Metadata Engine
 sidebar_position: 1
 slug: /databases_for_metadata
 ---
@@ -7,8 +7,6 @@ slug: /databases_for_metadata
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# How to Set Up Metadata Engine
-
 :::tip Version Tips
 The environment variable `META_PASSWORD` used in this document is a new feature in JuiceFS v1.0, and not applied to old clients. Please [upgrade the clients](../administration/upgrade.md) before using it if you are using the old ones.
 :::
diff --git a/docs/en/guide/how_to_set_up_object_storage.md b/docs/en/guide/how_to_set_up_object_storage.md
index b10d0dce..105aebc2 100644
--- a/docs/en/guide/how_to_set_up_object_storage.md
+++ b/docs/en/guide/how_to_set_up_object_storage.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: How to Set Up Object Storage
+title: How to Set Up Object Storage
 sidebar_position: 2
 slug: /how_to_setup_object_storage
 ---
@@ -7,8 +7,6 @@ slug: /how_to_setup_object_storage
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# How to Set Up Object Storage
-
 As you can learn from [JuiceFS Technical Architecture](../introduction/architecture.md), JuiceFS is a distributed file system with data and metadata stored separately. JuiceFS uses object storage as the main data storage and uses databases such as Redis, PostgreSQL and MySQL as metadata storage.
 
 ## Storage options
diff --git a/docs/en/guide/mount_at_boot.md b/docs/en/guide/mount_at_boot.md
index a37f0ee4..43c4836d 100644
--- a/docs/en/guide/mount_at_boot.md
+++ b/docs/en/guide/mount_at_boot.md
@@ -1,13 +1,9 @@
 ---
-sidebar_label: Mount JuiceFS at Boot Time
+title: Mount JuiceFS at Boot Time
 sidebar_position: 2
 slug: /mount_juicefs_at_boot_time
 ---
 
-# Mount JuiceFS at Boot Time
-
-This is a guide about how to mount JuiceFS automatically at boot.
-
 ## Linux
 
 Copy `juicefs` as `/sbin/mount.juicefs`, then edit `/etc/fstab` with following line:
diff --git a/docs/en/guide/quota.md b/docs/en/guide/quota.md
index 25a7db5d..964318b1 100644
--- a/docs/en/guide/quota.md
+++ b/docs/en/guide/quota.md
@@ -1,8 +1,7 @@
 ---
-sidebar_label: Storage Quota
+title: Storage Quota
 sidebar_position: 5
 ---
-# Storage Quota
 
 Since JuiceFS v0.14.2, JuiceFS have supported file system level storage quotas. This feature allows users to:
 
diff --git a/docs/en/introduction/io_processing.md b/docs/en/introduction/io_processing.md
index 482d5d91..2a4a6f98 100644
--- a/docs/en/introduction/io_processing.md
+++ b/docs/en/introduction/io_processing.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: Data Processing Flow
+sidebar_label: Data Processing Workflow
 sidebar_position: 3
 slug: /internals/io_processing
 ---
diff --git a/docs/en/reference/command_reference.md b/docs/en/reference/command_reference.md
index 899fbf4c..8f3259c5 100644
--- a/docs/en/reference/command_reference.md
+++ b/docs/en/reference/command_reference.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: Command Reference
+title: Command Reference
 sidebar_position: 1
 slug: /command_reference
 ---
@@ -7,10 +7,6 @@ slug: /command_reference
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# Command Reference
-
-There are many commands to help you manage your file system. This page provides a detailed reference for these commands.
-
 ## Overview
 
 If you run `juicefs` by itself, it will print all available commands. In addition, you can add `-h/--help` flag after each command to get more information of it, e.g., `juicefs format -h`.
diff --git a/docs/en/reference/fuse_mount_options.md b/docs/en/reference/fuse_mount_options.md
index e963f52b..5bb79a5f 100644
--- a/docs/en/reference/fuse_mount_options.md
+++ b/docs/en/reference/fuse_mount_options.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: FUSE Mount Options
+title: FUSE Mount Options
 sidebar_position: 6
 slug: /fuse_mount_options
 ---
-# FUSE Mount Options
 
 This guide lists important FUSE mount options. These mount options are specified by the option `-o`  when execute the command [`juicefs mount`](../reference/command_reference.md#mount) (use comma to separate multiple options). For example:
 
diff --git a/docs/en/reference/p8s_metrics.md b/docs/en/reference/p8s_metrics.md
index d8482a34..63ba03fc 100644
--- a/docs/en/reference/p8s_metrics.md
+++ b/docs/en/reference/p8s_metrics.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: JuiceFS Metrics
+title: JuiceFS Metrics
 sidebar_position: 2
 slug: /p8s_metrics
 ---
 
-# JuiceFS Metrics
-
 :::tip
 Please see the ["Monitoring"](../administration/monitoring.md) documentation to learn how to collect and display JuiceFS monitoring metrics.
 :::
diff --git a/docs/en/reference/posix_compatibility.md b/docs/en/reference/posix_compatibility.md
index 5a7c794b..d71a6436 100644
--- a/docs/en/reference/posix_compatibility.md
+++ b/docs/en/reference/posix_compatibility.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: POSIX Compatibility
+title: POSIX Compatibility
 sidebar_position: 6
 slug: /posix_compatibility
 ---
-# POSIX Compatibility
 
 JuiceFS ensures POSIX compatibility with the help of pjdfstest and LTP.
 
diff --git a/docs/en/security/metadata_automatic_backup.md b/docs/en/security/metadata_automatic_backup.md
deleted file mode 100644
index 9d279e2f..00000000
--- a/docs/en/security/metadata_automatic_backup.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-sidebar_label: Metadata Automatic Backup
-sidebar_position: 3
----
-# Metadata Automatic Backup
-
-Reference to [Metadata Automatic Backup](../administration/metadata_dump_load.md#automatic-backup)
\ No newline at end of file
diff --git a/docs/en/tutorials/aliyun.md b/docs/en/tutorials/aliyun.md
index 1e4ab53c..baf4fbd7 100644
--- a/docs/en/tutorials/aliyun.md
+++ b/docs/en/tutorials/aliyun.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on Alibaba Cloud
+title: Use JuiceFS on Alibaba Cloud
 sidebar_position: 6
 slug: /clouds/aliyun
 ---
-# Use JuiceFS on Alibaba Cloud
 
 As shown in the figure below, JuiceFS is driven by both the database and the object storage. The files stored in JuiceFS are split into fixed-size data blocks and stored in the object store according to certain rules, while the metadata corresponding to the data is stored in the database.
 
diff --git a/docs/en/tutorials/aws.md b/docs/en/tutorials/aws.md
index aa9ed091..b937dd83 100644
--- a/docs/en/tutorials/aws.md
+++ b/docs/en/tutorials/aws.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on AWS
+title: Use JuiceFS on AWS
 sidebar_position: 4
 slug: /clouds/aws
 ---
-# Use JuiceFS on AWS
 
 AWS is the world's leading cloud computing platform, offering almost all types of cloud computing services. Thanks to the rich product line of AWS, users can choose JuiceFS components in a very flexible way.
 
diff --git a/docs/en/tutorials/digitalocean.md b/docs/en/tutorials/digitalocean.md
index a84070d7..43fe5b14 100644
--- a/docs/en/tutorials/digitalocean.md
+++ b/docs/en/tutorials/digitalocean.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on DigitalOcean
+title: Use JuiceFS on DigitalOcean
 sidebar_position: 5
 slug: /clouds/digitalocean
 ---
-# Use JuiceFS on DigitalOcean
 
 JuiceFS is designed for the cloud, using the cloud platform out-of-the-box storage and database services, and can be configured and put into use in as little as a few minutes. This article uses the DigitalOcean as an example to introduce how to quickly and easily install and use JuiceFS on the cloud computing platform.
 
diff --git a/docs/en/tutorials/juicefs_on_colab.md b/docs/en/tutorials/juicefs_on_colab.md
index 91e66358..150db684 100644
--- a/docs/en/tutorials/juicefs_on_colab.md
+++ b/docs/en/tutorials/juicefs_on_colab.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: Use JuiceFS on Colab with Google CloudSQL and GCS
+title: Use JuiceFS on Colab with Google CloudSQL and GCS
 sidebar_position: 4
 slug: /juicefs_on_colab
 ---
 
-# Use JuiceFS on Colab with Google CloudSQL and GCS
-
 [Colaboratory](https://colab.research.google.com), or “Colab” for short, is a product from Google Research. Colab allows
 anybody to write and execute arbitrary python code through the browser, and is especially well suited to machine
 learning, data analysis and education.
diff --git a/docs/en/tutorials/juicefs_on_k3s.md b/docs/en/tutorials/juicefs_on_k3s.md
index 3b63fc22..17869172 100644
--- a/docs/en/tutorials/juicefs_on_k3s.md
+++ b/docs/en/tutorials/juicefs_on_k3s.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on K3s
+title: Use JuiceFS on K3s
 sidebar_position: 1
 slug: /juicefs_on_k3s
 ---
-# Use JuiceFS on K3s
 
 [K3s](https://k3s.io/) is a functionally optimized lightweight Kubernetes distribution that is fully compatible with Kubernetes, that is, almost all operations on Kubernetes can be performed on K3s. K3s has packaged the entire container orchestration system into a binary program with a capacity of less than 100MB, which greatly reduces the environment dependencies and steps for installation of deploying Kubernetes production clusters. Compared to Kubernetes, K3s has lower performance requirements for the operating system, and ARM devices such as Raspberry Pi can be used to form a cluster.
 
diff --git a/docs/en/tutorials/juicefs_on_kubesphere.md b/docs/en/tutorials/juicefs_on_kubesphere.md
index 77d297dc..54e0c8b4 100644
--- a/docs/en/tutorials/juicefs_on_kubesphere.md
+++ b/docs/en/tutorials/juicefs_on_kubesphere.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on KubeSphere
+title: Use JuiceFS on KubeSphere
 sidebar_position: 3
 slug: /juicefs_on_kubesphere
 ---
-# Use JuiceFS on KubeSphere
 
 [KubeSphere](https://kubesphere.com.cn/) is an application-centric multi-tenant container platform built on Kubernetes. It provides full-stack IT automated operation and maintenance capabilities and simplifies the DevOps workflow of the enterprise.
 
diff --git a/docs/en/tutorials/juicefs_on_rancher.md b/docs/en/tutorials/juicefs_on_rancher.md
index d32ca876..e7573b5a 100644
--- a/docs/en/tutorials/juicefs_on_rancher.md
+++ b/docs/en/tutorials/juicefs_on_rancher.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on Rancher
+title: Use JuiceFS on Rancher
 sidebar_position: 2
 slug: /juicefs_on_rancher
 ---
-# Use JuiceFS on Rancher
 
 [Rancher](https://rancher.com/) is an enterprise-level Kubernetes cluster management system, which can be used to quickly complete the deployment of Kubernetes clusters on various cloud computing platforms.
 
diff --git a/docs/en/tutorials/juicefs_on_wsl.md b/docs/en/tutorials/juicefs_on_wsl.md
index cea9c1e9..0ccf41ae 100644
--- a/docs/en/tutorials/juicefs_on_wsl.md
+++ b/docs/en/tutorials/juicefs_on_wsl.md
@@ -1,7 +1,6 @@
 ---
-sidebar_label: Use JuiceFS on WSL
+title: Use JuiceFS on WSL
 ---
-# Use JuiceFS on WSL
 
 WSL is called Windows Subsystem for Linux, which means Windows subsystem for Linux. It allows you to run most GNU/Linux native commands, tools, and programs in a Windows environment without the additional hardware overhead of using a virtual machine or dual system.
 
diff --git a/docs/en/tutorials/qcloud.md b/docs/en/tutorials/qcloud.md
index 77c61bed..a425e1b0 100644
--- a/docs/en/tutorials/qcloud.md
+++ b/docs/en/tutorials/qcloud.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Use JuiceFS on Tencent Cloud
+title: Use JuiceFS on Tencent Cloud
 sidebar_position: 7
 slug: /clouds/qcloud
 ---
-# Use JuiceFS on Tencent Cloud
 
 JuiceFS needs to be used with database and object storage together. Here we directly use Tencent Cloud's CVM cloud server, combined with cloud database and COS object storage.
 
diff --git a/docs/zh_cn/administration/destroy.md b/docs/zh_cn/administration/destroy.md
index f532ddf4..2dfece46 100644
--- a/docs/zh_cn/administration/destroy.md
+++ b/docs/zh_cn/administration/destroy.md
@@ -1,8 +1,7 @@
 ---
-sidebar_label: 销毁文件系统
+title: 销毁文件系统
 sidebar_position: 8
 ---
-# 如何销毁文件系统
 
 JuiceFS 客户端提供了 `destroy` 命令用以彻底销毁一个文件系统，销毁操作将会产生以下结果：
 
diff --git a/docs/zh_cn/administration/fault_diagnosis_and_analysis.md b/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
index 8b104059..44261eef 100644
--- a/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
+++ b/docs/zh_cn/administration/fault_diagnosis_and_analysis.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: 故障诊断和分析
+title: 问题排查方法
 sidebar_position: 5
 slug: /fault_diagnosis_and_analysis
 ---
@@ -7,8 +7,6 @@ slug: /fault_diagnosis_and_analysis
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# 故障诊断和分析
-
 ## 客户端日志
 
 JuiceFS 客户端在运行过程中会输出日志用于故障诊断，日志等级从低到高分别是：DEBUG、INFO、WARNING、ERROR、FATAL，默认只输出 INFO 级别以上的日志。如果需要输出 DEBUG 级别的日志，需要在运行 JuiceFS 客户端时显式开启，如加上 `--debug` 选项。
diff --git a/docs/zh_cn/administration/metadata/fdb_best_practices.md b/docs/zh_cn/administration/metadata/fdb_best_practices.md
index 6b60298d..aca9bfac 100644
--- a/docs/zh_cn/administration/metadata/fdb_best_practices.md
+++ b/docs/zh_cn/administration/metadata/fdb_best_practices.md
@@ -5,6 +5,7 @@ slug: /fdb_best_practices
 ---
 
 # FoundationDB 最佳实践
+
 fdb支持横向扩容，一旦数据存储达到集群的最高负载，只需要在集群中添加新的机器即可。配置集群的详细教程可见官网 https://apple.github.io/foundationdb/configuration.html ，对于不同场景不同机器数量的性能测试可见 https://apple.github.io/foundationdb/benchmarking.html。
 
 ## 系统要求
diff --git a/docs/zh_cn/administration/metadata/tikv_best_practices.md b/docs/zh_cn/administration/metadata/tikv_best_practices.md
index dbfc66a0..34c6c78f 100644
--- a/docs/zh_cn/administration/metadata/tikv_best_practices.md
+++ b/docs/zh_cn/administration/metadata/tikv_best_practices.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: TiKV 最佳实践
+sidebar_label: TiKV
 sidebar_position: 3
 ---
-# TiKV 最佳实践
\ No newline at end of file
+# TiKV 最佳实践
diff --git a/docs/zh_cn/administration/metadata_dump_load.md b/docs/zh_cn/administration/metadata_dump_load.md
index 68af4a79..119ef578 100644
--- a/docs/zh_cn/administration/metadata_dump_load.md
+++ b/docs/zh_cn/administration/metadata_dump_load.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 元数据备份和恢复
+title: 元数据备份和恢复
 sidebar_position: 2
 slug: /metadata_dump_load
 ---
-# 元数据备份和恢复
 
 :::tip 提示
 - JuiceFS v0.15.2 开始支持元数据手动备份、恢复和引擎间迁移。
diff --git a/docs/zh_cn/administration/monitoring.md b/docs/zh_cn/administration/monitoring.md
index da53e272..724acd86 100644
--- a/docs/zh_cn/administration/monitoring.md
+++ b/docs/zh_cn/administration/monitoring.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: 监控与数据可视化
+title: 监控与数据可视化
 sidebar_position: 3
 ---
 
-# 监控与数据可视化
-
 作为承载海量数据存储的分布式文件系统，用户通常需要直观地了解整个系统的容量、文件数量、CPU 负载、磁盘 IO、缓存等指标的变化。JuiceFS 通过 Prometheus 兼容的 API 对外提供实时的状态数据，只需将其添加到用户自建的 Prometheus Server 建立时序数据，然后通过 Grafana 等工具即可轻松实现 JucieFS 文件系统的可视化监控。
 
 ## 快速上手
diff --git a/docs/zh_cn/administration/status_check_and_maintenance.md b/docs/zh_cn/administration/status_check_and_maintenance.md
index 13060c97..f281ceac 100644
--- a/docs/zh_cn/administration/status_check_and_maintenance.md
+++ b/docs/zh_cn/administration/status_check_and_maintenance.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: 状态检查 & 维护
+title: 状态检查 & 维护
 sidebar_position: 4
 ---
 
-# 状态检查 & 维护
-
 任何一种存储系统在投入使用之后都需要定期进行检查和维护，尽早发现并修复潜在的问题，从而保证文件系统可靠运行、存储的数据完整一致。
 
 JuiceFS 提供了一系列检查和维护文件系统的工具，不但可以帮助我们了解文件系统的基本信息、运行状态，还能够帮助我们更容易地发现和修复潜在的问题。
diff --git a/docs/zh_cn/administration/sync_accounts_between_multiple_hosts.md b/docs/zh_cn/administration/sync_accounts_between_multiple_hosts.md
index 2710f589..cfea579a 100644
--- a/docs/zh_cn/administration/sync_accounts_between_multiple_hosts.md
+++ b/docs/zh_cn/administration/sync_accounts_between_multiple_hosts.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 多主机间同步账户
+title: 多主机间同步账户
 sidebar_position: 7
 slug: /sync_accounts_between_multiple_hosts
 ---
 
-# 多主机间同步账户
-
 JuiceFS 支持 Unix 文件权限（但不支持 POSIX extended ACL），以目录或文件的粒度管理权限。该行为与本地文件系统相同。
 
 为了让用户获得直观一致的权限管理体验（例如，用户 A 在主机 X 中访问的文件，在主机 Y 中也应该可以用相同的用户身份访问），想要访问 JuiceFS 存储的同一个用户，应该在所有主机上具有相同的 UID 和 GID。
diff --git a/docs/zh_cn/administration/troubleshooting.md b/docs/zh_cn/administration/troubleshooting.md
index f58a8cd8..5cd6a15d 100644
--- a/docs/zh_cn/administration/troubleshooting.md
+++ b/docs/zh_cn/administration/troubleshooting.md
@@ -137,3 +137,12 @@ grep "read (148153116," access.log
 /go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
 /go/pkg/tool/linux_amd64/compile: signal: killed
 ```
+
+如果编译环境与运行环境的 glibc 版本不同，会发生如下报错：
+
+```
+$ juicefs
+juicefs: /lib/aarch64-linux-gnu/libc.so.6: version 'GLIBC_2.28' not found (required by juicefs)
+```
+
+这需要你在运行环境重新编译 JuiceFS 客户端，大部分 Linux 发行版都预置了 glibc，你可以用 `ldd --version` 确认其版本。
diff --git a/docs/zh_cn/benchmark/_performance_tuning.md b/docs/zh_cn/benchmark/_performance_tuning.md
index f8dab853..c2001f35 100644
--- a/docs/zh_cn/benchmark/_performance_tuning.md
+++ b/docs/zh_cn/benchmark/_performance_tuning.md
@@ -1,5 +1,4 @@
 ---
-sidebar_label: 性能调优
+title: 性能调优
 sidebar_position: 5
 ---
-# JuiceFS 性能调优
\ No newline at end of file
diff --git a/docs/zh_cn/benchmark/benchmark.md b/docs/zh_cn/benchmark/benchmark.md
index 93e4ff76..9eed4db6 100644
--- a/docs/zh_cn/benchmark/benchmark.md
+++ b/docs/zh_cn/benchmark/benchmark.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 常规测试
+title: 常规测试
 sidebar_position: 1
 slug: .
 ---
-# JuiceFS 常规测试
 
 ### 基础测试
 
diff --git a/docs/zh_cn/benchmark/fio.md b/docs/zh_cn/benchmark/fio.md
index 57fdeee8..729ff365 100644
--- a/docs/zh_cn/benchmark/fio.md
+++ b/docs/zh_cn/benchmark/fio.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: fio 基准测试
+title: fio 基准测试
 sidebar_position: 7
 slug: /fio
 ---
-# fio 基准测试
 
 :::tip 提示
 JuiceFS v1.0+ 默认启用了回收站，基准测试会在文件系统中创建和删除临时文件，这些文件最终会被转存到回收站 `.trash` 占用存储空间，为了避免这种情况，可以在基准测试之前关闭回收站 `juicefs config META-URL --trash-days 0`，详情参考[回收站](../security/trash.md)。
diff --git a/docs/zh_cn/benchmark/metadata_engines_benchmark.md b/docs/zh_cn/benchmark/metadata_engines_benchmark.md
index ea42581b..036d47ab 100644
--- a/docs/zh_cn/benchmark/metadata_engines_benchmark.md
+++ b/docs/zh_cn/benchmark/metadata_engines_benchmark.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 元数据引擎性能测试
+title: 元数据引擎性能测试
 sidebar_position: 6
 slug: /metadata_engines_benchmark
 ---
 
-# 元数据引擎性能对比测试
-
 首先展示结论：
 
 - 对于纯元数据操作，MySQL 耗时约为 Redis 的 2～4 倍；TiKV 性能与 MySQL 接近，大部分场景下略优于 MySQL；etcd 的耗时约为 TiKV 的 1.5 倍
diff --git a/docs/zh_cn/benchmark/operations_profiling.md b/docs/zh_cn/benchmark/operations_profiling.md
index f64a610a..ccb126a4 100644
--- a/docs/zh_cn/benchmark/operations_profiling.md
+++ b/docs/zh_cn/benchmark/operations_profiling.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 性能诊断
+title: 性能诊断
 sidebar_position: 3
 slug: /operations_profiling
 ---
-# JuiceFS 性能诊断
 
 ## 介绍
 
diff --git a/docs/zh_cn/benchmark/performance_evaluation_guide.md b/docs/zh_cn/benchmark/performance_evaluation_guide.md
index 3fed4af4..e5fcc59f 100644
--- a/docs/zh_cn/benchmark/performance_evaluation_guide.md
+++ b/docs/zh_cn/benchmark/performance_evaluation_guide.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 性能评估指南
+title: 性能评估指南
 sidebar_position: 2
 slug: /performance_evaluation_guide
 ---
 
-# JuiceFS 性能评估指南
-
 在进行性能测试之前，最好写下该使用场景的大致描述，包括：
 
 1. 对接的应用是什么？比如 Apache Spark、PyTorch 或者是自己写的程序等
diff --git a/docs/zh_cn/benchmark/stats_watcher.md b/docs/zh_cn/benchmark/stats_watcher.md
index ae2e0a6b..32e15669 100644
--- a/docs/zh_cn/benchmark/stats_watcher.md
+++ b/docs/zh_cn/benchmark/stats_watcher.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 性能统计监控
+title: 性能统计监控
 sidebar_position: 4
 slug: /stats_watcher
 ---
-# JuiceFS 性能统计监控
 
 JuiceFS 预定义了许多监控指标来监测系统运行时的内部性能情况，并通过 Prometheus API [暴露对外接口](../administration/monitoring.md)。然而，在分析一些实际问题时，用户往往需要更实时的性能统计监控。因此，我们开发了 `stats` 命令，以类似 Linux `dstat` 工具的形式实时打印各个指标的每秒变化情况，如下图所示：
 
diff --git a/docs/zh_cn/community/_roadmap.md b/docs/zh_cn/community/_roadmap.md
index d52e7507..3b6fd404 100644
--- a/docs/zh_cn/community/_roadmap.md
+++ b/docs/zh_cn/community/_roadmap.md
@@ -1,5 +1,4 @@
 ---
-sidebar_label: 路线图
+title: 路线图
 sidebar_position: 3
 ---
-# 路线图
\ No newline at end of file
diff --git a/docs/zh_cn/community/adopters.md b/docs/zh_cn/community/adopters.md
index 43138296..786bfa5c 100644
--- a/docs/zh_cn/community/adopters.md
+++ b/docs/zh_cn/community/adopters.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 使用者
+title: 使用者
 sidebar_position: 1
 slug: /adopters
 ---
 
-# JuiceFS 使用者
-
 | 公司／团队                                       | 使用环境   | 使用场景               | 案例                                                                                                                                                                                                  |
 |--------------------------------------------------|------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | [小米](https://www.mi.com)                       | Production | AI                     |                                                                                                                                                                                                       |
diff --git a/docs/zh_cn/community/articles.md b/docs/zh_cn/community/articles.md
index 167e30e2..08dcb5ed 100644
--- a/docs/zh_cn/community/articles.md
+++ b/docs/zh_cn/community/articles.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 用户文章合集
+title: 用户文章合集
 sidebar_position: 2
 slug: /articles
 ---
 
-# 用户文章合集
-
 JuiceFS 广泛适用于各种数据存储和共享场景，本页汇总来自世界各地用户使用 JuiceFS 的实践，欢迎大家共同来维护这个列表。
 
 ## AI
diff --git a/docs/zh_cn/community/integrations.md b/docs/zh_cn/community/integrations.md
index 6226c239..62d0a619 100644
--- a/docs/zh_cn/community/integrations.md
+++ b/docs/zh_cn/community/integrations.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 社区集成
+title: 社区集成
 sidebar_position: 2
 slug: /integrations
 ---
 
-# 社区集成
-
 ## SDK
 
 - [旷视科技](https://megvii.com) 团队贡献了 [Python SDK](https://github.com/megvii-research/juicefs-python)。
@@ -23,4 +21,4 @@ slug: /integrations
 
 ## DevOps
 
-- [Terraform Provider for JuiceFS](https://github.com/toowoxx/terraform-provider-juicefs) 由 Toowoxx IT GmbH 贡献，他们是一家来自德国的 IT 服务公司。
\ No newline at end of file
+- [Terraform Provider for JuiceFS](https://github.com/toowoxx/terraform-provider-juicefs) 由 Toowoxx IT GmbH 贡献，他们是一家来自德国的 IT 服务公司。
diff --git a/docs/zh_cn/community/usage_tracking.md b/docs/zh_cn/community/usage_tracking.md
index aefbad89..5220ed13 100644
--- a/docs/zh_cn/community/usage_tracking.md
+++ b/docs/zh_cn/community/usage_tracking.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: 用量上报
+title: 用量上报
 sidebar_position: 4
 ---
 
-# 用量上报
-
 JuiceFS 默认会收集并上报**「匿名」**的使用数据。这些数据仅仅包含核心指标（如版本号、文件系统大小），不会包含任何用户信息或者敏感数据。你可以查看[这里](https://github.com/juicedata/juicefs/blob/main/pkg/usage/usage.go)检查相关代码。
 
 这些数据帮助我们理解社区如何使用这个项目。你可以简单地通过 `--no-usage-report` 选项关闭用量上报：
diff --git a/docs/zh_cn/deployment/how_to_use_on_kubernetes.md b/docs/zh_cn/deployment/how_to_use_on_kubernetes.md
index c4305f22..f8281635 100644
--- a/docs/zh_cn/deployment/how_to_use_on_kubernetes.md
+++ b/docs/zh_cn/deployment/how_to_use_on_kubernetes.md
@@ -13,7 +13,7 @@ JuiceFS 非常适合用作 Kubernetes 集群的存储层，阅读本文以了解
 1. 在 Kubernetes 节点上统一安装、挂载 JuiceFS，如果节点众多，考虑[自动化部署](./automation.md)。
 1. 在 pod 定义中使用 `hostPath` 卷，直接将宿主机上的 JuiceFS 子目录挂载到容器中：
 
-   ```yaml {8-15}
+   ```yaml {8-16}
    apiVersion: v1
    kind: Pod
    metadata:
@@ -34,8 +34,8 @@ JuiceFS 非常适合用作 Kubernetes 集群的存储层，阅读本文以了解
 
 相比以 CSI 驱动的方式来使用 JuiceFS，`hostPath` 更为简单直接，出问题也更易排查，但也要注意：
 
+* 为求管理方便，一般所有容器都在使用同一个宿主机挂载点，缺乏隔离可能导致数据安全问题，未来也无法在不同应用中单独调整 JuiceFS 挂载参数。请谨慎评估。
 * 所有节点都需要提前挂载 JuiceFS，因此集群加入新节点，需要在初始化流程里进行安装和挂载，否则新节点没有 JuiceFS 挂载点，容器将无法创建。
-* 为求管理方便，一般所有容器都在使用同一个宿主机挂载点，缺乏隔离可能导致数据安全问题，请谨慎评估。
 * 宿主机上的 JuiceFS 挂载进程所占用的系统资源（如 CPU、内存等）不受 Kubernetes 控制，有可能占用较多宿主机资源。可以考虑用 [`system-reserved`](https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/reserve-compute-resources/#system-reserved) 来适当调整 Kubernetes 的系统资源预留值，为 JuiceFS 挂载进程预留更多资源。
 * 如果宿主机上的 JuiceFS 挂载进程意外退出，将会导致应用 pod 无法正常访问挂载点，此时需要重新挂载 JuiceFS 文件系统并重建应用 pod。作为对比，JuiceFS CSI 驱动提供[「挂载点自动恢复」](https://juicefs.com/docs/zh/csi/recover-failed-mountpoint)功能来解决这个问题。
 * 如果你使用 Docker 作为 Kubernetes 容器运行环境，最好令 JuiceFS 先于 Docker 启动，否则在节点重启的时候，偶尔可能出现容器启动时，JuiceFS 尚未挂载好的情况，此时便会因该依赖问题启动失败。以 systemd 为例，可以用下方 unit file 来配置启动顺序：
diff --git a/docs/zh_cn/deployment/juicefs_on_docker.md b/docs/zh_cn/deployment/juicefs_on_docker.md
index bd9a9789..6ad0a29e 100644
--- a/docs/zh_cn/deployment/juicefs_on_docker.md
+++ b/docs/zh_cn/deployment/juicefs_on_docker.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Docker 使用 JuiceFS
+title: Docker 使用 JuiceFS
 sidebar_position: 3
 slug: /juicefs_on_docker
 ---
-# 在 Docker 使用 JuiceFS
 
 将 JuiceFS 作为 Docker 持久化存储有以下几种常用方法：
 
diff --git a/docs/zh_cn/deployment/s3_gateway.md b/docs/zh_cn/deployment/s3_gateway.md
index 409a6617..d1f59b7b 100644
--- a/docs/zh_cn/deployment/s3_gateway.md
+++ b/docs/zh_cn/deployment/s3_gateway.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 配置 JuiceFS S3 网关
+title: 配置 JuiceFS S3 网关
 sidebar_position: 6
 slug: /s3_gateway
 ---
 
-# 启用 JuiceFS 的 S3 网关
-
 JuiceFS 从 v0.11 开始引入了 S3 网关，这是一个通过 [MinIO S3 网关](https://docs.min.io/docs/minio-gateway-for-s3.html)实现的功能。它为 JuiceFS 中的文件提供跟 S3 兼容的 RESTful API，在不方便挂载的情况下能够用 s3cmd、AWS CLI、MinIO Client（mc）等工具管理 JuiceFS 上存储的文件。另外，S3 网关还提供了一个基于网页的文件管理器，用户使用浏览器就能对 JuiceFS 上的文件进行常规的增删管理。
 
 因为 JuiceFS 会将文件分块存储到底层的对象存储中，不能直接使用底层对象存储的接口和界面来直接访问文件，S3 网关提供了类似底层对象存储的访问能力，架构图如下：
diff --git a/docs/zh_cn/development/contributing_guide.md b/docs/zh_cn/development/contributing_guide.md
index 82a51173..2986f7a9 100644
--- a/docs/zh_cn/development/contributing_guide.md
+++ b/docs/zh_cn/development/contributing_guide.md
@@ -1,10 +1,8 @@
 ---
-sidebar_label: 贡献指南
+title: 贡献指南
 sidebar_position: 1
 ---
 
-# JuiceFS 贡献指南
-
 ## 基本准则
 
 - 在开始修复功能或错误之前，请先通过 GitHub、Slack 等渠道与我们沟通。此步骤的目的是确保没有其他人已经在处理它，如有必要，我们将要求您创建一个 GitHub issue。
diff --git a/docs/zh_cn/development/data_structures.md b/docs/zh_cn/development/data_structures.md
index 72e128fb..1641af8e 100644
--- a/docs/zh_cn/development/data_structures.md
+++ b/docs/zh_cn/development/data_structures.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 内部实现
+title: 内部实现
 sidebar_position: 3
 slug: /internals
 ---
-# JuiceFS 内部实现
 
 ## 1. 简介
 
@@ -880,4 +879,4 @@ objects:
 
 :::note 备注
 若同时开启压缩和加密，原始数据会先压缩再加密后上传到对象存储。
-:::
\ No newline at end of file
+:::
diff --git a/docs/zh_cn/getting-started/installation.md b/docs/zh_cn/getting-started/installation.md
index 1cafb09e..4269d78b 100644
--- a/docs/zh_cn/getting-started/installation.md
+++ b/docs/zh_cn/getting-started/installation.md
@@ -1,12 +1,10 @@
 ---
-sidebar_label: 安装
+title: 安装
 sidebar_position: 1
 slug: /installation
 pagination_prev: introduction/comparison/juicefs_vs_s3ql
 ---
 
-# 安装
-
 JuiceFS 有良好的跨平台能力，支持在几乎所有主流架构的各类操作系统上运行，包括且不限于 Linux、macOS、Windows 等。
 
 JuiceFS 客户端只有一个二进制文件，你可以下载预编译的版本直接解压使用，也可以用源代码手动编译。
diff --git a/docs/zh_cn/guide/cache_management.md b/docs/zh_cn/guide/cache_management.md
index fd031470..97f448c6 100644
--- a/docs/zh_cn/guide/cache_management.md
+++ b/docs/zh_cn/guide/cache_management.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 缓存
+title: 缓存
 sidebar_position: 3
 slug: /cache_management
 ---
-# 缓存
 
 对于一个由对象存储和数据库组合驱动的文件系统，缓存是本地客户端与远端服务之间高效交互的重要纽带。读写的数据可以提前或者异步载入缓存，再由客户端在后台与远端服务交互执行异步上传或预取数据。相比直接与远端服务交互，采用缓存技术可以大大降低存储操作的延时并提高数据吞吐量。
 
diff --git a/docs/zh_cn/guide/how_to_set_up_metadata_engine.md b/docs/zh_cn/guide/how_to_set_up_metadata_engine.md
index 1b5a29ea..3e33937c 100644
--- a/docs/zh_cn/guide/how_to_set_up_metadata_engine.md
+++ b/docs/zh_cn/guide/how_to_set_up_metadata_engine.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: 如何设置元数据引擎
+title: 如何设置元数据引擎
 sidebar_position: 1
 slug: /databases_for_metadata
 ---
@@ -7,8 +7,6 @@ slug: /databases_for_metadata
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# 如何设置元数据引擎
-
 :::tip 版本提示
 本文档使用的环境变量 `META_PASSWORD` 是 JuiceFS v1.0 新增功能，旧版客户端需要[升级](../administration/upgrade.md)后才能使用。
 :::
diff --git a/docs/zh_cn/guide/how_to_set_up_object_storage.md b/docs/zh_cn/guide/how_to_set_up_object_storage.md
index 588288fc..4b85c87b 100644
--- a/docs/zh_cn/guide/how_to_set_up_object_storage.md
+++ b/docs/zh_cn/guide/how_to_set_up_object_storage.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: 如何设置对象存储
+title: 如何设置对象存储
 sidebar_position: 2
 slug: /how_to_setup_object_storage
 ---
@@ -7,8 +7,6 @@ slug: /how_to_setup_object_storage
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# 如何设置对象存储
-
 通过阅读 [JuiceFS 的技术架构](../introduction/architecture.md)可以了解到，JuiceFS 是一个数据与元数据分离的分布式文件系统，以对象存储作为主要的数据存储，以 Redis、PostgreSQL、MySQL 等数据库作为元数据存储。
 
 ## 存储选项
diff --git a/docs/zh_cn/guide/mount_at_boot.md b/docs/zh_cn/guide/mount_at_boot.md
index 2d8498be..141a6301 100644
--- a/docs/zh_cn/guide/mount_at_boot.md
+++ b/docs/zh_cn/guide/mount_at_boot.md
@@ -1,13 +1,9 @@
 ---
-sidebar_label: 启动时自动挂载 JuiceFS
+title: 启动时自动挂载 JuiceFS
 sidebar_position: 2
 slug: /mount_juicefs_at_boot_time
 ---
 
-# 启动时自动挂载 JuiceFS
-
-这是一篇关于如何在启动时自动挂载 JuiceFS 的指南。
-
 ## Linux
 
 拷贝 `juicefs` 为 `/sbin/mount.juicefs`，然后按照下面的格式添加一行到 `/etc/fstab`：
diff --git a/docs/zh_cn/guide/quota.md b/docs/zh_cn/guide/quota.md
index e4be0ef2..b62d4b9e 100644
--- a/docs/zh_cn/guide/quota.md
+++ b/docs/zh_cn/guide/quota.md
@@ -1,8 +1,7 @@
 ---
-sidebar_label: 存储配额
+title: 存储配额
 sidebar_position: 5
 ---
-# 存储配额
 
 JuiceFS v0.14.2 开始支持文件系统级别的存储配额，该功能包括：
 
diff --git a/docs/zh_cn/introduction/io_processing.md b/docs/zh_cn/introduction/io_processing.md
index 08ef41a4..240b1e2c 100644
--- a/docs/zh_cn/introduction/io_processing.md
+++ b/docs/zh_cn/introduction/io_processing.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 读写请求处理流程
+title: 读写请求处理流程
 sidebar_position: 3
 slug: /internals/io_processing
 ---
-# 读写请求处理流程介绍
 
 ## 写入流程
 
diff --git a/docs/zh_cn/reference/command_reference.md b/docs/zh_cn/reference/command_reference.md
index d72e77e2..72fcb519 100644
--- a/docs/zh_cn/reference/command_reference.md
+++ b/docs/zh_cn/reference/command_reference.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: 命令参考
+title: 命令参考
 sidebar_position: 1
 slug: /command_reference
 ---
@@ -7,10 +7,6 @@ slug: /command_reference
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# JuiceFS 命令参考
-
-有许多命令可帮助您管理文件系统，该页面提供了有关这些命令的详细参考。
-
 ## 概览
 
 在终端输入 `juicefs` 并执行，你就会看到所有可用的命令。另外，你可以在每个命令后面添加 `-h/--help` 标记获得该命令的详细帮助信息。
@@ -326,7 +322,7 @@ consul 注册中心地址 (默认："127.0.0.1:8500")
 发送心跳的间隔 (秒);建议所有客户端使用相同的心跳值 (默认：12)
 
 `--upload-delay value`<br />
-数据上传到对象存储的延迟时间，支持秒分时精度，对应格式分别为 ("s", "m", "h")，默认为 0 秒
+数据上传到对象存储的延迟时间，支持秒分时精度，对应格式分别为 ("s", "m", "h")，默认为 0 秒。如果在等待的时间内数据被应用删除，则无需再上传到对象存储，既提升了性能也节省了成本，如果数据只是临时落盘，之后会迅速删除，考虑用该选项进行优化。
 
 `--no-bgjob`<br />
 禁用后台作业（清理、备份等）（默认：false）
diff --git a/docs/zh_cn/reference/fuse_mount_options.md b/docs/zh_cn/reference/fuse_mount_options.md
index 3b9ae460..e22c74f6 100644
--- a/docs/zh_cn/reference/fuse_mount_options.md
+++ b/docs/zh_cn/reference/fuse_mount_options.md
@@ -1,23 +1,37 @@
 ---
-sidebar_label: FUSE 挂载选项
+title: FUSE 挂载选项
 sidebar_position: 6
 slug: /fuse_mount_options
 ---
-# FUSE 挂载选项
 
-本指南列出了重要的 FUSE 挂载选项。当执行 [`juicefs mount`](../reference/command_reference.md#mount) 命令时，这些安装选项由 `-o` 选项指定，多个选项使用半角逗号分隔。例如：
+JuiceFS 文件系统为用户提供多种访问方式，FUSE 是其中较为常用的一种，即使用 `juicefs mount` 命令将文件系统挂载到本地的方式。用户可以根据需要添加 FUSE 支持的挂载选项，从而实现更细粒度的控制。
+
+本指南介绍 JuiceFS 常用的 FUSE 挂载选项，有两种添加挂载选项的方式：
+
+1. 手动执行 [`juicefs mount`](../reference/command_reference.md#mount) 命令时，通过 `-o` 选项指定，多个选项使用半角逗号分隔。
 
 ```bash
 juicefs mount -d -o allow_other,writeback_cache sqlite3://myjfs.db ~/jfs
 ```
 
-## debug
+2. Linux 发行版通过 `/etc/fstab` 定义自动挂载时，在 `options` 字段处直接添加选项，多个选项使用半角逗号分隔。   
+
+```
+# <file system>       <mount point>   <type>      <options>           <dump>  <pass>
+redis://localhost:6379/1    /jfs      juicefs     _netdev,allow_other   0       0
+```
+
+## default_permissions
+
+JuiceFS 在挂载时会自动启用该选项，无需显式指定。该选项将启用内核的文件访问权限检查，它会在文件系统之外进行，启用后，内核检查和文件系统检查必须全部成功才允许进一步操作，该选项通常与 `allow_other` 一起使用。
 
-启用调试日志
+:::tip
+内核执行的是标准的 UNIX 权限检查，基于 mode bits、uid/gid、目录所有权。
+:::
 
 ## allow_other
 
-默认只有挂载文件系统的用户才能访问文件系统中的文件，此选项可解锁该限制。设置此选项以后，所有用户，包括 root 用户都可以访问该文件系统中的文件。
+FUSE 默认只有挂载文件系统的用户可以访问挂载点中的文件，`allow_other` 选项可以让其他用户（包括 root 用户）也可以访问挂载点上的文件。
 
 默认情况下，这个选项只允许 root 用户使用，但是可以通过修改 `/etc/fuse.conf`，在该配置文件中开启 `user_allow_other` 配置选项解除限制。
 
@@ -28,3 +42,15 @@ juicefs mount -d -o allow_other,writeback_cache sqlite3://myjfs.db ~/jfs
 :::
 
 FUSE 支持[「writeback-cache 模式」](https://www.kernel.org/doc/Documentation/filesystems/fuse-io.txt)，这意味着 `write()` 系统调用通常可以非常快速地完成。当频繁写入非常小的数据（如 100 字节左右）时，建议启用此挂载选项。
+
+## user_id 和 group_id
+
+这两个选项用来指定挂载点的所有者 ID 和所有者组 ID，但仅允许以 root 身份指定，例如 `sudo juicefs mount -o user_id=100,group_id=100`。
+
+## debug
+
+该选项会将低层类库（go-fuse）的 Debug 信息输出到 `juicefs.log` 中，详情参考文档[故障诊断和分析](https://juicefs.com/docs/zh/community/fault_diagnosis_and_analysis)。
+
+:::note 注意
+该选项会将低层类库（go-fuse）的 Debug 信息输出到 `juicefs.log` 中，需要注意的是，该选项与 JuiceFS 客户端的全局 `--debug` 选项不同，前者是输出 go-fuse 类库的调试信息，后者是输出 JucieFS 客户端的调试信息。详情参考文档[故障诊断和分析](https://juicefs.com/docs/zh/community/fault_diagnosis_and_analysis)。
+:::
\ No newline at end of file
diff --git a/docs/zh_cn/reference/p8s_metrics.md b/docs/zh_cn/reference/p8s_metrics.md
index 71b0e816..79bba8d0 100644
--- a/docs/zh_cn/reference/p8s_metrics.md
+++ b/docs/zh_cn/reference/p8s_metrics.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: JuiceFS 监控指标
+title: JuiceFS 监控指标
 sidebar_position: 2
 slug: /p8s_metrics
 ---
 
-# JuiceFS 监控指标
-
 :::tip 提示
 请查看[「监控」](../administration/monitoring.md)文档了解如何收集及展示 JuiceFS 监控指标
 :::
diff --git a/docs/zh_cn/reference/posix_compatibility.md b/docs/zh_cn/reference/posix_compatibility.md
index 3d55e760..ec1a1a6c 100644
--- a/docs/zh_cn/reference/posix_compatibility.md
+++ b/docs/zh_cn/reference/posix_compatibility.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: POSIX 兼容性
+title: POSIX 兼容性
 sidebar_position: 6
 slug: /posix_compatibility
 ---
-# POSIX 兼容性
 
 JuiceFS 借助于 pjdfstest 和 LTP 来验证其对 POSIX 的兼容性。
 
diff --git a/docs/zh_cn/security/metadata_automatic_backup.md b/docs/zh_cn/security/metadata_automatic_backup.md
deleted file mode 100644
index 57bf32a5..00000000
--- a/docs/zh_cn/security/metadata_automatic_backup.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-sidebar_label: 元数据自动备份
-sidebar_position: 3
----
-# 元数据自动备份
-
-请参考：[元数据自动备份](../administration/metadata_dump_load.md#自动备份)
\ No newline at end of file
diff --git a/docs/zh_cn/tutorials/aliyun.md b/docs/zh_cn/tutorials/aliyun.md
index a61c2077..21de4cc3 100644
--- a/docs/zh_cn/tutorials/aliyun.md
+++ b/docs/zh_cn/tutorials/aliyun.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 在阿里云使用 JuiceFS
+title: 在阿里云使用 JuiceFS
 sidebar_position: 4
 slug: /clouds/aliyun
 ---
 
-# 在阿里云安装和使用 JuiceFS 存储
-
 如下图所示，JuiceFS 存储由数据库和对象存储共同驱动。存入 JuiceFS 的文件会按照一定的规则被拆分成固定大小的数据块存储在对象存储中，数据对应的元数据则会存储在数据库中。
 
 元数据完全独立存储，对文件的检索和处理并不会直接操作对象存储中的数据，而是先在数据库中操作元数据，只有当数据发生变化的时候，才会与对象存储交互。
diff --git a/docs/zh_cn/tutorials/aws.md b/docs/zh_cn/tutorials/aws.md
index 1b6a3e7b..17c603cb 100644
--- a/docs/zh_cn/tutorials/aws.md
+++ b/docs/zh_cn/tutorials/aws.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 在 AWS 上使用 JuiceFS
+title: 在 AWS 上使用 JuiceFS
 sidebar_position: 6
 slug: /clouds/aws
 ---
 
-# 在亚马逊 AWS 安装和使用 JuiceFS 存储
-
 亚马逊 AWS 是全球领先的云计算平台，提供几乎所有类型的云计算服务。得益于 AWS 丰富的产品线，用户可以非常灵活的搭配选择 JuiceFS 组成部分。
 
 ## 准备
diff --git a/docs/zh_cn/tutorials/digitalocean.md b/docs/zh_cn/tutorials/digitalocean.md
index 57c205c6..e1cd3017 100644
--- a/docs/zh_cn/tutorials/digitalocean.md
+++ b/docs/zh_cn/tutorials/digitalocean.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 在 DigitalOcean 使用 JuiceFS
+title: 在 DigitalOcean 使用 JuiceFS
 sidebar_position: 7
 slug: /clouds/digitalocean
 ---
-# 在 DigitalOcean 安装和使用 JuiceFS 存储
 
 JuiceFS 是面向云设计的，使用云平台开箱即用的存储和数据库服务，最快几分钟就能完成配置投入使用，本文以 DigitalOcean 平台为例，介绍如何在云计算平台上快速简单的安装和使用 JuiceFS。
 
diff --git a/docs/zh_cn/tutorials/juicefs_on_colab.md b/docs/zh_cn/tutorials/juicefs_on_colab.md
index 9a67920a..92a7ed8a 100644
--- a/docs/zh_cn/tutorials/juicefs_on_colab.md
+++ b/docs/zh_cn/tutorials/juicefs_on_colab.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 在 Colab 上通过 Google CloudSQL 和 GCS 使用 JuiceFS
+title: 在 Colab 上通过 Google CloudSQL 和 GCS 使用 JuiceFS
 sidebar_position: 4
 slug: /juicefs_on_colab
 ---
 
-# 在 Colab 上通过 Google CloudSQL 和 GCS 使用 JuiceFS
-
 [Colaboratory](https://colab.research.google.com), 或者简称“Colab”, 是 Google Research 的产品，它允许任何人通过浏览器编写和执行 python 代码，特别适合机器学习、数据分析和教育。
 Colab 支持从 Google Drive 将文件上传到 Colab 实例或从 Colab 实例下载文件。然而在某些情况下，Google Drive 可能不太方便与 Colab 一起使用，在这种情况下，JuiceFS 是一个很有用的工具，因为他允许在 Colab 实例之间，或在 Colab 实例与本地或本地机器之间轻松的同步文件。[这里是一个使用了 juicefs 的 Colab 笔记本示例](https://colab.research.google.com/drive/1wA8vRwqiihXkI6ViDU8Ud868UeYtmCo5)
 
diff --git a/docs/zh_cn/tutorials/juicefs_on_k3s.md b/docs/zh_cn/tutorials/juicefs_on_k3s.md
index 6d9d9a11..92229526 100644
--- a/docs/zh_cn/tutorials/juicefs_on_k3s.md
+++ b/docs/zh_cn/tutorials/juicefs_on_k3s.md
@@ -1,5 +1,5 @@
 ---
-sidebar_label: 在 K3s 上使用 JuiceFS
+title: 在 K3s 上使用 JuiceFS
 sidebar_position: 1
 slug: /juicefs_on_k3s
 ---
diff --git a/docs/zh_cn/tutorials/juicefs_on_kubesphere.md b/docs/zh_cn/tutorials/juicefs_on_kubesphere.md
index 99208e16..aa860960 100644
--- a/docs/zh_cn/tutorials/juicefs_on_kubesphere.md
+++ b/docs/zh_cn/tutorials/juicefs_on_kubesphere.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 在 KubeSphere 上使用 JuiceFS
+title: 在 KubeSphere 上使用 JuiceFS
 sidebar_position: 3
 slug: /juicefs_on_kubesphere
 ---
-# 在 KubeSphere 上使用 JuiceFS
 
 [KubeSphere](https://kubesphere.com.cn/) 是在 Kubernetes 之上构建的以应用为中心的多租户容器平台，提供全栈的 IT 自动化运维的能力，简化企业的 DevOps 工作流。
 
diff --git a/docs/zh_cn/tutorials/juicefs_on_rancher.md b/docs/zh_cn/tutorials/juicefs_on_rancher.md
index 390786f8..e9e6847f 100644
--- a/docs/zh_cn/tutorials/juicefs_on_rancher.md
+++ b/docs/zh_cn/tutorials/juicefs_on_rancher.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: 在 Rancher 上使用 JuiceFS
+title: 在 Rancher 上使用 JuiceFS
 sidebar_position: 2
 slug: /juicefs_on_rancher
 ---
-# 在 Rancher 上使用 JuiceFS
 
 简单来说，[Rancher](https://rancher.com/) 是一个企业级的 Kubernetes 集群管理工具，使用它可以非常轻松的在各种云计算平台上快速的完成 Kubernetes 集群的部署。
 
diff --git a/docs/zh_cn/tutorials/juicefs_on_wsl.md b/docs/zh_cn/tutorials/juicefs_on_wsl.md
index 39f03f33..3cc174fd 100644
--- a/docs/zh_cn/tutorials/juicefs_on_wsl.md
+++ b/docs/zh_cn/tutorials/juicefs_on_wsl.md
@@ -1,9 +1,7 @@
 ---
-sidebar_label: 在 WSL 中使用 JuiceFS
+title: 在 WSL 中使用 JuiceFS
 ---
 
-# 在 WSL 中使用 JuiceFS
-
 WSL 全称 Windows Subsystem for Linux，即适用于 Linux 的 Windows 子系统。它可以让你在 Windows 系统环境下运行大多数 GNU/Linux 原生命令、工具和程序，且不必像用虚拟机或双系统那样产生额外的硬件开销。
 
 ## 安装 WSL
diff --git a/docs/zh_cn/tutorials/qcloud.md b/docs/zh_cn/tutorials/qcloud.md
index 1e83a669..2569745a 100644
--- a/docs/zh_cn/tutorials/qcloud.md
+++ b/docs/zh_cn/tutorials/qcloud.md
@@ -1,11 +1,9 @@
 ---
-sidebar_label: 在腾讯云使用 JuiceFS
+title: 在腾讯云使用 JuiceFS
 sidebar_position: 5
 slug: /clouds/qcloud
 ---
 
-# 在腾讯云安装和使用 JuiceFS 存储
-
 如下图所示，JuiceFS 存储由数据库和对象存储共同驱动。存入 JuiceFS 的文件会按照一定的规则被拆分成固定大小的数据块存储在对象存储中，数据对应的元数据则会存储在数据库中。
 
 元数据完全独立存储，对文件的检索和处理并不会直接操作对象存储中的数据，而是先在数据库中操作元数据，只有当数据发生变化的时候，才会与对象存储交互。
diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go
index 7a700d99..989394ad 100644
--- a/pkg/meta/interface.go
+++ b/pkg/meta/interface.go
@@ -42,10 +42,12 @@ const (
 	CompactChunk = 1001
 	// Rmr is a message to remove a directory recursively.
 	Rmr = 1002
-	// Info is a message to get the internal info for file or directory.
-	Info = 1003
+	// LegacyInfo is a message to get the internal info for file or directory.
+	LegacyInfo = 1003
 	// FillCache is a message to build cache for target directories/files
 	FillCache = 1004
+	// InfoV2 is a message to get the internal info for file or directory.
+	InfoV2 = 1005
 )
 
 const (
@@ -267,6 +269,8 @@ type Meta interface {
 	GetSession(sid uint64, detail bool) (*Session, error)
 	// ListSessions returns all client sessions.
 	ListSessions() ([]*Session, error)
+	// ListLocks returns all locks of a inode.
+	ListLocks(ctx context.Context, inode Ino) ([]PLockItem, []FLockItem, error)
 	// Statistic scan metadata by visitors.
 	Statistic(ctx context.Context, slicesDelayedScan func(Slice) error, fileDelayedScan func(ino Ino, size uint64) error) error
 	// CleanStaleSessions cleans up sessions not active for more than 5 minutes
diff --git a/pkg/meta/redis_lock.go b/pkg/meta/redis_lock.go
index bb294ee8..2d828404 100644
--- a/pkg/meta/redis_lock.go
+++ b/pkg/meta/redis_lock.go
@@ -20,6 +20,7 @@
 package meta
 
 import (
+	"context"
 	"strconv"
 	"strings"
 	"syscall"
@@ -206,3 +207,38 @@ func (r *redisMeta) Setlk(ctx Context, inode Ino, owner uint64, block bool, ltyp
 	}
 	return errno(err)
 }
+
+func (r *redisMeta) ListLocks(ctx context.Context, inode Ino) ([]PLockItem, []FLockItem, error) {
+	fKey := r.flockKey(inode)
+	pKey := r.plockKey(inode)
+
+	rawFLocks, err := r.rdb.HGetAll(ctx, fKey).Result()
+	if err != nil {
+		return nil, nil, err
+	}
+	flocks := make([]FLockItem, 0, len(rawFLocks))
+	for k, v := range rawFLocks {
+		owner, err := parseOwnerKey(k)
+		if err != nil {
+			return nil, nil, err
+		}
+		flocks = append(flocks, FLockItem{*owner, v})
+	}
+
+	rawPLocks, err := r.rdb.HGetAll(ctx, pKey).Result()
+	if err != nil {
+		return nil, nil, err
+	}
+	plocks := make([]PLockItem, 0)
+	for k, d := range rawPLocks {
+		owner, err := parseOwnerKey(k)
+		if err != nil {
+			return nil, nil, err
+		}
+		ls := loadLocks([]byte(d))
+		for _, l := range ls {
+			plocks = append(plocks, PLockItem{*owner, l})
+		}
+	}
+	return plocks, flocks, nil
+}
diff --git a/pkg/meta/sql_lock.go b/pkg/meta/sql_lock.go
index d4543501..f0767f84 100644
--- a/pkg/meta/sql_lock.go
+++ b/pkg/meta/sql_lock.go
@@ -21,6 +21,7 @@ package meta
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"syscall"
 	"time"
@@ -247,3 +248,29 @@ func (m *dbMeta) Setlk(ctx Context, inode Ino, owner_ uint64, block bool, ltype
 	}
 	return err
 }
+
+func (r *dbMeta) ListLocks(ctx context.Context, inode Ino) ([]PLockItem, []FLockItem, error) {
+	var fs []flock
+	if err := r.db.Find(&fs, &flock{Inode: inode}); err != nil {
+		return nil, nil, err
+	}
+
+	flocks := make([]FLockItem, 0, len(fs))
+	for _, f := range fs {
+		flocks = append(flocks, FLockItem{ownerKey{f.Sid, uint64(f.Owner)}, string(f.Ltype)})
+	}
+
+	var ps []plock
+	if err := r.db.Find(&ps, &plock{Inode: inode}); err != nil {
+		return nil, nil, err
+	}
+
+	plocks := make([]PLockItem, 0)
+	for _, p := range ps {
+		ls := loadLocks(p.Records)
+		for _, l := range ls {
+			plocks = append(plocks, PLockItem{ownerKey{p.Sid, uint64(p.Owner)}, l})
+		}
+	}
+	return plocks, flocks, nil
+}
diff --git a/pkg/meta/tkv_lock.go b/pkg/meta/tkv_lock.go
index 90507878..d3376b16 100644
--- a/pkg/meta/tkv_lock.go
+++ b/pkg/meta/tkv_lock.go
@@ -17,6 +17,7 @@
 package meta
 
 import (
+	"context"
 	"syscall"
 	"time"
 
@@ -221,3 +222,29 @@ func (m *kvMeta) Setlk(ctx Context, inode Ino, owner uint64, block bool, ltype u
 	}
 	return errno(err)
 }
+
+func (m *kvMeta) ListLocks(ctx context.Context, inode Ino) ([]PLockItem, []FLockItem, error) {
+	fKey := m.flockKey(inode)
+	pKey := m.plockKey(inode)
+
+	var flocks []FLockItem
+	var plocks []PLockItem
+	err := m.txn(func(tx kvTxn) error {
+		fv := tx.get(fKey)
+		fs := unmarshalFlock(fv)
+		for k, t := range fs {
+			flocks = append(flocks, FLockItem{ownerKey{k.sid, k.owner}, string(t)})
+		}
+
+		pv := tx.get(pKey)
+		owners := unmarshalPlock(pv)
+		for k, records := range owners {
+			ls := loadLocks(records)
+			for _, l := range ls {
+				plocks = append(plocks, PLockItem{ownerKey{k.sid, k.owner}, l})
+			}
+		}
+		return nil
+	})
+	return plocks, flocks, err
+}
diff --git a/pkg/meta/utils.go b/pkg/meta/utils.go
index 86f910b7..d77fbc55 100644
--- a/pkg/meta/utils.go
+++ b/pkg/meta/utils.go
@@ -18,9 +18,11 @@ package meta
 
 import (
 	"bytes"
+	"fmt"
 	"net/url"
 	"runtime/debug"
 	"sort"
+	"strconv"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -134,6 +136,37 @@ type plockRecord struct {
 	End   uint64
 }
 
+type ownerKey struct {
+	Sid   uint64
+	Owner uint64
+}
+
+type PLockItem struct {
+	ownerKey
+	plockRecord
+}
+
+type FLockItem struct {
+	ownerKey
+	Type string
+}
+
+func parseOwnerKey(key string) (*ownerKey, error) {
+	pair := strings.Split(key, "_")
+	if len(pair) != 2 {
+		return nil, fmt.Errorf("invalid owner key: %s", key)
+	}
+	sid, err := strconv.ParseUint(pair[0], 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	owner, err := strconv.ParseUint(pair[1], 16, 64)
+	if err != nil {
+		return nil, err
+	}
+	return &ownerKey{sid, owner}, nil
+}
+
 func loadLocks(d []byte) []plockRecord {
 	var ls []plockRecord
 	rb := utils.FromBuffer(d)
diff --git a/pkg/vfs/internal.go b/pkg/vfs/internal.go
index f445a75d..a2487d0e 100644
--- a/pkg/vfs/internal.go
+++ b/pkg/vfs/internal.go
@@ -18,7 +18,9 @@ package vfs
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
+	"io"
 	"os"
 	"strconv"
 	"strings"
@@ -247,6 +249,59 @@ func (v *VFS) caclObjects(id uint64, size, offset, length uint32) []*obj {
 	return objs
 }
 
+type InfoResponse struct {
+	Ino     Ino
+	Failed  bool
+	Reason  string
+	Summary meta.Summary
+	Paths   []string
+	Chunks  []*chunkSlice
+	Objects []*chunkObj
+	PLocks  []meta.PLockItem
+	FLocks  []meta.FLockItem
+}
+
+type chunkSlice struct {
+	ChunkIndex uint64
+	meta.Slice
+}
+
+type chunkObj struct {
+	ChunkIndex     uint64
+	Key            string
+	Size, Off, Len uint32
+}
+
+func (r *InfoResponse) Encode() []byte {
+	resp, _ := json.Marshal(r)
+	buffer := utils.NewBuffer(4 + uint32(len(resp)))
+	buffer.Put32(uint32(len(resp)))
+	buffer.Put(resp)
+	return buffer.Bytes()
+}
+
+func (r *InfoResponse) Decode(reader io.Reader) error {
+	sizeBuf := make([]byte, 4)
+	n := 0
+	for n == 0 {
+		i, err := reader.Read(sizeBuf[n:])
+		if err != nil && err != io.EOF {
+			return err
+		}
+		if i == 1 && sizeBuf[0] == byte(syscall.EINVAL&0xff) {
+			return syscall.EINVAL
+		}
+		n += i
+	}
+
+	size := utils.ReadBuffer(sizeBuf).Get32()
+	respBuf := make([]byte, size)
+	if _, err := io.ReadFull(reader, respBuf); err != nil {
+		return err
+	}
+	return json.Unmarshal(respBuf, r)
+}
+
 func (v *VFS) handleInternalMsg(ctx meta.Context, cmd uint32, r *utils.Buffer, data *[]byte) {
 	switch cmd {
 	case meta.Rmr:
@@ -269,7 +324,7 @@ func (v *VFS) handleInternalMsg(ctx meta.Context, cmd uint32, r *utils.Buffer, d
 			}
 		}
 		*data = append(*data, uint8(st))
-	case meta.Info:
+	case meta.LegacyInfo:
 		var summary meta.Summary
 		inode := Ino(r.Get64())
 		var recursive uint8 = 1
@@ -329,6 +384,51 @@ func (v *VFS) handleInternalMsg(ctx meta.Context, cmd uint32, r *utils.Buffer, d
 		}
 		wb.Put32(uint32(w.Len()))
 		*data = append(*data, append(wb.Bytes(), w.Bytes()...)...)
+	case meta.InfoV2:
+		inode := Ino(r.Get64())
+		info := &InfoResponse{
+			Ino: inode,
+		}
+
+		var recursive uint8 = 1
+		if r.HasMore() {
+			recursive = r.Get8()
+		}
+		var raw bool
+		if r.HasMore() {
+			raw = r.Get8() != 0
+		}
+
+		r := meta.GetSummary(v.Meta, ctx, inode, &info.Summary, recursive != 0)
+		if r != 0 {
+			info.Failed = true
+			info.Reason = r.Error()
+			*data = append(*data, info.Encode()...)
+			return
+		}
+
+		info.Paths = v.Meta.GetPaths(ctx, inode)
+		if info.Summary.Files == 1 && info.Summary.Dirs == 0 {
+			for indx := uint64(0); indx*meta.ChunkSize < info.Summary.Length; indx++ {
+				var cs []meta.Slice
+				_ = v.Meta.Read(ctx, inode, uint32(indx), &cs)
+				for _, c := range cs {
+					if raw {
+						info.Chunks = append(info.Chunks, &chunkSlice{indx, c})
+					} else {
+						for _, o := range v.caclObjects(c.Id, c.Size, c.Off, c.Len) {
+							info.Objects = append(info.Objects, &chunkObj{indx, o.key, o.size, o.off, o.len})
+						}
+					}
+				}
+			}
+		}
+
+		var err error
+		if info.PLocks, info.FLocks, err = v.Meta.ListLocks(ctx, inode); err != nil {
+			info.Reason = err.Error()
+		}
+		*data = append(*data, info.Encode()...)
 	case meta.FillCache:
 		paths := strings.Split(string(r.Get(int(r.Get32()))), "\n")
 		concurrent := r.Get16()


Test patch:
diff --git a/.github/scripts/how_to_use_mutate_test.md b/.github/scripts/how_to_use_mutate_test.md
index 9406fc3c..f46e5c9e 100644
--- a/.github/scripts/how_to_use_mutate_test.md
+++ b/.github/scripts/how_to_use_mutate_test.md
@@ -38,3 +38,7 @@ For example:
 	if err != nil { //skip mutate
 		return "", fmt.Errorf("failed to execute command `lsb_release`: %s", err)
 	}
+
+# how to customize mutate test job in parallel?
+if the mutants of the target source file is more than 200, we will use 4 github jobs to run it. otherwise we will use 1 job to run.
+you can customize it in your test file with adding "//mutate_test_job_number: number", eg: //mutate_test_job_number: 8
\ No newline at end of file
diff --git a/.github/scripts/mutest.sh b/.github/scripts/mutest.sh
index 3c1b010f..c5a50c2d 100755
--- a/.github/scripts/mutest.sh
+++ b/.github/scripts/mutest.sh
@@ -41,7 +41,7 @@ fi
 python3 .github/scripts/check_coverage.py
 
 if [ $? -ne 0 ]; then
-	echo "mutate is out of code coverage", $COVERAGE_FILE, $MUTATE_CHANGED
+	echo "mutate is out of code coverage", $MUTATE_CHANGED
 	exit 101
 fi
 
@@ -59,7 +59,8 @@ fi
 
 mv $MUTATE_ORIGINAL $MUTATE_ORIGINAL.tmp
 cp $MUTATE_CHANGED $MUTATE_ORIGINAL
-
+echo "------------------------------------------------------------------------"
+echo "Start unit test with: $MUTATE_CHANGED"
 go test ./$PACKAGE_PATH/...  -run "$test_cases" -v -cover -count=1 -timeout=5m 
 # GOMUTESTING_TEST=$(go test -timeout $(printf '%ds' $MUTATE_TIMEOUT) $MUTATE_PACKAGE$TEST_RECURSIVE 2>&1)
 export GOMUTESTING_RESULT=$?
@@ -78,9 +79,7 @@ case $GOMUTESTING_RESULT in
 	exit 1
 	;;
 1) # tests failed -> PASS
-	if [ "$MUTATE_DEBUG" = true ] ; then
-		echo "$GOMUTESTING_DIFF"
-	fi
+	echo "$GOMUTESTING_DIFF"
 	echo "tests failed -> PASS"
 	exit 0
 	;;
diff --git a/.github/scripts/mutesting.py b/.github/scripts/mutesting.py
new file mode 100644
index 00000000..eea3d4c7
--- /dev/null
+++ b/.github/scripts/mutesting.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+import glob
+import os
+import sys
+from tkinter import Tcl
+
+def do_mutate_test(mutation_dir, index, total):
+    print(f'mutation dir is {mutation_dir}, inde is {index}, total is {total}', file=sys.stderr)
+    # os.system(f'ls -l {mutation_dir}')
+    list_of_files = Tcl().call('lsort', '-dict', glob.glob(mutation_dir + '/*.go.*') )
+    if len(list_of_files) > 0 and 'original' in list_of_files[-1]:
+        list_of_files = list_of_files[:-1]
+    # print('\n'.join(list_of_files), file=sys.stderr)
+    stats = {'passed':0, 'failed':0, 'compile_error':0, 'out_of_coverage':0, 'skip_by_comment':0, 'others':0, 'total':0}
+    count = int(len(list_of_files)/total) + 1
+    start = index*count
+    end = start + count
+    print(f'count:{count}, start:{start}, end:{end}', file=sys.stderr)
+    if end > len(list_of_files):
+        end = len(list_of_files)
+    for changed_file in list_of_files[start:end]:
+        # timestamp_str = time.strftime(  '%m/%d/%Y :: %H:%M:%S',
+        #                             time.gmtime(os.path.getmtime(changed_file))) 
+        # print(timestamp_str, ' -->', changed_file) 
+        os.environ['MUTATE_CHANGED'] = changed_file
+        ret = os.system('.github/scripts/mutest.sh') >> 8
+        if ret == 0:
+            stats['passed'] += 1
+        elif ret == 1:
+            stats['failed'] += 1
+        elif ret == 2:
+            stats['compile_error'] += 1
+        elif ret == 101:
+            stats['out_of_coverage'] += 1
+        elif ret == 102:
+            stats['skip_by_comment'] += 1
+        else:
+            stats['others'] += 1
+        stats['total'] += 1
+    if stats['passed'] + stats['failed'] == 0:
+        stats['score'] = 1.0
+    else:
+        stats['score'] = stats['passed'] / (stats['passed'] + stats['failed'])
+    return stats
+
+if __name__ == '__main__':
+    os.environ['MUTATE_PACKAGE'] = ''
+    mutation_dir = os.path.join(os.environ['MUTATION_DIR'], os.environ['PACKAGE_PATH'])
+    print(f'mutation dir is {mutation_dir}', file=sys.stderr)
+    original_file = os.environ['MUTATE_ORIGINAL']
+    print(f'original file is {original_file}', file=sys.stderr)
+    if not os.environ['JOB_INDEX']:
+        index = 0
+    else:
+        index = int(os.environ['JOB_INDEX'])-1
+    total = int(os.environ['JOB_TOTAL'])
+    stats = do_mutate_test(mutation_dir, index, total)
+    print(stats)
\ No newline at end of file
diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml
index d6a7e030..f30d6192 100644
--- a/.github/workflows/integrationtests.yml
+++ b/.github/workflows/integrationtests.yml
@@ -17,6 +17,12 @@ on:
       - '**.md'
       - '.github/**'
   workflow_dispatch:
+    inputs:
+      debug:
+        type: boolean
+        description: "Run the build with tmate debugging enabled"
+        required: false
+        default: false
 
 jobs:
   integrationtests:
@@ -104,5 +110,6 @@ jobs:
 
 
       - name: Setup upterm session
-        if: ${{ failure() }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
+        timeout-minutes: 60
         uses: lhotari/action-upterm@v1
diff --git a/.github/workflows/mutate-test.yml b/.github/workflows/mutate-test.yml
index fc3dfe2d..04a45245 100644
--- a/.github/workflows/mutate-test.yml
+++ b/.github/workflows/mutate-test.yml
@@ -13,6 +13,11 @@ on:
         description: "the go test file relative path you want to mutate, eg cmd/meta/xattr_test.go"
         required: true
         default: ""  
+      job_total:
+        type: string
+        description: "number of job to run mutation test"
+        required: true
+        default: "1"
       debug:
         type: boolean
         description: "Run the build with tmate debugging enabled"
@@ -28,36 +33,72 @@ jobs:
         with:
           fetch-depth: 0
 
+      - name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: '1.18.x'
+
+      - name: install go-mutesting
+        run: |
+          go install github.com/zimmski/go-mutesting/cmd/go-mutesting@latest
+
       - id: set-matrix
         run: |
+          sudo apt-get install jq
           if [ "${{github.event_name}}" == "pull_request"  ]; then
-            changed_file_str=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep _test.go$ | xargs)
+            echo github.event.pull_request.base.sha is ${{github.event.pull_request.base.sha}}
+            echo github.event.pull_request.head.sha is ${{github.event.pull_request.head.sha}}
+            echo github.sha is ${{ github.sha }}
+            changed_file_str=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep _test.go$ | xargs)
             echo "added or changed test files: $changed_file_str"
             changed_file_array=($changed_file_str)
-            test_files=""
-            for file_name in "${changed_file_array[@]}"
+            declare -a Jobs=();
+            for test_file_name in "${changed_file_array[@]}"
             do
-              if [[ "$file_name" == *_test.go ]]; then
-                test_files="\"$file_name\",$test_files"
+              echo "test_file_name is $test_file_name"
+              source_file_name=${test_file_name%"_test.go"}.go
+              echo "source_file_name is :" $source_file_name
+              black_list_file=black.list
+              TEST_FILE_NAME="$test_file_name" BLACK_LIST_FILE=$black_list_file python3 .github/scripts/parse_black_list.py 
+              echo "black list checksum: "
+              cat $black_list_file
+              total_count=$(go-mutesting $source_file_name --debug --no-exec --blacklist $black_list_file| grep "Save mutation into" | wc -l)
+              echo "total_count is $total_count"
+              job_total=$(TEST_FILE_NAME=$test_file_name python3 .github/scripts/parse_job_total.py)
+              echo "job_total specified: $job_total"
+              if [ $job_total -eq 0 ]; then
+                if [ $total_count -gt 200 ]; then
+                  job_total=4
+                else
+                  job_total=1
+                fi
               fi
+              echo "job_total: $job_total"
+              for i in `seq 1 $job_total` 
+              do
+                Jobs=("${Jobs[@]}" "$test_file_name-$i-$job_total")
+              done
             done
-            echo "test_files: $test_files"
-            if [ -n "$test_files" ]; then 
-              echo "test_files is not empty"
-              # echo '::set-output name=test_file_matrix::["cmd/meta/xattr_test.go", "cmd/meta/utils_test.go"]'
-              echo "::set-output name=test_file_matrix::[$test_files]"
-            else
-              echo "test_files is empty"
-              exit 1
-            fi
+            value=`printf '%s\n' "${Jobs[@]}" | jq -R . | jq -cs .`
+            echo "value: $value"
+            echo "matrix=$value" >> $GITHUB_OUTPUT
           elif [ "${{github.event_name}}" == "workflow_dispatch"  ]; then
-            test_file="\"${{github.event.inputs.test_file}}\""
-            echo "test file is $test_file"
-            echo "::set-output name=test_file_matrix::[$test_file]"
+            test_file_name=${{github.event.inputs.test_file}}
+            echo "test file is $test_file_name"
+            job_total=${{github.event.inputs.job_total}}
+            echo "job_total is $job_total"
+            declare -a Jobs=();
+            for i in `seq 1 $job_total` 
+              do
+                Jobs=("${Jobs[@]}" "$test_file_name-$i-$job_total")
+              done
+            value=`printf '%s\n' "${Jobs[@]}" | jq -R . | jq -cs .`
+            echo "value: $value"
+            echo "matrix=$value" >> $GITHUB_OUTPUT
           fi
 
     outputs:
-      test_file: ${{ steps.set-matrix.outputs.test_file_matrix }}
+      matrix: ${{ steps.set-matrix.outputs.matrix }}
 
   mutate-test:
     name: ${{matrix.test_file}}
@@ -65,7 +106,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        test_file: ${{ fromJson(needs.build-matrix.outputs.test_file) }}
+        test_file: ${{ fromJson(needs.build-matrix.outputs.matrix) }}
     runs-on: ubuntu-latest
     permissions:
       pull-requests: write
@@ -79,6 +120,14 @@ jobs:
           github_token: ${{ secrets.GITHUB_TOKEN }}
           job_name: ${{matrix.test_file}}
 
+      - name: Set Current Job Log URL to Env
+        run: |
+          job_url_id=$(echo job_url_${{matrix.test_file}} | sed -e 's:/:_:g' | sed -e 's:\.:_:g' | sed -e 's:-:_:g' )
+          echo "job_url_id is $job_url_id"
+          echo "job url is : ${{ steps.jobs.outputs.html_url }}"
+          echo "$job_url_id=${{ steps.jobs.outputs.html_url }}" >> $GITHUB_OUTPUT
+          echo $GITHUB_OUTPUT
+          
       - name: Build
         uses: ./.github/actions/build
 
@@ -92,6 +141,7 @@ jobs:
           sudo apt install -y redis-server
           sudo apt install -y libacl1-dev
           sudo apt install -y attr
+          sudo apt install python3-tk
           sudo mkdir -p /home/travis/.m2/
 
       - name: Download Zip
@@ -107,7 +157,6 @@ jobs:
           cd  /home/travis/.m2/litmus-0.13/ && ./configure && make && cd -
           tar -zxf /home/travis/.m2/etcd-v3.5.2-linux-amd64.tar.gz -C /home/travis/.m2/
 
-
       - name: Prepare Database
         timeout-minutes: 10
         run: |
@@ -143,13 +192,14 @@ jobs:
           for i in {2222,3306,5432,8020,9000,9007,4500} ; do echo "lsof port:"$i;sudo lsof -i :$i;if [ $? != 0 ];then sleep 5; else continue; fi;sudo lsof -i :$i; if [ $? != 0 ];then echo "service not ready, port:"$i; exit 1;fi; done
           sudo lsof -i :2379;if [ $? != 0 ];then echo "tikv is not ready";cat output.log;exit 1; fi
 
-
       - name: run mutate test
         # timeout-minutes: 120
         run: |
           sudo chmod 777 /var/jfsCache
-          test_file=${{matrix.test_file}}
-          echo "test file is :" $test_file
+          test_file=$(echo ${{matrix.test_file}} | awk -F'-' '{print $1}')
+          job_index=$(echo ${{matrix.test_file}} | awk -F'-' '{print $2}')
+          job_total=$(echo ${{matrix.test_file}} | awk -F'-' '{print $3}')
+          echo "test file is :$test_file, job_index is $job_index, job_total is $job_total"
           if [ -z "$test_file" ]; then 
             echo "test file is empty, will not run mutate test"
             exit 0
@@ -179,11 +229,25 @@ jobs:
           TEST_FILE_NAME="$test_file" BLACK_LIST_FILE=$black_list_file python3 .github/scripts/parse_black_list.py 
           echo "black list checksum: "
           cat $black_list_file
-          log_file=$(basename ${{matrix.test_file}}).log
-          COVERAGE_FILE=mutest-cov.out TEST_FILE_NAME="$test_file" PACKAGE_PATH="$package_path" go-mutesting $source_file --debug --exec=.github/scripts/mutest.sh  --do-not-remove-tmp-folder --blacklist $black_list_file | tee -a $log_file
-          mutate_report=`LOG_FILE=$log_file python3 .github/scripts/parse_mutate_log.py`
-          echo "mutate_report is $mutate_report"
+
+          go-mutesting $source_file --debug --no-exec --do-not-remove-tmp-folder  --blacklist $black_list_file | tee -a mutate.log
+          mutation_dir=$(cat mutate.log | grep "Save mutations into" | awk -F' ' '{print $4}' | sed -e 's:"::g')
+          echo "mutation dir is $mutation_dir"
+          JOB_INDEX=$job_index JOB_TOTAL=$job_total MUTATE_ORIGINAL=$source_file MUTATION_DIR=$mutation_dir COVERAGE_FILE=mutest-cov.out TEST_FILE_NAME="$test_file" PACKAGE_PATH="$package_path" python3 .github/scripts/mutesting.py | tee -a mutesting.log
+          if [ $? != 0 ]; then echo "run mutesting.py failed" && exit 1; fi
+          mutate_report=$(tail -1 mutesting.log)
+          echo "mutate_report: $mutate_report"
           echo "mutate_report=$mutate_report" >> $GITHUB_ENV
+          
+          # report_id=$(echo mutate_report_${{matrix.test_file}} | sed -e 's:/:_:g' | sed -e 's:\.:_:g' | sed -e 's:-:_:g' )
+          # echo "report_id is $report_id"
+          # echo "$report_id=$mutate_report" >> $GITHUB_ENV
+
+          # log_file=$(basename ${{matrix.test_file}}).log
+          # COVERAGE_FILE=mutest-cov.out TEST_FILE_NAME="$test_file" PACKAGE_PATH="$package_path" go-mutesting $source_file --debug --exec=.github/scripts/mutest.sh  --do-not-remove-tmp-folder --blacklist $black_list_file | tee -a $log_file
+          # mutate_report=`LOG_FILE=$log_file python3 .github/scripts/parse_mutate_log.py`
+          # echo "mutate_report is $mutate_report"
+          # echo "mutate_report=$mutate_report" >> $GITHUB_ENV
 
       - uses: mshick/add-pr-comment@v2
         with:
@@ -194,6 +258,32 @@ jobs:
             Report detail: ${{ steps.jobs.outputs.html_url }}
             Usage: https://github.com/juicedata/juicefs/blob/main/.github/scripts/how_to_use_mutate_test.md  
 
+      - name: Setup upterm session
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
+        timeout-minutes: 60
+        uses: lhotari/action-upterm@v1
+
+  success-all-test:
+    runs-on: ubuntu-latest
+    needs: [build-matrix, mutate-test]
+    if: always()
+    steps:
+      - uses: technote-space/workflow-conclusion-action@v2
+      # - name: print
+      #   run: |
+      #     files=($(echo ${{ needs.build-matrix.outputs.matrix }} | sed -e 's:"::g' | sed -e 's:\[::g' | sed -e 's:\]::g' | sed -e 's:,: :g'))
+      #     echo ${files[@]}
+      #     echo "job url is ${{needs.mutate-test.outputs.job_url_pkg_meta_config_test_go_1}}"
+      #     for file in ${files[@]}; 
+      #     do
+      #       echo "file is $file"
+      #       job_url_id=$(echo job_url_$file | sed -e 's:/:_:g' | sed -e 's:\.:_:g' | sed -e 's:-:_:g' )
+      #       echo "job_url_id is $job_url_id"
+      #     done
+      - name: Check Failure
+        if: env.WORKFLOW_CONCLUSION == 'failure'
+        run: exit 1
+
       - name: Send Slack Notification
         if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
         uses: juicedata/slack-notify-action@main
@@ -201,7 +291,7 @@ jobs:
           channel-id: "${{ secrets.SLACK_CHANNEL_ID_FOR_PR_CHECK_NOTIFY }}"
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"
 
-      - name: Setup upterm session
-        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug }}
-        timeout-minutes: 60
-        uses: lhotari/action-upterm@v1
+      - name: Success
+        if: ${{ success() }}
+        run: echo "All Done"
+
diff --git a/.github/workflows/pjdfstest.yml b/.github/workflows/pjdfstest.yml
index c834ac6e..5a0472bb 100644
--- a/.github/workflows/pjdfstest.yml
+++ b/.github/workflows/pjdfstest.yml
@@ -140,6 +140,6 @@ jobs:
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
 
       - name: Setup upterm session
-        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
         timeout-minutes: 60
         uses: lhotari/action-upterm@v1
\ No newline at end of file
diff --git a/.github/workflows/sdktest.yml b/.github/workflows/sdktest.yml
index d6608954..0cf7fa28 100644
--- a/.github/workflows/sdktest.yml
+++ b/.github/workflows/sdktest.yml
@@ -18,7 +18,12 @@ on:
       - '**.md'
       - '.github/**'
   workflow_dispatch:
-
+    inputs:
+      debug:
+        type: boolean
+        description: "Run the build with tmate debugging enabled"
+        required: false
+        default: false
 
 jobs:
   sdktest:
@@ -63,5 +68,6 @@ jobs:
           slack_bot_token: "${{ secrets.SLACK_BOT_TOKEN }}"  
 
       - name: Setup upterm session
-        if: ${{ failure() }}
+        if: ${{ failure() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug == 'true' }}
+        timeout-minutes: 60
         uses: lhotari/action-upterm@v1
diff --git a/cmd/config_test.go b/cmd/config_test.go
index 31c363cd..e139c317 100644
--- a/cmd/config_test.go
+++ b/cmd/config_test.go
@@ -25,6 +25,7 @@ import (
 	"github.com/juicedata/juicefs/pkg/meta"
 )
 
+//mutate_test_job_number: 3
 func getStdout(args []string) ([]byte, error) {
 	tmp, err := os.CreateTemp("/tmp", "jfstest-*")
 	if err != nil {
diff --git a/docs/en/benchmark/mdtest.md b/docs/en/benchmark/mdtest.md
index 91bb99bd..450c01a3 100644
--- a/docs/en/benchmark/mdtest.md
+++ b/docs/en/benchmark/mdtest.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: Benchmark with mdtest
+title: Benchmark with mdtest
 sidebar_position: 8
 slug: /mdtest
 ---
-# Benchmark with mdtest
 
 :::tip
 Trash is enabled in JuiceFS v1.0+ by default. As a result, temporary files are created and deleted in the file system during the benchmark, and these files will be eventually dumped into a directory named `.trash`. To avoid storage space being occupied by `.trash`, you can run command `juicefs config META-URL --trash-days 0` to disable Trash before benchmark. See [trash](../security/trash.md) for details.
diff --git a/docs/zh_cn/benchmark/mdtest.md b/docs/zh_cn/benchmark/mdtest.md
index 9bb08a6c..9d943127 100644
--- a/docs/zh_cn/benchmark/mdtest.md
+++ b/docs/zh_cn/benchmark/mdtest.md
@@ -1,9 +1,8 @@
 ---
-sidebar_label: mdtest 基准测试
+title: mdtest 基准测试
 sidebar_position: 8
 slug: /mdtest
 ---
-# mdtest 基准测试
 
 :::tip 提示
 JuiceFS v1.0+ 默认启用了回收站，基准测试会在文件系统中创建和删除临时文件，这些文件最终会被转存到回收站 `.trash` 占用存储空间，为了避免这种情况，可以在基准测试之前关闭回收站 `juicefs config META-URL --trash-days 0`，详情参考[回收站](../security/trash.md)。
diff --git a/pkg/fs/fs_test.go b/pkg/fs/fs_test.go
index 6430715d..23412479 100644
--- a/pkg/fs/fs_test.go
+++ b/pkg/fs/fs_test.go
@@ -30,6 +30,7 @@ import (
 	"github.com/juicedata/juicefs/pkg/vfs"
 )
 
+//mutate_test_job_number: 5
 func TestFileStat(t *testing.T) {
 	attr := meta.Attr{
 		Typ:   meta.TypeDirectory,
diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go
index 35655e5b..8e2ab054 100644
--- a/pkg/meta/base_test.go
+++ b/pkg/meta/base_test.go
@@ -64,6 +64,7 @@ func testMeta(t *testing.T, m Meta) {
 	testRemove(t, m)
 	testStickyBit(t, m)
 	testLocks(t, m)
+	testListLocks(t, m)
 	testConcurrentWrite(t, m)
 	testCompaction(t, m, false)
 	time.Sleep(time.Second)
@@ -596,6 +597,81 @@ func testStickyBit(t *testing.T, m Meta) {
 	}
 }
 
+func testListLocks(t *testing.T, m Meta) {
+	_ = m.Init(&Format{Name: "test"}, false)
+	ctx := Background
+	var inode Ino
+	var attr = &Attr{}
+	defer m.Unlink(ctx, 1, "f")
+	if st := m.Create(ctx, 1, "f", 0644, 0, 0, &inode, attr); st != 0 {
+		t.Fatalf("create f: %s", st)
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+
+	// flock
+	o1 := uint64(0xF000000000000001)
+	if st := m.Flock(ctx, inode, o1, syscall.F_WRLCK, false); st != 0 {
+		t.Fatalf("flock wlock: %s", st)
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 1 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+	if st := m.Flock(ctx, inode, o1, syscall.F_UNLCK, false); st != 0 {
+		t.Fatalf("flock unlock: %s", st)
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+	for i := 2; i < 10; i++ {
+		if st := m.Flock(ctx, inode, uint64(i), syscall.F_RDLCK, false); st != 0 {
+			t.Fatalf("flock wlock: %s", st)
+		}
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 8 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+	for i := 2; i < 10; i++ {
+		if st := m.Flock(ctx, inode, uint64(i), syscall.F_UNLCK, false); st != 0 {
+			t.Fatalf("flock unlock: %s", st)
+		}
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+
+	// plock
+	if st := m.Setlk(ctx, inode, o1, false, syscall.F_WRLCK, 0, 0xFFFF, 1); st != 0 {
+		t.Fatalf("plock rlock: %s", st)
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 1 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+	if st := m.Setlk(ctx, inode, o1, false, syscall.F_UNLCK, 0, 0xFFFF, 1); st != 0 {
+		t.Fatalf("plock unlock: %s", st)
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+	for i := 2; i < 10; i++ {
+		if st := m.Setlk(ctx, inode, uint64(i), false, syscall.F_RDLCK, 0, 0xFFFF, 1); st != 0 {
+			t.Fatalf("plock rlock: %s", st)
+		}
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 8 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+	for i := 2; i < 10; i++ {
+		if st := m.Setlk(ctx, inode, uint64(i), false, syscall.F_UNLCK, 0, 0xFFFF, 1); st != 0 {
+			t.Fatalf("plock unlock: %s", st)
+		}
+	}
+	if plocks, flocks, err := m.ListLocks(ctx, inode); err != nil || len(plocks) != 0 || len(flocks) != 0 {
+		t.Fatalf("list locks: %v %v %v", plocks, flocks, err)
+	}
+}
+
 func testLocks(t *testing.T, m Meta) {
 	_ = m.Init(&Format{Name: "test"}, false)
 	ctx := Background
diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go
index 870047e3..280cd068 100644
--- a/pkg/utils/utils_test.go
+++ b/pkg/utils/utils_test.go
@@ -81,6 +81,6 @@ func TestTimeout(t *testing.T) {
 		return nil
 	}, time.Millisecond*10)
 	if err == nil || !strings.HasPrefix(err.Error(), "timeout after") {
-		t.Fatalf("slow function should be  timeout: %s", err)
+		t.Fatalf("slow function should be timeout: %s", err)
 	}
 }
diff --git a/pkg/vfs/vfs_test.go b/pkg/vfs/vfs_test.go
index de31c07b..1c022f02 100644
--- a/pkg/vfs/vfs_test.go
+++ b/pkg/vfs/vfs_test.go
@@ -17,6 +17,7 @@
 package vfs
 
 import (
+	"bytes"
 	"fmt"
 	"log"
 	"reflect"
@@ -737,21 +738,40 @@ func TestInternalFile(t *testing.T) {
 	} else {
 		off += uint64(n)
 	}
-	// info
+	// legacy info
 	buf = make([]byte, 4+4+8)
 	w = utils.FromBuffer(buf)
-	w.Put32(meta.Info)
+	w.Put32(meta.LegacyInfo)
 	w.Put32(8)
 	w.Put64(1)
 	if e := v.Write(ctx, fe.Inode, w.Bytes(), off, fh); e != 0 {
-		t.Fatalf("write info: %s", e)
+		t.Fatalf("write legacy info: %s", e)
 	}
 	off += uint64(len(buf))
 	buf = make([]byte, 1024*10)
 	if n, e = readControl(buf, &off); e != 0 {
 		t.Fatalf("read result: %s %d", e, n)
 	} else if !strings.Contains(string(buf[:n]), "dirs:") {
-		t.Fatalf("info result: %s", string(buf[:n]))
+		t.Fatalf("legacy info result: %s", string(buf[:n]))
+	} else {
+		off += uint64(n)
+	}
+	// info v2
+	buf = make([]byte, 4+4+8)
+	w = utils.FromBuffer(buf)
+	w.Put32(meta.InfoV2)
+	w.Put32(8)
+	w.Put64(1)
+	if e := v.Write(ctx, fe.Inode, w.Bytes(), off, fh); e != 0 {
+		t.Fatalf("write info v2: %s", e)
+	}
+	off += uint64(len(buf))
+	buf = make([]byte, 1024*10)
+	var infoResp InfoResponse
+	if n, e = readControl(buf, &off); e != 0 {
+		t.Fatalf("read result: %s %d", e, n)
+	} else if infoResp.Decode(bytes.NewBuffer(buf[:n])) != nil {
+		t.Fatalf("info v2 result: %s", string(buf[:n]))
 	} else {
 		off += uint64(n)
 	}