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 }