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 : "fmt"
10 : "io"
11 : "os"
12 : "path/filepath"
13 : "sort"
14 : "text/tabwriter"
15 :
16 : "github.com/cockroachdb/pebble"
17 : "github.com/cockroachdb/pebble/internal/base"
18 : "github.com/cockroachdb/pebble/internal/humanize"
19 : "github.com/cockroachdb/pebble/internal/keyspan"
20 : "github.com/cockroachdb/pebble/internal/private"
21 : "github.com/cockroachdb/pebble/internal/rangedel"
22 : "github.com/cockroachdb/pebble/sstable"
23 : "github.com/cockroachdb/pebble/vfs"
24 : "github.com/spf13/cobra"
25 : )
26 :
27 : // sstableT implements sstable-level tools, including both configuration state
28 : // and the commands themselves.
29 : type sstableT struct {
30 : Root *cobra.Command
31 : Check *cobra.Command
32 : Layout *cobra.Command
33 : Properties *cobra.Command
34 : Scan *cobra.Command
35 : Space *cobra.Command
36 :
37 : // Configuration and state.
38 : opts *pebble.Options
39 : comparers sstable.Comparers
40 : mergers sstable.Mergers
41 :
42 : // Flags.
43 : fmtKey keyFormatter
44 : fmtValue valueFormatter
45 : start key
46 : end key
47 : filter key
48 : count int64
49 : verbose bool
50 : }
51 :
52 : func newSSTable(
53 : opts *pebble.Options, comparers sstable.Comparers, mergers sstable.Mergers,
54 1 : ) *sstableT {
55 1 : s := &sstableT{
56 1 : opts: opts,
57 1 : comparers: comparers,
58 1 : mergers: mergers,
59 1 : }
60 1 : s.fmtKey.mustSet("quoted")
61 1 : s.fmtValue.mustSet("[%x]")
62 1 :
63 1 : s.Root = &cobra.Command{
64 1 : Use: "sstable",
65 1 : Short: "sstable introspection tools",
66 1 : }
67 1 : s.Check = &cobra.Command{
68 1 : Use: "check <sstables>",
69 1 : Short: "verify checksums and metadata",
70 1 : Long: ``,
71 1 : Args: cobra.MinimumNArgs(1),
72 1 : Run: s.runCheck,
73 1 : }
74 1 : s.Layout = &cobra.Command{
75 1 : Use: "layout <sstables>",
76 1 : Short: "print sstable block and record layout",
77 1 : Long: `
78 1 : Print the layout for the sstables. The -v flag controls whether record layout
79 1 : is displayed or omitted.
80 1 : `,
81 1 : Args: cobra.MinimumNArgs(1),
82 1 : Run: s.runLayout,
83 1 : }
84 1 : s.Properties = &cobra.Command{
85 1 : Use: "properties <sstables>",
86 1 : Short: "print sstable properties",
87 1 : Long: `
88 1 : Print the properties for the sstables. The -v flag controls whether the
89 1 : properties are pretty-printed or displayed in a verbose/raw format.
90 1 : `,
91 1 : Args: cobra.MinimumNArgs(1),
92 1 : Run: s.runProperties,
93 1 : }
94 1 : s.Scan = &cobra.Command{
95 1 : Use: "scan <sstables>",
96 1 : Short: "print sstable records",
97 1 : Long: `
98 1 : Print the records in the sstables. The sstables are scanned in command line
99 1 : order which means the records will be printed in that order. Raw range
100 1 : tombstones are displayed interleaved with point records.
101 1 : `,
102 1 : Args: cobra.MinimumNArgs(1),
103 1 : Run: s.runScan,
104 1 : }
105 1 : s.Space = &cobra.Command{
106 1 : Use: "space <sstables>",
107 1 : Short: "print filesystem space used",
108 1 : Long: `
109 1 : Print the estimated space usage in the specified files for the
110 1 : inclusive-inclusive range specified by --start and --end.
111 1 : `,
112 1 : Args: cobra.MinimumNArgs(1),
113 1 : Run: s.runSpace,
114 1 : }
115 1 :
116 1 : s.Root.AddCommand(s.Check, s.Layout, s.Properties, s.Scan, s.Space)
117 1 : s.Root.PersistentFlags().BoolVarP(&s.verbose, "verbose", "v", false, "verbose output")
118 1 :
119 1 : s.Check.Flags().Var(
120 1 : &s.fmtKey, "key", "key formatter")
121 1 : s.Layout.Flags().Var(
122 1 : &s.fmtKey, "key", "key formatter")
123 1 : s.Layout.Flags().Var(
124 1 : &s.fmtValue, "value", "value formatter")
125 1 : s.Scan.Flags().Var(
126 1 : &s.fmtKey, "key", "key formatter")
127 1 : s.Scan.Flags().Var(
128 1 : &s.fmtValue, "value", "value formatter")
129 1 : for _, cmd := range []*cobra.Command{s.Scan, s.Space} {
130 1 : cmd.Flags().Var(
131 1 : &s.start, "start", "start key for the range")
132 1 : cmd.Flags().Var(
133 1 : &s.end, "end", "end key for the range")
134 1 : }
135 1 : s.Scan.Flags().Var(
136 1 : &s.filter, "filter", "only output records with matching prefix or overlapping range tombstones")
137 1 : s.Scan.Flags().Int64Var(
138 1 : &s.count, "count", 0, "key count for scan (0 is unlimited)")
139 1 :
140 1 : return s
141 : }
142 :
143 1 : func (s *sstableT) newReader(f vfs.File) (*sstable.Reader, error) {
144 1 : readable, err := sstable.NewSimpleReadable(f)
145 1 : if err != nil {
146 0 : return nil, err
147 0 : }
148 1 : o := sstable.ReaderOptions{
149 1 : Cache: pebble.NewCache(128 << 20 /* 128 MB */),
150 1 : Comparer: s.opts.Comparer,
151 1 : Filters: s.opts.Filters,
152 1 : }
153 1 : defer o.Cache.Unref()
154 1 : return sstable.NewReader(readable, o, s.comparers, s.mergers,
155 1 : private.SSTableRawTombstonesOpt.(sstable.ReaderOption))
156 : }
157 :
158 1 : func (s *sstableT) runCheck(cmd *cobra.Command, args []string) {
159 1 : stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
160 1 : s.foreachSstable(stderr, args, func(arg string) {
161 1 : f, err := s.opts.FS.Open(arg)
162 1 : if err != nil {
163 0 : fmt.Fprintf(stderr, "%s\n", err)
164 0 : return
165 0 : }
166 :
167 1 : fmt.Fprintf(stdout, "%s\n", arg)
168 1 :
169 1 : r, err := s.newReader(f)
170 1 :
171 1 : if err != nil {
172 1 : fmt.Fprintf(stdout, "%s\n", err)
173 1 : return
174 1 : }
175 1 : defer r.Close()
176 1 :
177 1 : // Update the internal formatter if this comparator has one specified.
178 1 : s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
179 1 : s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
180 1 :
181 1 : iter, err := r.NewIter(nil, nil)
182 1 : if err != nil {
183 0 : fmt.Fprintf(stderr, "%s\n", err)
184 0 : return
185 0 : }
186 :
187 : // If a split function is defined for the comparer, verify that
188 : // SeekPrefixGE can find every key in the table.
189 1 : var prefixIter sstable.Iterator
190 1 : if r.Split != nil {
191 1 : var err error
192 1 : prefixIter, err = r.NewIter(nil, nil)
193 1 : if err != nil {
194 0 : fmt.Fprintf(stderr, "%s\n", err)
195 0 : return
196 0 : }
197 : }
198 :
199 1 : var lastKey base.InternalKey
200 1 : for key, _ := iter.First(); key != nil; key, _ = iter.Next() {
201 1 : if base.InternalCompare(r.Compare, lastKey, *key) >= 0 {
202 1 : fmt.Fprintf(stdout, "WARNING: OUT OF ORDER KEYS!\n")
203 1 : if s.fmtKey.spec != "null" {
204 1 : fmt.Fprintf(stdout, " %s >= %s\n",
205 1 : lastKey.Pretty(s.fmtKey.fn), key.Pretty(s.fmtKey.fn))
206 1 : }
207 : }
208 1 : lastKey.Trailer = key.Trailer
209 1 : lastKey.UserKey = append(lastKey.UserKey[:0], key.UserKey...)
210 1 :
211 1 : if prefixIter != nil {
212 1 : n := r.Split(key.UserKey)
213 1 : prefix := key.UserKey[:n]
214 1 : key2, _ := prefixIter.SeekPrefixGE(prefix, key.UserKey, base.SeekGEFlagsNone)
215 1 : if key2 == nil {
216 0 : fmt.Fprintf(stdout, "WARNING: PREFIX ITERATION FAILURE!\n")
217 0 : if s.fmtKey.spec != "null" {
218 0 : fmt.Fprintf(stdout, " %s not found\n", key.Pretty(s.fmtKey.fn))
219 0 : }
220 : }
221 : }
222 : }
223 :
224 1 : if err := iter.Close(); err != nil {
225 1 : fmt.Fprintf(stdout, "%s\n", err)
226 1 : }
227 1 : if prefixIter != nil {
228 1 : if err := prefixIter.Close(); err != nil {
229 0 : fmt.Fprintf(stdout, "%s\n", err)
230 0 : }
231 : }
232 : })
233 : }
234 :
235 1 : func (s *sstableT) runLayout(cmd *cobra.Command, args []string) {
236 1 : stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
237 1 : s.foreachSstable(stderr, args, func(arg string) {
238 1 : f, err := s.opts.FS.Open(arg)
239 1 : if err != nil {
240 0 : fmt.Fprintf(stderr, "%s\n", err)
241 0 : return
242 0 : }
243 :
244 1 : fmt.Fprintf(stdout, "%s\n", arg)
245 1 :
246 1 : r, err := s.newReader(f)
247 1 : if err != nil {
248 0 : fmt.Fprintf(stdout, "%s\n", err)
249 0 : return
250 0 : }
251 1 : defer r.Close()
252 1 :
253 1 : // Update the internal formatter if this comparator has one specified.
254 1 : s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
255 1 : s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
256 1 :
257 1 : l, err := r.Layout()
258 1 : if err != nil {
259 0 : fmt.Fprintf(stderr, "%s\n", err)
260 0 : return
261 0 : }
262 1 : fmtRecord := func(key *base.InternalKey, value []byte) {
263 1 : formatKeyValue(stdout, s.fmtKey, s.fmtValue, key, value)
264 1 : }
265 1 : if s.fmtKey.spec == "null" && s.fmtValue.spec == "null" {
266 0 : fmtRecord = nil
267 0 : }
268 1 : l.Describe(stdout, s.verbose, r, fmtRecord)
269 : })
270 : }
271 :
272 1 : func (s *sstableT) runProperties(cmd *cobra.Command, args []string) {
273 1 : stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
274 1 : s.foreachSstable(stderr, args, func(arg string) {
275 1 : f, err := s.opts.FS.Open(arg)
276 1 : if err != nil {
277 0 : fmt.Fprintf(stderr, "%s\n", err)
278 0 : return
279 0 : }
280 :
281 1 : fmt.Fprintf(stdout, "%s\n", arg)
282 1 :
283 1 : r, err := s.newReader(f)
284 1 : if err != nil {
285 1 : fmt.Fprintf(stdout, "%s\n", err)
286 1 : return
287 1 : }
288 1 : defer r.Close()
289 1 :
290 1 : if s.verbose {
291 1 : fmt.Fprintf(stdout, "%s", r.Properties.String())
292 1 : return
293 1 : }
294 :
295 1 : stat, err := f.Stat()
296 1 : if err != nil {
297 0 : fmt.Fprintf(stderr, "%s\n", err)
298 0 : return
299 0 : }
300 :
301 1 : formatNull := func(s string) string {
302 1 : switch s {
303 1 : case "", "nullptr":
304 1 : return "-"
305 : }
306 1 : return s
307 : }
308 :
309 1 : tw := tabwriter.NewWriter(stdout, 2, 1, 2, ' ', 0)
310 1 : fmt.Fprintf(tw, "size\t\n")
311 1 : fmt.Fprintf(tw, " file\t%s\n", humanize.Bytes.Int64(stat.Size()))
312 1 : fmt.Fprintf(tw, " data\t%s\n", humanize.Bytes.Uint64(r.Properties.DataSize))
313 1 : fmt.Fprintf(tw, " blocks\t%d\n", r.Properties.NumDataBlocks)
314 1 : fmt.Fprintf(tw, " index\t%s\n", humanize.Bytes.Uint64(r.Properties.IndexSize))
315 1 : fmt.Fprintf(tw, " blocks\t%d\n", 1+r.Properties.IndexPartitions)
316 1 : fmt.Fprintf(tw, " top-level\t%s\n", humanize.Bytes.Uint64(r.Properties.TopLevelIndexSize))
317 1 : fmt.Fprintf(tw, " filter\t%s\n", humanize.Bytes.Uint64(r.Properties.FilterSize))
318 1 : fmt.Fprintf(tw, " raw-key\t%s\n", humanize.Bytes.Uint64(r.Properties.RawKeySize))
319 1 : fmt.Fprintf(tw, " raw-value\t%s\n", humanize.Bytes.Uint64(r.Properties.RawValueSize))
320 1 : fmt.Fprintf(tw, " pinned-key\t%d\n", r.Properties.SnapshotPinnedKeySize)
321 1 : fmt.Fprintf(tw, " pinned-val\t%d\n", r.Properties.SnapshotPinnedValueSize)
322 1 : fmt.Fprintf(tw, " point-del-key-size\t%d\n", r.Properties.RawPointTombstoneKeySize)
323 1 : fmt.Fprintf(tw, " point-del-value-size\t%d\n", r.Properties.RawPointTombstoneValueSize)
324 1 : fmt.Fprintf(tw, "records\t%d\n", r.Properties.NumEntries)
325 1 : fmt.Fprintf(tw, " set\t%d\n", r.Properties.NumEntries-
326 1 : (r.Properties.NumDeletions+r.Properties.NumMergeOperands))
327 1 : fmt.Fprintf(tw, " delete\t%d\n", r.Properties.NumPointDeletions())
328 1 : fmt.Fprintf(tw, " delete-sized\t%d\n", r.Properties.NumSizedDeletions)
329 1 : fmt.Fprintf(tw, " range-delete\t%d\n", r.Properties.NumRangeDeletions)
330 1 : fmt.Fprintf(tw, " range-key-set\t%d\n", r.Properties.NumRangeKeySets)
331 1 : fmt.Fprintf(tw, " range-key-unset\t%d\n", r.Properties.NumRangeKeyUnsets)
332 1 : fmt.Fprintf(tw, " range-key-delete\t%d\n", r.Properties.NumRangeKeyDels)
333 1 : fmt.Fprintf(tw, " merge\t%d\n", r.Properties.NumMergeOperands)
334 1 : fmt.Fprintf(tw, " global-seq-num\t%d\n", r.Properties.GlobalSeqNum)
335 1 : fmt.Fprintf(tw, " pinned\t%d\n", r.Properties.SnapshotPinnedKeys)
336 1 : fmt.Fprintf(tw, "index\t\n")
337 1 : fmt.Fprintf(tw, " key\t")
338 1 : fmt.Fprintf(tw, " value\t")
339 1 : fmt.Fprintf(tw, "comparer\t%s\n", r.Properties.ComparerName)
340 1 : fmt.Fprintf(tw, "merger\t%s\n", formatNull(r.Properties.MergerName))
341 1 : fmt.Fprintf(tw, "filter\t%s\n", formatNull(r.Properties.FilterPolicyName))
342 1 : fmt.Fprintf(tw, " prefix\t%t\n", r.Properties.PrefixFiltering)
343 1 : fmt.Fprintf(tw, " whole-key\t%t\n", r.Properties.WholeKeyFiltering)
344 1 : fmt.Fprintf(tw, "compression\t%s\n", r.Properties.CompressionName)
345 1 : fmt.Fprintf(tw, " options\t%s\n", r.Properties.CompressionOptions)
346 1 : fmt.Fprintf(tw, "user properties\t\n")
347 1 : fmt.Fprintf(tw, " collectors\t%s\n", r.Properties.PropertyCollectorNames)
348 1 : keys := make([]string, 0, len(r.Properties.UserProperties))
349 1 : for key := range r.Properties.UserProperties {
350 1 : keys = append(keys, key)
351 1 : }
352 1 : sort.Strings(keys)
353 1 : for _, key := range keys {
354 1 : fmt.Fprintf(tw, " %s\t%s\n", key, r.Properties.UserProperties[key])
355 1 : }
356 1 : tw.Flush()
357 : })
358 : }
359 :
360 1 : func (s *sstableT) runScan(cmd *cobra.Command, args []string) {
361 1 : stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
362 1 : s.foreachSstable(stderr, args, func(arg string) {
363 1 : f, err := s.opts.FS.Open(arg)
364 1 : if err != nil {
365 0 : fmt.Fprintf(stderr, "%s\n", err)
366 0 : return
367 0 : }
368 :
369 : // In filter-mode, we prefix ever line that is output with the sstable
370 : // filename.
371 1 : var prefix string
372 1 : if s.filter == nil {
373 1 : fmt.Fprintf(stdout, "%s\n", arg)
374 1 : } else {
375 1 : prefix = fmt.Sprintf("%s: ", arg)
376 1 : }
377 :
378 1 : r, err := s.newReader(f)
379 1 : if err != nil {
380 0 : fmt.Fprintf(stdout, "%s%s\n", prefix, err)
381 0 : return
382 0 : }
383 1 : defer r.Close()
384 1 :
385 1 : // Update the internal formatter if this comparator has one specified.
386 1 : s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
387 1 : s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
388 1 :
389 1 : iter, err := r.NewIter(nil, s.end)
390 1 : if err != nil {
391 0 : fmt.Fprintf(stderr, "%s%s\n", prefix, err)
392 0 : return
393 0 : }
394 1 : defer iter.Close()
395 1 : key, value := iter.SeekGE(s.start, base.SeekGEFlagsNone)
396 1 :
397 1 : // We configured sstable.Reader to return raw tombstones which requires a
398 1 : // bit more work here to put them in a form that can be iterated in
399 1 : // parallel with the point records.
400 1 : rangeDelIter, err := func() (keyspan.FragmentIterator, error) {
401 1 : iter, err := r.NewRawRangeDelIter()
402 1 : if err != nil {
403 0 : return nil, err
404 0 : }
405 1 : if iter == nil {
406 1 : return keyspan.NewIter(r.Compare, nil), nil
407 1 : }
408 1 : defer iter.Close()
409 1 :
410 1 : var tombstones []keyspan.Span
411 1 : for t := iter.First(); t != nil; t = iter.Next() {
412 1 : if s.end != nil && r.Compare(s.end, t.Start) <= 0 {
413 1 : // The range tombstone lies after the scan range.
414 1 : continue
415 : }
416 1 : if r.Compare(s.start, t.End) >= 0 {
417 1 : // The range tombstone lies before the scan range.
418 1 : continue
419 : }
420 1 : tombstones = append(tombstones, t.ShallowClone())
421 : }
422 :
423 1 : sort.Slice(tombstones, func(i, j int) bool {
424 1 : return r.Compare(tombstones[i].Start, tombstones[j].Start) < 0
425 1 : })
426 1 : return keyspan.NewIter(r.Compare, tombstones), nil
427 : }()
428 1 : if err != nil {
429 0 : fmt.Fprintf(stdout, "%s%s\n", prefix, err)
430 0 : return
431 0 : }
432 :
433 1 : defer rangeDelIter.Close()
434 1 : rangeDel := rangeDelIter.First()
435 1 : count := s.count
436 1 :
437 1 : var lastKey base.InternalKey
438 1 : for key != nil || rangeDel != nil {
439 1 : if key != nil && (rangeDel == nil || r.Compare(key.UserKey, rangeDel.Start) < 0) {
440 1 : // The filter specifies a prefix of the key.
441 1 : //
442 1 : // TODO(peter): Is using prefix comparison like this kosher for all
443 1 : // comparers? Probably not, but it is for common ones such as the
444 1 : // Pebble default and CockroachDB's comparer.
445 1 : if s.filter == nil || bytes.HasPrefix(key.UserKey, s.filter) {
446 1 : fmt.Fprint(stdout, prefix)
447 1 : v, _, err := value.Value(nil)
448 1 : if err != nil {
449 0 : fmt.Fprintf(stdout, "%s%s\n", prefix, err)
450 0 : return
451 0 : }
452 1 : formatKeyValue(stdout, s.fmtKey, s.fmtValue, key, v)
453 :
454 : }
455 1 : if base.InternalCompare(r.Compare, lastKey, *key) >= 0 {
456 1 : fmt.Fprintf(stdout, "%s WARNING: OUT OF ORDER KEYS!\n", prefix)
457 1 : }
458 1 : lastKey.Trailer = key.Trailer
459 1 : lastKey.UserKey = append(lastKey.UserKey[:0], key.UserKey...)
460 1 : key, value = iter.Next()
461 1 : } else {
462 1 : // If a filter is specified, we want to output any range tombstone
463 1 : // which overlaps the prefix. The comparison on the start key is
464 1 : // somewhat complex. Consider the tombstone [aaa,ccc). We want to
465 1 : // output this tombstone if filter is "aa", and if it "bbb".
466 1 : if s.filter == nil ||
467 1 : ((r.Compare(s.filter, rangeDel.Start) >= 0 ||
468 1 : bytes.HasPrefix(rangeDel.Start, s.filter)) &&
469 1 : r.Compare(s.filter, rangeDel.End) < 0) {
470 1 : fmt.Fprint(stdout, prefix)
471 1 : if err := rangedel.Encode(rangeDel, func(k base.InternalKey, v []byte) error {
472 1 : formatKeyValue(stdout, s.fmtKey, s.fmtValue, &k, v)
473 1 : return nil
474 1 : }); err != nil {
475 0 : fmt.Fprintf(stdout, "%s\n", err)
476 0 : os.Exit(1)
477 0 : }
478 : }
479 1 : rangeDel = rangeDelIter.Next()
480 : }
481 :
482 1 : if count > 0 {
483 1 : count--
484 1 : if count == 0 {
485 1 : break
486 : }
487 : }
488 : }
489 :
490 : // Handle range keys.
491 1 : rkIter, err := r.NewRawRangeKeyIter()
492 1 : if err != nil {
493 0 : fmt.Fprintf(stdout, "%s\n", err)
494 0 : os.Exit(1)
495 0 : }
496 1 : if rkIter != nil {
497 1 : defer rkIter.Close()
498 1 : for span := rkIter.SeekGE(s.start); span != nil; span = rkIter.Next() {
499 1 : // By default, emit the key, unless there is a filter.
500 1 : emit := s.filter == nil
501 1 : // Skip spans that start after the end key (if provided). End keys are
502 1 : // exclusive, e.g. [a, b), so we consider the interval [b, +inf).
503 1 : if s.end != nil && r.Compare(span.Start, s.end) >= 0 {
504 0 : emit = false
505 0 : }
506 : // Filters override the provided start / end bounds, if provided.
507 1 : if s.filter != nil && bytes.HasPrefix(span.Start, s.filter) {
508 1 : // In filter mode, each line is prefixed with the filename.
509 1 : fmt.Fprint(stdout, prefix)
510 1 : emit = true
511 1 : }
512 1 : if emit {
513 1 : formatSpan(stdout, s.fmtKey, s.fmtValue, span)
514 1 : }
515 : }
516 : }
517 :
518 1 : if err := iter.Close(); err != nil {
519 0 : fmt.Fprintf(stdout, "%s\n", err)
520 0 : }
521 : })
522 : }
523 :
524 1 : func (s *sstableT) runSpace(cmd *cobra.Command, args []string) {
525 1 : stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
526 1 : s.foreachSstable(stderr, args, func(arg string) {
527 1 : f, err := s.opts.FS.Open(arg)
528 1 : if err != nil {
529 0 : fmt.Fprintf(stderr, "%s\n", err)
530 0 : return
531 0 : }
532 1 : r, err := s.newReader(f)
533 1 : if err != nil {
534 0 : fmt.Fprintf(stderr, "%s\n", err)
535 0 : return
536 0 : }
537 1 : defer r.Close()
538 1 :
539 1 : bytes, err := r.EstimateDiskUsage(s.start, s.end)
540 1 : if err != nil {
541 0 : fmt.Fprintf(stderr, "%s\n", err)
542 0 : return
543 0 : }
544 1 : fmt.Fprintf(stdout, "%s: %d\n", arg, bytes)
545 : })
546 : }
547 :
548 1 : func (s *sstableT) foreachSstable(stderr io.Writer, args []string, fn func(arg string)) {
549 1 : // Loop over args, invoking fn for each file. Each directory is recursively
550 1 : // listed and fn is invoked on any file with an .sst or .ldb suffix.
551 1 : for _, arg := range args {
552 1 : info, err := s.opts.FS.Stat(arg)
553 1 : if err != nil || !info.IsDir() {
554 1 : fn(arg)
555 1 : continue
556 : }
557 1 : walk(stderr, s.opts.FS, arg, func(path string) {
558 1 : switch filepath.Ext(path) {
559 1 : case ".sst", ".ldb":
560 1 : fn(path)
561 : }
562 : })
563 : }
564 : }
|