LCOV - code coverage report
Current view: top level - pebble/tool - db.go (source / functions) Hit Total Coverage
Test: 2024-04-03 08:16Z 65d5ba68 - tests only.lcov Lines: 520 601 86.5 %
Date: 2024-04-03 08:16:43 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : package tool
       6             : 
       7             : import (
       8             :         "context"
       9             :         "fmt"
      10             :         "io"
      11             :         "text/tabwriter"
      12             : 
      13             :         "github.com/cockroachdb/errors"
      14             :         "github.com/cockroachdb/errors/oserror"
      15             :         "github.com/cockroachdb/pebble"
      16             :         "github.com/cockroachdb/pebble/internal/base"
      17             :         "github.com/cockroachdb/pebble/internal/humanize"
      18             :         "github.com/cockroachdb/pebble/internal/manifest"
      19             :         "github.com/cockroachdb/pebble/objstorage"
      20             :         "github.com/cockroachdb/pebble/objstorage/objstorageprovider"
      21             :         "github.com/cockroachdb/pebble/record"
      22             :         "github.com/cockroachdb/pebble/sstable"
      23             :         "github.com/cockroachdb/pebble/tool/logs"
      24             :         "github.com/spf13/cobra"
      25             : )
      26             : 
      27             : // dbT implements db-level tools, including both configuration state and the
      28             : // commands themselves.
      29             : type dbT struct {
      30             :         Root       *cobra.Command
      31             :         Check      *cobra.Command
      32             :         Checkpoint *cobra.Command
      33             :         Get        *cobra.Command
      34             :         Logs       *cobra.Command
      35             :         LSM        *cobra.Command
      36             :         Properties *cobra.Command
      37             :         Scan       *cobra.Command
      38             :         Set        *cobra.Command
      39             :         Space      *cobra.Command
      40             :         IOBench    *cobra.Command
      41             : 
      42             :         // Configuration.
      43             :         opts            *pebble.Options
      44             :         comparers       sstable.Comparers
      45             :         mergers         sstable.Mergers
      46             :         openErrEnhancer func(error) error
      47             :         openOptions     []OpenOption
      48             : 
      49             :         // Flags.
      50             :         comparerName  string
      51             :         mergerName    string
      52             :         fmtKey        keyFormatter
      53             :         fmtValue      valueFormatter
      54             :         start         key
      55             :         end           key
      56             :         count         int64
      57             :         allLevels     bool
      58             :         ioCount       int
      59             :         ioParallelism int
      60             :         ioSizes       string
      61             :         verbose       bool
      62             : }
      63             : 
      64             : func newDB(
      65             :         opts *pebble.Options,
      66             :         comparers sstable.Comparers,
      67             :         mergers sstable.Mergers,
      68             :         openErrEnhancer func(error) error,
      69             :         openOptions []OpenOption,
      70           1 : ) *dbT {
      71           1 :         d := &dbT{
      72           1 :                 opts:            opts,
      73           1 :                 comparers:       comparers,
      74           1 :                 mergers:         mergers,
      75           1 :                 openErrEnhancer: openErrEnhancer,
      76           1 :                 openOptions:     openOptions,
      77           1 :         }
      78           1 :         d.fmtKey.mustSet("quoted")
      79           1 :         d.fmtValue.mustSet("[%x]")
      80           1 : 
      81           1 :         d.Root = &cobra.Command{
      82           1 :                 Use:   "db",
      83           1 :                 Short: "DB introspection tools",
      84           1 :         }
      85           1 :         d.Check = &cobra.Command{
      86           1 :                 Use:   "check <dir>",
      87           1 :                 Short: "verify checksums and metadata",
      88           1 :                 Long: `
      89           1 : Verify sstable, manifest, and WAL checksums. Requires that the specified
      90           1 : database not be in use by another process.
      91           1 : `,
      92           1 :                 Args: cobra.ExactArgs(1),
      93           1 :                 Run:  d.runCheck,
      94           1 :         }
      95           1 :         d.Checkpoint = &cobra.Command{
      96           1 :                 Use:   "checkpoint <src-dir> <dest-dir>",
      97           1 :                 Short: "create a checkpoint",
      98           1 :                 Long: `
      99           1 : Creates a Pebble checkpoint in the specified destination directory. A checkpoint
     100           1 : is a point-in-time snapshot of DB state. Requires that the specified
     101           1 : database not be in use by another process.
     102           1 : `,
     103           1 :                 Args: cobra.ExactArgs(2),
     104           1 :                 Run:  d.runCheckpoint,
     105           1 :         }
     106           1 :         d.Get = &cobra.Command{
     107           1 :                 Use:   "get <dir> <key>",
     108           1 :                 Short: "get value for a key",
     109           1 :                 Long: `
     110           1 : Gets a value for a key, if it exists in DB. Prints a "not found" error if key
     111           1 : does not exist. Requires that the specified database not be in use by another
     112           1 : process.
     113           1 : `,
     114           1 :                 Args: cobra.ExactArgs(2),
     115           1 :                 Run:  d.runGet,
     116           1 :         }
     117           1 :         d.Logs = logs.NewCmd()
     118           1 :         d.LSM = &cobra.Command{
     119           1 :                 Use:   "lsm <dir>",
     120           1 :                 Short: "print LSM structure",
     121           1 :                 Long: `
     122           1 : Print the structure of the LSM tree. Requires that the specified database not
     123           1 : be in use by another process.
     124           1 : `,
     125           1 :                 Args: cobra.ExactArgs(1),
     126           1 :                 Run:  d.runLSM,
     127           1 :         }
     128           1 :         d.Properties = &cobra.Command{
     129           1 :                 Use:   "properties <dir>",
     130           1 :                 Short: "print aggregated sstable properties",
     131           1 :                 Long: `
     132           1 : Print SSTable properties, aggregated per level of the LSM.
     133           1 : `,
     134           1 :                 Args: cobra.ExactArgs(1),
     135           1 :                 Run:  d.runProperties,
     136           1 :         }
     137           1 :         d.Scan = &cobra.Command{
     138           1 :                 Use:   "scan <dir>",
     139           1 :                 Short: "print db records",
     140           1 :                 Long: `
     141           1 : Print the records in the DB. Requires that the specified database not be in use
     142           1 : by another process.
     143           1 : `,
     144           1 :                 Args: cobra.ExactArgs(1),
     145           1 :                 Run:  d.runScan,
     146           1 :         }
     147           1 :         d.Set = &cobra.Command{
     148           1 :                 Use:   "set <dir> <key> <value>",
     149           1 :                 Short: "set a value for a key",
     150           1 :                 Long: `
     151           1 : Adds a new key/value to the DB. Requires that the specified database
     152           1 : not be in use by another process.
     153           1 : `,
     154           1 :                 Args: cobra.ExactArgs(3),
     155           1 :                 Run:  d.runSet,
     156           1 :         }
     157           1 :         d.Space = &cobra.Command{
     158           1 :                 Use:   "space <dir>",
     159           1 :                 Short: "print filesystem space used",
     160           1 :                 Long: `
     161           1 : Print the estimated filesystem space usage for the inclusive-inclusive range
     162           1 : specified by --start and --end. Requires that the specified database not be in
     163           1 : use by another process.
     164           1 : `,
     165           1 :                 Args: cobra.ExactArgs(1),
     166           1 :                 Run:  d.runSpace,
     167           1 :         }
     168           1 :         d.IOBench = &cobra.Command{
     169           1 :                 Use:   "io-bench <dir>",
     170           1 :                 Short: "perform sstable IO benchmark",
     171           1 :                 Long: `
     172           1 : Run a random IO workload with various IO sizes against the sstables in the
     173           1 : specified database.
     174           1 : `,
     175           1 :                 Args: cobra.ExactArgs(1),
     176           1 :                 Run:  d.runIOBench,
     177           1 :         }
     178           1 : 
     179           1 :         d.Root.AddCommand(d.Check, d.Checkpoint, d.Get, d.Logs, d.LSM, d.Properties, d.Scan, d.Set, d.Space, d.IOBench)
     180           1 :         d.Root.PersistentFlags().BoolVarP(&d.verbose, "verbose", "v", false, "verbose output")
     181           1 : 
     182           1 :         for _, cmd := range []*cobra.Command{d.Check, d.Checkpoint, d.Get, d.LSM, d.Properties, d.Scan, d.Set, d.Space} {
     183           1 :                 cmd.Flags().StringVar(
     184           1 :                         &d.comparerName, "comparer", "", "comparer name (use default if empty)")
     185           1 :                 cmd.Flags().StringVar(
     186           1 :                         &d.mergerName, "merger", "", "merger name (use default if empty)")
     187           1 :         }
     188             : 
     189           1 :         for _, cmd := range []*cobra.Command{d.Scan, d.Space} {
     190           1 :                 cmd.Flags().Var(
     191           1 :                         &d.start, "start", "start key for the range")
     192           1 :                 cmd.Flags().Var(
     193           1 :                         &d.end, "end", "end key for the range")
     194           1 :         }
     195             : 
     196           1 :         d.Scan.Flags().Var(
     197           1 :                 &d.fmtKey, "key", "key formatter")
     198           1 :         for _, cmd := range []*cobra.Command{d.Scan, d.Get} {
     199           1 :                 cmd.Flags().Var(
     200           1 :                         &d.fmtValue, "value", "value formatter")
     201           1 :         }
     202             : 
     203           1 :         d.Scan.Flags().Int64Var(
     204           1 :                 &d.count, "count", 0, "key count for scan (0 is unlimited)")
     205           1 : 
     206           1 :         d.IOBench.Flags().BoolVar(
     207           1 :                 &d.allLevels, "all-levels", false, "if set, benchmark all levels (default is only L5/L6)")
     208           1 :         d.IOBench.Flags().IntVar(
     209           1 :                 &d.ioCount, "io-count", 10000, "number of IOs (per IO size) to benchmark")
     210           1 :         d.IOBench.Flags().IntVar(
     211           1 :                 &d.ioParallelism, "io-parallelism", 16, "number of goroutines issuing IO")
     212           1 :         d.IOBench.Flags().StringVar(
     213           1 :                 &d.ioSizes, "io-sizes-kb", "4,16,64,128,256,512,1024", "comma separated list of IO sizes in KB")
     214           1 : 
     215           1 :         return d
     216             : }
     217             : 
     218           1 : func (d *dbT) loadOptions(dir string) error {
     219           1 :         ls, err := d.opts.FS.List(dir)
     220           1 :         if err != nil || len(ls) == 0 {
     221           1 :                 // NB: We don't return the error here as we prefer to return the error from
     222           1 :                 // pebble.Open. Another way to put this is that a non-existent directory is
     223           1 :                 // not a failure in loading the options.
     224           1 :                 return nil
     225           1 :         }
     226             : 
     227           1 :         hooks := &pebble.ParseHooks{
     228           1 :                 NewComparer: func(name string) (*pebble.Comparer, error) {
     229           0 :                         if c := d.comparers[name]; c != nil {
     230           0 :                                 return c, nil
     231           0 :                         }
     232           0 :                         return nil, errors.Errorf("unknown comparer %q", errors.Safe(name))
     233             :                 },
     234           0 :                 NewMerger: func(name string) (*pebble.Merger, error) {
     235           0 :                         if m := d.mergers[name]; m != nil {
     236           0 :                                 return m, nil
     237           0 :                         }
     238           0 :                         return nil, errors.Errorf("unknown merger %q", errors.Safe(name))
     239             :                 },
     240           0 :                 SkipUnknown: func(name, value string) bool {
     241           0 :                         return true
     242           0 :                 },
     243             :         }
     244             : 
     245             :         // TODO(peter): RocksDB sometimes leaves multiple OPTIONS files in
     246             :         // existence. We parse all of them as the comparer and merger shouldn't be
     247             :         // changing. We could parse only the first or the latest. Not clear if this
     248             :         // matters.
     249           1 :         var dbOpts pebble.Options
     250           1 :         for _, filename := range ls {
     251           1 :                 ft, _, ok := base.ParseFilename(d.opts.FS, filename)
     252           1 :                 if !ok {
     253           1 :                         continue
     254             :                 }
     255           1 :                 switch ft {
     256           1 :                 case base.FileTypeOptions:
     257           1 :                         err := func() error {
     258           1 :                                 f, err := d.opts.FS.Open(d.opts.FS.PathJoin(dir, filename))
     259           1 :                                 if err != nil {
     260           0 :                                         return err
     261           0 :                                 }
     262           1 :                                 defer f.Close()
     263           1 : 
     264           1 :                                 data, err := io.ReadAll(f)
     265           1 :                                 if err != nil {
     266           0 :                                         return err
     267           0 :                                 }
     268             : 
     269           1 :                                 if err := dbOpts.Parse(string(data), hooks); err != nil {
     270           1 :                                         return err
     271           1 :                                 }
     272           1 :                                 return nil
     273             :                         }()
     274           1 :                         if err != nil {
     275           1 :                                 return err
     276           1 :                         }
     277             :                 }
     278             :         }
     279             : 
     280           1 :         if dbOpts.Comparer != nil {
     281           1 :                 d.opts.Comparer = dbOpts.Comparer
     282           1 :         }
     283           1 :         if dbOpts.Merger != nil {
     284           1 :                 d.opts.Merger = dbOpts.Merger
     285           1 :         }
     286           1 :         return nil
     287             : }
     288             : 
     289             : // OpenOption is an option that may be applied to the *pebble.Options before
     290             : // calling pebble.Open.
     291             : type OpenOption interface {
     292             :         Apply(dirname string, opts *pebble.Options)
     293             : }
     294             : 
     295           1 : func (d *dbT) openDB(dir string, openOptions ...OpenOption) (*pebble.DB, error) {
     296           1 :         db, err := d.openDBInternal(dir, openOptions...)
     297           1 :         if err != nil {
     298           1 :                 if d.openErrEnhancer != nil {
     299           1 :                         err = d.openErrEnhancer(err)
     300           1 :                 }
     301           1 :                 return nil, err
     302             :         }
     303           1 :         return db, nil
     304             : }
     305             : 
     306           1 : func (d *dbT) openDBInternal(dir string, openOptions ...OpenOption) (*pebble.DB, error) {
     307           1 :         if err := d.loadOptions(dir); err != nil {
     308           1 :                 return nil, errors.Wrap(err, "error loading options")
     309           1 :         }
     310           1 :         if d.comparerName != "" {
     311           1 :                 d.opts.Comparer = d.comparers[d.comparerName]
     312           1 :                 if d.opts.Comparer == nil {
     313           1 :                         return nil, errors.Errorf("unknown comparer %q", errors.Safe(d.comparerName))
     314           1 :                 }
     315             :         }
     316           1 :         if d.mergerName != "" {
     317           1 :                 d.opts.Merger = d.mergers[d.mergerName]
     318           1 :                 if d.opts.Merger == nil {
     319           1 :                         return nil, errors.Errorf("unknown merger %q", errors.Safe(d.mergerName))
     320           1 :                 }
     321             :         }
     322           1 :         opts := *d.opts
     323           1 :         for _, opt := range openOptions {
     324           1 :                 opt.Apply(dir, &opts)
     325           1 :         }
     326           1 :         for _, opt := range d.openOptions {
     327           0 :                 opt.Apply(dir, &opts)
     328           0 :         }
     329           1 :         opts.Cache = pebble.NewCache(128 << 20 /* 128 MB */)
     330           1 :         defer opts.Cache.Unref()
     331           1 :         return pebble.Open(dir, &opts)
     332             : }
     333             : 
     334           1 : func (d *dbT) closeDB(stderr io.Writer, db *pebble.DB) {
     335           1 :         if err := db.Close(); err != nil {
     336           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     337           0 :         }
     338             : }
     339             : 
     340           1 : func (d *dbT) runCheck(cmd *cobra.Command, args []string) {
     341           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
     342           1 :         db, err := d.openDB(args[0])
     343           1 :         if err != nil {
     344           1 :                 fmt.Fprintf(stderr, "%s\n", err)
     345           1 :                 return
     346           1 :         }
     347           1 :         defer d.closeDB(stderr, db)
     348           1 : 
     349           1 :         var stats pebble.CheckLevelsStats
     350           1 :         if err := db.CheckLevels(&stats); err != nil {
     351           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     352           0 :         }
     353           1 :         fmt.Fprintf(stdout, "checked %d %s and %d %s\n",
     354           1 :                 stats.NumPoints, makePlural("point", stats.NumPoints), stats.NumTombstones, makePlural("tombstone", int64(stats.NumTombstones)))
     355             : }
     356             : 
     357             : type nonReadOnly struct{}
     358             : 
     359           1 : func (n nonReadOnly) Apply(dirname string, opts *pebble.Options) {
     360           1 :         opts.ReadOnly = false
     361           1 :         // Increase the L0 compaction threshold to reduce the likelihood of an
     362           1 :         // unintended compaction changing test output.
     363           1 :         opts.L0CompactionThreshold = 10
     364           1 : }
     365             : 
     366           1 : func (d *dbT) runCheckpoint(cmd *cobra.Command, args []string) {
     367           1 :         stderr := cmd.ErrOrStderr()
     368           1 :         db, err := d.openDB(args[0], nonReadOnly{})
     369           1 :         if err != nil {
     370           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     371           0 :                 return
     372           0 :         }
     373           1 :         defer d.closeDB(stderr, db)
     374           1 :         destDir := args[1]
     375           1 : 
     376           1 :         if err := db.Checkpoint(destDir); err != nil {
     377           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     378           0 :         }
     379             : }
     380             : 
     381           1 : func (d *dbT) runGet(cmd *cobra.Command, args []string) {
     382           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
     383           1 :         db, err := d.openDB(args[0])
     384           1 :         if err != nil {
     385           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     386           0 :                 return
     387           0 :         }
     388           1 :         defer d.closeDB(stderr, db)
     389           1 :         var k key
     390           1 :         if err := k.Set(args[1]); err != nil {
     391           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     392           0 :                 return
     393           0 :         }
     394             : 
     395           1 :         val, closer, err := db.Get(k)
     396           1 :         if err != nil {
     397           1 :                 fmt.Fprintf(stderr, "%s\n", err)
     398           1 :                 return
     399           1 :         }
     400           1 :         defer func() {
     401           1 :                 if closer != nil {
     402           1 :                         closer.Close()
     403           1 :                 }
     404             :         }()
     405           1 :         if val != nil {
     406           1 :                 fmt.Fprintf(stdout, "%s\n", d.fmtValue.fn(k, val))
     407           1 :         }
     408             : }
     409             : 
     410           1 : func (d *dbT) runLSM(cmd *cobra.Command, args []string) {
     411           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
     412           1 :         db, err := d.openDB(args[0])
     413           1 :         if err != nil {
     414           1 :                 fmt.Fprintf(stderr, "%s\n", err)
     415           1 :                 return
     416           1 :         }
     417           1 :         defer d.closeDB(stderr, db)
     418           1 : 
     419           1 :         fmt.Fprintf(stdout, "%s", db.Metrics())
     420             : }
     421             : 
     422           1 : func (d *dbT) runScan(cmd *cobra.Command, args []string) {
     423           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
     424           1 :         db, err := d.openDB(args[0])
     425           1 :         if err != nil {
     426           1 :                 fmt.Fprintf(stderr, "%s\n", err)
     427           1 :                 return
     428           1 :         }
     429           1 :         defer d.closeDB(stderr, db)
     430           1 : 
     431           1 :         // Update the internal formatter if this comparator has one specified.
     432           1 :         if d.opts.Comparer != nil {
     433           1 :                 d.fmtKey.setForComparer(d.opts.Comparer.Name, d.comparers)
     434           1 :                 d.fmtValue.setForComparer(d.opts.Comparer.Name, d.comparers)
     435           1 :         }
     436             : 
     437           1 :         start := timeNow()
     438           1 :         fmtKeys := d.fmtKey.spec != "null"
     439           1 :         fmtValues := d.fmtValue.spec != "null"
     440           1 :         var count int64
     441           1 : 
     442           1 :         iter, _ := db.NewIter(&pebble.IterOptions{
     443           1 :                 UpperBound: d.end,
     444           1 :         })
     445           1 :         for valid := iter.SeekGE(d.start); valid; valid = iter.Next() {
     446           1 :                 if fmtKeys || fmtValues {
     447           1 :                         needDelimiter := false
     448           1 :                         if fmtKeys {
     449           1 :                                 fmt.Fprintf(stdout, "%s", d.fmtKey.fn(iter.Key()))
     450           1 :                                 needDelimiter = true
     451           1 :                         }
     452           1 :                         if fmtValues {
     453           1 :                                 if needDelimiter {
     454           1 :                                         stdout.Write([]byte{' '})
     455           1 :                                 }
     456           1 :                                 fmt.Fprintf(stdout, "%s", d.fmtValue.fn(iter.Key(), iter.Value()))
     457             :                         }
     458           1 :                         stdout.Write([]byte{'\n'})
     459             :                 }
     460             : 
     461           1 :                 count++
     462           1 :                 if d.count > 0 && count >= d.count {
     463           1 :                         break
     464             :                 }
     465             :         }
     466             : 
     467           1 :         if err := iter.Close(); err != nil {
     468           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     469           0 :         }
     470             : 
     471           1 :         elapsed := timeNow().Sub(start)
     472           1 : 
     473           1 :         fmt.Fprintf(stdout, "scanned %d %s in %0.1fs\n",
     474           1 :                 count, makePlural("record", count), elapsed.Seconds())
     475             : }
     476             : 
     477           1 : func (d *dbT) runSpace(cmd *cobra.Command, args []string) {
     478           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
     479           1 :         db, err := d.openDB(args[0])
     480           1 :         if err != nil {
     481           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     482           0 :                 return
     483           0 :         }
     484           1 :         defer d.closeDB(stdout, db)
     485           1 : 
     486           1 :         bytes, err := db.EstimateDiskUsage(d.start, d.end)
     487           1 :         if err != nil {
     488           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     489           0 :                 return
     490           0 :         }
     491           1 :         fmt.Fprintf(stdout, "%d\n", bytes)
     492             : }
     493             : 
     494           1 : func (d *dbT) runProperties(cmd *cobra.Command, args []string) {
     495           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
     496           1 :         dirname := args[0]
     497           1 :         err := func() error {
     498           1 :                 desc, err := pebble.Peek(dirname, d.opts.FS)
     499           1 :                 if err != nil {
     500           1 :                         return err
     501           1 :                 } else if !desc.Exists {
     502           0 :                         return oserror.ErrNotExist
     503           0 :                 }
     504           1 :                 manifestFilename := d.opts.FS.PathBase(desc.ManifestFilename)
     505           1 : 
     506           1 :                 // Replay the manifest to get the current version.
     507           1 :                 f, err := d.opts.FS.Open(desc.ManifestFilename)
     508           1 :                 if err != nil {
     509           0 :                         return errors.Wrapf(err, "pebble: could not open MANIFEST file %q", manifestFilename)
     510           0 :                 }
     511           1 :                 defer f.Close()
     512           1 : 
     513           1 :                 cmp := base.DefaultComparer
     514           1 :                 var bve manifest.BulkVersionEdit
     515           1 :                 bve.AddedByFileNum = make(map[base.FileNum]*manifest.FileMetadata)
     516           1 :                 rr := record.NewReader(f, 0 /* logNum */)
     517           1 :                 for {
     518           1 :                         r, err := rr.Next()
     519           1 :                         if err == io.EOF {
     520           1 :                                 break
     521             :                         }
     522           1 :                         if err != nil {
     523           0 :                                 return errors.Wrapf(err, "pebble: reading manifest %q", manifestFilename)
     524           0 :                         }
     525           1 :                         var ve manifest.VersionEdit
     526           1 :                         err = ve.Decode(r)
     527           1 :                         if err != nil {
     528           0 :                                 return err
     529           0 :                         }
     530           1 :                         if err := bve.Accumulate(&ve); err != nil {
     531           0 :                                 return err
     532           0 :                         }
     533           1 :                         if ve.ComparerName != "" {
     534           1 :                                 cmp = d.comparers[ve.ComparerName]
     535           1 :                                 d.fmtKey.setForComparer(ve.ComparerName, d.comparers)
     536           1 :                                 d.fmtValue.setForComparer(ve.ComparerName, d.comparers)
     537           1 :                         }
     538             :                 }
     539           1 :                 v, err := bve.Apply(
     540           1 :                         nil /* version */, cmp, d.opts.FlushSplitBytes,
     541           1 :                         d.opts.Experimental.ReadCompactionRate,
     542           1 :                 )
     543           1 :                 if err != nil {
     544           0 :                         return err
     545           0 :                 }
     546             : 
     547           1 :                 objProvider, err := objstorageprovider.Open(objstorageprovider.DefaultSettings(d.opts.FS, dirname))
     548           1 :                 if err != nil {
     549           0 :                         return err
     550           0 :                 }
     551           1 :                 defer objProvider.Close()
     552           1 : 
     553           1 :                 // Load and aggregate sstable properties.
     554           1 :                 tw := tabwriter.NewWriter(stdout, 2, 1, 4, ' ', 0)
     555           1 :                 var total props
     556           1 :                 var all []props
     557           1 :                 for _, l := range v.Levels {
     558           1 :                         iter := l.Iter()
     559           1 :                         var level props
     560           1 :                         for t := iter.First(); t != nil; t = iter.Next() {
     561           1 :                                 if t.Virtual {
     562           0 :                                         // TODO(bananabrick): Handle virtual sstables here. We don't
     563           0 :                                         // really have any stats or properties at this point. Maybe
     564           0 :                                         // we could approximate some of these properties for virtual
     565           0 :                                         // sstables by first grabbing properties for the backing
     566           0 :                                         // physical sstable, and then extrapolating.
     567           0 :                                         continue
     568             :                                 }
     569           1 :                                 err := d.addProps(objProvider, t.PhysicalMeta(), &level)
     570           1 :                                 if err != nil {
     571           0 :                                         return err
     572           0 :                                 }
     573             :                         }
     574           1 :                         all = append(all, level)
     575           1 :                         total.update(level)
     576             :                 }
     577           1 :                 all = append(all, total)
     578           1 : 
     579           1 :                 fmt.Fprintln(tw, "\tL0\tL1\tL2\tL3\tL4\tL5\tL6\tTOTAL")
     580           1 : 
     581           1 :                 fmt.Fprintf(tw, "count\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
     582           1 :                         propArgs(all, func(p *props) interface{} { return p.Count })...)
     583             : 
     584           1 :                 fmt.Fprintln(tw, "seq num\t\t\t\t\t\t\t\t")
     585           1 :                 fmt.Fprintf(tw, "  smallest\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
     586           1 :                         propArgs(all, func(p *props) interface{} { return p.SmallestSeqNum })...)
     587           1 :                 fmt.Fprintf(tw, "  largest\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
     588           1 :                         propArgs(all, func(p *props) interface{} { return p.LargestSeqNum })...)
     589             : 
     590           1 :                 fmt.Fprintln(tw, "size\t\t\t\t\t\t\t\t")
     591           1 :                 fmt.Fprintf(tw, "  data\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     592           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.DataSize) })...)
     593           1 :                 fmt.Fprintf(tw, "    blocks\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
     594           1 :                         propArgs(all, func(p *props) interface{} { return p.NumDataBlocks })...)
     595           1 :                 fmt.Fprintf(tw, "  index\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     596           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.IndexSize) })...)
     597           1 :                 fmt.Fprintf(tw, "    blocks\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
     598           1 :                         propArgs(all, func(p *props) interface{} { return p.NumIndexBlocks })...)
     599           1 :                 fmt.Fprintf(tw, "    top-level\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     600           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.TopLevelIndexSize) })...)
     601           1 :                 fmt.Fprintf(tw, "  filter\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     602           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.FilterSize) })...)
     603           1 :                 fmt.Fprintf(tw, "  raw-key\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     604           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawKeySize) })...)
     605           1 :                 fmt.Fprintf(tw, "  raw-value\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     606           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawValueSize) })...)
     607           1 :                 fmt.Fprintf(tw, "  pinned-key\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     608           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.SnapshotPinnedKeySize) })...)
     609           1 :                 fmt.Fprintf(tw, "  pinned-value\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     610           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.SnapshotPinnedValueSize) })...)
     611           1 :                 fmt.Fprintf(tw, "  point-del-key-size\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     612           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawPointTombstoneKeySize) })...)
     613           1 :                 fmt.Fprintf(tw, "  point-del-value-size\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     614           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Bytes.Uint64(p.RawPointTombstoneValueSize) })...)
     615             : 
     616           1 :                 fmt.Fprintln(tw, "records\t\t\t\t\t\t\t\t")
     617           1 :                 fmt.Fprintf(tw, "  set\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     618           1 :                         propArgs(all, func(p *props) interface{} {
     619           1 :                                 return humanize.Count.Uint64(p.NumEntries - p.NumDeletions - p.NumMergeOperands)
     620           1 :                         })...)
     621           1 :                 fmt.Fprintf(tw, "  delete\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     622           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumDeletions - p.NumRangeDeletions) })...)
     623           1 :                 fmt.Fprintf(tw, "  delete-sized\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     624           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumSizedDeletions) })...)
     625           1 :                 fmt.Fprintf(tw, "  range-delete\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     626           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeDeletions) })...)
     627           1 :                 fmt.Fprintf(tw, "  range-key-sets\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     628           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeKeySets) })...)
     629           1 :                 fmt.Fprintf(tw, "  range-key-unsets\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     630           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeKeyUnSets) })...)
     631           1 :                 fmt.Fprintf(tw, "  range-key-deletes\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     632           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumRangeKeyDeletes) })...)
     633           1 :                 fmt.Fprintf(tw, "  merge\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     634           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.NumMergeOperands) })...)
     635           1 :                 fmt.Fprintf(tw, "  pinned\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
     636           1 :                         propArgs(all, func(p *props) interface{} { return humanize.Count.Uint64(p.SnapshotPinnedKeys) })...)
     637             : 
     638           1 :                 if err := tw.Flush(); err != nil {
     639           0 :                         return err
     640           0 :                 }
     641           1 :                 return nil
     642             :         }()
     643           1 :         if err != nil {
     644           1 :                 fmt.Fprintln(stderr, err)
     645           1 :         }
     646             : }
     647             : 
     648           1 : func (d *dbT) runSet(cmd *cobra.Command, args []string) {
     649           1 :         stderr := cmd.ErrOrStderr()
     650           1 :         db, err := d.openDB(args[0], nonReadOnly{})
     651           1 :         if err != nil {
     652           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     653           0 :                 return
     654           0 :         }
     655           1 :         defer d.closeDB(stderr, db)
     656           1 :         var k, v key
     657           1 :         if err := k.Set(args[1]); err != nil {
     658           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     659           0 :                 return
     660           0 :         }
     661           1 :         if err := v.Set(args[2]); err != nil {
     662           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     663           0 :                 return
     664           0 :         }
     665             : 
     666           1 :         if err := db.Set(k, v, nil); err != nil {
     667           0 :                 fmt.Fprintf(stderr, "%s\n", err)
     668           0 :         }
     669             : }
     670             : 
     671           1 : func propArgs(props []props, getProp func(*props) interface{}) []interface{} {
     672           1 :         args := make([]interface{}, 0, len(props))
     673           1 :         for _, p := range props {
     674           1 :                 args = append(args, getProp(&p))
     675           1 :         }
     676           1 :         return args
     677             : }
     678             : 
     679             : type props struct {
     680             :         Count                      uint64
     681             :         SmallestSeqNum             uint64
     682             :         LargestSeqNum              uint64
     683             :         DataSize                   uint64
     684             :         FilterSize                 uint64
     685             :         IndexSize                  uint64
     686             :         NumDataBlocks              uint64
     687             :         NumIndexBlocks             uint64
     688             :         NumDeletions               uint64
     689             :         NumSizedDeletions          uint64
     690             :         NumEntries                 uint64
     691             :         NumMergeOperands           uint64
     692             :         NumRangeDeletions          uint64
     693             :         NumRangeKeySets            uint64
     694             :         NumRangeKeyUnSets          uint64
     695             :         NumRangeKeyDeletes         uint64
     696             :         RawKeySize                 uint64
     697             :         RawPointTombstoneKeySize   uint64
     698             :         RawPointTombstoneValueSize uint64
     699             :         RawValueSize               uint64
     700             :         SnapshotPinnedKeys         uint64
     701             :         SnapshotPinnedKeySize      uint64
     702             :         SnapshotPinnedValueSize    uint64
     703             :         TopLevelIndexSize          uint64
     704             : }
     705             : 
     706           1 : func (p *props) update(o props) {
     707           1 :         p.Count += o.Count
     708           1 :         if o.SmallestSeqNum != 0 && (o.SmallestSeqNum < p.SmallestSeqNum || p.SmallestSeqNum == 0) {
     709           1 :                 p.SmallestSeqNum = o.SmallestSeqNum
     710           1 :         }
     711           1 :         if o.LargestSeqNum > p.LargestSeqNum {
     712           1 :                 p.LargestSeqNum = o.LargestSeqNum
     713           1 :         }
     714           1 :         p.DataSize += o.DataSize
     715           1 :         p.FilterSize += o.FilterSize
     716           1 :         p.IndexSize += o.IndexSize
     717           1 :         p.NumDataBlocks += o.NumDataBlocks
     718           1 :         p.NumIndexBlocks += o.NumIndexBlocks
     719           1 :         p.NumDeletions += o.NumDeletions
     720           1 :         p.NumSizedDeletions += o.NumSizedDeletions
     721           1 :         p.NumEntries += o.NumEntries
     722           1 :         p.NumMergeOperands += o.NumMergeOperands
     723           1 :         p.NumRangeDeletions += o.NumRangeDeletions
     724           1 :         p.NumRangeKeySets += o.NumRangeKeySets
     725           1 :         p.NumRangeKeyUnSets += o.NumRangeKeyUnSets
     726           1 :         p.NumRangeKeyDeletes += o.NumRangeKeyDeletes
     727           1 :         p.RawKeySize += o.RawKeySize
     728           1 :         p.RawPointTombstoneKeySize += o.RawPointTombstoneKeySize
     729           1 :         p.RawPointTombstoneValueSize += o.RawPointTombstoneValueSize
     730           1 :         p.RawValueSize += o.RawValueSize
     731           1 :         p.SnapshotPinnedKeySize += o.SnapshotPinnedKeySize
     732           1 :         p.SnapshotPinnedValueSize += o.SnapshotPinnedValueSize
     733           1 :         p.SnapshotPinnedKeys += o.SnapshotPinnedKeys
     734           1 :         p.TopLevelIndexSize += o.TopLevelIndexSize
     735             : }
     736             : 
     737             : func (d *dbT) addProps(
     738             :         objProvider objstorage.Provider, m manifest.PhysicalFileMeta, p *props,
     739           1 : ) error {
     740           1 :         ctx := context.Background()
     741           1 :         f, err := objProvider.OpenForReading(ctx, base.FileTypeTable, m.FileBacking.DiskFileNum, objstorage.OpenOptions{})
     742           1 :         if err != nil {
     743           0 :                 return err
     744           0 :         }
     745           1 :         r, err := sstable.NewReader(f, sstable.ReaderOptions{}, d.mergers, d.comparers)
     746           1 :         if err != nil {
     747           0 :                 _ = f.Close()
     748           0 :                 return err
     749           0 :         }
     750           1 :         p.update(props{
     751           1 :                 Count:                      1,
     752           1 :                 SmallestSeqNum:             m.SmallestSeqNum,
     753           1 :                 LargestSeqNum:              m.LargestSeqNum,
     754           1 :                 DataSize:                   r.Properties.DataSize,
     755           1 :                 FilterSize:                 r.Properties.FilterSize,
     756           1 :                 IndexSize:                  r.Properties.IndexSize,
     757           1 :                 NumDataBlocks:              r.Properties.NumDataBlocks,
     758           1 :                 NumIndexBlocks:             1 + r.Properties.IndexPartitions,
     759           1 :                 NumDeletions:               r.Properties.NumDeletions,
     760           1 :                 NumSizedDeletions:          r.Properties.NumSizedDeletions,
     761           1 :                 NumEntries:                 r.Properties.NumEntries,
     762           1 :                 NumMergeOperands:           r.Properties.NumMergeOperands,
     763           1 :                 NumRangeDeletions:          r.Properties.NumRangeDeletions,
     764           1 :                 NumRangeKeySets:            r.Properties.NumRangeKeySets,
     765           1 :                 NumRangeKeyUnSets:          r.Properties.NumRangeKeyUnsets,
     766           1 :                 NumRangeKeyDeletes:         r.Properties.NumRangeKeyDels,
     767           1 :                 RawKeySize:                 r.Properties.RawKeySize,
     768           1 :                 RawPointTombstoneKeySize:   r.Properties.RawPointTombstoneKeySize,
     769           1 :                 RawPointTombstoneValueSize: r.Properties.RawPointTombstoneValueSize,
     770           1 :                 RawValueSize:               r.Properties.RawValueSize,
     771           1 :                 SnapshotPinnedKeySize:      r.Properties.SnapshotPinnedKeySize,
     772           1 :                 SnapshotPinnedValueSize:    r.Properties.SnapshotPinnedValueSize,
     773           1 :                 SnapshotPinnedKeys:         r.Properties.SnapshotPinnedKeys,
     774           1 :                 TopLevelIndexSize:          r.Properties.TopLevelIndexSize,
     775           1 :         })
     776           1 :         return r.Close()
     777             : }
     778             : 
     779           1 : func makePlural(singular string, count int64) string {
     780           1 :         if count > 1 {
     781           1 :                 return fmt.Sprintf("%ss", singular)
     782           1 :         }
     783           1 :         return singular
     784             : }

Generated by: LCOV version 1.14