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  }