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 }