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