github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/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) 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 }