diff --git a/cmd/gemini/root.go b/cmd/gemini/root.go index ba4c76cc..63b55aa4 100644 --- a/cmd/gemini/root.go +++ b/cmd/gemini/root.go @@ -63,6 +63,7 @@ var ( outFileArg string concurrency uint64 seed uint64 + schemaSeed uint64 dropSchema bool verbose bool mode string @@ -145,6 +146,8 @@ func run(_ *cobra.Command, _ []string) error { globalStatus := status.NewGlobalStatus(1000) defer utils.IgnoreError(logger.Sync) + rand.Seed(seed) + cons, err := gocql.ParseConsistencyWrapper(consistency) if err != nil { logger.Error("Unable parse consistency, error=%s. Falling back on Quorum", zap.Error(err)) @@ -198,7 +201,7 @@ func run(_ *cobra.Command, _ []string) error { return errors.Wrap(err, "cannot create schema") } } else { - schema = generators.GenSchema(schemaConfig) + schema = generators.GenSchema(schemaConfig, schemaSeed) } jsonSchema, _ := json.MarshalIndent(schema, "", " ") @@ -466,7 +469,8 @@ func init() { rootCmd.Flags().StringVarP(&schemaFile, "schema", "", "", "Schema JSON config file") rootCmd.Flags().StringVarP(&mode, "mode", "m", jobs.MixedMode, "Query operation mode. Mode options: write, read, mixed (default)") rootCmd.Flags().Uint64VarP(&concurrency, "concurrency", "c", 10, "Number of threads per table to run concurrently") - rootCmd.Flags().Uint64VarP(&seed, "seed", "s", 1, "PRNG seed value") + rootCmd.Flags().Uint64VarP(&seed, "seed", "s", 1, "Statement seed value") + rootCmd.Flags().Uint64VarP(&schemaSeed, "schema-seed", "", 1, "Schema seed value") rootCmd.Flags().BoolVarP(&dropSchema, "drop-schema", "d", false, "Drop schema before starting tests run") rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output during test run") rootCmd.Flags().BoolVarP(&failFast, "fail-fast", "f", false, "Stop on the first failure") @@ -533,8 +537,8 @@ func init() { func printSetup() error { tw := new(tabwriter.Writer) tw.Init(os.Stdout, 0, 8, 2, '\t', tabwriter.AlignRight) - rand.Seed(seed) fmt.Fprintf(tw, "Seed:\t%d\n", seed) + fmt.Fprintf(tw, "Schema seed:\t%d\n", schemaSeed) fmt.Fprintf(tw, "Maximum duration:\t%s\n", duration) fmt.Fprintf(tw, "Warmup duration:\t%s\n", warmup) fmt.Fprintf(tw, "Concurrency:\t%d\n", concurrency) diff --git a/pkg/generators/column_generator.go b/pkg/generators/column_generator.go index bcd00faa..254d1f80 100644 --- a/pkg/generators/column_generator.go +++ b/pkg/generators/column_generator.go @@ -26,51 +26,51 @@ func GenColumnName(prefix string, idx int) string { return fmt.Sprintf("%s%d", prefix, idx) } -func GenColumnType(numColumns int, sc *typedef.SchemaConfig) typedef.Type { - n := rand.Intn(numColumns + 5) +func GenColumnType(numColumns int, sc *typedef.SchemaConfig, r *rand.Rand) typedef.Type { + n := r.Intn(numColumns + 5) switch n { case numColumns: - return GenTupleType(sc) + return GenTupleType(sc, r) case numColumns + 1: - return GenUDTType(sc) + return GenUDTType(sc, r) case numColumns + 2: - return GenSetType(sc) + return GenSetType(sc, r) case numColumns + 3: - return GenListType(sc) + return GenListType(sc, r) case numColumns + 4: - return GenMapType(sc) + return GenMapType(sc, r) default: - return GenSimpleType(sc) + return GenSimpleType(sc, r) } } -func GenSimpleType(_ *typedef.SchemaConfig) typedef.SimpleType { - return typedef.AllTypes[rand.Intn(len(typedef.AllTypes))] +func GenSimpleType(_ *typedef.SchemaConfig, r *rand.Rand) typedef.SimpleType { + return typedef.AllTypes[r.Intn(len(typedef.AllTypes))] } -func GenTupleType(sc *typedef.SchemaConfig) typedef.Type { - n := rand.Intn(sc.MaxTupleParts) +func GenTupleType(sc *typedef.SchemaConfig, r *rand.Rand) typedef.Type { + n := r.Intn(sc.MaxTupleParts) if n < 2 { n = 2 } typeList := make([]typedef.SimpleType, n) for i := 0; i < n; i++ { - typeList[i] = GenSimpleType(sc) + typeList[i] = GenSimpleType(sc, r) } return &typedef.TupleType{ ComplexType: typedef.TYPE_TUPLE, ValueTypes: typeList, - Frozen: rand.Uint32()%2 == 0, + Frozen: r.Uint32()%2 == 0, } } -func GenUDTType(sc *typedef.SchemaConfig) *typedef.UDTType { - udtNum := rand.Uint32() +func GenUDTType(sc *typedef.SchemaConfig, r *rand.Rand) *typedef.UDTType { + udtNum := r.Uint32() typeName := fmt.Sprintf("udt_%d", udtNum) ts := make(map[string]typedef.SimpleType) - for i := 0; i < rand.Intn(sc.MaxUDTParts)+1; i++ { - ts[typeName+fmt.Sprintf("_%d", i)] = GenSimpleType(sc) + for i := 0; i < r.Intn(sc.MaxUDTParts)+1; i++ { + ts[typeName+fmt.Sprintf("_%d", i)] = GenSimpleType(sc, r) } return &typedef.UDTType{ @@ -81,18 +81,18 @@ func GenUDTType(sc *typedef.SchemaConfig) *typedef.UDTType { } } -func GenSetType(sc *typedef.SchemaConfig) *typedef.BagType { - return genBagType(typedef.TYPE_SET, sc) +func GenSetType(sc *typedef.SchemaConfig, r *rand.Rand) *typedef.BagType { + return genBagType(typedef.TYPE_SET, sc, r) } -func GenListType(sc *typedef.SchemaConfig) *typedef.BagType { - return genBagType(typedef.TYPE_LIST, sc) +func GenListType(sc *typedef.SchemaConfig, r *rand.Rand) *typedef.BagType { + return genBagType(typedef.TYPE_LIST, sc, r) } -func genBagType(kind string, sc *typedef.SchemaConfig) *typedef.BagType { +func genBagType(kind string, sc *typedef.SchemaConfig, r *rand.Rand) *typedef.BagType { var t typedef.SimpleType for { - t = GenSimpleType(sc) + t = GenSimpleType(sc, r) if t != typedef.TYPE_DURATION { break } @@ -100,32 +100,32 @@ func genBagType(kind string, sc *typedef.SchemaConfig) *typedef.BagType { return &typedef.BagType{ ComplexType: kind, ValueType: t, - Frozen: rand.Uint32()%2 == 0, + Frozen: r.Uint32()%2 == 0, } } -func GenMapType(sc *typedef.SchemaConfig) *typedef.MapType { - t := GenSimpleType(sc) +func GenMapType(sc *typedef.SchemaConfig, r *rand.Rand) *typedef.MapType { + t := GenSimpleType(sc, r) for { if _, ok := typedef.TypesMapKeyBlacklist[t]; !ok { break } - t = GenSimpleType(sc) + t = GenSimpleType(sc, r) } return &typedef.MapType{ ComplexType: typedef.TYPE_MAP, KeyType: t, - ValueType: GenSimpleType(sc), - Frozen: rand.Uint32()%2 == 0, + ValueType: GenSimpleType(sc, r), + Frozen: r.Uint32()%2 == 0, } } -func GenPartitionKeyColumnType() typedef.Type { - return typedef.PartitionKeyTypes[rand.Intn(len(typedef.PartitionKeyTypes))] +func GenPartitionKeyColumnType(r *rand.Rand) typedef.Type { + return typedef.PartitionKeyTypes[r.Intn(len(typedef.PartitionKeyTypes))] } -func GenPrimaryKeyColumnType() typedef.Type { - return typedef.PkTypes[rand.Intn(len(typedef.PkTypes))] +func GenPrimaryKeyColumnType(r *rand.Rand) typedef.Type { + return typedef.PkTypes[r.Intn(len(typedef.PkTypes))] } func GenIndexName(prefix string, idx int) string { diff --git a/pkg/generators/statement_generator.go b/pkg/generators/statement_generator.go index 30e358af..d411a3dd 100644 --- a/pkg/generators/statement_generator.go +++ b/pkg/generators/statement_generator.go @@ -18,12 +18,15 @@ import ( "fmt" "strings" + "golang.org/x/exp/rand" + "github.com/scylladb/gemini/pkg/builders" "github.com/scylladb/gemini/pkg/typedef" "github.com/scylladb/gemini/pkg/utils" ) -func GenSchema(sc typedef.SchemaConfig) *typedef.Schema { +func GenSchema(sc typedef.SchemaConfig, seed uint64) *typedef.Schema { + r := rand.New(rand.NewSource(seed)) builder := builders.NewSchemaBuilder() keyspace := typedef.Keyspace{ Name: "ks1", @@ -31,22 +34,22 @@ func GenSchema(sc typedef.SchemaConfig) *typedef.Schema { OracleReplication: sc.OracleReplicationStrategy, } builder.Keyspace(keyspace) - numTables := utils.RandInt(1, sc.GetMaxTables()) + numTables := utils.RandInt2(r, 1, sc.GetMaxTables()) for i := 0; i < numTables; i++ { - table := genTable(sc, fmt.Sprintf("table%d", i+1)) + table := genTable(sc, fmt.Sprintf("table%d", i+1), r) builder.Table(table) } return builder.Build() } -func genTable(sc typedef.SchemaConfig, tableName string) *typedef.Table { - partitionKeys := make(typedef.Columns, utils.RandInt(sc.GetMinPartitionKeys(), sc.GetMaxPartitionKeys())) +func genTable(sc typedef.SchemaConfig, tableName string, r *rand.Rand) *typedef.Table { + partitionKeys := make(typedef.Columns, utils.RandInt2(r, sc.GetMinPartitionKeys(), sc.GetMaxPartitionKeys())) for i := 0; i < len(partitionKeys); i++ { - partitionKeys[i] = &typedef.ColumnDef{Name: GenColumnName("pk", i), Type: GenPartitionKeyColumnType()} + partitionKeys[i] = &typedef.ColumnDef{Name: GenColumnName("pk", i), Type: GenPartitionKeyColumnType(r)} } - clusteringKeys := make(typedef.Columns, utils.RandInt(sc.GetMinClusteringKeys(), sc.GetMaxClusteringKeys())) + clusteringKeys := make(typedef.Columns, utils.RandInt2(r, sc.GetMinClusteringKeys(), sc.GetMaxClusteringKeys())) for i := 0; i < len(clusteringKeys); i++ { - clusteringKeys[i] = &typedef.ColumnDef{Name: GenColumnName("ck", i), Type: GenPrimaryKeyColumnType()} + clusteringKeys[i] = &typedef.ColumnDef{Name: GenColumnName("ck", i), Type: GenPrimaryKeyColumnType(r)} } table := typedef.Table{ Name: tableName, @@ -70,21 +73,21 @@ func genTable(sc typedef.SchemaConfig, tableName string) *typedef.Table { } return &table } - columns := make(typedef.Columns, utils.RandInt(sc.GetMinColumns(), sc.GetMaxColumns())) + columns := make(typedef.Columns, utils.RandInt2(r, sc.GetMinColumns(), sc.GetMaxColumns())) for i := 0; i < len(columns); i++ { - columns[i] = &typedef.ColumnDef{Name: GenColumnName("col", i), Type: GenColumnType(len(columns), &sc)} + columns[i] = &typedef.ColumnDef{Name: GenColumnName("col", i), Type: GenColumnType(len(columns), &sc, r)} } table.Columns = columns var indexes []typedef.IndexDef if sc.CQLFeature > typedef.CQL_FEATURE_BASIC && len(columns) > 0 { - indexes = CreateIndexesForColumn(&table, utils.RandInt(1, len(columns))) + indexes = CreateIndexesForColumn(&table, utils.RandInt2(r, 1, len(columns))) } table.Indexes = indexes var mvs []typedef.MaterializedView if sc.CQLFeature > typedef.CQL_FEATURE_BASIC && len(clusteringKeys) > 0 { - mvs = CreateMaterializedViews(columns, table.Name, partitionKeys, clusteringKeys) + mvs = CreateMaterializedViews(columns, table.Name, partitionKeys, clusteringKeys, r) } table.MaterializedViews = mvs @@ -142,12 +145,12 @@ func GetDropSchema(s *typedef.Schema) []string { } } -func CreateMaterializedViews(c typedef.Columns, tableName string, partitionKeys, clusteringKeys typedef.Columns) []typedef.MaterializedView { +func CreateMaterializedViews(c typedef.Columns, tableName string, partitionKeys, clusteringKeys typedef.Columns, r *rand.Rand) []typedef.MaterializedView { validColumns := c.ValidColumnsForPrimaryKey() var mvs []typedef.MaterializedView numMvs := 1 for i := 0; i < numMvs; i++ { - col := validColumns.Random() + col := validColumns.Random(r) if col == nil { fmt.Printf("unable to generate valid columns for materialized view") continue diff --git a/pkg/jobs/gen_ddl_stmt.go b/pkg/jobs/gen_ddl_stmt.go index 4d3fed50..8b2a7cec 100644 --- a/pkg/jobs/gen_ddl_stmt.go +++ b/pkg/jobs/gen_ddl_stmt.go @@ -18,7 +18,6 @@ import ( "fmt" "strings" - "github.com/pkg/errors" "golang.org/x/exp/rand" "github.com/scylladb/gemini/pkg/builders" @@ -36,9 +35,9 @@ func GenDDLStmt(s *typedef.Schema, t *typedef.Table, r *rand.Rand, _ *typedef.Pa // case 0: // Alter column not supported in Cassandra from 3.0.11 // return t.alterColumn(s.Keyspace.Name) case 2: - return genDropColumnStmt(t, s.Keyspace.Name, validCols.Random()) + return genDropColumnStmt(t, s.Keyspace.Name, validCols.Random(r)) default: - column := typedef.ColumnDef{Name: generators.GenColumnName("col", len(t.Columns)+1), Type: generators.GenColumnType(len(t.Columns)+1, sc)} + column := typedef.ColumnDef{Name: generators.GenColumnName("col", len(t.Columns)+1), Type: generators.GenColumnType(len(t.Columns)+1, sc, r)} return genAddColumnStmt(t, s.Keyspace.Name, &column) } } @@ -82,36 +81,6 @@ func genAddColumnStmt(t *typedef.Table, keyspace string, column *typedef.ColumnD }, nil } -//nolint:unused -func alterColumn(t *typedef.Table, keyspace string) ([]*typedef.Stmt, func(), error) { - var stmts []*typedef.Stmt - idx := rand.Intn(len(t.Columns)) - column := t.Columns[idx] - oldType, isSimpleType := column.Type.(typedef.SimpleType) - if !isSimpleType { - return nil, func() {}, errors.Errorf("complex type=%s cannot be altered", column.Name) - } - compatTypes := typedef.CompatibleColumnTypes[oldType] - if len(compatTypes) == 0 { - return nil, func() {}, errors.Errorf("simple type=%s has no compatible coltypes so it cannot be altered", column.Name) - } - newType := compatTypes.Random() - newColumn := typedef.ColumnDef{Name: column.Name, Type: newType} - stmt := "ALTER TABLE " + keyspace + "." + t.Name + " ALTER " + column.Name + " TYPE " + column.Type.CQLDef() - stmts = append(stmts, &typedef.Stmt{ - StmtCache: &typedef.StmtCache{ - Query: &builders.AlterTableBuilder{ - Stmt: stmt, - }, - QueryType: typedef.AlterColumnStatementType, - }, - }) - return stmts, func() { - t.Columns[idx] = &newColumn - t.ResetQueryCache() - }, nil -} - func genDropColumnStmt(t *typedef.Table, keyspace string, column *typedef.ColumnDef) (*typedef.Stmts, error) { var stmts []*typedef.Stmt diff --git a/pkg/typedef/columns.go b/pkg/typedef/columns.go index dbf267e6..cb00f9ea 100644 --- a/pkg/typedef/columns.go +++ b/pkg/typedef/columns.go @@ -124,8 +124,8 @@ func (c Columns) ValidColumnsForPrimaryKey() Columns { return validCols } -func (c Columns) Random() *ColumnDef { - return c[rand.Intn(len(c))] +func (c Columns) Random(r *rand.Rand) *ColumnDef { + return c[r.Intn(len(c))] } func (c Columns) LenValues() int { diff --git a/pkg/typedef/columns_test.go b/pkg/typedef/columns_test.go index 945f63c1..7e68802b 100644 --- a/pkg/typedef/columns_test.go +++ b/pkg/typedef/columns_test.go @@ -22,6 +22,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "golang.org/x/exp/rand" "github.com/scylladb/gemini/pkg/generators" "github.com/scylladb/gemini/pkg/typedef" @@ -50,6 +51,8 @@ var allSimpleTypes = []typedef.SimpleType{ typedef.TYPE_VARINT, } +var r = rand.New(rand.NewSource(rand.Uint64())) + func TestColumnMarshalUnmarshal(t *testing.T) { t.Parallel() type testCase struct { @@ -146,11 +149,11 @@ func TestPrimitives(t *testing.T) { cols := typedef.Columns{ &typedef.ColumnDef{ Name: "pk_mv_0", - Type: generators.GenListType(sc), + Type: generators.GenListType(sc, r), }, &typedef.ColumnDef{ Name: "pk_mv_1", - Type: generators.GenTupleType(sc), + Type: generators.GenTupleType(sc, r), }, &typedef.ColumnDef{ Name: "ct_1", @@ -249,23 +252,23 @@ func getTestSchema() *typedef.Schema { columns := typedef.Columns{ &typedef.ColumnDef{ Name: generators.GenColumnName("col", 0), - Type: generators.GenMapType(sc), + Type: generators.GenMapType(sc, r), }, &typedef.ColumnDef{ Name: generators.GenColumnName("col", 1), - Type: generators.GenSetType(sc), + Type: generators.GenSetType(sc, r), }, &typedef.ColumnDef{ Name: generators.GenColumnName("col", 2), - Type: generators.GenListType(sc), + Type: generators.GenListType(sc, r), }, &typedef.ColumnDef{ Name: generators.GenColumnName("col", 3), - Type: generators.GenTupleType(sc), + Type: generators.GenTupleType(sc, r), }, &typedef.ColumnDef{ Name: generators.GenColumnName("col", 4), - Type: generators.GenUDTType(sc), + Type: generators.GenUDTType(sc, r), }, } @@ -276,13 +279,13 @@ func getTestSchema() *typedef.Schema { PartitionKeys: typedef.Columns{ &typedef.ColumnDef{ Name: generators.GenColumnName("pk", 0), - Type: generators.GenSimpleType(sc), + Type: generators.GenSimpleType(sc, r), }, }, ClusteringKeys: typedef.Columns{ &typedef.ColumnDef{ Name: generators.GenColumnName("ck", 0), - Type: generators.GenSimpleType(sc), + Type: generators.GenSimpleType(sc, r), }, }, Columns: columns, diff --git a/pkg/typedef/simple_type.go b/pkg/typedef/simple_type.go index 0f7295f6..b507cad6 100644 --- a/pkg/typedef/simple_type.go +++ b/pkg/typedef/simple_type.go @@ -44,8 +44,8 @@ func (l SimpleTypes) Contains(colType Type) bool { return false } -func (l SimpleTypes) Random() SimpleType { - return l[rand.Intn(len(l))] +func (l SimpleTypes) Random(r *rand.Rand) SimpleType { + return l[r.Intn(len(l))] } type SimpleType string diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 83973361..14616c2c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -74,13 +74,6 @@ func RandInt2(rnd *rand.Rand, min, max int) int { return min + rnd.Intn(max-min) } -func RandInt(min, max int) int { - if max <= min { - return min - } - return min + rand.Intn(max-min) -} - func IgnoreError(fn func() error) { _ = fn() }