From 029f6f5102f65fb6302f7041fca50de234ad0743 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 24 Mar 2022 12:42:40 +0200 Subject: [PATCH 1/2] Fix panic due to error in HasWork The panic was happening in the `Init` but it was due to the fact that HasWork wrongfully returned True when it should've returned false. --- lib/executor/ramping_arrival_rate.go | 2 +- lib/executor/ramping_arrival_rate_test.go | 24 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/executor/ramping_arrival_rate.go b/lib/executor/ramping_arrival_rate.go index 7eaf7ef9338..3e47252058d 100644 --- a/lib/executor/ramping_arrival_rate.go +++ b/lib/executor/ramping_arrival_rate.go @@ -84,7 +84,7 @@ func (varc RampingArrivalRateConfig) GetPreAllocatedVUs(et *lib.ExecutionTuple) // GetMaxVUs is just a helper method that returns the scaled max VUs. func (varc RampingArrivalRateConfig) GetMaxVUs(et *lib.ExecutionTuple) int64 { - return et.Segment.Scale(varc.MaxVUs.Int64) + return et.ScaleInt64(varc.MaxVUs.Int64) } // GetDescription returns a human-readable description of the executor options diff --git a/lib/executor/ramping_arrival_rate_test.go b/lib/executor/ramping_arrival_rate_test.go index 41925bc7c07..36ce5094a18 100644 --- a/lib/executor/ramping_arrival_rate_test.go +++ b/lib/executor/ramping_arrival_rate_test.go @@ -766,3 +766,27 @@ func TestRampingArrivalRateGlobalIters(t *testing.T) { }) } } + +func TestRampingArrivalRateCornerCase(t *testing.T) { + t.Parallel() + config := &RampingArrivalRateConfig{ + TimeUnit: types.NullDurationFrom(time.Second), + StartRate: null.IntFrom(1), + Stages: []Stage{ + { + Duration: types.NullDurationFrom(1 * time.Second), + Target: null.IntFrom(1), + }, + }, + MaxVUs: null.IntFrom(2), + } + + et, err := lib.NewExecutionTuple(newExecutionSegmentFromString("1/5:2/5"), newExecutionSegmentSequenceFromString("0,1/5,2/5,1")) + require.NoError(t, err) + es := lib.NewExecutionState(lib.Options{}, et, 10, 50) + + executor, err := config.NewExecutor(es, nil) + require.NoError(t, err) + + require.False(t, executor.GetConfig().HasWork(et)) +} From 61c90febc4132da0edea533eec2d2396a224528a Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 24 Mar 2022 13:50:35 +0200 Subject: [PATCH 2/2] Use only ExecutionTuple.ScaleInt64 in executors Most executors use that already and while ExecutionSegment.Scale gives the same result in a lot of cases - it doesn't in some. This potentially can lead to small (1 VU/iteration) discrepancies but more importantly panics. Especially in cases where the differences are when one returns a 0 and the other a 1 for a given value. --- lib/executor/constant_vus.go | 2 +- lib/executor/externally_controlled.go | 16 ++++++++-------- lib/executor/per_vu_iterations.go | 2 +- lib/executor/ramping_arrival_rate.go | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/executor/constant_vus.go b/lib/executor/constant_vus.go index b8311dda64f..16f2f79d400 100644 --- a/lib/executor/constant_vus.go +++ b/lib/executor/constant_vus.go @@ -73,7 +73,7 @@ var _ lib.ExecutorConfig = &ConstantVUsConfig{} // GetVUs returns the scaled VUs for the executor. func (clvc ConstantVUsConfig) GetVUs(et *lib.ExecutionTuple) int64 { - return et.Segment.Scale(clvc.VUs.Int64) + return et.ScaleInt64(clvc.VUs.Int64) } // GetDescription returns a human-readable description of the executor options diff --git a/lib/executor/externally_controlled.go b/lib/executor/externally_controlled.go index af3c88a81c2..5494cc9cb1b 100644 --- a/lib/executor/externally_controlled.go +++ b/lib/executor/externally_controlled.go @@ -144,8 +144,8 @@ func (mec ExternallyControlledConfig) Validate() []error { func (mec ExternallyControlledConfig) GetExecutionRequirements(et *lib.ExecutionTuple) []lib.ExecutionStep { startVUs := lib.ExecutionStep{ TimeOffset: 0, - PlannedVUs: uint64(et.Segment.Scale(mec.MaxVUs.Int64)), // user-configured, VUs to be pre-initialized - MaxUnplannedVUs: 0, // intentional, see function comment + PlannedVUs: uint64(et.ScaleInt64(mec.MaxVUs.Int64)), // user-configured, VUs to be pre-initialized + MaxUnplannedVUs: 0, // intentional, see function comment } maxDuration := mec.Duration.TimeDuration() @@ -434,11 +434,11 @@ func (rs *externallyControlledRunState) progressFn() (float64, []string) { func (rs *externallyControlledRunState) handleConfigChange(oldCfg, newCfg ExternallyControlledConfigParams) error { executionState := rs.executor.executionState - segment := executionState.Options.ExecutionSegment - oldActiveVUs := segment.Scale(oldCfg.VUs.Int64) - oldMaxVUs := segment.Scale(oldCfg.MaxVUs.Int64) - newActiveVUs := segment.Scale(newCfg.VUs.Int64) - newMaxVUs := segment.Scale(newCfg.MaxVUs.Int64) + et := executionState.ExecutionTuple + oldActiveVUs := et.ScaleInt64(oldCfg.VUs.Int64) + oldMaxVUs := et.ScaleInt64(oldCfg.MaxVUs.Int64) + newActiveVUs := et.ScaleInt64(newCfg.VUs.Int64) + newMaxVUs := et.ScaleInt64(newCfg.MaxVUs.Int64) rs.executor.logger.WithFields(logrus.Fields{ "oldActiveVUs": oldActiveVUs, "oldMaxVUs": oldMaxVUs, @@ -523,7 +523,7 @@ func (mex *ExternallyControlled) Run( logrus.Fields{"type": externallyControlledType, "duration": duration}, ).Debug("Starting executor run...") - startMaxVUs := mex.executionState.Options.ExecutionSegment.Scale(mex.config.MaxVUs.Int64) + startMaxVUs := mex.executionState.ExecutionTuple.ScaleInt64(mex.config.MaxVUs.Int64) ss := &lib.ScenarioState{ Name: mex.config.Name, diff --git a/lib/executor/per_vu_iterations.go b/lib/executor/per_vu_iterations.go index 681cd009349..aed0fc2b051 100644 --- a/lib/executor/per_vu_iterations.go +++ b/lib/executor/per_vu_iterations.go @@ -70,7 +70,7 @@ var _ lib.ExecutorConfig = &PerVUIterationsConfig{} // GetVUs returns the scaled VUs for the executor. func (pvic PerVUIterationsConfig) GetVUs(et *lib.ExecutionTuple) int64 { - return et.Segment.Scale(pvic.VUs.Int64) + return et.ScaleInt64(pvic.VUs.Int64) } // GetIterations returns the UNSCALED iteration count for the executor. It's diff --git a/lib/executor/ramping_arrival_rate.go b/lib/executor/ramping_arrival_rate.go index 3e47252058d..096f1cf4fac 100644 --- a/lib/executor/ramping_arrival_rate.go +++ b/lib/executor/ramping_arrival_rate.go @@ -79,7 +79,7 @@ var _ lib.ExecutorConfig = &RampingArrivalRateConfig{} // GetPreAllocatedVUs is just a helper method that returns the scaled pre-allocated VUs. func (varc RampingArrivalRateConfig) GetPreAllocatedVUs(et *lib.ExecutionTuple) int64 { - return et.Segment.Scale(varc.PreAllocatedVUs.Int64) + return et.ScaleInt64(varc.PreAllocatedVUs.Int64) } // GetMaxVUs is just a helper method that returns the scaled max VUs. @@ -90,9 +90,9 @@ func (varc RampingArrivalRateConfig) GetMaxVUs(et *lib.ExecutionTuple) int64 { // GetDescription returns a human-readable description of the executor options func (varc RampingArrivalRateConfig) GetDescription(et *lib.ExecutionTuple) string { // TODO: something better? always show iterations per second? - maxVUsRange := fmt.Sprintf("maxVUs: %d", et.Segment.Scale(varc.PreAllocatedVUs.Int64)) + maxVUsRange := fmt.Sprintf("maxVUs: %d", et.ScaleInt64(varc.PreAllocatedVUs.Int64)) if varc.MaxVUs.Int64 > varc.PreAllocatedVUs.Int64 { - maxVUsRange += fmt.Sprintf("-%d", et.Segment.Scale(varc.MaxVUs.Int64)) + maxVUsRange += fmt.Sprintf("-%d", et.ScaleInt64(varc.MaxVUs.Int64)) } maxUnscaledRate := getStagesUnscaledMaxTarget(varc.StartRate.Int64, varc.Stages) maxArrRatePerSec, _ := getArrivalRatePerSec( @@ -143,8 +143,8 @@ func (varc RampingArrivalRateConfig) GetExecutionRequirements(et *lib.ExecutionT return []lib.ExecutionStep{ { TimeOffset: 0, - PlannedVUs: uint64(et.Segment.Scale(varc.PreAllocatedVUs.Int64)), - MaxUnplannedVUs: uint64(et.Segment.Scale(varc.MaxVUs.Int64 - varc.PreAllocatedVUs.Int64)), + PlannedVUs: uint64(et.ScaleInt64(varc.PreAllocatedVUs.Int64)), + MaxUnplannedVUs: uint64(et.ScaleInt64(varc.MaxVUs.Int64 - varc.PreAllocatedVUs.Int64)), }, { TimeOffset: sumStagesDuration(varc.Stages) + varc.GracefulStop.TimeDuration(),