From af18a3af6dcb4b44884b694ae895e83ef6b44b1c Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Apr 2020 10:28:08 +0300 Subject: [PATCH 01/23] use ExecutionTuple --- lib/executor/executors_test.go | 4 ++-- lib/executor/variable_looping_vus.go | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/executor/executors_test.go b/lib/executor/executors_test.go index 385c94563cc..4b21a23f56c 100644 --- a/lib/executor/executors_test.go +++ b/lib/executor/executors_test.go @@ -428,13 +428,13 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 18 * time.Second, PlannedVUs: 4}, {TimeOffset: 20 * time.Second, PlannedVUs: 1}, } - rawStepsNoZeroEnd := conf.getRawExecutionSteps(nil, false) + rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) assert.Equal(t, expRawStepsNoZeroEnd, rawStepsNoZeroEnd) endOffset, isFinal := lib.GetEndOffset(rawStepsNoZeroEnd) assert.Equal(t, 20*time.Second, endOffset) assert.Equal(t, false, isFinal) - rawStepsZeroEnd := conf.getRawExecutionSteps(nil, true) + rawStepsZeroEnd := conf.getRawExecutionSteps(et, true) assert.Equal(t, append(expRawStepsNoZeroEnd, lib.ExecutionStep{TimeOffset: 23 * time.Second, PlannedVUs: 0}), rawStepsZeroEnd, diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index b2965303a6d..13fc1a356aa 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -187,7 +187,7 @@ func (vlvc VariableLoopingVUsConfig) Validate() []error { // // More information: https://github.com/loadimpact/k6/issues/997#issuecomment-484416866 //nolint:funlen -func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(es *lib.ExecutionSegment, zeroEnd bool) []lib.ExecutionStep { +func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple, zeroEnd bool) []lib.ExecutionStep { // For accurate results, calculations are done with the unscaled values, and // the values are scaled only before we add them to the steps result slice fromVUs := vlvc.StartVUs.Int64 @@ -200,7 +200,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(es *lib.ExecutionSegme } // Reserve the scaled StartVUs at the beginning - prevScaledVUs := es.Scale(vlvc.StartVUs.Int64) + prevScaledVUs := et.ES.Scale(vlvc.StartVUs.Int64) steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(prevScaledVUs)}} timeFromStart := time.Duration(0) totalDuration := time.Duration(0) @@ -221,7 +221,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(es *lib.ExecutionSegme // Handle 0-duration stages, i.e. instant VU jumps if stageDuration == 0 { fromVUs = stageEndVUs - prevScaledVUs = es.Scale(stageEndVUs) + prevScaledVUs = et.ES.Scale(stageEndVUs) steps = append(steps, lib.ExecutionStep{ TimeOffset: timeFromStart, PlannedVUs: uint64(prevScaledVUs), @@ -254,7 +254,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(es *lib.ExecutionSegme stepGlobalVUs := fromVUs + int64( math.Round((float64(t)*float64(stageEndVUs-fromVUs))/float64(stageDuration)), ) - stepScaledVus := es.Scale(stepGlobalVUs) + stepScaledVus := et.ES.Scale(stepGlobalVUs) if stepScaledVus == prevScaledVUs { // only add steps when there's a change in the number of VUs @@ -272,7 +272,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(es *lib.ExecutionSegme } fromVUs = stageEndVUs - prevScaledVUs = es.Scale(stageEndVUs) + prevScaledVUs = et.ES.Scale(stageEndVUs) timeFromStart += stageDuration steps = append(steps, lib.ExecutionStep{ TimeOffset: timeFromStart, @@ -436,7 +436,7 @@ func (vlvc VariableLoopingVUsConfig) reserveVUsForGracefulRampDowns( //nolint:fu // - If the last stage's target is more than 0, the VUs at the end of the // executor's life will have more time to finish their last iterations. func (vlvc VariableLoopingVUsConfig) GetExecutionRequirements(et *lib.ExecutionTuple) []lib.ExecutionStep { - steps := vlvc.getRawExecutionSteps(et.ES, false) + steps := vlvc.getRawExecutionSteps(et, false) executorEndOffset := sumStagesDuration(vlvc.Stages) + time.Duration(vlvc.GracefulStop.Duration) // Handle graceful ramp-downs, if we have them @@ -484,8 +484,7 @@ var _ lib.Executor = &VariableLoopingVUs{} // and see what happens)... :/ so maybe see how it can be split? // nolint:funlen,gocognit func (vlv VariableLoopingVUs) Run(ctx context.Context, out chan<- stats.SampleContainer) (err error) { - segment := vlv.executionState.Options.ExecutionSegment - rawExecutionSteps := vlv.config.getRawExecutionSteps(segment, true) + rawExecutionSteps := vlv.config.getRawExecutionSteps(vlv.executionState.ExecutionTuple, true) regularDuration, isFinal := lib.GetEndOffset(rawExecutionSteps) if !isFinal { return fmt.Errorf("%s expected raw end offset at %s to be final", vlv.config.GetName(), regularDuration) From 4c59d5c102ca53364aef2ded898d2aaff1251bb1 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Apr 2020 11:34:48 +0300 Subject: [PATCH 02/23] Add test with execution segmeng 0:1/3 --- lib/executor/executors_test.go | 100 ------------ lib/executor/variable_looping_vus.go | 9 +- lib/executor/variable_looping_vus_test.go | 181 ++++++++++++++++++++++ 3 files changed, 182 insertions(+), 108 deletions(-) diff --git a/lib/executor/executors_test.go b/lib/executor/executors_test.go index 4b21a23f56c..d3eaac4e016 100644 --- a/lib/executor/executors_test.go +++ b/lib/executor/executors_test.go @@ -388,103 +388,3 @@ func TestConfigMapParsingAndValidation(t *testing.T) { }) } } - -func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { - t.Parallel() - et, err := lib.NewExecutionTuple(nil, nil) - require.NoError(t, err) - conf := NewVariableLoopingVUsConfig("test") - conf.StartVUs = null.IntFrom(4) - conf.Stages = []Stage{ - {Target: null.IntFrom(6), Duration: types.NullDurationFrom(2 * time.Second)}, - {Target: null.IntFrom(1), Duration: types.NullDurationFrom(5 * time.Second)}, - {Target: null.IntFrom(5), Duration: types.NullDurationFrom(4 * time.Second)}, - {Target: null.IntFrom(1), Duration: types.NullDurationFrom(4 * time.Second)}, - {Target: null.IntFrom(4), Duration: types.NullDurationFrom(3 * time.Second)}, - {Target: null.IntFrom(4), Duration: types.NullDurationFrom(2 * time.Second)}, - {Target: null.IntFrom(1), Duration: types.NullDurationFrom(0 * time.Second)}, - {Target: null.IntFrom(1), Duration: types.NullDurationFrom(3 * time.Second)}, - } - - expRawStepsNoZeroEnd := []lib.ExecutionStep{ - {TimeOffset: 0 * time.Second, PlannedVUs: 4}, - {TimeOffset: 1 * time.Second, PlannedVUs: 5}, - {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 3 * time.Second, PlannedVUs: 5}, - {TimeOffset: 4 * time.Second, PlannedVUs: 4}, - {TimeOffset: 5 * time.Second, PlannedVUs: 3}, - {TimeOffset: 6 * time.Second, PlannedVUs: 2}, - {TimeOffset: 7 * time.Second, PlannedVUs: 1}, - {TimeOffset: 8 * time.Second, PlannedVUs: 2}, - {TimeOffset: 9 * time.Second, PlannedVUs: 3}, - {TimeOffset: 10 * time.Second, PlannedVUs: 4}, - {TimeOffset: 11 * time.Second, PlannedVUs: 5}, - {TimeOffset: 12 * time.Second, PlannedVUs: 4}, - {TimeOffset: 13 * time.Second, PlannedVUs: 3}, - {TimeOffset: 14 * time.Second, PlannedVUs: 2}, - {TimeOffset: 15 * time.Second, PlannedVUs: 1}, - {TimeOffset: 16 * time.Second, PlannedVUs: 2}, - {TimeOffset: 17 * time.Second, PlannedVUs: 3}, - {TimeOffset: 18 * time.Second, PlannedVUs: 4}, - {TimeOffset: 20 * time.Second, PlannedVUs: 1}, - } - rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) - assert.Equal(t, expRawStepsNoZeroEnd, rawStepsNoZeroEnd) - endOffset, isFinal := lib.GetEndOffset(rawStepsNoZeroEnd) - assert.Equal(t, 20*time.Second, endOffset) - assert.Equal(t, false, isFinal) - - rawStepsZeroEnd := conf.getRawExecutionSteps(et, true) - assert.Equal(t, - append(expRawStepsNoZeroEnd, lib.ExecutionStep{TimeOffset: 23 * time.Second, PlannedVUs: 0}), - rawStepsZeroEnd, - ) - endOffset, isFinal = lib.GetEndOffset(rawStepsZeroEnd) - assert.Equal(t, 23*time.Second, endOffset) - assert.Equal(t, true, isFinal) - - // GracefulStop and GracefulRampDown equal to the default 30 sec - assert.Equal(t, []lib.ExecutionStep{ - {TimeOffset: 0 * time.Second, PlannedVUs: 4}, - {TimeOffset: 1 * time.Second, PlannedVUs: 5}, - {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 33 * time.Second, PlannedVUs: 5}, - {TimeOffset: 42 * time.Second, PlannedVUs: 4}, - {TimeOffset: 50 * time.Second, PlannedVUs: 1}, - {TimeOffset: 53 * time.Second, PlannedVUs: 0}, - }, conf.GetExecutionRequirements(et)) - - // Try a longer GracefulStop than the GracefulRampDown - conf.GracefulStop = types.NullDurationFrom(80 * time.Second) - assert.Equal(t, []lib.ExecutionStep{ - {TimeOffset: 0 * time.Second, PlannedVUs: 4}, - {TimeOffset: 1 * time.Second, PlannedVUs: 5}, - {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 33 * time.Second, PlannedVUs: 5}, - {TimeOffset: 42 * time.Second, PlannedVUs: 4}, - {TimeOffset: 50 * time.Second, PlannedVUs: 1}, - {TimeOffset: 103 * time.Second, PlannedVUs: 0}, - }, conf.GetExecutionRequirements(et)) - - // Try a much shorter GracefulStop than the GracefulRampDown - conf.GracefulStop = types.NullDurationFrom(3 * time.Second) - assert.Equal(t, []lib.ExecutionStep{ - {TimeOffset: 0 * time.Second, PlannedVUs: 4}, - {TimeOffset: 1 * time.Second, PlannedVUs: 5}, - {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 26 * time.Second, PlannedVUs: 0}, - }, conf.GetExecutionRequirements(et)) - - // Try a zero GracefulStop - conf.GracefulStop = types.NullDurationFrom(0 * time.Second) - assert.Equal(t, []lib.ExecutionStep{ - {TimeOffset: 0 * time.Second, PlannedVUs: 4}, - {TimeOffset: 1 * time.Second, PlannedVUs: 5}, - {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 23 * time.Second, PlannedVUs: 0}, - }, conf.GetExecutionRequirements(et)) - - // Try a zero GracefulStop and GracefulRampDown, i.e. raw steps with 0 end cap - conf.GracefulRampDown = types.NullDurationFrom(0 * time.Second) - assert.Equal(t, rawStepsZeroEnd, conf.GetExecutionRequirements(et)) -} diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 13fc1a356aa..dabbd22a1cb 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -234,9 +234,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // every minIntervalBetweenVUAdjustments. No floats or ratios, // since nanoseconds should be good enough for anyone... :) stepInterval := stageDuration / time.Duration(stageVUAbsDiff) - if stepInterval < minIntervalBetweenVUAdjustments { - stepInterval = minIntervalBetweenVUAdjustments - } // Loop through the potential steps, adding an item to the // result only when there's a change in the number of VUs. @@ -246,11 +243,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // important that the scaling via the execution segment should // happen AFTER the rest of the calculations have been done and // we've rounded the global "global" number of VUs. - for t := stepInterval; ; t += stepInterval { // Skip the first step, since we've already added that - if time.Duration(abs(int64(stageDuration-t))) < minIntervalBetweenVUAdjustments { - // Skip the last step of the stage, add it below to correct any minor clock skew - break - } + for t := stepInterval; t < stageDuration; t += stepInterval { // Skip the first step, since we've already added that stepGlobalVUs := fromVUs + int64( math.Round((float64(t)*float64(stageEndVUs-fromVUs))/float64(stageDuration)), ) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index fb42d7556a0..4d6c33035ae 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -165,3 +165,184 @@ func TestVariableLoopingVUsRampDownNoWobble(t *testing.T) { } assert.Equal(t, []int64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, vuChanges) } + +func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { + t.Parallel() + et, err := lib.NewExecutionTuple(nil, nil) + require.NoError(t, err) + conf := NewVariableLoopingVUsConfig("test") + conf.StartVUs = null.IntFrom(4) + conf.Stages = []Stage{ + {Target: null.IntFrom(6), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(5 * time.Second)}, + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(4 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(4 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(3 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(0 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(3 * time.Second)}, + } + + expRawStepsNoZeroEnd := []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 5}, + {TimeOffset: 2 * time.Second, PlannedVUs: 6}, + {TimeOffset: 3 * time.Second, PlannedVUs: 5}, + {TimeOffset: 4 * time.Second, PlannedVUs: 4}, + {TimeOffset: 5 * time.Second, PlannedVUs: 3}, + {TimeOffset: 6 * time.Second, PlannedVUs: 2}, + {TimeOffset: 7 * time.Second, PlannedVUs: 1}, + {TimeOffset: 8 * time.Second, PlannedVUs: 2}, + {TimeOffset: 9 * time.Second, PlannedVUs: 3}, + {TimeOffset: 10 * time.Second, PlannedVUs: 4}, + {TimeOffset: 11 * time.Second, PlannedVUs: 5}, + {TimeOffset: 12 * time.Second, PlannedVUs: 4}, + {TimeOffset: 13 * time.Second, PlannedVUs: 3}, + {TimeOffset: 14 * time.Second, PlannedVUs: 2}, + {TimeOffset: 15 * time.Second, PlannedVUs: 1}, + {TimeOffset: 16 * time.Second, PlannedVUs: 2}, + {TimeOffset: 17 * time.Second, PlannedVUs: 3}, + {TimeOffset: 18 * time.Second, PlannedVUs: 4}, + {TimeOffset: 20 * time.Second, PlannedVUs: 1}, + } + rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) + assert.Equal(t, expRawStepsNoZeroEnd, rawStepsNoZeroEnd) + endOffset, isFinal := lib.GetEndOffset(rawStepsNoZeroEnd) + assert.Equal(t, 20*time.Second, endOffset) + assert.Equal(t, false, isFinal) + + rawStepsZeroEnd := conf.getRawExecutionSteps(et, true) + assert.Equal(t, + append(expRawStepsNoZeroEnd, lib.ExecutionStep{TimeOffset: 23 * time.Second, PlannedVUs: 0}), + rawStepsZeroEnd, + ) + endOffset, isFinal = lib.GetEndOffset(rawStepsZeroEnd) + assert.Equal(t, 23*time.Second, endOffset) + assert.Equal(t, true, isFinal) + + // GracefulStop and GracefulRampDown equal to the default 30 sec + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 5}, + {TimeOffset: 2 * time.Second, PlannedVUs: 6}, + {TimeOffset: 33 * time.Second, PlannedVUs: 5}, + {TimeOffset: 42 * time.Second, PlannedVUs: 4}, + {TimeOffset: 50 * time.Second, PlannedVUs: 1}, + {TimeOffset: 53 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a longer GracefulStop than the GracefulRampDown + conf.GracefulStop = types.NullDurationFrom(80 * time.Second) + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 5}, + {TimeOffset: 2 * time.Second, PlannedVUs: 6}, + {TimeOffset: 33 * time.Second, PlannedVUs: 5}, + {TimeOffset: 42 * time.Second, PlannedVUs: 4}, + {TimeOffset: 50 * time.Second, PlannedVUs: 1}, + {TimeOffset: 103 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a much shorter GracefulStop than the GracefulRampDown + conf.GracefulStop = types.NullDurationFrom(3 * time.Second) + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 5}, + {TimeOffset: 2 * time.Second, PlannedVUs: 6}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a zero GracefulStop + conf.GracefulStop = types.NullDurationFrom(0 * time.Second) + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 5}, + {TimeOffset: 2 * time.Second, PlannedVUs: 6}, + {TimeOffset: 23 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a zero GracefulStop and GracefulRampDown, i.e. raw steps with 0 end cap + conf.GracefulRampDown = types.NullDurationFrom(0 * time.Second) + assert.Equal(t, rawStepsZeroEnd, conf.GetExecutionRequirements(et)) +} + +func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { + t.Parallel() + et, err := lib.NewExecutionTuple(newExecutionSegmentFromString("0:1/3"), nil) + require.NoError(t, err) + conf := NewVariableLoopingVUsConfig("test") + conf.StartVUs = null.IntFrom(4) + conf.Stages = []Stage{ + {Target: null.IntFrom(6), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(5 * time.Second)}, + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(4 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(4 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(3 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(0 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(3 * time.Second)}, + } + + expRawStepsNoZeroEnd := []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 2 * time.Second, PlannedVUs: 2}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 2}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 16 * time.Second, PlannedVUs: 1}, + {TimeOffset: 18 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + } + rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) + assert.Equal(t, expRawStepsNoZeroEnd, rawStepsNoZeroEnd) + endOffset, isFinal := lib.GetEndOffset(rawStepsNoZeroEnd) + assert.Equal(t, 20*time.Second, endOffset) + assert.Equal(t, true, isFinal) + + rawStepsZeroEnd := conf.getRawExecutionSteps(et, true) + assert.Equal(t, expRawStepsNoZeroEnd, rawStepsZeroEnd) + endOffset, isFinal = lib.GetEndOffset(rawStepsZeroEnd) + assert.Equal(t, 20*time.Second, endOffset) + assert.Equal(t, true, isFinal) + + // GracefulStop and GracefulRampDown equal to the default 30 sec + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 42 * time.Second, PlannedVUs: 1}, + {TimeOffset: 50 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a longer GracefulStop than the GracefulRampDown + conf.GracefulStop = types.NullDurationFrom(80 * time.Second) + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 42 * time.Second, PlannedVUs: 1}, + {TimeOffset: 50 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a much shorter GracefulStop than the GracefulRampDown + conf.GracefulStop = types.NullDurationFrom(3 * time.Second) + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a zero GracefulStop + conf.GracefulStop = types.NullDurationFrom(0 * time.Second) + assert.Equal(t, []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 23 * time.Second, PlannedVUs: 0}, + }, conf.GetExecutionRequirements(et)) + + // Try a zero GracefulStop and GracefulRampDown, i.e. raw steps with 0 end cap + conf.GracefulRampDown = types.NullDurationFrom(0 * time.Second) + assert.Equal(t, rawStepsZeroEnd, conf.GetExecutionRequirements(et)) +} From 721a47e6072ccfec05774c1f867a7b286a8968f2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Apr 2020 12:16:16 +0300 Subject: [PATCH 03/23] Fix not adding steps with the same amount of VUs --- lib/executor/variable_looping_vus.go | 40 +++++++++-------------- lib/executor/variable_looping_vus_test.go | 2 -- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index dabbd22a1cb..23ddc135ac1 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -39,10 +39,6 @@ import ( const variableLoopingVUsType = "variable-looping-vus" -// How often we can make VU adjustments when processing stages -// TODO: make configurable, in some bounds? -const minIntervalBetweenVUAdjustments = 100 * time.Millisecond - func init() { lib.RegisterExecutorConfigType( variableLoopingVUsType, @@ -200,11 +196,18 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple } // Reserve the scaled StartVUs at the beginning - prevScaledVUs := et.ES.Scale(vlvc.StartVUs.Int64) + prevScaledVUs := et.ScaleInt64(vlvc.StartVUs.Int64) steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(prevScaledVUs)}} timeFromStart := time.Duration(0) totalDuration := time.Duration(0) + addStep := func(step lib.ExecutionStep) { + if len(steps) == 0 || steps[len(steps)-1].PlannedVUs != step.PlannedVUs { + steps = append(steps, step) + prevScaledVUs = int64(step.PlannedVUs) + } + } + for _, stage := range vlvc.Stages { stageEndVUs := stage.Target.Int64 stageDuration := time.Duration(stage.Duration.Duration) @@ -221,18 +224,14 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // Handle 0-duration stages, i.e. instant VU jumps if stageDuration == 0 { fromVUs = stageEndVUs - prevScaledVUs = et.ES.Scale(stageEndVUs) - steps = append(steps, lib.ExecutionStep{ + addStep(lib.ExecutionStep{ TimeOffset: timeFromStart, - PlannedVUs: uint64(prevScaledVUs), + PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), }) continue } - // For each stage, limit any VU adjustments between the previous - // number of VUs and the stage's target to happen at most once - // every minIntervalBetweenVUAdjustments. No floats or ratios, - // since nanoseconds should be good enough for anyone... :) + // No floats or ratios,since nanoseconds should be good enough for anyone... :) stepInterval := stageDuration / time.Duration(stageVUAbsDiff) // Loop through the potential steps, adding an item to the @@ -247,29 +246,22 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple stepGlobalVUs := fromVUs + int64( math.Round((float64(t)*float64(stageEndVUs-fromVUs))/float64(stageDuration)), ) - stepScaledVus := et.ES.Scale(stepGlobalVUs) - - if stepScaledVus == prevScaledVUs { - // only add steps when there's a change in the number of VUs - continue - } // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() - steps = append(steps, lib.ExecutionStep{ + addStep(lib.ExecutionStep{ TimeOffset: timeFromStart + t, - PlannedVUs: uint64(stepScaledVus), + PlannedVUs: uint64(et.ScaleInt64(stepGlobalVUs)), }) - prevScaledVUs = stepScaledVus } fromVUs = stageEndVUs - prevScaledVUs = et.ES.Scale(stageEndVUs) timeFromStart += stageDuration - steps = append(steps, lib.ExecutionStep{ + + addStep(lib.ExecutionStep{ TimeOffset: timeFromStart, - PlannedVUs: uint64(prevScaledVUs), + PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), }) } diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 4d6c33035ae..ba010f7c7cc 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -286,7 +286,6 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { expRawStepsNoZeroEnd := []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 2 * time.Second, PlannedVUs: 2}, {TimeOffset: 4 * time.Second, PlannedVUs: 1}, {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, @@ -294,7 +293,6 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { {TimeOffset: 12 * time.Second, PlannedVUs: 1}, {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, - {TimeOffset: 18 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, } rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) From 44512c79952b572feb324a589858f0eb9e79a030 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Apr 2020 12:44:37 +0300 Subject: [PATCH 04/23] Simplify and probably stabilize the calculation of the steps --- lib/executor/variable_looping_vus.go | 65 +++++++--------------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 23ddc135ac1..2b14e19c9b6 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -23,7 +23,6 @@ package executor import ( "context" "fmt" - "math" "sync" "sync/atomic" "time" @@ -188,13 +187,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // the values are scaled only before we add them to the steps result slice fromVUs := vlvc.StartVUs.Int64 - abs := func(n int64) int64 { // sigh... - if n < 0 { - return -n - } - return n - } - // Reserve the scaled StartVUs at the beginning prevScaledVUs := et.ScaleInt64(vlvc.StartVUs.Int64) steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(prevScaledVUs)}} @@ -213,47 +205,22 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple stageDuration := time.Duration(stage.Duration.Duration) totalDuration += stageDuration - stageVUAbsDiff := abs(stageEndVUs - fromVUs) - if stageVUAbsDiff == 0 { - // We don't have to do anything but update the time offset - // if the number of VUs wasn't changed in this stage - timeFromStart += stageDuration - continue - } - - // Handle 0-duration stages, i.e. instant VU jumps - if stageDuration == 0 { - fromVUs = stageEndVUs - addStep(lib.ExecutionStep{ - TimeOffset: timeFromStart, - PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), - }) - continue - } - - // No floats or ratios,since nanoseconds should be good enough for anyone... :) - stepInterval := stageDuration / time.Duration(stageVUAbsDiff) - - // Loop through the potential steps, adding an item to the - // result only when there's a change in the number of VUs. - // - // IMPORTANT: we have to be very careful of rounding errors, - // both from the step duration and from the VUs. It's especially - // important that the scaling via the execution segment should - // happen AFTER the rest of the calculations have been done and - // we've rounded the global "global" number of VUs. - for t := stepInterval; t < stageDuration; t += stepInterval { // Skip the first step, since we've already added that - stepGlobalVUs := fromVUs + int64( - math.Round((float64(t)*float64(stageEndVUs-fromVUs))/float64(stageDuration)), - ) - - // VU reservation for gracefully ramping down is handled as a - // separate method: reserveVUsForGracefulRampDowns() - - addStep(lib.ExecutionStep{ - TimeOffset: timeFromStart + t, - PlannedVUs: uint64(et.ScaleInt64(stepGlobalVUs)), - }) + stageVUDiff := stageEndVUs - fromVUs + if stageDuration != 0 && stageVUDiff != 0 { + var sign int64 = 1 + if stageVUDiff < 0 { + sign = -1 + } + // Loop through the potential steps, adding an item to the + // result only when there's a change in the number of VUs. + for i := sign; i != stageVUDiff; i += sign { // Skip the first step, since we've already added that + // VU reservation for gracefully ramping down is handled as a + // separate method: reserveVUsForGracefulRampDowns() + addStep(lib.ExecutionStep{ + TimeOffset: timeFromStart + (stageDuration*time.Duration(i))/time.Duration(stageVUDiff), + PlannedVUs: uint64(et.ScaleInt64(fromVUs + i)), + }) + } } fromVUs = stageEndVUs From 7402ee048747c730d9e159468e2b84ed5bf5c6f2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Apr 2020 12:51:39 +0300 Subject: [PATCH 05/23] drop unneded variable --- lib/executor/variable_looping_vus.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 2b14e19c9b6..448b0680a72 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -191,7 +191,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple prevScaledVUs := et.ScaleInt64(vlvc.StartVUs.Int64) steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(prevScaledVUs)}} timeFromStart := time.Duration(0) - totalDuration := time.Duration(0) addStep := func(step lib.ExecutionStep) { if len(steps) == 0 || steps[len(steps)-1].PlannedVUs != step.PlannedVUs { @@ -203,7 +202,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple for _, stage := range vlvc.Stages { stageEndVUs := stage.Target.Int64 stageDuration := time.Duration(stage.Duration.Duration) - totalDuration += stageDuration stageVUDiff := stageEndVUs - fromVUs if stageDuration != 0 && stageVUDiff != 0 { @@ -234,7 +232,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple if zeroEnd && steps[len(steps)-1].PlannedVUs != 0 { // If the last PlannedVUs value wasn't 0, add a last step with 0 - steps = append(steps, lib.ExecutionStep{TimeOffset: totalDuration, PlannedVUs: 0}) + steps = append(steps, lib.ExecutionStep{TimeOffset: timeFromStart, PlannedVUs: 0}) } return steps } From e4607a1698ee8b6d034a3470ba8c54f1573bc93b Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 10 Apr 2020 13:31:10 +0300 Subject: [PATCH 06/23] Add one test with executionTuples for looping vus --- lib/executor/variable_looping_vus_test.go | 173 ++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index ba010f7c7cc..52fb7974f85 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -344,3 +344,176 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { conf.GracefulRampDown = types.NullDurationFrom(0 * time.Second) assert.Equal(t, rawStepsZeroEnd, conf.GetExecutionRequirements(et)) } + +func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) { + t.Parallel() + + conf := NewVariableLoopingVUsConfig("test") + conf.StartVUs = null.IntFrom(4) + conf.Stages = []Stage{ + {Target: null.IntFrom(6), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(5 * time.Second)}, + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(4 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(4 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(3 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(0 * time.Second)}, + {Target: null.IntFrom(1), Duration: types.NullDurationFrom(3 * time.Second)}, + } + /* + + Graph of the above: + ^ + 8 | + 7 | + 6 | + + 5 |/ \ + + 4 + \ / \ +-+ + 3 | \ / \ / | + 2 | \ / \ / | + 1 | + + +--+ + 0 +-------------------------------------------------------------> + 0123456789012345678901234567890 + + */ + + testCases := []struct { + expectedSteps []lib.ExecutionStep + et *lib.ExecutionTuple + }{ + { + et: mustNewExecutionTuple(nil, nil), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 5}, + {TimeOffset: 2 * time.Second, PlannedVUs: 6}, + {TimeOffset: 3 * time.Second, PlannedVUs: 5}, + {TimeOffset: 4 * time.Second, PlannedVUs: 4}, + {TimeOffset: 5 * time.Second, PlannedVUs: 3}, + {TimeOffset: 6 * time.Second, PlannedVUs: 2}, + {TimeOffset: 7 * time.Second, PlannedVUs: 1}, + {TimeOffset: 8 * time.Second, PlannedVUs: 2}, + {TimeOffset: 9 * time.Second, PlannedVUs: 3}, + {TimeOffset: 10 * time.Second, PlannedVUs: 4}, + {TimeOffset: 11 * time.Second, PlannedVUs: 5}, + {TimeOffset: 12 * time.Second, PlannedVUs: 4}, + {TimeOffset: 13 * time.Second, PlannedVUs: 3}, + {TimeOffset: 14 * time.Second, PlannedVUs: 2}, + {TimeOffset: 15 * time.Second, PlannedVUs: 1}, + {TimeOffset: 16 * time.Second, PlannedVUs: 2}, + {TimeOffset: 17 * time.Second, PlannedVUs: 3}, + {TimeOffset: 18 * time.Second, PlannedVUs: 4}, + {TimeOffset: 20 * time.Second, PlannedVUs: 1}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), nil), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 2}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 16 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), newExecutionSegmentSequenceFromString("0,1/3,1")), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 2}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 16 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("1/3:2/3"), nil), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 2}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 16 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("2/3:1"), nil), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 2}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 16 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), newExecutionSegmentSequenceFromString("0,1/3,2/3,1")), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 2}, + {TimeOffset: 5 * time.Second, PlannedVUs: 1}, + {TimeOffset: 10 * time.Second, PlannedVUs: 2}, + {TimeOffset: 13 * time.Second, PlannedVUs: 1}, + {TimeOffset: 18 * time.Second, PlannedVUs: 2}, + {TimeOffset: 20 * time.Second, PlannedVUs: 1}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("1/3:2/3"), newExecutionSegmentSequenceFromString("0,1/3,2/3,1")), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 1 * time.Second, PlannedVUs: 2}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 2}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 16 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + }, + }, + { + et: mustNewExecutionTuple(newExecutionSegmentFromString("2/3:1"), newExecutionSegmentSequenceFromString("0,1/3,2/3,1")), + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 1}, + {TimeOffset: 2 * time.Second, PlannedVUs: 2}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 9 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 17 * time.Second, PlannedVUs: 1}, + {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + }, + }, + } + + for _, testCase := range testCases { + et := testCase.et + expectedSteps := testCase.expectedSteps + + t.Run(et.String(), func(t *testing.T) { + rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) + assert.Equal(t, expectedSteps, rawStepsNoZeroEnd) + }) + } +} From 42f04759620f325fb995f6ae0676de13c9cc11c7 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Mon, 13 Apr 2020 16:19:18 +0300 Subject: [PATCH 07/23] delete preveScaledVUs and 'optimize' addStep ;) --- lib/executor/variable_looping_vus.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 448b0680a72..b54e9f2b892 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -188,14 +188,12 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple fromVUs := vlvc.StartVUs.Int64 // Reserve the scaled StartVUs at the beginning - prevScaledVUs := et.ScaleInt64(vlvc.StartVUs.Int64) - steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(prevScaledVUs)}} + steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(et.ScaleInt64(vlvc.StartVUs.Int64))}} timeFromStart := time.Duration(0) addStep := func(step lib.ExecutionStep) { - if len(steps) == 0 || steps[len(steps)-1].PlannedVUs != step.PlannedVUs { + if steps[len(steps)-1].PlannedVUs != step.PlannedVUs { steps = append(steps, step) - prevScaledVUs = int64(step.PlannedVUs) } } From 71c088f5eec35727540430ddda9965b1ef778d44 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 14 Apr 2020 23:41:28 +0300 Subject: [PATCH 08/23] add benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name time/op VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/normal-8 461µs ± 1% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/rollercoaster-8 5.31ms ± 2% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/normal-8 463µs ± 2% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 5.28ms ± 1% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 311µs ± 2% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 4.22ms ± 2% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 209µs ± 4% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 2.82ms ± 3% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 354µs ± 3% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 4.70ms ± 4% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 9.77ms ± 3% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 60.4ms ± 3% name alloc/op VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/normal-8 1.07MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/rollercoaster-8 14.6MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/normal-8 1.07MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 14.6MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 254kB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 4.62MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 49.1kB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 1.38MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 352kB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 5.83MB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 352kB ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 5.83MB ± 0% name allocs/op VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/normal-8 20.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/rollercoaster-8 31.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/normal-8 20.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 31.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 15.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 26.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 11.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 21.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 16.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 27.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 16.0 ± 0% VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 27.0 ± 0% --- lib/executor/variable_looping_vus_test.go | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 52fb7974f85..cea9b7a28a4 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -22,6 +22,8 @@ package executor import ( "context" + "encoding/json" + "fmt" "sync/atomic" "testing" "time" @@ -517,3 +519,66 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) }) } } + +func BenchmarkVarriableArrivalRateGetRawExecutionSteps(b *testing.B) { + testCases := []struct { + seq string + seg string + }{ + {}, + {seg: "0:1"}, + {seq: "0,0.3,0.5,0.6,0.7,0.8,0.9,1", seg: "0:0.3"}, + {seq: "0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1", seg: "0:0.1"}, + {seg: "2/5:4/5"}, + {seg: "2235/5213:4/5"}, // just wanted it to be ugly ;D + } + + stageCases := []struct { + name string + stages string + }{ + { + name: "normal", + stages: `[{"duration":"5m", "target":5000},{"duration":"5m", "target":5000},{"duration":"5m", "target":10000},{"duration":"5m", "target":10000}]`, + }, { + name: "rollercoaster", + stages: `[{"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}, + {"duration":"5m", "target":5000},{"duration":"5m", "target":0}]`, + }, + } + for _, tc := range testCases { + tc := tc + b.Run(fmt.Sprintf("seq:%s;segment:%s", tc.seq, tc.seg), func(b *testing.B) { + ess, err := lib.NewExecutionSegmentSequenceFromString(tc.seq) + require.NoError(b, err) + segment, err := lib.NewExecutionSegmentFromString(tc.seg) + require.NoError(b, err) + if tc.seg == "" { + segment = nil // specifically for the optimization + } + et, err := lib.NewExecutionTuple(segment, &ess) + require.NoError(b, err) + for _, stageCase := range stageCases { + var st []Stage + require.NoError(b, json.Unmarshal([]byte(stageCase.stages), &st)) + vlvc := VariableLoopingVUsConfig{ + Stages: st, + } + b.Run(stageCase.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = vlvc.getRawExecutionSteps(et, false) + } + }) + } + }) + } +} From 96e503fe610244c21d23acc9fe62adf30a1c9725 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Tue, 14 Apr 2020 22:37:22 +0300 Subject: [PATCH 09/23] Rewrite the varriable looping vus to fix a bug and speed it up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the variable arrival rate instead of stopping VUs right away after they need to stopped them on the next step down. This is also not how the previous stages worked and is not how it was designed to work in the first place so it needed to be fixed. The speed up comes from the fact that we now skip some calculations if the test is not ran with a full execution segment. Unfortunately this is slower for the full execution segment as there we still do all the calculations but now we additionally do more stuff, maybe some more optimizations can be done name old time/op new time/op delta VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/normal-8 461µs ± 1% 571µs ± 3% +23.70% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/rollercoaster-8 5.31ms ± 2% 6.19ms ± 2% +16.69% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/normal-8 463µs ± 2% 569µs ± 2% +22.93% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 5.28ms ± 1% 6.21ms ± 2% +17.71% (p=0.000 n=19+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 311µs ± 2% 163µs ± 2% -47.38% (p=0.000 n=19+19) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 4.22ms ± 2% 2.10ms ± 2% -50.13% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 209µs ± 4% 44µs ± 3% -78.90% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 2.82ms ± 3% 0.68ms ± 2% -75.98% (p=0.000 n=20+19) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 354µs ± 3% 217µs ± 2% -38.76% (p=0.000 n=19+18) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 4.70ms ± 4% 2.71ms ± 2% -42.24% (p=0.000 n=20+19) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 9.77ms ± 3% 0.21ms ± 2% -97.90% (p=0.000 n=19+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 60.4ms ± 3% 2.5ms ± 2% -95.86% (p=0.000 n=20+20) name old alloc/op new alloc/op delta VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/normal-8 1.07MB ± 0% 1.07MB ± 0% ~ (all equal) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/rollercoaster-8 14.6MB ± 0% 14.6MB ± 0% ~ (p=0.121 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/normal-8 1.07MB ± 0% 1.07MB ± 0% +0.01% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 14.6MB ± 0% 14.6MB ± 0% +0.00% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 254kB ± 0% 254kB ± 0% +0.04% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 4.62MB ± 0% 4.62MB ± 0% +0.00% (p=0.000 n=20+19) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 49.1kB ± 0% 49.3kB ± 0% +0.24% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 1.38MB ± 0% 1.38MB ± 0% +0.01% (p=0.000 n=19+19) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 352kB ± 0% 352kB ± 0% +0.04% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 5.83MB ± 0% 5.83MB ± 0% +0.00% (p=0.000 n=19+17) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 352kB ± 0% 352kB ± 0% +0.04% (p=0.000 n=16+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 5.83MB ± 0% 5.83MB ± 0% +0.00% (p=0.000 n=20+19) name old allocs/op new allocs/op delta VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/normal-8 20.0 ± 0% 20.0 ± 0% ~ (all equal) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:/rollercoaster-8 31.0 ± 0% 31.0 ± 0% ~ (all equal) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/normal-8 20.0 ± 0% 22.0 ± 0% +10.00% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 31.0 ± 0% 33.0 ± 0% +6.45% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 15.0 ± 0% 19.0 ± 0% +26.67% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 26.0 ± 0% 30.0 ± 0% +15.38% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 11.0 ± 0% 16.0 ± 0% +45.45% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 21.0 ± 0% 26.0 ± 0% +23.81% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 16.0 ± 0% 19.0 ± 0% +18.75% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 27.0 ± 0% 30.0 ± 0% +11.11% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 16.0 ± 0% 19.0 ± 0% +18.75% (p=0.000 n=20+20) VarriableArrivalRateGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 27.0 ± 0% 30.0 ± 0% +11.11% (p=0.000 n=20+20) --- lib/executor/variable_looping_vus.go | 76 +++++++--- lib/executor/variable_looping_vus_test.go | 172 ++++++++++++++-------- 2 files changed, 165 insertions(+), 83 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index b54e9f2b892..dfbb30935a1 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -189,7 +189,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // Reserve the scaled StartVUs at the beginning steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(et.ScaleInt64(vlvc.StartVUs.Int64))}} - timeFromStart := time.Duration(0) + var timeTillEnd time.Duration addStep := func(step lib.ExecutionStep) { if steps[len(steps)-1].PlannedVUs != step.PlannedVUs { @@ -197,40 +197,72 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple } } + start, offsets, _ := et.GetStripedOffsets(et.ES) + var localIndex int64 // this is the index of the vu for this execution segment + next := func(sign int64) int64 { + r := offsets[int(localIndex)%len(offsets)] + localIndex += sign + return r + } + i := start + 1 // this is the index for the full execution segment for _, stage := range vlvc.Stages { stageEndVUs := stage.Target.Int64 stageDuration := time.Duration(stage.Duration.Duration) + timeTillEnd += stageDuration stageVUDiff := stageEndVUs - fromVUs - if stageDuration != 0 && stageVUDiff != 0 { - var sign int64 = 1 - if stageVUDiff < 0 { - sign = -1 + switch { + case stageDuration == 0: + addStep(lib.ExecutionStep{ + TimeOffset: timeTillEnd, + PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), + }) + case stageVUDiff != 0: + // Get the index to the start if they are not there + if i > fromVUs { + for ; i > fromVUs; i -= next(-1) { + if localIndex == 0 { // we want ot enter for this index but not actually go below 0 + break + } + } + } else { + for ; i < fromVUs; i += next(1) { // <= test + } } - // Loop through the potential steps, adding an item to the - // result only when there's a change in the number of VUs. - for i := sign; i != stageVUDiff; i += sign { // Skip the first step, since we've already added that - // VU reservation for gracefully ramping down is handled as a - // separate method: reserveVUsForGracefulRampDowns() - addStep(lib.ExecutionStep{ - TimeOffset: timeFromStart + (stageDuration*time.Duration(i))/time.Duration(stageVUDiff), - PlannedVUs: uint64(et.ScaleInt64(fromVUs + i)), - }) + + if i > stageEndVUs { // ramp down + // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it + // it will just go to it + for ; i > stageEndVUs; i -= next(-1) { + // VU reservation for gracefully ramping down is handled as a + // separate method: reserveVUsForGracefulRampDowns() + addStep(lib.ExecutionStep{ + TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), + PlannedVUs: uint64(localIndex), + }) + if localIndex == 0 { // we want ot enter for this index but not actually go below 0 + break + } + } + } else { + // here we want the emit for the last one as this case it actually should emit that + // we start it + for ; i <= stageEndVUs; i += next(1) { + // VU reservation for gracefully ramping down is handled as a + // separate method: reserveVUsForGracefulRampDowns() + addStep(lib.ExecutionStep{ + TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), + PlannedVUs: uint64(localIndex + 1), + }) + } } } - fromVUs = stageEndVUs - timeFromStart += stageDuration - - addStep(lib.ExecutionStep{ - TimeOffset: timeFromStart, - PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), - }) } if zeroEnd && steps[len(steps)-1].PlannedVUs != 0 { // If the last PlannedVUs value wasn't 0, add a last step with 0 - steps = append(steps, lib.ExecutionStep{TimeOffset: timeFromStart, PlannedVUs: 0}) + steps = append(steps, lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: 0}) } return steps } diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index cea9b7a28a4..4d90128543e 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -189,19 +189,19 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 3 * time.Second, PlannedVUs: 5}, - {TimeOffset: 4 * time.Second, PlannedVUs: 4}, - {TimeOffset: 5 * time.Second, PlannedVUs: 3}, - {TimeOffset: 6 * time.Second, PlannedVUs: 2}, - {TimeOffset: 7 * time.Second, PlannedVUs: 1}, + {TimeOffset: 2 * time.Second, PlannedVUs: 5}, + {TimeOffset: 3 * time.Second, PlannedVUs: 4}, + {TimeOffset: 4 * time.Second, PlannedVUs: 3}, + {TimeOffset: 5 * time.Second, PlannedVUs: 2}, + {TimeOffset: 6 * time.Second, PlannedVUs: 1}, {TimeOffset: 8 * time.Second, PlannedVUs: 2}, {TimeOffset: 9 * time.Second, PlannedVUs: 3}, {TimeOffset: 10 * time.Second, PlannedVUs: 4}, {TimeOffset: 11 * time.Second, PlannedVUs: 5}, - {TimeOffset: 12 * time.Second, PlannedVUs: 4}, - {TimeOffset: 13 * time.Second, PlannedVUs: 3}, - {TimeOffset: 14 * time.Second, PlannedVUs: 2}, - {TimeOffset: 15 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 4}, + {TimeOffset: 12 * time.Second, PlannedVUs: 3}, + {TimeOffset: 13 * time.Second, PlannedVUs: 2}, + {TimeOffset: 14 * time.Second, PlannedVUs: 1}, {TimeOffset: 16 * time.Second, PlannedVUs: 2}, {TimeOffset: 17 * time.Second, PlannedVUs: 3}, {TimeOffset: 18 * time.Second, PlannedVUs: 4}, @@ -227,8 +227,8 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 33 * time.Second, PlannedVUs: 5}, - {TimeOffset: 42 * time.Second, PlannedVUs: 4}, + {TimeOffset: 32 * time.Second, PlannedVUs: 5}, + {TimeOffset: 41 * time.Second, PlannedVUs: 4}, {TimeOffset: 50 * time.Second, PlannedVUs: 1}, {TimeOffset: 53 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -239,8 +239,8 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 33 * time.Second, PlannedVUs: 5}, - {TimeOffset: 42 * time.Second, PlannedVUs: 4}, + {TimeOffset: 32 * time.Second, PlannedVUs: 5}, + {TimeOffset: 41 * time.Second, PlannedVUs: 4}, {TimeOffset: 50 * time.Second, PlannedVUs: 1}, {TimeOffset: 103 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -288,12 +288,12 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { expRawStepsNoZeroEnd := []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, - {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 11 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, } @@ -313,7 +313,7 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { assert.Equal(t, []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 42 * time.Second, PlannedVUs: 1}, + {TimeOffset: 41 * time.Second, PlannedVUs: 1}, {TimeOffset: 50 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -322,7 +322,7 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { assert.Equal(t, []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 42 * time.Second, PlannedVUs: 1}, + {TimeOffset: 41 * time.Second, PlannedVUs: 1}, {TimeOffset: 50 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -347,7 +347,7 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { assert.Equal(t, rawStepsZeroEnd, conf.GetExecutionRequirements(et)) } -func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) { +func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { t.Parallel() conf := NewVariableLoopingVUsConfig("test") @@ -361,6 +361,12 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) {Target: null.IntFrom(4), Duration: types.NullDurationFrom(2 * time.Second)}, {Target: null.IntFrom(1), Duration: types.NullDurationFrom(0 * time.Second)}, {Target: null.IntFrom(1), Duration: types.NullDurationFrom(3 * time.Second)}, + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(0 * time.Second)}, + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(3 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(0 * time.Second)}, + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(4), Duration: types.NullDurationFrom(4 * time.Second)}, } /* @@ -369,13 +375,13 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) 8 | 7 | 6 | + - 5 |/ \ + - 4 + \ / \ +-+ - 3 | \ / \ / | - 2 | \ / \ / | - 1 | + + +--+ - 0 +-------------------------------------------------------------> - 0123456789012345678901234567890 + 5 |/ \ + +--+ + 4 + \ / \ +-+ | | * + 3 | \ / \ / | | | / + 2 | \ / \ / | | | + / + 1 | + + +--+ |/ \ / + 0 +-------------------------+---+------------------------------> + 01234567890123456789012345678901234567890 */ @@ -389,23 +395,33 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 3 * time.Second, PlannedVUs: 5}, - {TimeOffset: 4 * time.Second, PlannedVUs: 4}, - {TimeOffset: 5 * time.Second, PlannedVUs: 3}, - {TimeOffset: 6 * time.Second, PlannedVUs: 2}, - {TimeOffset: 7 * time.Second, PlannedVUs: 1}, + {TimeOffset: 2 * time.Second, PlannedVUs: 5}, + {TimeOffset: 3 * time.Second, PlannedVUs: 4}, + {TimeOffset: 4 * time.Second, PlannedVUs: 3}, + {TimeOffset: 5 * time.Second, PlannedVUs: 2}, + {TimeOffset: 6 * time.Second, PlannedVUs: 1}, {TimeOffset: 8 * time.Second, PlannedVUs: 2}, {TimeOffset: 9 * time.Second, PlannedVUs: 3}, {TimeOffset: 10 * time.Second, PlannedVUs: 4}, {TimeOffset: 11 * time.Second, PlannedVUs: 5}, - {TimeOffset: 12 * time.Second, PlannedVUs: 4}, - {TimeOffset: 13 * time.Second, PlannedVUs: 3}, - {TimeOffset: 14 * time.Second, PlannedVUs: 2}, - {TimeOffset: 15 * time.Second, PlannedVUs: 1}, + {TimeOffset: 11 * time.Second, PlannedVUs: 4}, + {TimeOffset: 12 * time.Second, PlannedVUs: 3}, + {TimeOffset: 13 * time.Second, PlannedVUs: 2}, + {TimeOffset: 14 * time.Second, PlannedVUs: 1}, {TimeOffset: 16 * time.Second, PlannedVUs: 2}, {TimeOffset: 17 * time.Second, PlannedVUs: 3}, {TimeOffset: 18 * time.Second, PlannedVUs: 4}, {TimeOffset: 20 * time.Second, PlannedVUs: 1}, + {TimeOffset: 23 * time.Second, PlannedVUs: 5}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 27 * time.Second, PlannedVUs: 1}, + {TimeOffset: 28 * time.Second, PlannedVUs: 2}, + {TimeOffset: 28 * time.Second, PlannedVUs: 1}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, + {TimeOffset: 31 * time.Second, PlannedVUs: 1}, + {TimeOffset: 32 * time.Second, PlannedVUs: 2}, + {TimeOffset: 33 * time.Second, PlannedVUs: 3}, + {TimeOffset: 34 * time.Second, PlannedVUs: 4}, }, }, { @@ -413,14 +429,19 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, - {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 11 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + {TimeOffset: 23 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 28 * time.Second, PlannedVUs: 1}, + {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, { @@ -428,14 +449,19 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, - {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 11 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + {TimeOffset: 23 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 28 * time.Second, PlannedVUs: 1}, + {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, { @@ -443,14 +469,19 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, - {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 11 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + {TimeOffset: 23 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 28 * time.Second, PlannedVUs: 1}, + {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, { @@ -458,25 +489,36 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, - {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 11 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + {TimeOffset: 23 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 28 * time.Second, PlannedVUs: 1}, + {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, { et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), newExecutionSegmentSequenceFromString("0,1/3,2/3,1")), expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 2}, - {TimeOffset: 5 * time.Second, PlannedVUs: 1}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, {TimeOffset: 10 * time.Second, PlannedVUs: 2}, - {TimeOffset: 13 * time.Second, PlannedVUs: 1}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, {TimeOffset: 18 * time.Second, PlannedVUs: 2}, {TimeOffset: 20 * time.Second, PlannedVUs: 1}, + {TimeOffset: 23 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 27 * time.Second, PlannedVUs: 1}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, + {TimeOffset: 31 * time.Second, PlannedVUs: 1}, + {TimeOffset: 34 * time.Second, PlannedVUs: 2}, }, }, { @@ -484,14 +526,19 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, - {TimeOffset: 15 * time.Second, PlannedVUs: 0}, + {TimeOffset: 11 * time.Second, PlannedVUs: 1}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + {TimeOffset: 23 * time.Second, PlannedVUs: 2}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 28 * time.Second, PlannedVUs: 1}, + {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, { @@ -499,12 +546,15 @@ func TestVariableLoopingVUsConfigExecutionPlanExecutionTupleTests(t *testing.T) expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 2 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 2 * time.Second, PlannedVUs: 1}, + {TimeOffset: 5 * time.Second, PlannedVUs: 0}, {TimeOffset: 9 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 13 * time.Second, PlannedVUs: 0}, {TimeOffset: 17 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, + {TimeOffset: 23 * time.Second, PlannedVUs: 1}, + {TimeOffset: 26 * time.Second, PlannedVUs: 0}, + {TimeOffset: 33 * time.Second, PlannedVUs: 1}, }, }, } From 89d5b736e7d4b5b9820ea465bc0bd32f0cbdb9a4 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 15 Apr 2020 17:48:18 +0300 Subject: [PATCH 10/23] Rename variable looping vus benchmark --- lib/executor/variable_looping_vus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 4d90128543e..86008c9b8ea 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -570,7 +570,7 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { } } -func BenchmarkVarriableArrivalRateGetRawExecutionSteps(b *testing.B) { +func BenchmarkVarriableLoopingVUsGetRawExecutionSteps(b *testing.B) { testCases := []struct { seq string seg string From 1bce1febff8d83be7fb90f99a1a2eaa83377fa75 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 15 Apr 2020 19:01:07 +0300 Subject: [PATCH 11/23] Add a bunch of corner case tests for variable looping vus --- lib/executor/variable_looping_vus_test.go | 140 ++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 86008c9b8ea..1e25f68a75b 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -570,6 +570,146 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { } } +func TestVarriableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + expectedSteps []lib.ExecutionStep + et *lib.ExecutionTuple + stages []Stage + start int64 + }{ + { + name: "going up then down straight away", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 2}, + {TimeOffset: 0 * time.Second, PlannedVUs: 5}, + {TimeOffset: 0 * time.Second, PlannedVUs: 4}, + {TimeOffset: 1 * time.Second, PlannedVUs: 3}, + }, + stages: []Stage{ + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(0 * time.Second)}, + {Target: null.IntFrom(3), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + start: 2, + }, + { + name: "jump up then go up again", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 3}, + {TimeOffset: 1 * time.Second, PlannedVUs: 4}, + {TimeOffset: 2 * time.Second, PlannedVUs: 5}, + }, + stages: []Stage{ + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + start: 3, + }, + { + name: "up down up down", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + {TimeOffset: 1 * time.Second, PlannedVUs: 1}, + {TimeOffset: 2 * time.Second, PlannedVUs: 2}, + {TimeOffset: 2 * time.Second, PlannedVUs: 1}, + {TimeOffset: 3 * time.Second, PlannedVUs: 0}, + {TimeOffset: 5 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 2}, + {TimeOffset: 6 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + }, + stages: []Stage{ + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + }, + { + name: "up down up down in half", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + {TimeOffset: 1 * time.Second, PlannedVUs: 1}, + {TimeOffset: 3 * time.Second, PlannedVUs: 0}, + {TimeOffset: 5 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + }, + et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/2"), nil), + stages: []Stage{ + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + }, + { + name: "up down up down in the other half", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + {TimeOffset: 2 * time.Second, PlannedVUs: 1}, + {TimeOffset: 2 * time.Second, PlannedVUs: 0}, + {TimeOffset: 6 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + }, + et: mustNewExecutionTuple(newExecutionSegmentFromString("1/2:1"), nil), + stages: []Stage{ + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + }, + { + name: "up down up down in with nothing", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + }, + et: mustNewExecutionTuple(newExecutionSegmentFromString("2/3:1"), newExecutionSegmentSequenceFromString("0,1/3,2/3,1")), + stages: []Stage{ + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + }, + { + name: "up down up down in with funky sequence", // panics if there are no localIndex == 0 guards + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + {TimeOffset: 1 * time.Second, PlannedVUs: 1}, + {TimeOffset: 3 * time.Second, PlannedVUs: 0}, + {TimeOffset: 5 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + }, + et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), newExecutionSegmentSequenceFromString("0,1/3,1/2,2/3,1")), + stages: []Stage{ + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, + }, + }, + } + + for _, testCase := range testCases { + conf := NewVariableLoopingVUsConfig("test") + conf.StartVUs = null.IntFrom(testCase.start) + conf.Stages = testCase.stages + et := testCase.et + if et == nil { + et = mustNewExecutionTuple(nil, nil) + } + expectedSteps := testCase.expectedSteps + + t.Run(testCase.name, func(t *testing.T) { + rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false) + assert.Equal(t, expectedSteps, rawStepsNoZeroEnd) + }) + } + +} + func BenchmarkVarriableLoopingVUsGetRawExecutionSteps(b *testing.B) { testCases := []struct { seq string From b75355c31cb28a8055facd80a2adc2b4b195360f Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Wed, 15 Apr 2020 20:46:01 +0300 Subject: [PATCH 12/23] Varriable->Variable --- lib/executor/variable_looping_vus_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 1e25f68a75b..42b741ee053 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -570,7 +570,7 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { } } -func TestVarriableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { +func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { t.Parallel() testCases := []struct { @@ -707,10 +707,9 @@ func TestVarriableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { assert.Equal(t, expectedSteps, rawStepsNoZeroEnd) }) } - } -func BenchmarkVarriableLoopingVUsGetRawExecutionSteps(b *testing.B) { +func BenchmarkVariableLoopingVUsGetRawExecutionSteps(b *testing.B) { testCases := []struct { seq string seg string From 8faff961134636895586aa436f69e8dd7b5cf9e8 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Apr 2020 11:37:24 +0300 Subject: [PATCH 13/23] Fix going back if the offsets are different --- lib/executor/variable_looping_vus.go | 8 ++++++-- lib/executor/variable_looping_vus_test.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index dfbb30935a1..cea07156057 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -199,8 +199,12 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple start, offsets, _ := et.GetStripedOffsets(et.ES) var localIndex int64 // this is the index of the vu for this execution segment - next := func(sign int64) int64 { - r := offsets[int(localIndex)%len(offsets)] + next := func(sign int64) (r int64) { + if sign == 1 { + r = offsets[int(localIndex)%len(offsets)] + } else { + r = offsets[int(localIndex-1)%len(offsets)] + } localIndex += sign return r } diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 42b741ee053..ba49a768863 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -690,6 +690,26 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { {Target: null.IntFrom(0), Duration: types.NullDurationFrom(2 * time.Second)}, }, }, + { + name: "strange", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + {TimeOffset: 1 * time.Second, PlannedVUs: 1}, + {TimeOffset: 5 * time.Second, PlannedVUs: 2}, + {TimeOffset: 8 * time.Second, PlannedVUs: 3}, + {TimeOffset: 11 * time.Second, PlannedVUs: 4}, + {TimeOffset: 15 * time.Second, PlannedVUs: 5}, + {TimeOffset: 18 * time.Second, PlannedVUs: 6}, + {TimeOffset: 23 * time.Second, PlannedVUs: 7}, + {TimeOffset: 35 * time.Second, PlannedVUs: 8}, + {TimeOffset: 44 * time.Second, PlannedVUs: 9}, + }, + et: mustNewExecutionTuple(newExecutionSegmentFromString("0:0.3"), newExecutionSegmentSequenceFromString("0,0.3,0.6,0.9,1")), + stages: []Stage{ + {Target: null.IntFrom(20), Duration: types.NullDurationFrom(20 * time.Second)}, + {Target: null.IntFrom(30), Duration: types.NullDurationFrom(30 * time.Second)}, + }, + }, } for _, testCase := range testCases { From eea43fc373b8b0a4659f9507c960a9463d5f1c96 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 16 Apr 2020 11:40:22 +0300 Subject: [PATCH 14/23] don't use switch use ifs --- lib/executor/variable_looping_vus.go | 75 +++++++++++++++------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index cea07156057..8620586abff 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -215,51 +215,54 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple timeTillEnd += stageDuration stageVUDiff := stageEndVUs - fromVUs - switch { - case stageDuration == 0: + if stageVUDiff == 0 { + continue + } + if stageDuration == 0 { addStep(lib.ExecutionStep{ TimeOffset: timeTillEnd, PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), }) - case stageVUDiff != 0: - // Get the index to the start if they are not there - if i > fromVUs { - for ; i > fromVUs; i -= next(-1) { - if localIndex == 0 { // we want ot enter for this index but not actually go below 0 - break - } - } - } else { - for ; i < fromVUs; i += next(1) { // <= test + fromVUs = stageEndVUs + continue + } + // Get the index to the start if they are not there + if i > fromVUs { + for ; i > fromVUs; i -= next(-1) { + if localIndex == 0 { // we want ot enter for this index but not actually go below 0 + break } } + } else { + for ; i < fromVUs; i += next(1) { // <= test + } + } - if i > stageEndVUs { // ramp down - // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it - // it will just go to it - for ; i > stageEndVUs; i -= next(-1) { - // VU reservation for gracefully ramping down is handled as a - // separate method: reserveVUsForGracefulRampDowns() - addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), - PlannedVUs: uint64(localIndex), - }) - if localIndex == 0 { // we want ot enter for this index but not actually go below 0 - break - } - } - } else { - // here we want the emit for the last one as this case it actually should emit that - // we start it - for ; i <= stageEndVUs; i += next(1) { - // VU reservation for gracefully ramping down is handled as a - // separate method: reserveVUsForGracefulRampDowns() - addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), - PlannedVUs: uint64(localIndex + 1), - }) + if i > stageEndVUs { // ramp down + // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it + // it will just go to it + for ; i > stageEndVUs; i -= next(-1) { + // VU reservation for gracefully ramping down is handled as a + // separate method: reserveVUsForGracefulRampDowns() + addStep(lib.ExecutionStep{ + TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), + PlannedVUs: uint64(localIndex), + }) + if localIndex == 0 { // we want ot enter for this index but not actually go below 0 + break } } + } else { + // here we want the emit for the last one as this case it actually should emit that + // we start it + for ; i <= stageEndVUs; i += next(1) { + // VU reservation for gracefully ramping down is handled as a + // separate method: reserveVUsForGracefulRampDowns() + addStep(lib.ExecutionStep{ + TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), + PlannedVUs: uint64(localIndex + 1), + }) + } } fromVUs = stageEndVUs } From 35d4a5e1f6c34f9b3ea6d3f9fbb5e06873c49d19 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 23 Apr 2020 12:33:47 +0300 Subject: [PATCH 15/23] Refactor getRawExecutionSteps to be more logical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This has no (or questionable) performance difference for the benchmarks name old time/op new time/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 560µs ± 2% 560µs ± 3% ~ (p=0.563 n=19+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 6.02ms ± 2% 5.97ms ± 2% -0.82% (p=0.017 n=20+18) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 562µs ± 2% 561µs ± 2% ~ (p=0.647 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 5.93ms ± 3% 5.94ms ± 3% ~ (p=0.904 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 162µs ± 2% 161µs ± 2% ~ (p=0.247 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 2.07ms ± 1% 2.07ms ± 1% ~ (p=0.678 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 43.2µs ± 4% 43.5µs ± 2% ~ (p=0.108 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 655µs ± 1% 654µs ± 2% ~ (p=0.613 n=18+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 214µs ± 2% 215µs ± 2% ~ (p=0.383 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 2.63ms ± 2% 2.64ms ± 2% ~ (p=0.547 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 202µs ± 1% 202µs ± 2% ~ (p=0.678 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 2.42ms ± 2% 2.43ms ± 2% ~ (p=0.565 n=20+20) name old alloc/op new alloc/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 1.07MB ± 0% 1.07MB ± 0% -0.00% (p=0.037 n=16+17) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 14.6MB ± 0% 14.6MB ± 0% ~ (p=0.983 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 1.07MB ± 0% 1.07MB ± 0% ~ (p=0.096 n=20+15) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 14.6MB ± 0% 14.6MB ± 0% ~ (p=0.495 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 254kB ± 0% 254kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 4.62MB ± 0% 4.62MB ± 0% ~ (p=0.796 n=20+18) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 49.3kB ± 0% 49.3kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 1.38MB ± 0% 1.38MB ± 0% ~ (p=0.566 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 352kB ± 0% 352kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 5.83MB ± 0% 5.83MB ± 0% ~ (p=0.772 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 352kB ± 0% 352kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 5.83MB ± 0% 5.83MB ± 0% +0.00% (p=0.013 n=20+19) name old allocs/op new allocs/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 20.0 ± 0% 20.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 31.0 ± 0% 31.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 22.0 ± 0% 22.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 33.0 ± 0% 33.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 19.0 ± 0% 19.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 30.0 ± 0% 30.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 16.0 ± 0% 16.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 26.0 ± 0% 26.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 19.0 ± 0% 19.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 30.0 ± 0% 30.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 19.0 ± 0% 19.0 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 30.0 ± 0% 30.0 ± 0% ~ (all equal) --- lib/executor/variable_looping_vus.go | 102 +++++++----- lib/executor/variable_looping_vus_test.go | 193 +++++++++++++++++++++- 2 files changed, 256 insertions(+), 39 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 8620586abff..2bd36fdf292 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -187,8 +187,12 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // the values are scaled only before we add them to the steps result slice fromVUs := vlvc.StartVUs.Int64 + start, offsets, lcd := et.GetStripedOffsets(et.ES) + var index = segmentedIndex{start: start, lcd: lcd, offsets: offsets} + index.goTo(vlvc.StartVUs.Int64) // Reserve the scaled StartVUs at the beginning - steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(et.ScaleInt64(vlvc.StartVUs.Int64))}} + steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(index.local)}} + var timeTillEnd time.Duration addStep := func(step lib.ExecutionStep) { @@ -197,18 +201,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple } } - start, offsets, _ := et.GetStripedOffsets(et.ES) - var localIndex int64 // this is the index of the vu for this execution segment - next := func(sign int64) (r int64) { - if sign == 1 { - r = offsets[int(localIndex)%len(offsets)] - } else { - r = offsets[int(localIndex-1)%len(offsets)] - } - localIndex += sign - return r - } - i := start + 1 // this is the index for the full execution segment for _, stage := range vlvc.Stages { stageEndVUs := stage.Target.Int64 stageDuration := time.Duration(stage.Duration.Duration) @@ -219,48 +211,38 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple continue } if stageDuration == 0 { - addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd, - PlannedVUs: uint64(et.ScaleInt64(stageEndVUs)), - }) + index.goTo(stageEndVUs) + addStep(lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: uint64(index.local)}) fromVUs = stageEndVUs continue } - // Get the index to the start if they are not there - if i > fromVUs { - for ; i > fromVUs; i -= next(-1) { - if localIndex == 0 { // we want ot enter for this index but not actually go below 0 - break - } - } - } else { - for ; i < fromVUs; i += next(1) { // <= test - } - } - if i > stageEndVUs { // ramp down + if index.global > stageEndVUs { // ramp down // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it // it will just go to it - for ; i > stageEndVUs; i -= next(-1) { + for ; index.global > stageEndVUs; index.prev() { + if index.global > fromVUs { + continue + } // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), - PlannedVUs: uint64(localIndex), + TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.global)/stageVUDiff), + PlannedVUs: uint64(index.local - 1), }) - if localIndex == 0 { // we want ot enter for this index but not actually go below 0 - break - } } } else { // here we want the emit for the last one as this case it actually should emit that // we start it - for ; i <= stageEndVUs; i += next(1) { + for ; index.global <= stageEndVUs; index.next() { + if index.global < fromVUs { + continue + } // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - (stageDuration*time.Duration((stageEndVUs-i)))/time.Duration(stageVUDiff), - PlannedVUs: uint64(localIndex + 1), + TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.global)/stageVUDiff), + PlannedVUs: uint64(index.local), }) } } @@ -274,6 +256,50 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple return steps } +type segmentedIndex struct { // TODO: rename ... although this is probably the best name so far :D + start, lcd int64 + offsets []int64 + local, global int64 +} + +func (s *segmentedIndex) next() { + if s.local == 0 { + s.global += s.start + 1 + } else { + s.global += s.offsets[int(s.local-1)%len(s.offsets)] + } + s.local++ +} + +func (s *segmentedIndex) prev() { + if s.local == 1 { + s.global -= s.start + 1 + } else { + s.global -= s.offsets[int(s.local-2)%len(s.offsets)] + } + s.local-- +} + +func (s *segmentedIndex) goTo(value int64) { // TODO optimize + var gi int64 + s.local = (value / s.lcd) * int64(len(s.offsets)) + s.global = s.local / int64(len(s.offsets)) * s.lcd // TODO optimize ? + i := s.start + for ; i < value%s.lcd; gi, i = gi+1, i+s.offsets[gi] { + s.local++ + } + + if gi > 0 { + s.global += i - s.offsets[gi-1] + } else if s.local > 0 { + s.global -= s.offsets[len(s.offsets)-1] - s.start + } + + if s.local > 0 { + s.global++ // this is to fix the fact it starts from 0 + } +} + // If the graceful ramp-downs are enabled, we need to reserve any VUs that may // potentially have to finish running iterations when we're scaling their number // down. This would prevent attempts from other executors to use them while the diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index ba49a768863..5c8856b1cd1 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -79,7 +79,7 @@ func TestVariableLoopingVUsRun(t *testing.T) { sampleTimes := []time.Duration{ 500 * time.Millisecond, 1000 * time.Millisecond, - 700 * time.Millisecond, + 800 * time.Millisecond, } errCh := make(chan error) @@ -791,3 +791,194 @@ func BenchmarkVariableLoopingVUsGetRawExecutionSteps(b *testing.B) { }) } } + +func TestSegmentedIndex(t *testing.T) { + // TODO ... more structure ? + t.Run("full", func(t *testing.T) { + s := segmentedIndex{start: 0, lcd: 1, offsets: []int64{1}} + + s.next() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 2, s.local) + + s.next() + assert.EqualValues(t, 3, s.global) + assert.EqualValues(t, 3, s.local) + + s.prev() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 2, s.local) + + s.prev() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 2, s.local) + }) + + t.Run("half", func(t *testing.T) { + s := segmentedIndex{start: 0, lcd: 2, offsets: []int64{2}} + + s.next() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + + s.next() + assert.EqualValues(t, 3, s.global) + assert.EqualValues(t, 2, s.local) + + s.next() + assert.EqualValues(t, 5, s.global) + assert.EqualValues(t, 3, s.local) + + s.prev() + assert.EqualValues(t, 3, s.global) + assert.EqualValues(t, 2, s.local) + + s.prev() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 1, s.global) + assert.EqualValues(t, 1, s.local) + }) + + t.Run("the other half", func(t *testing.T) { + s := segmentedIndex{start: 1, lcd: 2, offsets: []int64{2}} + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.next() + assert.EqualValues(t, 4, s.global) + assert.EqualValues(t, 2, s.local) + + s.next() + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 3, s.local) + + s.prev() + assert.EqualValues(t, 4, s.global) + assert.EqualValues(t, 2, s.local) + + s.prev() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + }) + + t.Run("strange", func(t *testing.T) { + s := segmentedIndex{start: 1, lcd: 7, offsets: []int64{4, 3}} + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.next() + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 2, s.local) + + s.next() + assert.EqualValues(t, 9, s.global) + assert.EqualValues(t, 3, s.local) + + s.prev() + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 2, s.local) + + s.prev() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + + s.next() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.goTo(6) + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 2, s.local) + + s.goTo(5) + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.goTo(7) + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 2, s.local) + + s.goTo(8) + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 2, s.local) + + s.goTo(9) + assert.EqualValues(t, 9, s.global) + assert.EqualValues(t, 3, s.local) + + s.prev() + assert.EqualValues(t, 6, s.global) + assert.EqualValues(t, 2, s.local) + + s.prev() + assert.EqualValues(t, 2, s.global) + assert.EqualValues(t, 1, s.local) + + s.prev() + assert.EqualValues(t, 0, s.global) + assert.EqualValues(t, 0, s.local) + }) +} From d0a8ac5645713250671abd0f04c322e30dc8cd68 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 23 Apr 2020 13:37:12 +0300 Subject: [PATCH 16/23] Optimize getRawExecutionSteps by preallocating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 560µs ± 3% 302µs ± 3% -46.11% (p=0.000 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 5.97ms ± 2% 2.80ms ± 3% -53.04% (p=0.000 n=18+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 561µs ± 2% 303µs ± 3% -45.93% (p=0.000 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 5.94ms ± 3% 2.81ms ± 3% -52.63% (p=0.000 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 161µs ± 2% 86µs ± 4% -46.52% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 2.07ms ± 1% 1.08ms ± 3% -47.87% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 43.5µs ± 2% 30.0µs ± 3% -31.11% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 654µs ± 2% 331µs ± 2% -49.46% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 215µs ± 2% 114µs ± 4% -46.86% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 2.64ms ± 2% 1.33ms ± 3% -49.72% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 202µs ± 2% 111µs ± 3% -45.27% (p=0.000 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 2.43ms ± 2% 1.21ms ± 3% -50.08% (p=0.000 n=20+20) name old alloc/op new alloc/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 1.07MB ± 0% 0.25MB ± 0% -77.10% (p=0.000 n=17+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 14.6MB ± 0% 2.6MB ± 0% -81.87% (p=0.000 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 1.07MB ± 0% 0.25MB ± 0% -77.09% (p=0.000 n=15+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 14.6MB ± 0% 2.6MB ± 0% -81.87% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 254kB ± 0% 74kB ± 0% -70.93% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 4.62MB ± 0% 0.79MB ± 0% -82.80% (p=0.000 n=18+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 49.3kB ± 0% 24.7kB ± 0% -49.86% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 1.38MB ± 0% 0.27MB ± 0% -80.47% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 352kB ± 0% 98kB ± 0% -72.06% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 5.83MB ± 0% 1.06MB ± 0% -81.88% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 352kB ± 0% 90kB ± 0% -74.39% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 5.83MB ± 0% 0.98MB ± 0% -83.14% (p=0.000 n=19+20) name old allocs/op new allocs/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 20.0 ± 0% 1.0 ± 0% -95.00% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 31.0 ± 0% 1.0 ± 0% -96.77% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 22.0 ± 0% 3.0 ± 0% -86.36% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 33.0 ± 0% 3.0 ± 0% -90.91% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 19.0 ± 0% 5.0 ± 0% -73.68% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 30.0 ± 0% 5.0 ± 0% -83.33% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 16.0 ± 0% 6.0 ± 0% -62.50% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 26.0 ± 0% 6.0 ± 0% -76.92% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 19.0 ± 0% 4.0 ± 0% -78.95% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 30.0 ± 0% 4.0 ± 0% -86.67% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 19.0 ± 0% 4.0 ± 0% -78.95% (p=0.000 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 30.0 ± 0% 4.0 ± 0% -86.67% (p=0.000 n=20+20) --- lib/executor/variable_looping_vus.go | 47 +++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 2bd36fdf292..d1619522ce2 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -183,18 +183,16 @@ func (vlvc VariableLoopingVUsConfig) Validate() []error { // More information: https://github.com/loadimpact/k6/issues/997#issuecomment-484416866 //nolint:funlen func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple, zeroEnd bool) []lib.ExecutionStep { - // For accurate results, calculations are done with the unscaled values, and - // the values are scaled only before we add them to the steps result slice - fromVUs := vlvc.StartVUs.Int64 - - start, offsets, lcd := et.GetStripedOffsets(et.ES) - var index = segmentedIndex{start: start, lcd: lcd, offsets: offsets} - index.goTo(vlvc.StartVUs.Int64) + var ( + timeTillEnd time.Duration + fromVUs = vlvc.StartVUs.Int64 + start, offsets, lcd = et.GetStripedOffsets(et.ES) + index = segmentedIndex{start: start, lcd: lcd, offsets: offsets} + ) + index.goTo(fromVUs) + var steps = make([]lib.ExecutionStep, 0, vlvc.precalculateTheRequiredSteps(et, zeroEnd)) // Reserve the scaled StartVUs at the beginning - steps := []lib.ExecutionStep{{TimeOffset: 0, PlannedVUs: uint64(index.local)}} - - var timeTillEnd time.Duration - + steps = append(steps, lib.ExecutionStep{TimeOffset: 0, PlannedVUs: uint64(index.local)}) addStep := func(step lib.ExecutionStep) { if steps[len(steps)-1].PlannedVUs != step.PlannedVUs { steps = append(steps, step) @@ -300,6 +298,33 @@ func (s *segmentedIndex) goTo(value int64) { // TODO optimize } } +func absInt64(a int64) int64 { + if a < 0 { + return -a + } + return a +} + +func (vlvc VariableLoopingVUsConfig) precalculateTheRequiredSteps(et *lib.ExecutionTuple, zeroEnd bool) int { + p := et.ScaleInt64(vlvc.StartVUs.Int64) + var result int64 + result++ // for the first one + + if zeroEnd { + result++ // for the last one - this one can be more then needed + } + for _, stage := range vlvc.Stages { + stageEndVUs := et.ScaleInt64(stage.Target.Int64) + if stage.Duration.Duration == 0 { + result++ + } else { + result += absInt64(p - stageEndVUs) + } + p = stageEndVUs + } + return int(result) +} + // If the graceful ramp-downs are enabled, we need to reserve any VUs that may // potentially have to finish running iterations when we're scaling their number // down. This would prevent attempts from other executors to use them while the From df30a710a2bce7474c040852025a6b77a1857bc2 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Thu, 23 Apr 2020 13:49:20 +0300 Subject: [PATCH 17/23] fix TestVariableLoopingVUsRampDownNoWobble --- lib/executor/variable_looping_vus_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 5c8856b1cd1..328679be1b2 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -132,7 +132,7 @@ func TestVariableLoopingVUsRampDownNoWobble(t *testing.T) { sampleTimes := []time.Duration{ 100 * time.Millisecond, - 3400 * time.Millisecond, + 3200 * time.Millisecond, } const rampDownSamples = 50 From c60f01e8a8fb63ef50e5f95597ed636fbe728b01 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 24 Apr 2020 17:46:09 +0300 Subject: [PATCH 18/23] Don't stop VUs right away when stepping down but instead on the "next" step This reverts the "fix" from 1eb3e7ab as apperantly this is how it previously worked and is also what we want. --- lib/executor/variable_looping_vus.go | 2 +- lib/executor/variable_looping_vus_test.go | 166 ++++++++++++---------- 2 files changed, 94 insertions(+), 74 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index d1619522ce2..da3c85e3d6d 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -225,7 +225,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.global)/stageVUDiff), + TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.global+1)/stageVUDiff), PlannedVUs: uint64(index.local - 1), }) } diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 328679be1b2..5c69f80dd95 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -189,19 +189,19 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 2 * time.Second, PlannedVUs: 5}, - {TimeOffset: 3 * time.Second, PlannedVUs: 4}, - {TimeOffset: 4 * time.Second, PlannedVUs: 3}, - {TimeOffset: 5 * time.Second, PlannedVUs: 2}, - {TimeOffset: 6 * time.Second, PlannedVUs: 1}, + {TimeOffset: 3 * time.Second, PlannedVUs: 5}, + {TimeOffset: 4 * time.Second, PlannedVUs: 4}, + {TimeOffset: 5 * time.Second, PlannedVUs: 3}, + {TimeOffset: 6 * time.Second, PlannedVUs: 2}, + {TimeOffset: 7 * time.Second, PlannedVUs: 1}, {TimeOffset: 8 * time.Second, PlannedVUs: 2}, {TimeOffset: 9 * time.Second, PlannedVUs: 3}, {TimeOffset: 10 * time.Second, PlannedVUs: 4}, {TimeOffset: 11 * time.Second, PlannedVUs: 5}, - {TimeOffset: 11 * time.Second, PlannedVUs: 4}, - {TimeOffset: 12 * time.Second, PlannedVUs: 3}, - {TimeOffset: 13 * time.Second, PlannedVUs: 2}, - {TimeOffset: 14 * time.Second, PlannedVUs: 1}, + {TimeOffset: 12 * time.Second, PlannedVUs: 4}, + {TimeOffset: 13 * time.Second, PlannedVUs: 3}, + {TimeOffset: 14 * time.Second, PlannedVUs: 2}, + {TimeOffset: 15 * time.Second, PlannedVUs: 1}, {TimeOffset: 16 * time.Second, PlannedVUs: 2}, {TimeOffset: 17 * time.Second, PlannedVUs: 3}, {TimeOffset: 18 * time.Second, PlannedVUs: 4}, @@ -227,8 +227,8 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 32 * time.Second, PlannedVUs: 5}, - {TimeOffset: 41 * time.Second, PlannedVUs: 4}, + {TimeOffset: 33 * time.Second, PlannedVUs: 5}, + {TimeOffset: 42 * time.Second, PlannedVUs: 4}, {TimeOffset: 50 * time.Second, PlannedVUs: 1}, {TimeOffset: 53 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -239,8 +239,8 @@ func TestVariableLoopingVUsConfigExecutionPlanExample(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 32 * time.Second, PlannedVUs: 5}, - {TimeOffset: 41 * time.Second, PlannedVUs: 4}, + {TimeOffset: 33 * time.Second, PlannedVUs: 5}, + {TimeOffset: 42 * time.Second, PlannedVUs: 4}, {TimeOffset: 50 * time.Second, PlannedVUs: 1}, {TimeOffset: 103 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -288,12 +288,12 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { expRawStepsNoZeroEnd := []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 11 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, } @@ -313,7 +313,7 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { assert.Equal(t, []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 41 * time.Second, PlannedVUs: 1}, + {TimeOffset: 42 * time.Second, PlannedVUs: 1}, {TimeOffset: 50 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -322,7 +322,7 @@ func TestVariableLoopingVUsConfigExecutionPlanExampleOneThird(t *testing.T) { assert.Equal(t, []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 41 * time.Second, PlannedVUs: 1}, + {TimeOffset: 42 * time.Second, PlannedVUs: 1}, {TimeOffset: 50 * time.Second, PlannedVUs: 0}, }, conf.GetExecutionRequirements(et)) @@ -395,19 +395,19 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 4}, {TimeOffset: 1 * time.Second, PlannedVUs: 5}, {TimeOffset: 2 * time.Second, PlannedVUs: 6}, - {TimeOffset: 2 * time.Second, PlannedVUs: 5}, - {TimeOffset: 3 * time.Second, PlannedVUs: 4}, - {TimeOffset: 4 * time.Second, PlannedVUs: 3}, - {TimeOffset: 5 * time.Second, PlannedVUs: 2}, - {TimeOffset: 6 * time.Second, PlannedVUs: 1}, + {TimeOffset: 3 * time.Second, PlannedVUs: 5}, + {TimeOffset: 4 * time.Second, PlannedVUs: 4}, + {TimeOffset: 5 * time.Second, PlannedVUs: 3}, + {TimeOffset: 6 * time.Second, PlannedVUs: 2}, + {TimeOffset: 7 * time.Second, PlannedVUs: 1}, {TimeOffset: 8 * time.Second, PlannedVUs: 2}, {TimeOffset: 9 * time.Second, PlannedVUs: 3}, {TimeOffset: 10 * time.Second, PlannedVUs: 4}, {TimeOffset: 11 * time.Second, PlannedVUs: 5}, - {TimeOffset: 11 * time.Second, PlannedVUs: 4}, - {TimeOffset: 12 * time.Second, PlannedVUs: 3}, - {TimeOffset: 13 * time.Second, PlannedVUs: 2}, - {TimeOffset: 14 * time.Second, PlannedVUs: 1}, + {TimeOffset: 12 * time.Second, PlannedVUs: 4}, + {TimeOffset: 13 * time.Second, PlannedVUs: 3}, + {TimeOffset: 14 * time.Second, PlannedVUs: 2}, + {TimeOffset: 15 * time.Second, PlannedVUs: 1}, {TimeOffset: 16 * time.Second, PlannedVUs: 2}, {TimeOffset: 17 * time.Second, PlannedVUs: 3}, {TimeOffset: 18 * time.Second, PlannedVUs: 4}, @@ -416,8 +416,8 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 27 * time.Second, PlannedVUs: 1}, {TimeOffset: 28 * time.Second, PlannedVUs: 2}, - {TimeOffset: 28 * time.Second, PlannedVUs: 1}, - {TimeOffset: 29 * time.Second, PlannedVUs: 0}, + {TimeOffset: 29 * time.Second, PlannedVUs: 1}, + {TimeOffset: 30 * time.Second, PlannedVUs: 0}, {TimeOffset: 31 * time.Second, PlannedVUs: 1}, {TimeOffset: 32 * time.Second, PlannedVUs: 2}, {TimeOffset: 33 * time.Second, PlannedVUs: 3}, @@ -429,18 +429,18 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 11 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, {TimeOffset: 23 * time.Second, PlannedVUs: 2}, {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 28 * time.Second, PlannedVUs: 1}, - {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, @@ -449,18 +449,18 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 11 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, {TimeOffset: 23 * time.Second, PlannedVUs: 2}, {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 28 * time.Second, PlannedVUs: 1}, - {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, @@ -469,18 +469,18 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 11 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, {TimeOffset: 23 * time.Second, PlannedVUs: 2}, {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 28 * time.Second, PlannedVUs: 1}, - {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, @@ -489,18 +489,18 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 11 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, {TimeOffset: 23 * time.Second, PlannedVUs: 2}, {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 28 * time.Second, PlannedVUs: 1}, - {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, @@ -508,15 +508,15 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), newExecutionSegmentSequenceFromString("0,1/3,2/3,1")), expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 2}, - {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 5 * time.Second, PlannedVUs: 1}, {TimeOffset: 10 * time.Second, PlannedVUs: 2}, - {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 13 * time.Second, PlannedVUs: 1}, {TimeOffset: 18 * time.Second, PlannedVUs: 2}, {TimeOffset: 20 * time.Second, PlannedVUs: 1}, {TimeOffset: 23 * time.Second, PlannedVUs: 2}, {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 27 * time.Second, PlannedVUs: 1}, - {TimeOffset: 29 * time.Second, PlannedVUs: 0}, + {TimeOffset: 30 * time.Second, PlannedVUs: 0}, {TimeOffset: 31 * time.Second, PlannedVUs: 1}, {TimeOffset: 34 * time.Second, PlannedVUs: 2}, }, @@ -526,18 +526,18 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 1 * time.Second, PlannedVUs: 2}, - {TimeOffset: 3 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 1}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, {TimeOffset: 8 * time.Second, PlannedVUs: 1}, {TimeOffset: 11 * time.Second, PlannedVUs: 2}, - {TimeOffset: 11 * time.Second, PlannedVUs: 1}, - {TimeOffset: 14 * time.Second, PlannedVUs: 0}, + {TimeOffset: 12 * time.Second, PlannedVUs: 1}, + {TimeOffset: 15 * time.Second, PlannedVUs: 0}, {TimeOffset: 16 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, {TimeOffset: 23 * time.Second, PlannedVUs: 2}, {TimeOffset: 26 * time.Second, PlannedVUs: 0}, {TimeOffset: 28 * time.Second, PlannedVUs: 1}, - {TimeOffset: 28 * time.Second, PlannedVUs: 0}, + {TimeOffset: 29 * time.Second, PlannedVUs: 0}, {TimeOffset: 32 * time.Second, PlannedVUs: 1}, }, }, @@ -546,10 +546,10 @@ func TestVariableLoopingVUsExecutionTupleTests(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 1}, {TimeOffset: 2 * time.Second, PlannedVUs: 2}, - {TimeOffset: 2 * time.Second, PlannedVUs: 1}, - {TimeOffset: 5 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 6 * time.Second, PlannedVUs: 0}, {TimeOffset: 9 * time.Second, PlannedVUs: 1}, - {TimeOffset: 13 * time.Second, PlannedVUs: 0}, + {TimeOffset: 14 * time.Second, PlannedVUs: 0}, {TimeOffset: 17 * time.Second, PlannedVUs: 1}, {TimeOffset: 20 * time.Second, PlannedVUs: 0}, {TimeOffset: 23 * time.Second, PlannedVUs: 1}, @@ -585,8 +585,8 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 2}, {TimeOffset: 0 * time.Second, PlannedVUs: 5}, - {TimeOffset: 0 * time.Second, PlannedVUs: 4}, - {TimeOffset: 1 * time.Second, PlannedVUs: 3}, + {TimeOffset: 1 * time.Second, PlannedVUs: 4}, + {TimeOffset: 2 * time.Second, PlannedVUs: 3}, }, stages: []Stage{ {Target: null.IntFrom(5), Duration: types.NullDurationFrom(0 * time.Second)}, @@ -612,12 +612,12 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { {TimeOffset: 0 * time.Second, PlannedVUs: 0}, {TimeOffset: 1 * time.Second, PlannedVUs: 1}, {TimeOffset: 2 * time.Second, PlannedVUs: 2}, - {TimeOffset: 2 * time.Second, PlannedVUs: 1}, - {TimeOffset: 3 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 1}, + {TimeOffset: 4 * time.Second, PlannedVUs: 0}, {TimeOffset: 5 * time.Second, PlannedVUs: 1}, {TimeOffset: 6 * time.Second, PlannedVUs: 2}, - {TimeOffset: 6 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 7 * time.Second, PlannedVUs: 1}, + {TimeOffset: 8 * time.Second, PlannedVUs: 0}, }, stages: []Stage{ {Target: null.IntFrom(2), Duration: types.NullDurationFrom(2 * time.Second)}, @@ -631,9 +631,9 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 0}, {TimeOffset: 1 * time.Second, PlannedVUs: 1}, - {TimeOffset: 3 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 0}, {TimeOffset: 5 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 0}, }, et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/2"), nil), stages: []Stage{ @@ -648,9 +648,9 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 0}, {TimeOffset: 2 * time.Second, PlannedVUs: 1}, - {TimeOffset: 2 * time.Second, PlannedVUs: 0}, + {TimeOffset: 3 * time.Second, PlannedVUs: 0}, {TimeOffset: 6 * time.Second, PlannedVUs: 1}, - {TimeOffset: 6 * time.Second, PlannedVUs: 0}, + {TimeOffset: 7 * time.Second, PlannedVUs: 0}, }, et: mustNewExecutionTuple(newExecutionSegmentFromString("1/2:1"), nil), stages: []Stage{ @@ -678,9 +678,9 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { expectedSteps: []lib.ExecutionStep{ {TimeOffset: 0 * time.Second, PlannedVUs: 0}, {TimeOffset: 1 * time.Second, PlannedVUs: 1}, - {TimeOffset: 3 * time.Second, PlannedVUs: 0}, + {TimeOffset: 4 * time.Second, PlannedVUs: 0}, {TimeOffset: 5 * time.Second, PlannedVUs: 1}, - {TimeOffset: 7 * time.Second, PlannedVUs: 0}, + {TimeOffset: 8 * time.Second, PlannedVUs: 0}, }, et: mustNewExecutionTuple(newExecutionSegmentFromString("0:1/3"), newExecutionSegmentSequenceFromString("0,1/3,1/2,2/3,1")), stages: []Stage{ @@ -710,6 +710,26 @@ func TestVariableLoopingVUsGetRawExecutionStepsCornerCases(t *testing.T) { {Target: null.IntFrom(30), Duration: types.NullDurationFrom(30 * time.Second)}, }, }, + { + name: "more up and down", + expectedSteps: []lib.ExecutionStep{ + {TimeOffset: 0 * time.Second, PlannedVUs: 0}, + {TimeOffset: 1 * time.Second, PlannedVUs: 1}, + {TimeOffset: 2 * time.Second, PlannedVUs: 2}, + {TimeOffset: 3 * time.Second, PlannedVUs: 3}, + {TimeOffset: 4 * time.Second, PlannedVUs: 4}, + {TimeOffset: 5 * time.Second, PlannedVUs: 5}, + {TimeOffset: 6 * time.Second, PlannedVUs: 4}, + {TimeOffset: 7 * time.Second, PlannedVUs: 3}, + {TimeOffset: 8 * time.Second, PlannedVUs: 2}, + {TimeOffset: 9 * time.Second, PlannedVUs: 1}, + {TimeOffset: 10 * time.Second, PlannedVUs: 0}, + }, + stages: []Stage{ + {Target: null.IntFrom(5), Duration: types.NullDurationFrom(5 * time.Second)}, + {Target: null.IntFrom(0), Duration: types.NullDurationFrom(5 * time.Second)}, + }, + }, } for _, testCase := range testCases { From 70275a0ae4db88052ca8156abb74c5ab20d60051 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 24 Apr 2020 18:14:03 +0300 Subject: [PATCH 19/23] drop two ifs that are no longer needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 302µs ± 3% 299µs ± 2% -0.83% (p=0.018 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 2.80ms ± 3% 2.80ms ± 4% ~ (p=0.879 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 303µs ± 3% 302µs ± 3% ~ (p=0.550 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 2.81ms ± 3% 2.80ms ± 2% ~ (p=0.444 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 86.3µs ± 4% 86.7µs ± 2% ~ (p=0.718 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 1.08ms ± 3% 1.07ms ± 3% ~ (p=0.166 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 30.0µs ± 3% 29.8µs ± 3% ~ (p=0.175 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 331µs ± 2% 329µs ± 3% ~ (p=0.369 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 114µs ± 4% 115µs ± 4% ~ (p=0.158 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 1.33ms ± 3% 1.32ms ± 3% ~ (p=0.771 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 111µs ± 3% 110µs ± 4% ~ (p=0.057 n=19+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 1.21ms ± 3% 1.20ms ± 2% ~ (p=0.096 n=20+20) name old alloc/op new alloc/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 246kB ± 0% 246kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 2.65MB ± 0% 2.65MB ± 0% ~ (p=0.736 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 246kB ± 0% 246kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 2.65MB ± 0% 2.65MB ± 0% ~ (p=0.468 n=20+19) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 73.8kB ± 0% 73.8kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 795kB ± 0% 795kB ± 0% ~ (p=0.304 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 24.7kB ± 0% 24.7kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 270kB ± 0% 270kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 98.4kB ± 0% 98.4kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 1.06MB ± 0% 1.06MB ± 0% ~ (p=0.985 n=20+20) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 90.3kB ± 0% 90.3kB ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 983kB ± 0% 983kB ± 0% ~ (p=0.081 n=20+19) name old allocs/op new allocs/op delta VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/normal-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:/rollercoaster-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/normal-8 3.00 ± 0% 3.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:0:1/rollercoaster-8 3.00 ± 0% 3.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/normal-8 5.00 ± 0% 5.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.3,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.3/rollercoaster-8 5.00 ± 0% 5.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/normal-8 6.00 ± 0% 6.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1;segment:0:0.1/rollercoaster-8 6.00 ± 0% 6.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/normal-8 4.00 ± 0% 4.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2/5:4/5/rollercoaster-8 4.00 ± 0% 4.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/normal-8 4.00 ± 0% 4.00 ± 0% ~ (all equal) VariableLoopingVUsGetRawExecutionSteps/seq:;segment:2235/5213:4/5/rollercoaster-8 4.00 ± 0% 4.00 ± 0% ~ (all equal) --- lib/executor/variable_looping_vus.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index da3c85e3d6d..b4b5bb1c87f 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -219,9 +219,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it // it will just go to it for ; index.global > stageEndVUs; index.prev() { - if index.global > fromVUs { - continue - } // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ @@ -233,9 +230,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple // here we want the emit for the last one as this case it actually should emit that // we start it for ; index.global <= stageEndVUs; index.next() { - if index.global < fromVUs { - continue - } // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ From 964b477c2edbea29755cad105fc6dd73d72cfb5c Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 24 Apr 2020 19:01:45 +0300 Subject: [PATCH 20/23] rename local and global to scaled and unscaled --- lib/executor/variable_looping_vus.go | 58 ++++---- lib/executor/variable_looping_vus_test.go | 172 +++++++++++----------- 2 files changed, 114 insertions(+), 116 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index b4b5bb1c87f..7f0c1f376bd 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -192,7 +192,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple index.goTo(fromVUs) var steps = make([]lib.ExecutionStep, 0, vlvc.precalculateTheRequiredSteps(et, zeroEnd)) // Reserve the scaled StartVUs at the beginning - steps = append(steps, lib.ExecutionStep{TimeOffset: 0, PlannedVUs: uint64(index.local)}) + steps = append(steps, lib.ExecutionStep{TimeOffset: 0, PlannedVUs: uint64(index.scaled)}) addStep := func(step lib.ExecutionStep) { if steps[len(steps)-1].PlannedVUs != step.PlannedVUs { steps = append(steps, step) @@ -210,31 +210,29 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple } if stageDuration == 0 { index.goTo(stageEndVUs) - addStep(lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: uint64(index.local)}) + addStep(lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: uint64(index.scaled)}) fromVUs = stageEndVUs continue } - if index.global > stageEndVUs { // ramp down + if index.unscaled > stageEndVUs { // ramp down // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it // it will just go to it - for ; index.global > stageEndVUs; index.prev() { + for ; index.unscaled > stageEndVUs; index.prev() { // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.global+1)/stageVUDiff), - PlannedVUs: uint64(index.local - 1), + TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled+1)/stageVUDiff), + PlannedVUs: uint64(index.scaled - 1), }) } } else { - // here we want the emit for the last one as this case it actually should emit that - // we start it - for ; index.global <= stageEndVUs; index.next() { + for ; index.unscaled <= stageEndVUs; index.next() { // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.global)/stageVUDiff), - PlannedVUs: uint64(index.local), + TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled)/stageVUDiff), + PlannedVUs: uint64(index.scaled), }) } } @@ -249,46 +247,46 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple } type segmentedIndex struct { // TODO: rename ... although this is probably the best name so far :D - start, lcd int64 - offsets []int64 - local, global int64 + start, lcd int64 + offsets []int64 + scaled, unscaled int64 } func (s *segmentedIndex) next() { - if s.local == 0 { - s.global += s.start + 1 + if s.scaled == 0 { + s.unscaled += s.start + 1 } else { - s.global += s.offsets[int(s.local-1)%len(s.offsets)] + s.unscaled += s.offsets[int(s.scaled-1)%len(s.offsets)] } - s.local++ + s.scaled++ } func (s *segmentedIndex) prev() { - if s.local == 1 { - s.global -= s.start + 1 + if s.scaled == 1 { + s.unscaled -= s.start + 1 } else { - s.global -= s.offsets[int(s.local-2)%len(s.offsets)] + s.unscaled -= s.offsets[int(s.scaled-2)%len(s.offsets)] } - s.local-- + s.scaled-- } func (s *segmentedIndex) goTo(value int64) { // TODO optimize var gi int64 - s.local = (value / s.lcd) * int64(len(s.offsets)) - s.global = s.local / int64(len(s.offsets)) * s.lcd // TODO optimize ? + s.scaled = (value / s.lcd) * int64(len(s.offsets)) + s.unscaled = s.scaled / int64(len(s.offsets)) * s.lcd // TODO optimize ? i := s.start for ; i < value%s.lcd; gi, i = gi+1, i+s.offsets[gi] { - s.local++ + s.scaled++ } if gi > 0 { - s.global += i - s.offsets[gi-1] - } else if s.local > 0 { - s.global -= s.offsets[len(s.offsets)-1] - s.start + s.unscaled += i - s.offsets[gi-1] + } else if s.scaled > 0 { + s.unscaled -= s.offsets[len(s.offsets)-1] - s.start } - if s.local > 0 { - s.global++ // this is to fix the fact it starts from 0 + if s.scaled > 0 { + s.unscaled++ // this is to fix the fact it starts from 0 } } diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 5c69f80dd95..89a9101866e 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -818,187 +818,187 @@ func TestSegmentedIndex(t *testing.T) { s := segmentedIndex{start: 0, lcd: 1, offsets: []int64{1}} s.next() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.next() - assert.EqualValues(t, 3, s.global) - assert.EqualValues(t, 3, s.local) + assert.EqualValues(t, 3, s.unscaled) + assert.EqualValues(t, 3, s.scaled) s.prev() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.prev() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 2, s.scaled) }) t.Run("half", func(t *testing.T) { s := segmentedIndex{start: 0, lcd: 2, offsets: []int64{2}} s.next() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.next() - assert.EqualValues(t, 3, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 3, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.next() - assert.EqualValues(t, 5, s.global) - assert.EqualValues(t, 3, s.local) + assert.EqualValues(t, 5, s.unscaled) + assert.EqualValues(t, 3, s.scaled) s.prev() - assert.EqualValues(t, 3, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 3, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.prev() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 1, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 1, s.unscaled) + assert.EqualValues(t, 1, s.scaled) }) t.Run("the other half", func(t *testing.T) { s := segmentedIndex{start: 1, lcd: 2, offsets: []int64{2}} s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.next() - assert.EqualValues(t, 4, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 4, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.next() - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 3, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 3, s.scaled) s.prev() - assert.EqualValues(t, 4, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 4, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.prev() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) }) t.Run("strange", func(t *testing.T) { s := segmentedIndex{start: 1, lcd: 7, offsets: []int64{4, 3}} s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.next() - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.next() - assert.EqualValues(t, 9, s.global) - assert.EqualValues(t, 3, s.local) + assert.EqualValues(t, 9, s.unscaled) + assert.EqualValues(t, 3, s.scaled) s.prev() - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.prev() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) s.next() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.goTo(6) - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.goTo(5) - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.goTo(7) - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.goTo(8) - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.goTo(9) - assert.EqualValues(t, 9, s.global) - assert.EqualValues(t, 3, s.local) + assert.EqualValues(t, 9, s.unscaled) + assert.EqualValues(t, 3, s.scaled) s.prev() - assert.EqualValues(t, 6, s.global) - assert.EqualValues(t, 2, s.local) + assert.EqualValues(t, 6, s.unscaled) + assert.EqualValues(t, 2, s.scaled) s.prev() - assert.EqualValues(t, 2, s.global) - assert.EqualValues(t, 1, s.local) + assert.EqualValues(t, 2, s.unscaled) + assert.EqualValues(t, 1, s.scaled) s.prev() - assert.EqualValues(t, 0, s.global) - assert.EqualValues(t, 0, s.local) + assert.EqualValues(t, 0, s.unscaled) + assert.EqualValues(t, 0, s.scaled) }) } From 37492ac0896d2d2c72e0ea26841ee9bd4d747682 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Fri, 24 Apr 2020 19:25:37 +0300 Subject: [PATCH 21/23] misc changes and comment fixes/additions --- lib/executor/variable_looping_vus.go | 38 +++++++++++++++------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 7f0c1f376bd..b4d983cf605 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -181,21 +181,20 @@ func (vlvc VariableLoopingVUsConfig) Validate() []error { // 00000000001111111111222 (t/10) // // More information: https://github.com/loadimpact/k6/issues/997#issuecomment-484416866 -//nolint:funlen func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple, zeroEnd bool) []lib.ExecutionStep { var ( timeTillEnd time.Duration fromVUs = vlvc.StartVUs.Int64 start, offsets, lcd = et.GetStripedOffsets(et.ES) + steps = make([]lib.ExecutionStep, 0, vlvc.precalculateTheRequiredSteps(et, zeroEnd)) index = segmentedIndex{start: start, lcd: lcd, offsets: offsets} ) - index.goTo(fromVUs) - var steps = make([]lib.ExecutionStep, 0, vlvc.precalculateTheRequiredSteps(et, zeroEnd)) + // Reserve the scaled StartVUs at the beginning - steps = append(steps, lib.ExecutionStep{TimeOffset: 0, PlannedVUs: uint64(index.scaled)}) - addStep := func(step lib.ExecutionStep) { - if steps[len(steps)-1].PlannedVUs != step.PlannedVUs { - steps = append(steps, step) + steps = append(steps, lib.ExecutionStep{TimeOffset: 0, PlannedVUs: uint64(index.goTo(fromVUs))}) + addStep := func(timeOffset time.Duration, plannedVUs uint64) { + if steps[len(steps)-1].PlannedVUs != plannedVUs { + steps = append(steps, lib.ExecutionStep{TimeOffset: timeOffset, PlannedVUs: plannedVUs}) } } @@ -209,8 +208,7 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple continue } if stageDuration == 0 { - index.goTo(stageEndVUs) - addStep(lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: uint64(index.scaled)}) + addStep(timeTillEnd, uint64(index.goTo(stageEndVUs))) fromVUs = stageEndVUs continue } @@ -221,19 +219,21 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple for ; index.unscaled > stageEndVUs; index.prev() { // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() - addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled+1)/stageVUDiff), - PlannedVUs: uint64(index.scaled - 1), - }) + addStep( // this is the time that we should go up 1 if we are ramping up + // but we are ramping down so we should go 1 down, but because we want to not + // stop VUs immediately we stop it on the next unscaled VU's time + timeTillEnd-time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled+1)/stageVUDiff), + uint64(index.scaled-1), + ) } } else { for ; index.unscaled <= stageEndVUs; index.next() { // VU reservation for gracefully ramping down is handled as a // separate method: reserveVUsForGracefulRampDowns() - addStep(lib.ExecutionStep{ - TimeOffset: timeTillEnd - time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled)/stageVUDiff), - PlannedVUs: uint64(index.scaled), - }) + addStep( + timeTillEnd-time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled)/stageVUDiff), + uint64(index.scaled), + ) } } fromVUs = stageEndVUs @@ -270,7 +270,7 @@ func (s *segmentedIndex) prev() { s.scaled-- } -func (s *segmentedIndex) goTo(value int64) { // TODO optimize +func (s *segmentedIndex) goTo(value int64) int64 { // TODO optimize var gi int64 s.scaled = (value / s.lcd) * int64(len(s.offsets)) s.unscaled = s.scaled / int64(len(s.offsets)) * s.lcd // TODO optimize ? @@ -288,6 +288,8 @@ func (s *segmentedIndex) goTo(value int64) { // TODO optimize if s.scaled > 0 { s.unscaled++ // this is to fix the fact it starts from 0 } + + return s.scaled } func absInt64(a int64) int64 { From c03b5e0490d58321fd9711e1ba264a67104e3c22 Mon Sep 17 00:00:00 2001 From: Mihail Stoykov Date: Sat, 25 Apr 2020 15:06:52 +0300 Subject: [PATCH 22/23] Steal some documentation and implementation ideas from the more readable branch --- lib/executor/variable_looping_vus.go | 62 ++++++++++++++++++---------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index b4d983cf605..09a572b2122 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -213,13 +213,14 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple continue } + // VU reservation for gracefully ramping down is handled as a + // separate method: reserveVUsForGracefulRampDowns() if index.unscaled > stageEndVUs { // ramp down // here we don't want to emit for the equal to stageEndVUs as it doesn't go below it // it will just go to it for ; index.unscaled > stageEndVUs; index.prev() { - // VU reservation for gracefully ramping down is handled as a - // separate method: reserveVUsForGracefulRampDowns() - addStep( // this is the time that we should go up 1 if we are ramping up + addStep( + // this is the time that we should go up 1 if we are ramping up // but we are ramping down so we should go 1 down, but because we want to not // stop VUs immediately we stop it on the next unscaled VU's time timeTillEnd-time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled+1)/stageVUDiff), @@ -228,8 +229,6 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple } } else { for ; index.unscaled <= stageEndVUs; index.next() { - // VU reservation for gracefully ramping down is handled as a - // separate method: reserveVUsForGracefulRampDowns() addStep( timeTillEnd-time.Duration(int64(stageDuration)*(stageEndVUs-index.unscaled)/stageVUDiff), uint64(index.scaled), @@ -249,44 +248,63 @@ func (vlvc VariableLoopingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple type segmentedIndex struct { // TODO: rename ... although this is probably the best name so far :D start, lcd int64 offsets []int64 - scaled, unscaled int64 + scaled, unscaled int64 // for both the first element(vu) is 1 not 0 } +// goes to the next scaled index and move the unscaled one accordingly func (s *segmentedIndex) next() { - if s.scaled == 0 { - s.unscaled += s.start + 1 - } else { - s.unscaled += s.offsets[int(s.scaled-1)%len(s.offsets)] + if s.scaled == 0 { // the 1 element(VU) is at the start + s.unscaled += s.start + 1 // the first element of the start 0, but the here we need it to be 1 so we add 1 + } else { // if we are not at the first element we need to go through the offsets, looping over them + s.unscaled += s.offsets[int(s.scaled-1)%len(s.offsets)] // slice's index start at 0 ours start at 1 } s.scaled++ } +// prev goest to the previous scaled value and sets the unscaled one accordingly +// calling prev when s.scaled == 0 is undefined func (s *segmentedIndex) prev() { - if s.scaled == 1 { - s.unscaled -= s.start + 1 - } else { - s.unscaled -= s.offsets[int(s.scaled-2)%len(s.offsets)] + if s.scaled == 1 { // we are the first need to go to the 0th element which means we need to remove the start + s.unscaled -= s.start + 1 // this could've been just settign to 0 + } else { // not at the first element - need to get the previously added offset so + s.unscaled -= s.offsets[int(s.scaled-2)%len(s.offsets)] // slice's index start 0 our start at 1 } s.scaled-- } +// goTo sets the scaled index to it's biggest value for which the corresponding unscaled index is +// is smaller or equal to value func (s *segmentedIndex) goTo(value int64) int64 { // TODO optimize var gi int64 - s.scaled = (value / s.lcd) * int64(len(s.offsets)) - s.unscaled = s.scaled / int64(len(s.offsets)) * s.lcd // TODO optimize ? + // Because of the cyclical nature of the striping algorithm (with a cycle + // length of LCD, the least common denominator), when scaling large values + // (i.e. many multiples of the LCD), we can quickly calculate how many times + // the cycle repeats. + wholeCycles := (value / s.lcd) + // So we can set some approximate initial values quickly, since we also know + // precisely how many scaled values there are per cycle length. + s.scaled = wholeCycles * int64(len(s.offsets)) + s.unscaled = wholeCycles*s.lcd + s.start + 1 // our indexes are from 1 the start is from 0 + // Approach the final value using the slow algorithm with the step by step loop + // TODO: this can be optimized by another array with size offsets that instead of the offsets + // from the previous is the offset from either 0 or start i := s.start for ; i < value%s.lcd; gi, i = gi+1, i+s.offsets[gi] { s.scaled++ + s.unscaled += s.offsets[gi] } - if gi > 0 { - s.unscaled += i - s.offsets[gi-1] - } else if s.scaled > 0 { - s.unscaled -= s.offsets[len(s.offsets)-1] - s.start + if gi > 0 { // there were more values after the wholecycles + // the last offset actually shouldn't have been added + s.unscaled -= s.offsets[gi-1] + } else if s.scaled > 0 { // we didn't actually have more values after the wholecycles but we still had some + // in this case the unscaled value needs to move back by the last offset as it would've been + // the one to get it from the value it needs to be to it's current one + s.unscaled -= s.offsets[len(s.offsets)-1] } - if s.scaled > 0 { - s.unscaled++ // this is to fix the fact it starts from 0 + if s.scaled == 0 { + s.unscaled = 0 // we would've added the start and 1 } return s.scaled From fb145c2cee4ea2c5182066bf2c61b7746e740199 Mon Sep 17 00:00:00 2001 From: na-- Date: Mon, 27 Apr 2020 17:45:51 +0300 Subject: [PATCH 23/23] Add a randomized test for the variable-looping-vus requirements (#1413) --- lib/execution_segment_test.go | 52 ++++----- lib/executor/variable_looping_vus_test.go | 124 +++++++++++++++++++++- 2 files changed, 142 insertions(+), 34 deletions(-) diff --git a/lib/execution_segment_test.go b/lib/execution_segment_test.go index 3513f50cd89..7f430db8a62 100644 --- a/lib/execution_segment_test.go +++ b/lib/execution_segment_test.go @@ -237,6 +237,7 @@ func TestExecutionTupleScale(t *testing.T) { require.Equal(t, int64(1), et.ScaleInt64(2)) require.Equal(t, int64(1), et.ScaleInt64(3)) } + func TestBigScale(t *testing.T) { es := new(ExecutionSegment) ess, err := NewExecutionSegmentSequenceFromString("0,7/20,7/10,1") @@ -431,29 +432,24 @@ func TestExecutionSegmentStringSequences(t *testing.T) { // Return a randomly distributed sequence of n amount of // execution segments whose length totals 1. -func generateRandomSequence(n int64, r *rand.Rand) (ExecutionSegmentSequence, error) { +func generateRandomSequence(t testing.TB, n, m int64, r *rand.Rand) ExecutionSegmentSequence { var err error - var ess = ExecutionSegmentSequence(make([]*ExecutionSegment, n)) - var numerators = make([]int64, n) + ess := ExecutionSegmentSequence(make([]*ExecutionSegment, n)) + numerators := make([]int64, n) var denominator int64 for i := int64(0); i < n; i++ { - for numerators[i] == 0 { - numerators[i] = r.Int63n(n) - denominator += numerators[i] - } + numerators[i] = 1 + r.Int63n(m) + denominator += numerators[i] } - ess[0], err = NewExecutionSegment(big.NewRat(0, 1), big.NewRat(numerators[0], denominator)) - if err != nil { - return nil, err - } - for i := int64(1); i < n; i++ { - ess[i], err = NewExecutionSegment(ess[i-1].to, new(big.Rat).Add(big.NewRat(numerators[i], denominator), ess[i-1].to)) - if err != nil { - return nil, err - } + from := big.NewRat(0, 1) + for i := int64(0); i < n; i++ { + to := new(big.Rat).Add(big.NewRat(numerators[i], denominator), from) + ess[i], err = NewExecutionSegment(from, to) + require.NoError(t, err) + from = to } - return ess, nil + return ess } // Ensure that the sum of scaling all execution segments in @@ -468,8 +464,7 @@ func TestExecutionSegmentScaleConsistency(t *testing.T) { const numTests = 10 for i := 0; i < numTests; i++ { scale := rand.Int31n(99) + 2 - seq, err := generateRandomSequence(r.Int63n(9)+2, r) - require.NoError(t, err) + seq := generateRandomSequence(t, r.Int63n(9)+2, 100, r) t.Run(fmt.Sprintf("%d_%s", scale, seq), func(t *testing.T) { var total int64 @@ -493,8 +488,7 @@ func TestExecutionTupleScaleConsistency(t *testing.T) { const numTests = 10 for i := 0; i < numTests; i++ { scale := rand.Int31n(99) + 2 - seq, err := generateRandomSequence(r.Int63n(9)+2, r) - require.NoError(t, err) + seq := generateRandomSequence(t, r.Int63n(9)+2, 200, r) et, err := NewExecutionTuple(seq[0], &seq) require.NoError(t, err) @@ -534,8 +528,7 @@ func TestExecutionSegmentScaleNoWobble(t *testing.T) { // Random segments const numTests = 10 for i := 0; i < numTests; i++ { - seq, err := generateRandomSequence(r.Int63n(9)+2, r) - require.NoError(t, err) + seq := generateRandomSequence(t, r.Int63n(9)+2, 100, r) es := seq[rand.Intn(len(seq))] @@ -628,15 +621,14 @@ func TestSequenceLCD(t *testing.T) { } func BenchmarkGetStripedOffsets(b *testing.B) { - var lengths = [...]int64{10, 100} + lengths := [...]int64{10, 100} const seed = 777 r := rand.New(rand.NewSource(seed)) for _, length := range lengths { length := length b.Run(fmt.Sprintf("length%d,seed%d", length, seed), func(b *testing.B) { - sequence, err := generateRandomSequence(length, r) - require.NoError(b, err) + sequence := generateRandomSequence(b, length, 100, r) b.ResetTimer() for i := 0; i < b.N; i++ { segment := sequence[int(r.Int63())%len(sequence)] @@ -649,11 +641,11 @@ func BenchmarkGetStripedOffsets(b *testing.B) { } func BenchmarkGetStripedOffsetsEven(b *testing.B) { - var lengths = [...]int64{10, 100, 1000} + lengths := [...]int64{10, 100, 1000} generateSequence := func(n int64) ExecutionSegmentSequence { var err error - var ess = ExecutionSegmentSequence(make([]*ExecutionSegment, n)) - var numerators = make([]int64, n) + ess := ExecutionSegmentSequence(make([]*ExecutionSegment, n)) + numerators := make([]int64, n) var denominator int64 for i := int64(0); i < n; i++ { numerators[i] = 1 // nice and simple :) @@ -731,7 +723,7 @@ func mustNewExecutionSegmentSequence(str string) *ExecutionSegmentSequence { } func TestNewExecutionTuple(t *testing.T) { - var testCases = []struct { + testCases := []struct { seg *ExecutionSegment seq *ExecutionSegmentSequence scaleTests map[int64]int64 diff --git a/lib/executor/variable_looping_vus_test.go b/lib/executor/variable_looping_vus_test.go index 89a9101866e..92bcc481560 100644 --- a/lib/executor/variable_looping_vus_test.go +++ b/lib/executor/variable_looping_vus_test.go @@ -24,6 +24,8 @@ import ( "context" "encoding/json" "fmt" + "math/big" + "math/rand" "sync/atomic" "testing" "time" @@ -63,7 +65,7 @@ func TestVariableLoopingVUsRun(t *testing.T) { et, err := lib.NewExecutionTuple(nil, nil) require.NoError(t, err) es := lib.NewExecutionState(lib.Options{}, et, 10, 50) - var ctx, cancel, executor, _ = setupExecutor( + ctx, cancel, executor, _ := setupExecutor( t, config, es, simpleRunner(func(ctx context.Context) error { // Sleeping for a weird duration somewhat offset from the @@ -85,7 +87,7 @@ func TestVariableLoopingVUsRun(t *testing.T) { errCh := make(chan error) go func() { errCh <- executor.Run(ctx, nil) }() - var result = make([]int64, len(sampleTimes)) + result := make([]int64, len(sampleTimes)) for i, d := range sampleTimes { time.Sleep(d) result[i] = es.GetCurrentlyActiveVUsCount() @@ -121,7 +123,7 @@ func TestVariableLoopingVUsRampDownNoWobble(t *testing.T) { et, err := lib.NewExecutionTuple(nil, nil) require.NoError(t, err) es := lib.NewExecutionState(lib.Options{}, et, 10, 50) - var ctx, cancel, executor, _ = setupExecutor( + ctx, cancel, executor, _ := setupExecutor( t, config, es, simpleRunner(func(ctx context.Context) error { time.Sleep(1 * time.Second) @@ -139,7 +141,7 @@ func TestVariableLoopingVUsRampDownNoWobble(t *testing.T) { errCh := make(chan error) go func() { errCh <- executor.Run(ctx, nil) }() - var result = make([]int64, len(sampleTimes)+rampDownSamples) + result := make([]int64, len(sampleTimes)+rampDownSamples) for i, d := range sampleTimes { time.Sleep(d) result[i] = es.GetCurrentlyActiveVUsCount() @@ -1002,3 +1004,117 @@ func TestSegmentedIndex(t *testing.T) { assert.EqualValues(t, 0, s.scaled) }) } + +// TODO: delete in favor of lib.generateRandomSequence() after +// https://github.com/loadimpact/k6/issues/1302 is done (can't import now due to +// import loops...) +func generateRandomSequence(t testing.TB, n, m int64, r *rand.Rand) lib.ExecutionSegmentSequence { + var err error + ess := lib.ExecutionSegmentSequence(make([]*lib.ExecutionSegment, n)) + numerators := make([]int64, n) + var denominator int64 + for i := int64(0); i < n; i++ { + numerators[i] = 1 + r.Int63n(m) + denominator += numerators[i] + } + from := big.NewRat(0, 1) + for i := int64(0); i < n; i++ { + to := new(big.Rat).Add(big.NewRat(numerators[i], denominator), from) + ess[i], err = lib.NewExecutionSegment(from, to) + require.NoError(t, err) + from = to + } + + return ess +} + +func TestSumRandomSegmentSequenceMatchesNoSegment(t *testing.T) { + t.Parallel() + + seed := time.Now().UnixNano() + r := rand.New(rand.NewSource(seed)) + t.Logf("Random source seeded with %d\n", seed) + + const ( + numTests = 10 + maxStages = 10 + minStageDuration = 1 * time.Second + maxStageDuration = 10 * time.Minute + maxVUs = 300 + segmentSeqMaxLen = 15 + maxNumerator = 300 + ) + getTestConfig := func(name string) VariableLoopingVUsConfig { + stagesCount := 1 + r.Int31n(maxStages) + stages := make([]Stage, stagesCount) + for s := int32(0); s < stagesCount; s++ { + dur := time.Duration(r.Int63n(int64(maxStageDuration - minStageDuration))).Round(time.Second) + stages[s] = Stage{Duration: types.NullDurationFrom(dur), Target: null.IntFrom(r.Int63n(maxVUs))} + } + + c := NewVariableLoopingVUsConfig(name) + c.GracefulRampDown = types.NullDurationFrom(0) + c.GracefulStop = types.NullDurationFrom(0) + c.StartVUs = null.IntFrom(r.Int63n(maxVUs)) + c.Stages = stages + return c + } + + subtractChildSteps := func(t *testing.T, parent, child []lib.ExecutionStep) { + t.Logf("subtractChildSteps()") + for _, step := range child { + t.Logf(" child planned VUs for time offset %s: %d", step.TimeOffset, step.PlannedVUs) + } + sub := uint64(0) + ci := 0 + for pi, p := range parent { + // We iterate over all parent steps and match them to child steps. + // Once we have a match, we remove the child step's plannedVUs from + // the parent steps until a new match, when we adjust the subtracted + // amount again. + if p.TimeOffset > child[ci].TimeOffset && ci != len(child)-1 { + t.Errorf("ERR Could not match child offset %s with any parent time offset", child[ci].TimeOffset) + } + if p.TimeOffset == child[ci].TimeOffset { + t.Logf("Setting sub to %d at t=%s", child[ci].PlannedVUs, child[ci].TimeOffset) + sub = child[ci].PlannedVUs + if ci != len(child)-1 { + ci++ + } + } + t.Logf("Subtracting %d VUs (out of %d) at t=%s", sub, p.PlannedVUs, p.TimeOffset) + parent[pi].PlannedVUs -= sub + } + } + + for i := 0; i < numTests; i++ { + name := fmt.Sprintf("random%02d", i) + t.Run(name, func(t *testing.T) { + c := getTestConfig(name) + ranSeqLen := 2 + r.Int63n(segmentSeqMaxLen-1) + t.Logf("Config: %#v, ranSeqLen: %d", c, ranSeqLen) + randomSequence := generateRandomSequence(t, ranSeqLen, maxNumerator, r) + t.Logf("Random sequence: %s", randomSequence) + fullSeg, err := lib.NewExecutionTuple(nil, nil) + require.NoError(t, err) + fullRawSteps := c.getRawExecutionSteps(fullSeg, false) + + for _, step := range fullRawSteps { + t.Logf("original planned VUs for time offset %s: %d", step.TimeOffset, step.PlannedVUs) + } + + for s := 0; s < len(randomSequence); s++ { + et, err := lib.NewExecutionTuple(randomSequence[s], &randomSequence) + require.NoError(t, err) + segRawSteps := c.getRawExecutionSteps(et, false) + subtractChildSteps(t, fullRawSteps, segRawSteps) + } + + for _, step := range fullRawSteps { + if step.PlannedVUs != 0 { + t.Errorf("ERR Remaining planned VUs for time offset %s are not 0 but %d", step.TimeOffset, step.PlannedVUs) + } + } + }) + } +}