github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/tool/wal.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  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  
    13  	"github.com/cockroachdb/pebble"
    14  	"github.com/cockroachdb/pebble/internal/base"
    15  	"github.com/cockroachdb/pebble/rangekey"
    16  	"github.com/cockroachdb/pebble/record"
    17  	"github.com/cockroachdb/pebble/sstable"
    18  	"github.com/spf13/cobra"
    19  )
    20  
    21  // walT implements WAL-level tools, including both configuration state and the
    22  // commands themselves.
    23  type walT struct {
    24  	Root *cobra.Command
    25  	Dump *cobra.Command
    26  
    27  	opts     *pebble.Options
    28  	fmtKey   keyFormatter
    29  	fmtValue valueFormatter
    30  
    31  	defaultComparer string
    32  	comparers       sstable.Comparers
    33  	verbose         bool
    34  }
    35  
    36  func newWAL(opts *pebble.Options, comparers sstable.Comparers, defaultComparer string) *walT {
    37  	w := &walT{
    38  		opts: opts,
    39  	}
    40  	w.fmtKey.mustSet("quoted")
    41  	w.fmtValue.mustSet("size")
    42  	w.comparers = comparers
    43  	w.defaultComparer = defaultComparer
    44  
    45  	w.Root = &cobra.Command{
    46  		Use:   "wal",
    47  		Short: "WAL introspection tools",
    48  	}
    49  	w.Dump = &cobra.Command{
    50  		Use:   "dump <wal-files>",
    51  		Short: "print WAL contents",
    52  		Long: `
    53  Print the contents of the WAL files.
    54  `,
    55  		Args: cobra.MinimumNArgs(1),
    56  		Run:  w.runDump,
    57  	}
    58  
    59  	w.Root.AddCommand(w.Dump)
    60  	w.Root.PersistentFlags().BoolVarP(&w.verbose, "verbose", "v", false, "verbose output")
    61  
    62  	w.Dump.Flags().Var(
    63  		&w.fmtKey, "key", "key formatter")
    64  	w.Dump.Flags().Var(
    65  		&w.fmtValue, "value", "value formatter")
    66  	return w
    67  }
    68  
    69  func (w *walT) runDump(cmd *cobra.Command, args []string) {
    70  	stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
    71  	w.fmtKey.setForComparer(w.defaultComparer, w.comparers)
    72  	w.fmtValue.setForComparer(w.defaultComparer, w.comparers)
    73  
    74  	for _, arg := range args {
    75  		func() {
    76  			// Parse the filename in order to extract the file number. This is
    77  			// necessary in case WAL recycling was used (which it is usually is). If
    78  			// we can't parse the filename or it isn't a log file, we'll plow ahead
    79  			// anyways (which will likely fail when we try to read the file).
    80  			_, fileNum, ok := base.ParseFilename(w.opts.FS, arg)
    81  			if !ok {
    82  				fileNum = base.FileNum(0).DiskFileNum()
    83  			}
    84  
    85  			f, err := w.opts.FS.Open(arg)
    86  			if err != nil {
    87  				fmt.Fprintf(stderr, "%s\n", err)
    88  				return
    89  			}
    90  			defer f.Close()
    91  
    92  			fmt.Fprintf(stdout, "%s\n", arg)
    93  
    94  			var b pebble.Batch
    95  			var buf bytes.Buffer
    96  			rr := record.NewReader(f, fileNum.FileNum())
    97  			for {
    98  				offset := rr.Offset()
    99  				r, err := rr.Next()
   100  				if err == nil {
   101  					buf.Reset()
   102  					_, err = io.Copy(&buf, r)
   103  				}
   104  				if err != nil {
   105  					// It is common to encounter a zeroed or invalid chunk due to WAL
   106  					// preallocation and WAL recycling. We need to distinguish these
   107  					// errors from EOF in order to recognize that the record was
   108  					// truncated, but want to otherwise treat them like EOF.
   109  					switch err {
   110  					case record.ErrZeroedChunk:
   111  						fmt.Fprintf(stdout, "EOF [%s] (may be due to WAL preallocation)\n", err)
   112  					case record.ErrInvalidChunk:
   113  						fmt.Fprintf(stdout, "EOF [%s] (may be due to WAL recycling)\n", err)
   114  					default:
   115  						fmt.Fprintf(stdout, "%s\n", err)
   116  					}
   117  					return
   118  				}
   119  
   120  				b = pebble.Batch{}
   121  				if err := b.SetRepr(buf.Bytes()); err != nil {
   122  					fmt.Fprintf(stdout, "corrupt batch within log file %q: %v", arg, err)
   123  					return
   124  				}
   125  				fmt.Fprintf(stdout, "%d(%d) seq=%d count=%d\n",
   126  					offset, len(b.Repr()), b.SeqNum(), b.Count())
   127  				for r, idx := b.Reader(), 0; ; idx++ {
   128  					kind, ukey, value, ok, err := r.Next()
   129  					if !ok {
   130  						if err != nil {
   131  							fmt.Fprintf(stdout, "corrupt batch within log file %q: %v", arg, err)
   132  						}
   133  						break
   134  					}
   135  					fmt.Fprintf(stdout, "    %s(", kind)
   136  					switch kind {
   137  					case base.InternalKeyKindDelete:
   138  						fmt.Fprintf(stdout, "%s", w.fmtKey.fn(ukey))
   139  					case base.InternalKeyKindSet:
   140  						fmt.Fprintf(stdout, "%s,%s", w.fmtKey.fn(ukey), w.fmtValue.fn(ukey, value))
   141  					case base.InternalKeyKindMerge:
   142  						fmt.Fprintf(stdout, "%s,%s", w.fmtKey.fn(ukey), w.fmtValue.fn(ukey, value))
   143  					case base.InternalKeyKindLogData:
   144  						fmt.Fprintf(stdout, "<%d>", len(value))
   145  					case base.InternalKeyKindIngestSST:
   146  						fileNum, _ := binary.Uvarint(ukey)
   147  						fmt.Fprintf(stdout, "%s", base.FileNum(fileNum))
   148  					case base.InternalKeyKindSingleDelete:
   149  						fmt.Fprintf(stdout, "%s", w.fmtKey.fn(ukey))
   150  					case base.InternalKeyKindSetWithDelete:
   151  						fmt.Fprintf(stdout, "%s", w.fmtKey.fn(ukey))
   152  					case base.InternalKeyKindRangeDelete:
   153  						fmt.Fprintf(stdout, "%s,%s", w.fmtKey.fn(ukey), w.fmtKey.fn(value))
   154  					case base.InternalKeyKindRangeKeySet, base.InternalKeyKindRangeKeyUnset, base.InternalKeyKindRangeKeyDelete:
   155  						ik := base.MakeInternalKey(ukey, b.SeqNum()+uint64(idx), kind)
   156  						s, err := rangekey.Decode(ik, value, nil)
   157  						if err != nil {
   158  							fmt.Fprintf(stdout, "%s: error decoding %s", w.fmtKey.fn(ukey), err)
   159  						} else {
   160  							fmt.Fprintf(stdout, "%s", s.Pretty(w.fmtKey.fn))
   161  						}
   162  					case base.InternalKeyKindDeleteSized:
   163  						v, _ := binary.Uvarint(value)
   164  						fmt.Fprintf(stdout, "%s,%d", w.fmtKey.fn(ukey), v)
   165  					}
   166  					fmt.Fprintf(stdout, ")\n")
   167  				}
   168  			}
   169  		}()
   170  	}
   171  }