github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/storage/metamorphic/generator.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package metamorphic
    12  
    13  import (
    14  	"bufio"
    15  	"context"
    16  	"fmt"
    17  	"io"
    18  	"math/rand"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/base"
    23  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    24  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    25  	"github.com/cockroachdb/cockroach/pkg/storage"
    26  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    27  	"github.com/cockroachdb/pebble"
    28  )
    29  
    30  const zipfMax uint64 = 100000
    31  
    32  func makeStorageConfig(path string) base.StorageConfig {
    33  	return base.StorageConfig{
    34  		Dir:      path,
    35  		Settings: cluster.MakeTestingClusterSettings(),
    36  	}
    37  }
    38  
    39  func createTestRocksDBEngine(path string, seed int64) (storage.Engine, error) {
    40  	cache := storage.NewRocksDBCache(1 << 20)
    41  	defer cache.Release()
    42  	cfg := storage.RocksDBConfig{
    43  		StorageConfig: makeStorageConfig(path),
    44  		ReadOnly:      false,
    45  	}
    46  
    47  	return storage.NewRocksDB(cfg, cache)
    48  }
    49  
    50  func createTestPebbleEngine(path string, seed int64) (storage.Engine, error) {
    51  	pebbleConfig := storage.PebbleConfig{
    52  		StorageConfig: makeStorageConfig(path),
    53  		Opts:          storage.DefaultPebbleOptions(),
    54  	}
    55  	pebbleConfig.Opts.Cache = pebble.NewCache(1 << 20)
    56  	defer pebbleConfig.Opts.Cache.Unref()
    57  
    58  	return storage.NewPebble(context.Background(), pebbleConfig)
    59  }
    60  
    61  func createTestPebbleManySSTs(path string, seed int64) (storage.Engine, error) {
    62  	pebbleConfig := storage.PebbleConfig{
    63  		StorageConfig: makeStorageConfig(path),
    64  		Opts:          storage.DefaultPebbleOptions(),
    65  	}
    66  	levels := pebbleConfig.Opts.Levels
    67  	for i := range levels {
    68  		if i == 0 {
    69  			levels[i].TargetFileSize = 1 << 8 // 256 bytes
    70  		} else {
    71  			levels[i].TargetFileSize = levels[i-1].TargetFileSize * 2
    72  		}
    73  	}
    74  	pebbleConfig.Opts.Cache = pebble.NewCache(1 << 20)
    75  	defer pebbleConfig.Opts.Cache.Unref()
    76  
    77  	return storage.NewPebble(context.Background(), pebbleConfig)
    78  }
    79  
    80  func rngIntRange(rng *rand.Rand, min int64, max int64) int64 {
    81  	return min + rng.Int63n(max)
    82  }
    83  
    84  func createTestPebbleVarOpts(path string, seed int64) (storage.Engine, error) {
    85  	opts := storage.DefaultPebbleOptions()
    86  
    87  	rng := rand.New(rand.NewSource(seed))
    88  	opts.BytesPerSync = 1 << rngIntRange(rng, 8, 30)
    89  	opts.LBaseMaxBytes = 1 << rngIntRange(rng, 8, 30)
    90  	opts.L0CompactionThreshold = int(rngIntRange(rng, 1, 10))
    91  	opts.L0StopWritesThreshold = int(rngIntRange(rng, 1, 32))
    92  	if opts.L0StopWritesThreshold < opts.L0CompactionThreshold {
    93  		opts.L0StopWritesThreshold = opts.L0CompactionThreshold
    94  	}
    95  	for i := range opts.Levels {
    96  		if i == 0 {
    97  			opts.Levels[i].BlockRestartInterval = int(rngIntRange(rng, 1, 64))
    98  			opts.Levels[i].BlockSize = 1 << rngIntRange(rng, 1, 20)
    99  			opts.Levels[i].BlockSizeThreshold = int(rngIntRange(rng, 50, 100))
   100  			opts.Levels[i].IndexBlockSize = opts.Levels[i].BlockSize
   101  			opts.Levels[i].TargetFileSize = 1 << rngIntRange(rng, 1, 20)
   102  		} else {
   103  			opts.Levels[i] = opts.Levels[i-1]
   104  			opts.Levels[i].TargetFileSize = opts.Levels[i-1].TargetFileSize * 2
   105  		}
   106  	}
   107  	opts.MaxManifestFileSize = 1 << rngIntRange(rng, 1, 28)
   108  	opts.MaxOpenFiles = int(rngIntRange(rng, 20, 2000))
   109  	opts.MemTableSize = 1 << rngIntRange(rng, 10, 28)
   110  	opts.MemTableStopWritesThreshold = int(rngIntRange(rng, 2, 7))
   111  	opts.MinCompactionRate = int(rngIntRange(rng, 1<<8, 8<<20))
   112  	opts.MinFlushRate = int(rngIntRange(rng, 1<<8, 4<<20))
   113  	opts.MaxConcurrentCompactions = int(rngIntRange(rng, 1, 4))
   114  
   115  	opts.Cache = pebble.NewCache(1 << rngIntRange(rng, 1, 30))
   116  	defer opts.Cache.Unref()
   117  
   118  	pebbleConfig := storage.PebbleConfig{
   119  		StorageConfig: makeStorageConfig(path),
   120  		Opts:          opts,
   121  	}
   122  
   123  	return storage.NewPebble(context.Background(), pebbleConfig)
   124  }
   125  
   126  type engineImpl struct {
   127  	name   string
   128  	create func(path string, seed int64) (storage.Engine, error)
   129  }
   130  
   131  var _ fmt.Stringer = &engineImpl{}
   132  
   133  func (e *engineImpl) String() string {
   134  	return e.name
   135  }
   136  
   137  var engineImplRocksDB = engineImpl{"rocksdb", createTestRocksDBEngine}
   138  var engineImplPebble = engineImpl{"pebble", createTestPebbleEngine}
   139  var engineImplPebbleManySSTs = engineImpl{"pebble_many_ssts", createTestPebbleManySSTs}
   140  var engineImplPebbleVarOpts = engineImpl{"pebble_var_opts", createTestPebbleVarOpts}
   141  
   142  // Object to store info corresponding to one metamorphic test run. Responsible
   143  // for generating and executing operations.
   144  type metaTestRunner struct {
   145  	ctx             context.Context
   146  	w               io.Writer
   147  	t               *testing.T
   148  	rng             *rand.Rand
   149  	seed            int64
   150  	path            string
   151  	engineImpls     []engineImpl
   152  	curEngine       int
   153  	restarts        bool
   154  	engine          storage.Engine
   155  	tsGenerator     tsGenerator
   156  	opGenerators    map[operandType]operandGenerator
   157  	txnGenerator    *txnGenerator
   158  	rwGenerator     *readWriterGenerator
   159  	iterGenerator   *iteratorGenerator
   160  	keyGenerator    *keyGenerator
   161  	valueGenerator  *valueGenerator
   162  	pastTSGenerator *pastTSGenerator
   163  	nextTSGenerator *nextTSGenerator
   164  	floatGenerator  *floatGenerator
   165  	openIters       map[iteratorID]iteratorInfo
   166  	openBatches     map[readWriterID]storage.ReadWriter
   167  	openTxns        map[txnID]*roachpb.Transaction
   168  	nameToGenerator map[string]*opGenerator
   169  	ops             []opRun
   170  	weights         []int
   171  }
   172  
   173  func (m *metaTestRunner) init() {
   174  	// Use a passed-in seed. Using the same seed for two consecutive metamorphic
   175  	// test runs should guarantee the same operations being generated.
   176  	m.rng = rand.New(rand.NewSource(m.seed))
   177  	m.tsGenerator.init(m.rng)
   178  	m.curEngine = 0
   179  
   180  	var err error
   181  	m.engine, err = m.engineImpls[0].create(m.path, m.seed)
   182  	if err != nil {
   183  		m.engine = nil
   184  		m.t.Fatal(err)
   185  	}
   186  
   187  	// Initialize opGenerator structs. These retain all generation time
   188  	// state of open objects.
   189  	m.txnGenerator = &txnGenerator{
   190  		rng:         m.rng,
   191  		tsGenerator: &m.tsGenerator,
   192  		txnIDMap:    make(map[txnID]*roachpb.Transaction),
   193  		openBatches: make(map[txnID]map[readWriterID]struct{}),
   194  		testRunner:  m,
   195  	}
   196  	m.rwGenerator = &readWriterGenerator{
   197  		rng:        m.rng,
   198  		m:          m,
   199  		batchIDMap: make(map[readWriterID]storage.Batch),
   200  	}
   201  	m.iterGenerator = &iteratorGenerator{
   202  		rng:          m.rng,
   203  		readerToIter: make(map[readWriterID][]iteratorID),
   204  		iterInfo:     make(map[iteratorID]iteratorInfo),
   205  	}
   206  	m.keyGenerator = &keyGenerator{
   207  		rng:         m.rng,
   208  		tsGenerator: &m.tsGenerator,
   209  	}
   210  	m.valueGenerator = &valueGenerator{m.rng}
   211  	m.pastTSGenerator = &pastTSGenerator{
   212  		rng:         m.rng,
   213  		tsGenerator: &m.tsGenerator,
   214  	}
   215  	m.nextTSGenerator = &nextTSGenerator{
   216  		pastTSGenerator: pastTSGenerator{
   217  			rng:         m.rng,
   218  			tsGenerator: &m.tsGenerator,
   219  		},
   220  	}
   221  	m.floatGenerator = &floatGenerator{rng: m.rng}
   222  
   223  	m.opGenerators = map[operandType]operandGenerator{
   224  		operandTransaction: m.txnGenerator,
   225  		operandReadWriter:  m.rwGenerator,
   226  		operandMVCCKey:     m.keyGenerator,
   227  		operandPastTS:      m.pastTSGenerator,
   228  		operandNextTS:      m.nextTSGenerator,
   229  		operandValue:       m.valueGenerator,
   230  		operandIterator:    m.iterGenerator,
   231  		operandFloat:       m.floatGenerator,
   232  	}
   233  
   234  	m.nameToGenerator = make(map[string]*opGenerator)
   235  	m.weights = make([]int, len(opGenerators))
   236  	for i := range opGenerators {
   237  		m.weights[i] = opGenerators[i].weight
   238  		m.nameToGenerator[opGenerators[i].name] = &opGenerators[i]
   239  	}
   240  	m.ops = nil
   241  	m.openIters = make(map[iteratorID]iteratorInfo)
   242  	m.openBatches = make(map[readWriterID]storage.ReadWriter)
   243  	m.openTxns = make(map[txnID]*roachpb.Transaction)
   244  }
   245  
   246  func (m *metaTestRunner) closeGenerators() {
   247  	closingOrder := []operandGenerator{
   248  		m.iterGenerator,
   249  		m.rwGenerator,
   250  		m.txnGenerator,
   251  	}
   252  	for _, generator := range closingOrder {
   253  		generator.closeAll()
   254  	}
   255  }
   256  
   257  // Run this function in a defer to ensure any Fatals on m.t do not cause panics
   258  // due to leaked iterators.
   259  func (m *metaTestRunner) closeAll() {
   260  	if m.engine == nil {
   261  		// Engine already closed; possibly running in a defer after a panic.
   262  		return
   263  	}
   264  	// If there are any open objects, close them.
   265  	for _, iter := range m.openIters {
   266  		iter.iter.Close()
   267  	}
   268  	for _, batch := range m.openBatches {
   269  		batch.Close()
   270  	}
   271  	// TODO(itsbilal): Abort all txns.
   272  	m.openIters = make(map[iteratorID]iteratorInfo)
   273  	m.openBatches = make(map[readWriterID]storage.ReadWriter)
   274  	m.openTxns = make(map[txnID]*roachpb.Transaction)
   275  	if m.engine != nil {
   276  		m.engine.Close()
   277  		m.engine = nil
   278  	}
   279  }
   280  
   281  // Getters and setters for txns, batches, and iterators.
   282  func (m *metaTestRunner) getTxn(id txnID) *roachpb.Transaction {
   283  	txn, ok := m.openTxns[id]
   284  	if !ok {
   285  		panic(fmt.Sprintf("txn with id %s not found", string(id)))
   286  	}
   287  	return txn
   288  }
   289  
   290  func (m *metaTestRunner) setTxn(id txnID, txn *roachpb.Transaction) {
   291  	m.openTxns[id] = txn
   292  }
   293  
   294  func (m *metaTestRunner) getReadWriter(id readWriterID) storage.ReadWriter {
   295  	if id == "engine" {
   296  		return m.engine
   297  	}
   298  
   299  	batch, ok := m.openBatches[id]
   300  	if !ok {
   301  		panic(fmt.Sprintf("batch with id %s not found", string(id)))
   302  	}
   303  	return batch
   304  }
   305  
   306  func (m *metaTestRunner) setReadWriter(id readWriterID, rw storage.ReadWriter) {
   307  	if id == "engine" {
   308  		// no-op
   309  		return
   310  	}
   311  	m.openBatches[id] = rw
   312  }
   313  
   314  func (m *metaTestRunner) getIterInfo(id iteratorID) iteratorInfo {
   315  	iter, ok := m.openIters[id]
   316  	if !ok {
   317  		panic(fmt.Sprintf("iter with id %s not found", string(id)))
   318  	}
   319  	return iter
   320  }
   321  
   322  func (m *metaTestRunner) setIterInfo(id iteratorID, iterInfo iteratorInfo) {
   323  	m.openIters[id] = iterInfo
   324  }
   325  
   326  // generateAndRun generates n operations using a TPCC-style deck shuffle with
   327  // weighted probabilities of each operation appearing.
   328  func (m *metaTestRunner) generateAndRun(n int) {
   329  	deck := newDeck(m.rng, m.weights...)
   330  	for i := 0; i < n; i++ {
   331  		op := &opGenerators[deck.Int()]
   332  
   333  		m.resolveAndAddOp(op)
   334  	}
   335  
   336  	for i := range m.ops {
   337  		opRun := &m.ops[i]
   338  		output := opRun.op.run(m.ctx)
   339  		m.printOp(opRun.name, opRun.args, output)
   340  	}
   341  }
   342  
   343  // Closes the current engine and starts another one up, with the same path.
   344  // Returns the engine transition that
   345  func (m *metaTestRunner) restart() (string, string) {
   346  	m.closeAll()
   347  	oldEngineName := m.engineImpls[m.curEngine].name
   348  	// TODO(itsbilal): Select engines at random instead of cycling through them.
   349  	m.curEngine++
   350  	if m.curEngine >= len(m.engineImpls) {
   351  		// If we're restarting more times than the number of engine implementations
   352  		// specified, loop back around to the first engine type specified.
   353  		m.curEngine = 0
   354  	}
   355  
   356  	var err error
   357  	m.engine, err = m.engineImpls[m.curEngine].create(m.path, m.seed)
   358  	if err != nil {
   359  		m.engine = nil
   360  		m.t.Fatal(err)
   361  	}
   362  	return oldEngineName, m.engineImpls[m.curEngine].name
   363  }
   364  
   365  func (m *metaTestRunner) parseFileAndRun(f io.Reader) {
   366  	reader := bufio.NewReader(f)
   367  	lineCount := uint64(0)
   368  	for {
   369  		var opName, argListString, expectedOutput string
   370  		var firstByte byte
   371  		var err error
   372  
   373  		lineCount++
   374  		// Read the first byte to check if this line is a comment.
   375  		firstByte, err = reader.ReadByte()
   376  		if err != nil {
   377  			if err == io.EOF {
   378  				break
   379  			}
   380  			m.t.Fatal(err)
   381  		}
   382  		if firstByte == '#' {
   383  			// Advance to the end of the line and continue.
   384  			if _, err := reader.ReadString('\n'); err != nil {
   385  				if err == io.EOF {
   386  					break
   387  				}
   388  				m.t.Fatal(err)
   389  			}
   390  			continue
   391  		}
   392  
   393  		if opName, err = reader.ReadString('('); err != nil {
   394  			if err == io.EOF {
   395  				break
   396  			}
   397  			m.t.Fatal(err)
   398  		}
   399  		opName = string(firstByte) + opName[:len(opName)-1]
   400  
   401  		if argListString, err = reader.ReadString(')'); err != nil {
   402  			m.t.Fatal(err)
   403  		}
   404  
   405  		// Parse argument list
   406  		argStrings := strings.Split(argListString, ", ")
   407  		// Special handling for last element: could end with ), or could just be )
   408  		lastElem := argStrings[len(argStrings)-1]
   409  		if strings.HasSuffix(lastElem, ")") {
   410  			lastElem = lastElem[:len(lastElem)-1]
   411  			if len(lastElem) > 0 {
   412  				argStrings[len(argStrings)-1] = lastElem
   413  			} else {
   414  				argStrings = argStrings[:len(argStrings)-1]
   415  			}
   416  		} else {
   417  			m.t.Fatalf("while parsing: last element %s did not have ) suffix", lastElem)
   418  		}
   419  
   420  		if _, err = reader.ReadString('>'); err != nil {
   421  			m.t.Fatal(err)
   422  		}
   423  		// Space after arrow.
   424  		if _, err = reader.Discard(1); err != nil {
   425  			m.t.Fatal(err)
   426  		}
   427  		if expectedOutput, err = reader.ReadString('\n'); err != nil {
   428  			m.t.Fatal(err)
   429  		}
   430  		opGenerator := m.nameToGenerator[opName]
   431  		m.ops = append(m.ops, opRun{
   432  			name:           opGenerator.name,
   433  			op:             opGenerator.generate(m.ctx, m, argStrings...),
   434  			args:           argStrings,
   435  			lineNum:        lineCount,
   436  			expectedOutput: expectedOutput,
   437  		})
   438  	}
   439  
   440  	for i := range m.ops {
   441  		op := &m.ops[i]
   442  		actualOutput := op.op.run(m.ctx)
   443  		m.printOp(op.name, op.args, actualOutput)
   444  		if strings.Compare(strings.TrimSpace(op.expectedOutput), strings.TrimSpace(actualOutput)) != 0 {
   445  			// Error messages can sometimes mismatch. If both outputs contain "error",
   446  			// consider this a pass.
   447  			if strings.Contains(op.expectedOutput, "error") && strings.Contains(actualOutput, "error") {
   448  				continue
   449  			}
   450  			m.t.Fatalf("mismatching output at line %d: expected %s, got %s", op.lineNum, op.expectedOutput, actualOutput)
   451  		}
   452  	}
   453  }
   454  
   455  func (m *metaTestRunner) generateAndAddOp(run opReference) mvccOp {
   456  	opGenerator := run.generator
   457  
   458  	// This operation might require other operations to run before it runs. Call
   459  	// the dependentOps method to resolve these dependencies.
   460  	if opGenerator.dependentOps != nil {
   461  		for _, opReference := range opGenerator.dependentOps(m, run.args...) {
   462  			m.generateAndAddOp(opReference)
   463  		}
   464  	}
   465  
   466  	op := opGenerator.generate(m.ctx, m, run.args...)
   467  	m.ops = append(m.ops, opRun{
   468  		name: opGenerator.name,
   469  		op:   op,
   470  		args: run.args,
   471  	})
   472  	return op
   473  }
   474  
   475  // Resolve all operands (including recursively queueing openers for operands as
   476  // necessary) and add the specified operation to the operations list.
   477  func (m *metaTestRunner) resolveAndAddOp(op *opGenerator) {
   478  	argStrings := make([]string, len(op.operands))
   479  
   480  	// Operation op depends on some operands to exist in an open state.
   481  	// If those operands' opGenerators report a zero count for that object's open
   482  	// instances, recursively call generateAndAddOp with that operand type's
   483  	// opener.
   484  	for i, operand := range op.operands {
   485  		opGenerator := m.opGenerators[operand]
   486  		// Special case: if this is an opener operation, and the operand is the
   487  		// last one in the list of operands, call getNew() to get a new ID instead.
   488  		if i == len(op.operands)-1 && op.isOpener {
   489  			argStrings[i] = opGenerator.getNew()
   490  			continue
   491  		}
   492  		if opGenerator.count() == 0 {
   493  			// Add this operation to the list first, so that it creates the
   494  			// dependency.
   495  			m.resolveAndAddOp(m.nameToGenerator[opGenerator.opener()])
   496  		}
   497  		argStrings[i] = opGenerator.get()
   498  	}
   499  
   500  	m.generateAndAddOp(opReference{
   501  		generator: op,
   502  		args:      argStrings,
   503  	})
   504  }
   505  
   506  // Print passed-in operation, arguments and output string to output file.
   507  func (m *metaTestRunner) printOp(opName string, argStrings []string, output string) {
   508  	fmt.Fprintf(m.w, "%s(", opName)
   509  	for i, arg := range argStrings {
   510  		if i > 0 {
   511  			fmt.Fprintf(m.w, ", ")
   512  		}
   513  		fmt.Fprintf(m.w, "%s", arg)
   514  	}
   515  	fmt.Fprintf(m.w, ") -> %s\n", output)
   516  }
   517  
   518  // printComment prints a comment line into the output file. Supports single-line
   519  // comments only.
   520  func (m *metaTestRunner) printComment(comment string) {
   521  	fmt.Fprintf(m.w, "# %s\n", comment)
   522  }
   523  
   524  // Monotonically increasing timestamp generator.
   525  type tsGenerator struct {
   526  	lastTS hlc.Timestamp
   527  	zipf   *rand.Zipf
   528  }
   529  
   530  func (t *tsGenerator) init(rng *rand.Rand) {
   531  	t.zipf = rand.NewZipf(rng, 2, 5, zipfMax)
   532  }
   533  
   534  func (t *tsGenerator) generate() hlc.Timestamp {
   535  	t.lastTS.WallTime++
   536  	return t.lastTS
   537  }
   538  
   539  func (t *tsGenerator) randomPastTimestamp(rng *rand.Rand) hlc.Timestamp {
   540  	var result hlc.Timestamp
   541  
   542  	// Return a result that's skewed toward the latest wall time.
   543  	result.WallTime = int64(float64(t.lastTS.WallTime) * float64((zipfMax - t.zipf.Uint64())) / float64(zipfMax))
   544  	return result
   545  }