From 776f81b1304356be2807d6ad39d1e593559889ef Mon Sep 17 00:00:00 2001 From: Dusan Malusev Date: Fri, 25 Oct 2024 12:36:07 +0200 Subject: [PATCH] improvement(jobs): job refactoring and randomization 1. Refactoring the Statement Generators into generators package 2. Each table gets it's own random number for validation and generation 3. Warmup consolidated into Job, runs with its own validator and statement generator -> propably can be removed, as Warmup is just a mutation without DDL and deletes Future Plans 1. Remove stopFlag as context.Context can do the same, hard kill is to CANCEL the Global Parent, and soft kill is cancelation of the current context 2. Generators Refactoring Signed-off-by: Dusan Malusev --- .gitignore | 1 + .run/Run Gemini.run.xml | 1 - cmd/gemini/root.go | 29 +++--- pkg/jobs/ddl.go | 17 ++-- pkg/jobs/jobs.go | 194 +++++++++++++++++++++---------------- pkg/jobs/mode.go | 26 +++++ pkg/jobs/mutation.go | 27 +++--- pkg/jobs/validation.go | 27 +++--- pkg/jobs/warmup.go | 14 +-- pkg/typedef/simple_type.go | 2 +- pkg/utils/utils.go | 33 +++++-- results/.gitkeep | 0 12 files changed, 216 insertions(+), 155 deletions(-) create mode 100644 pkg/jobs/mode.go create mode 100644 results/.gitkeep diff --git a/.gitignore b/.gitignore index 97f5c956..af30c10c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ cmd/gemini/dist/ bin/ coverage.txt dist/ +results/*.log diff --git a/.run/Run Gemini.run.xml b/.run/Run Gemini.run.xml index 3e4f4ec2..ea9ee6a5 100644 --- a/.run/Run Gemini.run.xml +++ b/.run/Run Gemini.run.xml @@ -8,7 +8,6 @@ - \ No newline at end of file diff --git a/cmd/gemini/root.go b/cmd/gemini/root.go index fbfbcebb..dc90f6e1 100644 --- a/cmd/gemini/root.go +++ b/cmd/gemini/root.go @@ -267,7 +267,6 @@ func run(_ *cobra.Command, _ []string) error { stopFlag := stop.NewFlag("main") warmupStopFlag := stop.NewFlag("warmup") stop.StartOsSignalsTransmitter(logger, stopFlag, warmupStopFlag) - pump := jobs.NewPump(stopFlag, logger) gens, err := createGenerators(schema, schemaConfig, intSeed, partitionCount, logger) if err != nil { @@ -278,8 +277,10 @@ func run(_ *cobra.Command, _ []string) error { if !nonInteractive { sp := createSpinner(interactive()) ticker := time.NewTicker(time.Second) + go func() { defer done() + defer ticker.Stop() for { select { case <-stopFlag.SignalChannel(): @@ -291,17 +292,9 @@ func run(_ *cobra.Command, _ []string) error { }() } - if warmup > 0 && !stopFlag.IsHardOrSoft() { - jobsList := jobs.New(jobs.WarmupMode, warmup, concurrency) - if err = jobsList.Do(ctx, schema, schemaConfig, st, pump, gens, globalStatus, logger, intSeed, warmupStopFlag, failFast, verbose); err != nil { - logger.Error("warmup encountered an error", zap.Error(err)) - stopFlag.SetHard(true) - } - } - if !stopFlag.IsHardOrSoft() { - jobsList := jobs.New(mode, duration, concurrency) - if err = jobsList.Do(ctx, schema, schemaConfig, st, pump, gens, globalStatus, logger, intSeed, stopFlag.CreateChild("workload"), failFast, verbose); err != nil { + jobsList := jobs.New(mode, duration, concurrency, stopFlag, logger, schema, st, globalStatus, &schemaConfig, intSeed, gens, failFast, warmup) + if err = jobsList.Run(ctx); err != nil { logger.Debug("error detected", zap.Error(err)) } } @@ -313,14 +306,16 @@ func run(_ *cobra.Command, _ []string) error { return nil } -func createFile(fname string, def *os.File) (*os.File, error) { - if fname != "" { - f, err := os.Create(fname) +func createFile(name string, def *os.File) (*os.File, error) { + if name != "" { + f, err := os.Create(name) if err != nil { - return nil, errors.Wrapf(err, "Unable to open output file %s", fname) + return nil, errors.Wrapf(err, "Unable to open output file %s", name) } + return f, nil } + return def, nil } @@ -473,7 +468,7 @@ func init() { 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") - rootCmd.Flags().BoolVarP(&nonInteractive, "non-interactive", "", false, "Statement in non-interactive mode (disable progress indicator)") + rootCmd.Flags().BoolVarP(&nonInteractive, "non-interactive", "", false, "Run in non-interactive mode (disable progress indicator)") rootCmd.Flags().DurationVarP(&duration, "duration", "", 30*time.Second, "") rootCmd.Flags().StringVarP(&outFileArg, "outfile", "", "", "Specify the name of the file where the results should go") rootCmd.Flags().StringVarP(&bind, "bind", "b", ":2112", "Specify the interface and port which to bind prometheus metrics on. Default is ':2112'") @@ -497,7 +492,7 @@ func init() { rootCmd.Flags().IntVarP(&minColumns, "min-columns", "", 8, "Minimum number of generated columns") rootCmd.Flags().StringVarP(&datasetSize, "dataset-size", "", "large", "Specify the type of dataset size to use, small|large") rootCmd.Flags().StringVarP(&cqlFeatures, "cql-features", "", "basic", "Specify the type of cql features to use, basic|normal|all") - rootCmd.Flags().BoolVarP(&useMaterializedViews, "materialized-views", "", false, "Statement gemini with materialized views support") + rootCmd.Flags().BoolVarP(&useMaterializedViews, "materialized-views", "", false, "Run gemini with materialized views support") rootCmd.Flags().StringVarP(&level, "level", "", "info", "Specify the logging level, debug|info|warn|error|dpanic|panic|fatal") rootCmd.Flags().IntVarP(&maxRetriesMutate, "max-mutation-retries", "", 2, "Maximum number of attempts to apply a mutation") rootCmd.Flags().DurationVarP( diff --git a/pkg/jobs/ddl.go b/pkg/jobs/ddl.go index 9cd98d6f..8f2de1b8 100644 --- a/pkg/jobs/ddl.go +++ b/pkg/jobs/ddl.go @@ -11,20 +11,21 @@ import ( "github.com/scylladb/gemini/pkg/generators/statements" "github.com/scylladb/gemini/pkg/joberror" + "github.com/scylladb/gemini/pkg/typedef" ) -func (m *mutation) DDL(ctx context.Context) error { - m.table.RLock() +func (m *mutation) DDL(ctx context.Context, table *typedef.Table) error { + table.RLock() // Scylla does not allow changing the DDL of a table with materialized views. - if len(m.table.MaterializedViews) > 0 { - m.table.RUnlock() + if len(table.MaterializedViews) > 0 { + table.RUnlock() return nil } - m.table.RUnlock() + table.RUnlock() - m.table.Lock() - defer m.table.Unlock() - ddlStmts, err := statements.GenDDLStmt(m.schema, m.table, m.random, m.partitionRangeConfig, m.schemaCfg) + table.Lock() + defer table.Unlock() + ddlStmts, err := statements.GenDDLStmt(m.schema, table, m.random, m.partitionRangeConfig, m.schemaCfg) if err != nil { m.logger.Error("Failed! DDL Mutation statement generation failed", zap.Error(err)) m.globalStatus.WriteErrors.Add(1) diff --git a/pkg/jobs/jobs.go b/pkg/jobs/jobs.go index 2389464b..bfe2ca17 100644 --- a/pkg/jobs/jobs.go +++ b/pkg/jobs/jobs.go @@ -29,47 +29,26 @@ import ( "github.com/scylladb/gemini/pkg/typedef" ) -type Mode []string - -const ( - WriteMode = "write" - ReadMode = "read" - MixedMode = "mixed" - WarmupMode = "warmup" -) - -func ModeFromString(m string) Mode { - switch m { - case WriteMode: - return Mode{WriteMode} - case ReadMode: - return Mode{ReadMode} - case MixedMode: - return Mode{WriteMode, ReadMode} - case WarmupMode: - return Mode{WarmupMode} - default: - return Mode{} - } -} - -type List struct { - name string - duration time.Duration - logger *zap.Logger - random *rand.Rand - stopFlag *stop.Flag - workers uint64 - jobs []Job - generators []*generators.Generator - schema *typedef.Schema - verbose bool - failFast bool +type Runner struct { + duration time.Duration + logger *zap.Logger + random *rand.Rand + stopFlag *stop.Flag + workers uint64 + generators []*generators.Generator + schema *typedef.Schema + failFast bool + schemaCfg *typedef.SchemaConfig + warmup time.Duration + globalStatus *status.GlobalStatus + pump <-chan time.Duration + store store.Store + mode Mode } type Job interface { Name() string - Do(context.Context, generators.Interface) error + Do(context.Context, generators.Interface, *typedef.Table) error } func New( @@ -79,70 +58,121 @@ func New( stopFlag *stop.Flag, logger *zap.Logger, schema *typedef.Schema, - table *typedef.Table, store store.Store, globalStatus *status.GlobalStatus, schemaCfg *typedef.SchemaConfig, seed uint64, gens []*generators.Generator, - pump <-chan time.Duration, failFast bool, - verbose bool, -) List { - partitionRangeConfig := schemaCfg.GetPartitionRangeConfig() - rnd := rand.New(rand.NewSource(seed)) - - jobs := make([]Job, 0, 2) - name := "work cycle" - for _, m := range ModeFromString(mode) { - switch m { - case WriteMode: - jobs = append(jobs, NewMutation(logger, schema, table, store, &partitionRangeConfig, globalStatus, stopFlag, schemaCfg, pump, failFast, verbose)) - case ReadMode: - jobs = append(jobs, NewValidation(logger, pump, schema, schemaCfg, table, store, rnd, &partitionRangeConfig, globalStatus, stopFlag, failFast)) - case WarmupMode: - jobs = append(jobs, NewWarmup(logger, schema, table, store, &partitionRangeConfig, globalStatus, stopFlag, failFast, verbose)) - name = "warmup cycle" - } - } - - return List{ - name: name, - jobs: jobs, - duration: duration, - workers: workers, - stopFlag: stopFlag, - failFast: failFast, - verbose: verbose, - random: rnd, - generators: gens, - schema: schema, + warmup time.Duration, +) *Runner { + return &Runner{ + warmup: warmup, + globalStatus: globalStatus, + pump: NewPump(stopFlag, logger.Named("Pump")), + store: store, + mode: ModeFromString(mode), + logger: logger, + schemaCfg: schemaCfg, + duration: duration, + workers: workers, + stopFlag: stopFlag, + failFast: failFast, + random: rand.New(rand.NewSource(seed)), + generators: gens, + schema: schema, } } -func (l List) Name() string { - return l.name +func (l *Runner) Name() string { + return "Runner" } -func (l List) Do(ctx context.Context) error { +func (l *Runner) Run(ctx context.Context) error { ctx = l.stopFlag.CancelContextOnSignal(ctx, stop.SignalHardStop) - g, gCtx := errgroup.WithContext(ctx) + partitionRangeConfig := l.schemaCfg.GetPartitionRangeConfig() + + l.logger.Info("start jobs") + + if l.warmup > 0 { + l.logger.Info("Warmup Job Started", + zap.Int("duration", int(l.warmup.Seconds())), + zap.Int("workers", int(l.workers)), + ) + time.AfterFunc(l.warmup, func() { + l.logger.Info("jobs time is up, begins jobs completion") + l.stopFlag.SetSoft(true) + }) + + warmup := func(_ <-chan time.Duration, rnd *rand.Rand) Job { + return NewWarmup(l.logger, l.schema, l.store, &partitionRangeConfig, l.globalStatus, l.schemaCfg, l.stopFlag, rnd, l.failFast) + } + + if err := l.start(ctx, warmup); err != nil { + return err + } + } + time.AfterFunc(l.duration, func() { l.logger.Info("jobs time is up, begins jobs completion") l.stopFlag.SetSoft(true) }) - l.logger.Info("start jobs") + if l.mode.IsWrite() { + return l.start(ctx, func(pump <-chan time.Duration, rnd *rand.Rand) Job { + return NewMutation( + l.logger.Named("Mutation"), + l.schema, + l.store, + &partitionRangeConfig, + l.globalStatus, + l.stopFlag, + rnd, + l.schemaCfg, + pump, + l.failFast, + ) + }) + } + + return l.start(ctx, func(pump <-chan time.Duration, rnd *rand.Rand) Job { + return NewValidation( + l.logger, + pump, + l.schema, l.schemaCfg, + l.store, + rnd, + &partitionRangeConfig, + l.globalStatus, + l.stopFlag, + l.failFast, + ) + }) +} - for j := range l.schema.Tables { +func (l *Runner) start(ctx context.Context, job func(<-chan time.Duration, *rand.Rand) Job) error { + g, gCtx := errgroup.WithContext(ctx) + + g.SetLimit(int(l.workers)) + + partitionRangeConfig := l.schemaCfg.GetPartitionRangeConfig() + + for j, table := range l.schema.Tables { gen := l.generators[j] - for i := 0; i < int(l.workers); i++ { - for idx := range l.jobs { - jobF := l.jobs[idx] - g.Go(func() error { - return jobF.Do(gCtx, gen) - }) - } + pump := NewPump(l.stopFlag, l.logger.Named("Pump-"+table.Name)) + rnd := rand.New(rand.NewSource(l.random.Uint64())) + + v := NewValidation(l.logger, pump, l.schema, l.schemaCfg, l.store, rnd, &partitionRangeConfig, l.globalStatus, l.stopFlag, l.failFast) + j := job(pump, rnd) + + g.TryGo(func() error { + return v.Do(gCtx, gen, table) + }) + + for i := 0; i < int(l.workers)-1; i++ { + g.TryGo(func() error { + return j.Do(gCtx, gen, table) + }) } } diff --git a/pkg/jobs/mode.go b/pkg/jobs/mode.go new file mode 100644 index 00000000..e11ae571 --- /dev/null +++ b/pkg/jobs/mode.go @@ -0,0 +1,26 @@ +package jobs + +type Mode []string + +const ( + WriteMode = "write" + ReadMode = "read" + MixedMode = "mixed" +) + +func (m Mode) IsWrite() bool { + return m[0] == WriteMode +} + +func ModeFromString(m string) Mode { + switch m { + case WriteMode: + return Mode{WriteMode} + case ReadMode: + return Mode{ReadMode} + case MixedMode: + return Mode{WriteMode, ReadMode} + default: + return Mode{} + } +} diff --git a/pkg/jobs/mutation.go b/pkg/jobs/mutation.go index 0ba2d6ab..252a52d8 100644 --- a/pkg/jobs/mutation.go +++ b/pkg/jobs/mutation.go @@ -24,13 +24,11 @@ type ( stopFlag *stop.Flag pump <-chan time.Duration failFast bool - verbose bool } mutation struct { logger *zap.Logger schema *typedef.Schema - table *typedef.Table store store.Store partitionRangeConfig *typedef.PartitionRangeConfig schemaCfg *typedef.SchemaConfig @@ -43,32 +41,30 @@ type ( func NewMutation( logger *zap.Logger, schema *typedef.Schema, - table *typedef.Table, store store.Store, partitionRangeConfig *typedef.PartitionRangeConfig, globalStatus *status.GlobalStatus, stopFlag *stop.Flag, + rnd *rand.Rand, schemaCfg *typedef.SchemaConfig, pump <-chan time.Duration, failFast bool, - verbose bool, ) *Mutation { return &Mutation{ - logger: logger.Named("mutation"), + logger: logger, mutation: mutation{ logger: logger.Named("mutation-with-deletes"), schema: schema, - table: table, store: store, partitionRangeConfig: partitionRangeConfig, globalStatus: globalStatus, deletes: true, schemaCfg: schemaCfg, + random: rnd, }, stopFlag: stopFlag, pump: pump, failFast: failFast, - verbose: verbose, } } @@ -76,7 +72,7 @@ func (m *Mutation) Name() string { return "Mutation" } -func (m *Mutation) Do(ctx context.Context, generator generators.Interface) error { +func (m *Mutation) Do(ctx context.Context, generator generators.Interface, table *typedef.Table) error { m.logger.Info("starting mutation loop") defer m.logger.Info("ending mutation loop") @@ -84,6 +80,7 @@ func (m *Mutation) Do(ctx context.Context, generator generators.Interface) error if m.stopFlag.IsHardOrSoft() { return nil } + select { case <-m.stopFlag.SignalChannel(): m.logger.Debug("mutation job terminated") @@ -95,9 +92,9 @@ func (m *Mutation) Do(ctx context.Context, generator generators.Interface) error var err error if m.mutation.ShouldDoDDL() { - err = m.mutation.DDL(ctx) + err = m.mutation.DDL(ctx, table) } else { - err = m.mutation.Statement(ctx, generator) + err = m.mutation.Statement(ctx, generator, table) } if err != nil { @@ -111,13 +108,14 @@ func (m *Mutation) Do(ctx context.Context, generator generators.Interface) error } } -func (m *mutation) Statement(ctx context.Context, generator generators.Interface) error { - mutateStmt, err := statements.GenMutateStmt(m.schema, m.table, generator, m.random, m.partitionRangeConfig, m.deletes) +func (m *mutation) Statement(ctx context.Context, generator generators.Interface, table *typedef.Table) error { + mutateStmt, err := statements.GenMutateStmt(m.schema, table, generator, m.random, m.partitionRangeConfig, m.deletes) if err != nil { m.logger.Error("Failed! Mutation statement generation failed", zap.Error(err)) m.globalStatus.WriteErrors.Add(1) return err } + if mutateStmt == nil { if w := m.logger.Check(zap.DebugLevel, "no statement generated"); w != nil { w.Write(zap.String("job", "mutation")) @@ -157,6 +155,7 @@ func (m *mutation) ShouldDoDDL() bool { return false } - ind := m.random.Intn(1000000) - return ind%100000 == 0 + // 2% Change of DDL Happening + ind := m.random.Intn(100) + return ind < 2 } diff --git a/pkg/jobs/validation.go b/pkg/jobs/validation.go index 3b31f8df..41c9f2be 100644 --- a/pkg/jobs/validation.go +++ b/pkg/jobs/validation.go @@ -24,7 +24,6 @@ type Validation struct { pump <-chan time.Duration schema *typedef.Schema schemaConfig *typedef.SchemaConfig - table *typedef.Table store store.Store random *rand.Rand partitionRangeConfig *typedef.PartitionRangeConfig @@ -38,7 +37,6 @@ func NewValidation( pump <-chan time.Duration, schema *typedef.Schema, schemaConfig *typedef.SchemaConfig, - table *typedef.Table, store store.Store, random *rand.Rand, partitionRangeConfig *typedef.PartitionRangeConfig, @@ -51,7 +49,6 @@ func NewValidation( pump: pump, schema: schema, schemaConfig: schemaConfig, - table: table, store: store, random: random, partitionRangeConfig: partitionRangeConfig, @@ -65,11 +62,11 @@ func (v *Validation) Name() string { return "Validation" } -func (v *Validation) validate(ctx context.Context, generator generators.Interface) error { - stmt, cleanup := statements.GenCheckStmt(v.schema, v.table, generator, v.random, v.partitionRangeConfig) +func (v *Validation) validate(ctx context.Context, generator generators.Interface, table *typedef.Table) error { + stmt, cleanup := statements.GenCheckStmt(v.schema, table, generator, v.random, v.partitionRangeConfig) defer cleanup() - err := validation(ctx, v.schemaConfig, v.table, v.store, stmt, v.logger) + err := validation(ctx, v.schemaConfig, table, v.store, stmt, v.logger) switch { case err == nil: @@ -88,7 +85,7 @@ func (v *Validation) validate(ctx context.Context, generator generators.Interfac return nil } -func (v *Validation) Do(ctx context.Context, generator generators.Interface) error { +func (v *Validation) Do(ctx context.Context, generator generators.Interface, table *typedef.Table) error { v.logger.Info("starting validation loop") defer v.logger.Info("ending validation loop") @@ -103,7 +100,7 @@ func (v *Validation) Do(ctx context.Context, generator generators.Interface) err time.Sleep(hb) } - if err := v.validate(ctx, generator); errors.Is(err, context.Canceled) { + if err := v.validate(ctx, generator, table); errors.Is(err, context.Canceled) { return nil } @@ -139,6 +136,13 @@ func validation( var lastErr, err error attempt := 1 for ; ; attempt++ { + select { + case <-time.After(delay): + case <-ctx.Done(): + logger.Info(fmt.Sprintf("Retring failed validation stoped by done context. %d attempt from %d attempts. Error: %s", attempt, maxAttempts, err)) + return nil + } + lastErr = err err = s.Check(ctx, table, stmt, attempt == maxAttempts) @@ -161,13 +165,6 @@ func validation( } else { logger.Info(fmt.Sprintf("Retring failed validation. %d attempt from %d attempts. Error: %s", attempt, maxAttempts, err)) } - - select { - case <-time.After(delay): - case <-ctx.Done(): - logger.Info(fmt.Sprintf("Retring failed validation stoped by done context. %d attempt from %d attempts. Error: %s", attempt, maxAttempts, err)) - return nil - } } if attempt > 1 { diff --git a/pkg/jobs/warmup.go b/pkg/jobs/warmup.go index 6ef14777..8e4106ed 100644 --- a/pkg/jobs/warmup.go +++ b/pkg/jobs/warmup.go @@ -2,6 +2,7 @@ package jobs import ( "context" + "golang.org/x/exp/rand" "go.uber.org/zap" @@ -17,34 +18,33 @@ type Warmup struct { logger *zap.Logger stopFlag *stop.Flag failFast bool - verbose bool } func NewWarmup( logger *zap.Logger, schema *typedef.Schema, - table *typedef.Table, store store.Store, partitionRangeConfig *typedef.PartitionRangeConfig, globalStatus *status.GlobalStatus, + schemaCfg *typedef.SchemaConfig, stopFlag *stop.Flag, + rnd *rand.Rand, failFast bool, - verbose bool, ) *Warmup { return &Warmup{ logger: logger.Named("mutation"), mutation: mutation{ logger: logger.Named("mutation-without-deletes"), schema: schema, - table: table, store: store, partitionRangeConfig: partitionRangeConfig, + schemaCfg: schemaCfg, globalStatus: globalStatus, + random: rnd, deletes: false, }, stopFlag: stopFlag, failFast: failFast, - verbose: verbose, } } @@ -52,7 +52,7 @@ func (w *Warmup) Name() string { return "Warmup" } -func (w *Warmup) Do(ctx context.Context, generator generators.Interface) error { +func (w *Warmup) Do(ctx context.Context, generator generators.Interface, table *typedef.Table) error { w.logger.Info("starting warmup loop") defer w.logger.Info("ending warmup loop") @@ -62,7 +62,7 @@ func (w *Warmup) Do(ctx context.Context, generator generators.Interface) error { return nil } - _ = w.mutation.Statement(ctx, generator) + _ = w.mutation.Statement(ctx, generator, table) if w.failFast && w.mutation.globalStatus.HasErrors() { w.stopFlag.SetSoft(true) return nil diff --git a/pkg/typedef/simple_type.go b/pkg/typedef/simple_type.go index f2ad0c04..93d04334 100644 --- a/pkg/typedef/simple_type.go +++ b/pkg/typedef/simple_type.go @@ -206,7 +206,7 @@ func (st SimpleType) genValue(r *rand.Rand, p *PartitionRangeConfig) any { case TYPE_FLOAT: return r.Float32() case TYPE_INET: - return net.ParseIP(utils.RandIPV4Address(r, r.Intn(255), 2)).String() + return net.ParseIP(utils.RandIPV4Address(r)).String() case TYPE_INT: return r.Int31() case TYPE_SMALLINT: diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4f3ab17a..0cb04b60 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -18,6 +18,7 @@ import ( "encoding/hex" "fmt" "github.com/pkg/errors" + "golang.org/x/exp/constraints" "strconv" "strings" "time" @@ -55,22 +56,34 @@ func RandTime(rnd *rand.Rand) int64 { return rnd.Int63n(86400000000000) } -func RandIPV4Address(rnd *rand.Rand, v, pos int) string { +func ipV4Builder[T constraints.Integer](bytes [4]T) string { + var builder strings.Builder + builder.Grow(16) // Maximum length of an IPv4 address + + for _, b := range bytes { + builder.WriteString(strconv.FormatUint(uint64(b), 10)) + builder.WriteRune('.') + } + + return builder.String()[:builder.Len()-1] +} + +func RandIPV4Address(rnd *rand.Rand) string { + return ipV4Builder([4]int{rnd.Intn(256), rnd.Intn(256), rnd.Intn(256), rnd.Intn(256)}) +} + +func RandIPV4AddressPositional(rnd *rand.Rand, v, pos int) string { if pos < 0 || pos > 4 { panic(fmt.Sprintf("invalid position for the desired value of the IP part %d, 0-3 supported", pos)) } if v < 0 || v > 255 { panic(fmt.Sprintf("invalid value for the desired position %d of the IP, 0-255 suppoerted", v)) } - var blocks []string - for i := 0; i < 4; i++ { - if i == pos { - blocks = append(blocks, strconv.Itoa(v)) - } else { - blocks = append(blocks, strconv.Itoa(rnd.Intn(255))) - } - } - return strings.Join(blocks, ".") + + data := [4]int{rnd.Intn(255), rnd.Intn(255), rnd.Intn(255), rnd.Intn(255)} + data[pos] = v + + return ipV4Builder(data) } func RandInt2(rnd *rand.Rand, min, max int) int { diff --git a/results/.gitkeep b/results/.gitkeep new file mode 100644 index 00000000..e69de29b