Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix max duration calculations with multiple 0 vus stages at end #2573

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions core/local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,37 @@ func TestExecutionSchedulerEndTime(t *testing.T) {
assert.True(t, runTime < 10*time.Second, "took more than 10 seconds")
}

func TestExecutionSchedulerEndTime0VUsAtEnd(t *testing.T) {
t.Parallel()
runner := &minirunner.MiniRunner{
Fn: func(ctx context.Context, _ *lib.State, out chan<- metrics.SampleContainer) error {
time.Sleep(100 * time.Millisecond)
return nil
},
}
_, cancel, execScheduler, _ := newTestExecutionScheduler(t, runner, nil, lib.Options{
Stages: []lib.Stage{
{
Duration: types.NullDurationFrom(time.Second),
Target: null.IntFrom(1),
},
{
Duration: types.NullDurationFrom(time.Second),
Target: null.IntFrom(0),
},
{
Duration: types.NullDurationFrom(time.Hour),
Target: null.IntFrom(0),
},
},
})
defer cancel()

endTime, isFinal := lib.GetEndOffset(execScheduler.GetExecutionPlan())
assert.Equal(t, time.Hour+2*time.Second, endTime) // because of the big 0 vu stage at end
assert.True(t, isFinal)
}

func TestExecutionSchedulerRuntimeErrors(t *testing.T) {
t.Parallel()
runner := &minirunner.MiniRunner{
Expand Down
35 changes: 31 additions & 4 deletions lib/executor/ramping_vus.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,17 @@ func (vlvc RampingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple, zeroEn
}
}

for _, stage := range vlvc.Stages {
for i, stage := range vlvc.Stages {
stageEndVUs := stage.Target.Int64
stageDuration := stage.Duration.TimeDuration()
timeTillEnd += stageDuration

stageVUDiff := stageEndVUs - fromVUs
if stageVUDiff == 0 {
if i == len(vlvc.Stages)-1 {
// add the last step always
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
steps = append(steps, lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: uint64(scaled)})
}
continue
}
if stageDuration == 0 {
Expand Down Expand Up @@ -244,7 +248,7 @@ func (vlvc RampingVUsConfig) getRawExecutionSteps(et *lib.ExecutionTuple, zeroEn
}

if zeroEnd {
steps = append(steps, lib.ExecutionStep{TimeOffset: timeTillEnd, PlannedVUs: 0})
addStep(timeTillEnd, 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again ... maybe I should just check that plannedVUs and timeOffset are different and add it only then 🤔

On the other hand we care to have a zero end and addStep will check for that it just might not be at the timeTillEnd offset 🤔 .... or maybe even that isn't possible with the changes above

}
return steps
}
Expand Down Expand Up @@ -426,6 +430,14 @@ func (vlvc RampingVUsConfig) reserveVUsForGracefulRampDowns( //nolint:funlen
})
lastPlannedVUs = rawStep.PlannedVUs
}
/*
mstoykov marked this conversation as resolved.
Show resolved Hide resolved
if newSteps[len(newSteps)-1].TimeOffset < rawSteps[len(rawSteps)-1].TimeOffset {
newSteps = append(newSteps, lib.ExecutionStep{
TimeOffset: rawSteps[len(rawSteps)-1].TimeOffset,
PlannedVUs: rawSteps[len(rawSteps)-1].PlannedVUs,
})
}
*/

return newSteps
}
Expand All @@ -449,19 +461,34 @@ func (vlvc RampingVUsConfig) reserveVUsForGracefulRampDowns( //nolint:funlen
// - 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 RampingVUsConfig) GetExecutionRequirements(et *lib.ExecutionTuple) []lib.ExecutionStep {
steps := vlvc.getRawExecutionSteps(et, false)
origSteps := vlvc.getRawExecutionSteps(et, false)

executorEndOffset := sumStagesDuration(vlvc.Stages) + vlvc.GracefulStop.TimeDuration()
// Handle graceful ramp-downs, if we have them
steps := origSteps
if vlvc.GracefulRampDown.Duration > 0 {
steps = vlvc.reserveVUsForGracefulRampDowns(steps, executorEndOffset)
}
lastIndex := len(steps)
for ; lastIndex > 0; lastIndex-- {
if steps[lastIndex-1].PlannedVUs != 0 {
break
}
}

if len(steps) > lastIndex && steps[lastIndex].TimeOffset < executorEndOffset {
executorEndOffset = steps[lastIndex].TimeOffset
}
// add one step for the end of the gracefulStop
if steps[len(steps)-1].PlannedVUs != 0 || steps[len(steps)-1].TimeOffset != executorEndOffset {
if steps[len(steps)-1].PlannedVUs != 0 || steps[len(steps)-1].TimeOffset < executorEndOffset {
steps = append(steps, lib.ExecutionStep{TimeOffset: executorEndOffset, PlannedVUs: 0})
}

if steps[len(steps)-1].TimeOffset < origSteps[len(origSteps)-1].TimeOffset {
// This can only happen on multiple 0 vus stages at the end
steps = append(steps, lib.ExecutionStep{TimeOffset: origSteps[len(origSteps)-1].TimeOffset, PlannedVUs: 0})
}

return steps
}

Expand Down
31 changes: 15 additions & 16 deletions lib/executor/ramping_vus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,14 +438,14 @@ func TestRampingVUsConfigExecutionPlanExample(t *testing.T) {
conf := NewRampingVUsConfig("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)},
{Target: null.IntFrom(6), Duration: types.NullDurationFrom(2 * time.Second)}, // 2
{Target: null.IntFrom(1), Duration: types.NullDurationFrom(5 * time.Second)}, // 7
{Target: null.IntFrom(5), Duration: types.NullDurationFrom(4 * time.Second)}, // 11
{Target: null.IntFrom(1), Duration: types.NullDurationFrom(4 * time.Second)}, // 15
{Target: null.IntFrom(4), Duration: types.NullDurationFrom(3 * time.Second)}, // 18
{Target: null.IntFrom(4), Duration: types.NullDurationFrom(2 * time.Second)}, // 20
{Target: null.IntFrom(1), Duration: types.NullDurationFrom(0 * time.Second)}, // 20
{Target: null.IntFrom(1), Duration: types.NullDurationFrom(3 * time.Second)}, // 23
}

expRawStepsNoZeroEnd := []lib.ExecutionStep{
Expand All @@ -469,11 +469,12 @@ func TestRampingVUsConfigExecutionPlanExample(t *testing.T) {
{TimeOffset: 17 * time.Second, PlannedVUs: 3},
{TimeOffset: 18 * time.Second, PlannedVUs: 4},
{TimeOffset: 20 * time.Second, PlannedVUs: 1},
{TimeOffset: 23 * 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, 23*time.Second, endOffset)
assert.Equal(t, false, isFinal)

rawStepsZeroEnd := conf.getRawExecutionSteps(et, true)
Expand Down Expand Up @@ -559,13 +560,13 @@ func TestRampingVUsConfigExecutionPlanExampleOneThird(t *testing.T) {
{TimeOffset: 15 * time.Second, PlannedVUs: 0},
{TimeOffset: 16 * time.Second, PlannedVUs: 1},
{TimeOffset: 20 * time.Second, PlannedVUs: 0},
{TimeOffset: 23 * time.Second, PlannedVUs: 0},
}
expRawStepsZeroEnd := expRawStepsNoZeroEnd // no need to copy
expRawStepsZeroEnd = append(expRawStepsZeroEnd, lib.ExecutionStep{TimeOffset: 23 * 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, 23*time.Second, endOffset)
assert.Equal(t, true, isFinal)

rawStepsZeroEnd := conf.getRawExecutionSteps(et, true)
Expand All @@ -580,7 +581,6 @@ func TestRampingVUsConfigExecutionPlanExampleOneThird(t *testing.T) {
{TimeOffset: 1 * time.Second, PlannedVUs: 2},
{TimeOffset: 42 * time.Second, PlannedVUs: 1},
{TimeOffset: 50 * time.Second, PlannedVUs: 0},
{TimeOffset: 53 * time.Second, PlannedVUs: 0},
}, conf.GetExecutionRequirements(et))

// Try a longer GracefulStop than the GracefulRampDown
Expand All @@ -590,7 +590,6 @@ func TestRampingVUsConfigExecutionPlanExampleOneThird(t *testing.T) {
{TimeOffset: 1 * time.Second, PlannedVUs: 2},
{TimeOffset: 42 * time.Second, PlannedVUs: 1},
{TimeOffset: 50 * time.Second, PlannedVUs: 0},
{TimeOffset: 103 * time.Second, PlannedVUs: 0},
}, conf.GetExecutionRequirements(et))

// Try a much shorter GracefulStop than the GracefulRampDown
Expand All @@ -611,7 +610,7 @@ func TestRampingVUsConfigExecutionPlanExampleOneThird(t *testing.T) {

// 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))
assert.Equal(t, expRawStepsZeroEnd, conf.GetExecutionRequirements(et))
}

func TestRampingVUsConfigExecutionPlanZerosAtEnd(t *testing.T) {
Expand All @@ -637,13 +636,13 @@ func TestRampingVUsConfigExecutionPlanZerosAtEnd(t *testing.T) {
{TimeOffset: 7 * time.Second, PlannedVUs: 2},
{TimeOffset: 8 * time.Second, PlannedVUs: 1},
{TimeOffset: 9 * time.Second, PlannedVUs: 0},
{TimeOffset: 14 * time.Second, PlannedVUs: 0},
}
expRawStepsZeroEnd := expRawStepsNoZeroEnd // no need to copy
expRawStepsZeroEnd = append(expRawStepsZeroEnd, lib.ExecutionStep{TimeOffset: 14 * time.Second, PlannedVUs: 0})
rawStepsNoZeroEnd := conf.getRawExecutionSteps(et, false)
assert.Equal(t, expRawStepsNoZeroEnd, rawStepsNoZeroEnd)
endOffset, isFinal := lib.GetEndOffset(rawStepsNoZeroEnd)
assert.Equal(t, 9*time.Second, endOffset)
assert.Equal(t, 14*time.Second, endOffset)
assert.Equal(t, true, isFinal)

rawStepsZeroEnd := conf.getRawExecutionSteps(et, true)
Expand Down
3 changes: 2 additions & 1 deletion lib/executors.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ func (scs ScenarioConfigs) GetFullExecutionRequirements(et *ExecutionTuple) []Ex
stepsLen := len(consolidatedSteps)
if stepsLen == 0 ||
consolidatedSteps[stepsLen-1].PlannedVUs != newPlannedVUs ||
consolidatedSteps[stepsLen-1].MaxUnplannedVUs != newMaxUnplannedVUs {
consolidatedSteps[stepsLen-1].MaxUnplannedVUs != newMaxUnplannedVUs ||
consolidatedSteps[stepsLen-1].TimeOffset != currentTimeOffset {
consolidatedSteps = append(consolidatedSteps, ExecutionStep{
TimeOffset: currentTimeOffset,
PlannedVUs: newPlannedVUs,
Expand Down