github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/metamorphic/options.go (about) 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 metamorphic 6 7 import ( 8 "bytes" 9 "fmt" 10 "os" 11 "path/filepath" 12 "time" 13 14 "github.com/zuoyebang/bitalostable" 15 "github.com/zuoyebang/bitalostable/bloom" 16 "github.com/zuoyebang/bitalostable/internal/cache" 17 "github.com/zuoyebang/bitalostable/internal/testkeys" 18 "github.com/zuoyebang/bitalostable/internal/testkeys/blockprop" 19 "github.com/zuoyebang/bitalostable/vfs" 20 "golang.org/x/exp/rand" 21 ) 22 23 func parseOptions(opts *testOptions, data string) error { 24 hooks := &bitalostable.ParseHooks{ 25 NewCache: bitalostable.NewCache, 26 NewFilterPolicy: func(name string) (bitalostable.FilterPolicy, error) { 27 if name == "none" { 28 return nil, nil 29 } 30 return bloom.FilterPolicy(10), nil 31 }, 32 SkipUnknown: func(name, value string) bool { 33 switch name { 34 case "TestOptions": 35 return true 36 case "TestOptions.strictfs": 37 opts.strictFS = true 38 return true 39 case "TestOptions.ingest_using_apply": 40 opts.ingestUsingApply = true 41 return true 42 case "TestOptions.replace_single_delete": 43 opts.replaceSingleDelete = true 44 return true 45 case "TestOptions.use_disk": 46 opts.useDisk = true 47 return true 48 case "TestOptions.initial_state_desc": 49 opts.initialStateDesc = value 50 return true 51 case "TestOptions.initial_state_path": 52 opts.initialStatePath = value 53 return true 54 case "TestOptions.use_block_property_collector": 55 opts.useBlockPropertyCollector = true 56 opts.opts.BlockPropertyCollectors = blockPropertyCollectorConstructors 57 return true 58 default: 59 return false 60 } 61 }, 62 } 63 err := opts.opts.Parse(data, hooks) 64 return err 65 } 66 67 func optionsToString(opts *testOptions) string { 68 var buf bytes.Buffer 69 if opts.strictFS { 70 fmt.Fprint(&buf, " strictfs=true\n") 71 } 72 if opts.ingestUsingApply { 73 fmt.Fprint(&buf, " ingest_using_apply=true\n") 74 } 75 if opts.replaceSingleDelete { 76 fmt.Fprint(&buf, " replace_single_delete=true\n") 77 } 78 if opts.useDisk { 79 fmt.Fprint(&buf, " use_disk=true\n") 80 } 81 if opts.initialStatePath != "" { 82 fmt.Fprintf(&buf, " initial_state_path=%s\n", opts.initialStatePath) 83 } 84 if opts.initialStateDesc != "" { 85 fmt.Fprintf(&buf, " initial_state_desc=%s\n", opts.initialStateDesc) 86 } 87 if opts.useBlockPropertyCollector { 88 fmt.Fprintf(&buf, " use_block_property_collector=%t\n", opts.useBlockPropertyCollector) 89 } 90 91 s := opts.opts.String() 92 if buf.Len() == 0 { 93 return s 94 } 95 return s + "\n[TestOptions]\n" + buf.String() 96 } 97 98 func defaultTestOptions() *testOptions { 99 return &testOptions{ 100 opts: defaultOptions(), 101 useBlockPropertyCollector: true, 102 } 103 } 104 105 func defaultOptions() *bitalostable.Options { 106 opts := &bitalostable.Options{ 107 Comparer: testkeys.Comparer, 108 FS: vfs.NewMem(), 109 FormatMajorVersion: bitalostable.FormatNewest, 110 Levels: []bitalostable.LevelOptions{{ 111 FilterPolicy: bloom.FilterPolicy(10), 112 }}, 113 } 114 opts.EnsureDefaults() 115 return opts 116 } 117 118 type testOptions struct { 119 opts *bitalostable.Options 120 useDisk bool 121 strictFS bool 122 // Use Batch.Apply rather than DB.Ingest. 123 ingestUsingApply bool 124 // Replace a SINGLEDEL with a DELETE. 125 replaceSingleDelete bool 126 // The path on the local filesystem where the initial state of the database 127 // exists. Empty if the test run begins from an empty database state. 128 initialStatePath string 129 // A human-readable string describing the initial state of the database. 130 // Empty if the test run begins from an empty database state. 131 initialStateDesc string 132 // Use a block property collector, which may be used by block property 133 // filters. 134 useBlockPropertyCollector bool 135 } 136 137 func standardOptions() []*testOptions { 138 // The index labels are not strictly necessary, but they make it easier to 139 // find which options correspond to a failure. 140 stdOpts := []string{ 141 0: "", // default options 142 1: ` 143 [Options] 144 cache_size=1 145 `, 146 2: ` 147 [Options] 148 disable_wal=true 149 `, 150 3: ` 151 [Options] 152 l0_compaction_threshold=1 153 `, 154 4: ` 155 [Options] 156 l0_compaction_threshold=1 157 l0_stop_writes_threshold=1 158 `, 159 5: ` 160 [Options] 161 lbase_max_bytes=1 162 `, 163 6: ` 164 [Options] 165 max_manifest_file_size=1 166 `, 167 7: ` 168 [Options] 169 max_open_files=1 170 `, 171 8: ` 172 [Options] 173 mem_table_size=2000 174 `, 175 9: ` 176 [Options] 177 mem_table_stop_writes_threshold=2 178 `, 179 10: ` 180 [Options] 181 wal_dir=data/wal 182 `, 183 11: ` 184 [Level "0"] 185 block_restart_interval=1 186 `, 187 12: ` 188 [Level "0"] 189 block_size=1 190 `, 191 13: ` 192 [Level "0"] 193 compression=NoCompression 194 `, 195 14: ` 196 [Level "0"] 197 index_block_size=1 198 `, 199 15: ` 200 [Level "0"] 201 target_file_size=1 202 `, 203 16: ` 204 [Level "0"] 205 filter_policy=none 206 `, 207 // 1GB 208 17: ` 209 [Options] 210 bytes_per_sync=1073741824 211 [TestOptions] 212 strictfs=true 213 `, 214 18: ` 215 [Options] 216 max_concurrent_compactions=2 217 `, 218 19: ` 219 [TestOptions] 220 ingest_using_apply=true 221 `, 222 20: ` 223 [TestOptions] 224 replace_single_delete=true 225 `, 226 21: ` 227 [TestOptions] 228 use_disk=true 229 `, 230 22: ` 231 [Options] 232 max_writer_concurrency=2 233 force_writer_parallelism=true 234 `, 235 23: ` 236 [TestOptions] 237 use_block_property_collector=false 238 `, 239 } 240 241 opts := make([]*testOptions, len(stdOpts)) 242 for i := range opts { 243 opts[i] = defaultTestOptions() 244 if err := parseOptions(opts[i], stdOpts[i]); err != nil { 245 panic(err) 246 } 247 } 248 return opts 249 } 250 251 func randomOptions(rng *rand.Rand) *testOptions { 252 var testOpts = &testOptions{} 253 opts := defaultOptions() 254 opts.BytesPerSync = 1 << uint(rng.Intn(28)) // 1B - 256MB 255 opts.Cache = cache.New(1 << uint(rng.Intn(30))) // 1B - 1GB 256 opts.DisableWAL = rng.Intn(2) == 0 257 opts.FlushDelayDeleteRange = time.Millisecond * time.Duration(5*rng.Intn(245)) // 5-250ms 258 opts.FlushDelayRangeKey = time.Millisecond * time.Duration(5*rng.Intn(245)) // 5-250ms 259 opts.FlushSplitBytes = 1 << rng.Intn(20) // 1B - 1MB 260 // The metamorphic test exercise range keys, so we cannot use an older 261 // FormatMajorVersion than bitalostable.FormatRangeKeys. 262 opts.FormatMajorVersion = bitalostable.FormatRangeKeys 263 n := int(bitalostable.FormatNewest - opts.FormatMajorVersion) 264 if n > 0 { 265 opts.FormatMajorVersion += bitalostable.FormatMajorVersion(rng.Intn(n)) 266 } 267 opts.Experimental.L0CompactionConcurrency = 1 + rng.Intn(4) // 1-4 268 opts.Experimental.MinDeletionRate = 1 << uint(20+rng.Intn(10)) // 1MB - 1GB 269 opts.Experimental.ValidateOnIngest = rng.Intn(2) != 0 270 opts.L0CompactionThreshold = 1 + rng.Intn(100) // 1 - 100 271 opts.L0CompactionFileThreshold = 1 << rng.Intn(11) // 1 - 1024 272 opts.L0StopWritesThreshold = 1 + rng.Intn(100) // 1 - 100 273 if opts.L0StopWritesThreshold < opts.L0CompactionThreshold { 274 opts.L0StopWritesThreshold = opts.L0CompactionThreshold 275 } 276 opts.LBaseMaxBytes = 1 << uint(rng.Intn(30)) // 1B - 1GB 277 maxConcurrentCompactions := rng.Intn(3) + 1 // 1-3 278 opts.MaxConcurrentCompactions = func() int { 279 return maxConcurrentCompactions 280 } 281 opts.MaxManifestFileSize = 1 << uint(rng.Intn(30)) // 1B - 1GB 282 opts.MemTableSize = 2 << (10 + uint(rng.Intn(16))) // 2KB - 256MB 283 opts.MemTableStopWritesThreshold = 2 + rng.Intn(5) // 2 - 5 284 if rng.Intn(2) == 0 { 285 opts.WALDir = "data/wal" 286 } 287 if rng.Intn(4) == 0 { 288 // Enable Writer parallelism for 25% of the random options. Setting 289 // MaxWriterConcurrency to any value greater than or equal to 1 has the 290 // same effect currently. 291 opts.Experimental.MaxWriterConcurrency = 2 292 opts.Experimental.ForceWriterParallelism = true 293 } 294 var lopts bitalostable.LevelOptions 295 lopts.BlockRestartInterval = 1 + rng.Intn(64) // 1 - 64 296 lopts.BlockSize = 1 << uint(rng.Intn(24)) // 1 - 16MB 297 lopts.BlockSizeThreshold = 50 + rng.Intn(50) // 50 - 100 298 lopts.IndexBlockSize = 1 << uint(rng.Intn(24)) // 1 - 16MB 299 lopts.TargetFileSize = 1 << uint(rng.Intn(28)) // 1 - 256MB 300 opts.Levels = []bitalostable.LevelOptions{lopts} 301 302 testOpts.opts = opts 303 // Explicitly disable disk-backed FS's for the random configurations. The 304 // single standard test configuration that uses a disk-backed FS is 305 // sufficient. 306 testOpts.useDisk = false 307 testOpts.strictFS = rng.Intn(2) != 0 // Only relevant for MemFS. 308 if testOpts.strictFS { 309 opts.DisableWAL = false 310 } 311 testOpts.ingestUsingApply = rng.Intn(2) != 0 312 testOpts.replaceSingleDelete = rng.Intn(2) != 0 313 testOpts.useBlockPropertyCollector = rng.Intn(2) != 0 314 if testOpts.useBlockPropertyCollector { 315 testOpts.opts.BlockPropertyCollectors = blockPropertyCollectorConstructors 316 } 317 return testOpts 318 } 319 320 func setupInitialState(dir string, testOpts *testOptions) error { 321 // Copy (vfs.Default,<initialStatePath>) to (testOpts.opts.FS,<dir>). 322 ok, err := vfs.Clone( 323 vfs.Default, 324 testOpts.opts.FS, 325 testOpts.initialStatePath, 326 dir, 327 vfs.CloneSync, 328 vfs.CloneSkip(func(filename string) bool { 329 // Skip the archive of historical files, any checkpoints created by 330 // operations and files staged for ingest in tmp. 331 b := filepath.Base(filename) 332 return b == "archive" || b == "checkpoints" || b == "tmp" 333 })) 334 if err != nil { 335 return err 336 } else if !ok { 337 return os.ErrNotExist 338 } 339 340 // Tests with wal_dir set store their WALs in a `wal` directory. The source 341 // database (initialStatePath) could've had wal_dir set, or the current test 342 // options (testOpts) could have wal_dir set, or both. 343 fs := testOpts.opts.FS 344 walDir := fs.PathJoin(dir, "wal") 345 if err := fs.MkdirAll(walDir, os.ModePerm); err != nil { 346 return err 347 } 348 349 // Copy <dir>/wal/*.log -> <dir>. 350 src, dst := walDir, dir 351 if testOpts.opts.WALDir != "" { 352 // Copy <dir>/*.log -> <dir>/wal. 353 src, dst = dst, src 354 } 355 return moveLogs(fs, src, dst) 356 } 357 358 func moveLogs(fs vfs.FS, srcDir, dstDir string) error { 359 ls, err := fs.List(srcDir) 360 if err != nil { 361 return err 362 } 363 for _, f := range ls { 364 if filepath.Ext(f) != ".log" { 365 continue 366 } 367 src := fs.PathJoin(srcDir, f) 368 dst := fs.PathJoin(dstDir, f) 369 if err := fs.Rename(src, dst); err != nil { 370 return err 371 } 372 } 373 return nil 374 } 375 376 var blockPropertyCollectorConstructors = []func() bitalostable.BlockPropertyCollector{ 377 blockprop.NewBlockPropertyCollector, 378 }