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