github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/table_stats_test.go (about)

     1  // Copyright 2020 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 pebble
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/cockroachdb/datadriven"
    14  	"github.com/cockroachdb/pebble/internal/base"
    15  	"github.com/cockroachdb/pebble/internal/keyspan"
    16  	"github.com/cockroachdb/pebble/internal/rangekey"
    17  	"github.com/cockroachdb/pebble/internal/testkeys"
    18  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider"
    19  	"github.com/cockroachdb/pebble/sstable"
    20  	"github.com/cockroachdb/pebble/vfs"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestTableStats(t *testing.T) {
    25  	// loadedInfo is protected by d.mu.
    26  	var loadedInfo *TableStatsInfo
    27  	opts := &Options{
    28  		FS: vfs.NewMem(),
    29  		EventListener: &EventListener{
    30  			TableStatsLoaded: func(info TableStatsInfo) {
    31  				loadedInfo = &info
    32  			},
    33  		},
    34  	}
    35  	opts.DisableAutomaticCompactions = true
    36  	opts.Comparer = testkeys.Comparer
    37  	opts.FormatMajorVersion = FormatRangeKeys
    38  
    39  	d, err := Open("", opts)
    40  	require.NoError(t, err)
    41  	defer func() {
    42  		if d != nil {
    43  			require.NoError(t, closeAllSnapshots(d))
    44  			require.NoError(t, d.Close())
    45  		}
    46  	}()
    47  
    48  	datadriven.RunTest(t, "testdata/table_stats", func(t *testing.T, td *datadriven.TestData) string {
    49  		switch td.Cmd {
    50  		case "disable":
    51  			d.mu.Lock()
    52  			d.opts.private.disableTableStats = true
    53  			d.mu.Unlock()
    54  			return ""
    55  
    56  		case "enable":
    57  			d.mu.Lock()
    58  			d.opts.private.disableTableStats = false
    59  			d.maybeCollectTableStatsLocked()
    60  			d.mu.Unlock()
    61  			return ""
    62  
    63  		case "define":
    64  			require.NoError(t, closeAllSnapshots(d))
    65  			require.NoError(t, d.Close())
    66  			loadedInfo = nil
    67  
    68  			d, err = runDBDefineCmd(td, opts)
    69  			if err != nil {
    70  				return err.Error()
    71  			}
    72  			d.mu.Lock()
    73  			s := d.mu.versions.currentVersion().String()
    74  			d.mu.Unlock()
    75  			return s
    76  
    77  		case "reopen":
    78  			require.NoError(t, d.Close())
    79  			loadedInfo = nil
    80  
    81  			// Open using existing file system.
    82  			d, err = Open("", opts)
    83  			require.NoError(t, err)
    84  			return ""
    85  
    86  		case "batch":
    87  			b := d.NewBatch()
    88  			if err := runBatchDefineCmd(td, b); err != nil {
    89  				return err.Error()
    90  			}
    91  			b.Commit(nil)
    92  			return ""
    93  
    94  		case "flush":
    95  			if err := d.Flush(); err != nil {
    96  				return err.Error()
    97  			}
    98  
    99  			d.mu.Lock()
   100  			s := d.mu.versions.currentVersion().String()
   101  			d.mu.Unlock()
   102  			return s
   103  
   104  		case "ingest":
   105  			if err = runBuildCmd(td, d, d.opts.FS); err != nil {
   106  				return err.Error()
   107  			}
   108  			if err = runIngestCmd(td, d, d.opts.FS); err != nil {
   109  				return err.Error()
   110  			}
   111  			d.mu.Lock()
   112  			s := d.mu.versions.currentVersion().String()
   113  			d.mu.Unlock()
   114  			return s
   115  
   116  		case "metric":
   117  			m := d.Metrics()
   118  			// TODO(jackson): Make a generalized command that uses reflection to
   119  			// pull out arbitrary Metrics fields.
   120  			var buf bytes.Buffer
   121  			for _, arg := range td.CmdArgs {
   122  				switch arg.String() {
   123  				case "keys.missized-tombstones-count":
   124  					fmt.Fprintf(&buf, "%s: %d", arg.String(), m.Keys.MissizedTombstonesCount)
   125  				default:
   126  					return fmt.Sprintf("unrecognized metric %s", arg)
   127  				}
   128  			}
   129  			return buf.String()
   130  
   131  		case "lsm":
   132  			d.mu.Lock()
   133  			s := d.mu.versions.currentVersion().String()
   134  			d.mu.Unlock()
   135  			return s
   136  
   137  		case "build":
   138  			if err := runBuildCmd(td, d, d.opts.FS); err != nil {
   139  				return err.Error()
   140  			}
   141  			return ""
   142  
   143  		case "ingest-and-excise":
   144  			if err := runIngestAndExciseCmd(td, d, d.opts.FS); err != nil {
   145  				return err.Error()
   146  			}
   147  			// Wait for a possible flush.
   148  			d.mu.Lock()
   149  			for d.mu.compact.flushing {
   150  				d.mu.compact.cond.Wait()
   151  			}
   152  			d.mu.Unlock()
   153  			return ""
   154  
   155  		case "wait-pending-table-stats":
   156  			return runTableStatsCmd(td, d)
   157  
   158  		case "wait-loaded-initial":
   159  			d.mu.Lock()
   160  			for d.mu.tableStats.loading || !d.mu.tableStats.loadedInitial {
   161  				d.mu.tableStats.cond.Wait()
   162  			}
   163  			s := loadedInfo.String()
   164  			d.mu.Unlock()
   165  			return s
   166  
   167  		case "compact":
   168  			if err := runCompactCmd(td, d); err != nil {
   169  				return err.Error()
   170  			}
   171  			d.mu.Lock()
   172  			// Disable the "dynamic base level" code for this test.
   173  			d.mu.versions.picker.forceBaseLevel1()
   174  			s := d.mu.versions.currentVersion().String()
   175  			d.mu.Unlock()
   176  			return s
   177  
   178  		case "metadata-stats":
   179  			// Prints some metadata about some sstable which is currently in the
   180  			// latest version.
   181  			return runMetadataCommand(t, td, d)
   182  
   183  		case "properties":
   184  			return runSSTablePropertiesCmd(t, td, d)
   185  
   186  		default:
   187  			return fmt.Sprintf("unknown command: %s", td.Cmd)
   188  		}
   189  	})
   190  }
   191  
   192  func TestTableRangeDeletionIter(t *testing.T) {
   193  	var m *fileMetadata
   194  	cmp := base.DefaultComparer.Compare
   195  	fs := vfs.NewMem()
   196  	datadriven.RunTest(t, "testdata/table_stats_deletion_iter", func(t *testing.T, td *datadriven.TestData) string {
   197  		switch cmd := td.Cmd; cmd {
   198  		case "build":
   199  			f, err := fs.Create("tmp.sst")
   200  			if err != nil {
   201  				return err.Error()
   202  			}
   203  			w := sstable.NewWriter(objstorageprovider.NewFileWritable(f), sstable.WriterOptions{
   204  				TableFormat: sstable.TableFormatMax,
   205  			})
   206  			m = &fileMetadata{}
   207  			for _, line := range strings.Split(td.Input, "\n") {
   208  				s := keyspan.ParseSpan(line)
   209  				// Range dels can be written sequentially. Range keys must be collected.
   210  				rKeySpan := &keyspan.Span{Start: s.Start, End: s.End}
   211  				for _, k := range s.Keys {
   212  					if rangekey.IsRangeKey(k.Kind()) {
   213  						rKeySpan.Keys = append(rKeySpan.Keys, k)
   214  					} else {
   215  						k := base.InternalKey{UserKey: s.Start, Trailer: k.Trailer}
   216  						if err = w.Add(k, s.End); err != nil {
   217  							return err.Error()
   218  						}
   219  					}
   220  				}
   221  				err = rangekey.Encode(rKeySpan, func(k base.InternalKey, v []byte) error {
   222  					return w.AddRangeKey(k, v)
   223  				})
   224  				if err != nil {
   225  					return err.Error()
   226  				}
   227  			}
   228  			if err = w.Close(); err != nil {
   229  				return err.Error()
   230  			}
   231  			meta, err := w.Metadata()
   232  			if err != nil {
   233  				return err.Error()
   234  			}
   235  			if meta.HasPointKeys {
   236  				m.ExtendPointKeyBounds(cmp, meta.SmallestPoint, meta.LargestPoint)
   237  			}
   238  			if meta.HasRangeDelKeys {
   239  				m.ExtendPointKeyBounds(cmp, meta.SmallestRangeDel, meta.LargestRangeDel)
   240  			}
   241  			if meta.HasRangeKeys {
   242  				m.ExtendRangeKeyBounds(cmp, meta.SmallestRangeKey, meta.LargestRangeKey)
   243  			}
   244  			return m.DebugString(base.DefaultFormatter, false /* verbose */)
   245  		case "spans":
   246  			f, err := fs.Open("tmp.sst")
   247  			if err != nil {
   248  				return err.Error()
   249  			}
   250  			var r *sstable.Reader
   251  			readable, err := sstable.NewSimpleReadable(f)
   252  			if err != nil {
   253  				return err.Error()
   254  			}
   255  			r, err = sstable.NewReader(readable, sstable.ReaderOptions{})
   256  			if err != nil {
   257  				return err.Error()
   258  			}
   259  			defer r.Close()
   260  			iter, err := newCombinedDeletionKeyspanIter(base.DefaultComparer, r, m)
   261  			if err != nil {
   262  				return err.Error()
   263  			}
   264  			defer iter.Close()
   265  			var buf bytes.Buffer
   266  			for s := iter.First(); s != nil; s = iter.Next() {
   267  				buf.WriteString(s.String() + "\n")
   268  			}
   269  			if buf.Len() == 0 {
   270  				return "(none)"
   271  			}
   272  			return buf.String()
   273  		default:
   274  			return fmt.Sprintf("unknown command: %s", cmd)
   275  		}
   276  	})
   277  }