feat: add syncthing debug database-statistics command (#10117)
This adds a command that shows database statistics. Currently it requires a fork of the sqlite package to add the dbstats virtual table; the modernc variant already has it. This also provides the canonical mapping between folder ID and database file, for tinkerers... ``` % ./bin/syncthing debug database-statistics DATABASE FOLDER ID TABLE SIZE FILL ======== ====== == ===== ==== ==== main.db - folders 4 KiB 8.4 % main.db - folders_database_name 4 KiB 6.0 % main.db - kv 4 KiB 41.1 % main.db - schemamigrations 4 KiB 3.9 % main.db - sqlite_autoindex_folders_1 4 KiB 3.7 % ... folder.0007-txpxsvyd.db w3ejt-fn4dm indexids 4 KiB 1.5 % folder.0007-txpxsvyd.db w3ejt-fn4dm kv 4 KiB 0.8 % folder.0007-txpxsvyd.db w3ejt-fn4dm mtimes 608 KiB 81.5 % folder.0007-txpxsvyd.db w3ejt-fn4dm schemamigrations 4 KiB 3.9 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_blocklists_1 4108 KiB 89.5 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_blocks_1 700020 KiB 88.1 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_devices_1 4 KiB 3.6 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_autoindex_kv_1 4 KiB 0.6 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_schema 12 KiB 45.9 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_sequence 4 KiB 1.0 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_stat1 4 KiB 12.2 % folder.0007-txpxsvyd.db w3ejt-fn4dm sqlite_stat4 4 KiB 0.2 % folder.0007-txpxsvyd.db w3ejt-fn4dm (total) 1906020 KiB 92.8 % main.db + children - (total) 2205888 KiB 92.0 % ```
This commit is contained in:
+44
-20
@@ -8,6 +8,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
@@ -37,6 +39,7 @@ import (
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/generate"
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syncthing/syncthing/internal/db/sqlite"
|
||||
_ "github.com/syncthing/syncthing/lib/automaxprocs"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
@@ -825,24 +828,6 @@ func exitCodeForUpgrade(err error) int {
|
||||
return svcutil.ExitError.AsInt()
|
||||
}
|
||||
|
||||
// convertLegacyArgs returns the slice of arguments with single dash long
|
||||
// flags converted to double dash long flags.
|
||||
func convertLegacyArgs(args []string) []string {
|
||||
// Legacy args begin with a single dash, followed by two or more characters.
|
||||
legacyExp := regexp.MustCompile(`^-\w{2,}`)
|
||||
|
||||
res := make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
if legacyExp.MatchString(arg) {
|
||||
res[i] = "-" + arg
|
||||
} else {
|
||||
res[i] = arg
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type versionCmd struct{}
|
||||
|
||||
func (versionCmd) Run() error {
|
||||
@@ -900,7 +885,8 @@ func (u upgradeCmd) Run() error {
|
||||
release, err := checkUpgrade()
|
||||
if err == nil {
|
||||
lf := flock.New(locations.Get(locations.LockFile))
|
||||
locked, err := lf.TryLock()
|
||||
var locked bool
|
||||
locked, err = lf.TryLock()
|
||||
if err != nil {
|
||||
l.Warnln("Upgrade:", err)
|
||||
os.Exit(1)
|
||||
@@ -930,7 +916,8 @@ func (browserCmd) Run() error {
|
||||
}
|
||||
|
||||
type debugCmd struct {
|
||||
ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"`
|
||||
ResetDatabase resetDatabaseCmd `cmd:"" help:"Reset the database, forcing a full rescan and resync"`
|
||||
DatabaseStatistics databaseStatsCmd `cmd:"" help:"Display database size statistics"`
|
||||
}
|
||||
|
||||
type resetDatabaseCmd struct{}
|
||||
@@ -945,6 +932,43 @@ func (resetDatabaseCmd) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type databaseStatsCmd struct{}
|
||||
|
||||
func (c databaseStatsCmd) Run() error {
|
||||
db, err := sqlite.Open(locations.Get(locations.Database))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ds, err := db.Statistics()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 2, 2, 2, ' ', 0)
|
||||
hdr := fmt.Sprintf("%s\t%s\t%s\t%12s\t%7s\n", "DATABASE", "FOLDER ID", "TABLE", "SIZE", "FILL")
|
||||
fmt.Fprint(tw, hdr)
|
||||
fmt.Fprint(tw, regexp.MustCompile(`[A-Z]`).ReplaceAllString(hdr, "="))
|
||||
c.printStat(tw, ds)
|
||||
return tw.Flush()
|
||||
}
|
||||
|
||||
func (c databaseStatsCmd) printStat(w io.Writer, s *sqlite.DatabaseStatistics) {
|
||||
for _, table := range s.Tables {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%8d KiB\t%5.01f %%\n", s.Name, cmp.Or(s.FolderID, "-"), table.Name, table.Size/1024, float64(table.Size-table.Unused)*100/float64(table.Size))
|
||||
}
|
||||
for _, next := range s.Children {
|
||||
c.printStat(w, &next)
|
||||
s.Total.Size += next.Total.Size
|
||||
s.Total.Unused += next.Total.Unused
|
||||
}
|
||||
|
||||
totalName := s.Name
|
||||
if len(s.Children) > 0 {
|
||||
totalName += " + children"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%8d KiB\t%5.01f %%\n", totalName, cmp.Or(s.FolderID, "-"), "(total)", s.Total.Size/1024, float64(s.Total.Size-s.Total.Unused)*100/float64(s.Total.Size))
|
||||
}
|
||||
|
||||
func setConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error {
|
||||
homeSet := homeDir != ""
|
||||
confSet := confDir != ""
|
||||
|
||||
Reference in New Issue
Block a user