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