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