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

handling large vu shifts #3470

Merged
merged 17 commits into from
Feb 28, 2024
Merged
27 changes: 27 additions & 0 deletions lib/executor/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import (
"go.k6.io/k6/ui/pb"
)

const (
// maxConcurrentVUs is an arbitrary limit for sanity checks.
// It prevents running an exaggeratedly large number of concurrent VUs which may lead to an out-of-memory.
maxConcurrentVUs int = 100_000_000
)

func sumStagesDuration(stages []Stage) (result time.Duration) {
for _, s := range stages {
result += s.Duration.TimeDuration()
Expand All @@ -33,6 +39,27 @@ func getStagesUnscaledMaxTarget(unscaledStartValue int64, stages []Stage) int64
return max
}

// validateTargetShifts validates the VU Target shifts.
// It will append an error for any VU target that is larger than the maximum value allowed.
// Each Stage needs a Target value. The stages array can be empty. The Targes could be negative.
func validateTargetShifts(startVUs int64, stages []Stage) []error {
var errors []error

if startVUs > int64(maxConcurrentVUs) {
errors = append(errors, fmt.Errorf(
"the startVUs exceed max limit of %d", maxConcurrentVUs))
}

for i := 0; i < len(stages); i++ {
if stages[i].Target.Int64 > int64(maxConcurrentVUs) {
errors = append(errors, fmt.Errorf(
"target for stage %d exceeds max limit of %d", i+1, maxConcurrentVUs))
}
}

return errors
}

// A helper function to avoid code duplication
func validateStages(stages []Stage) []error {
var errors []error
Expand Down
5 changes: 4 additions & 1 deletion lib/executor/ramping_vus.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ func (vlvc RampingVUsConfig) Validate() []error {
errors = append(errors, fmt.Errorf("either startVUs or one of the stages' target values must be greater than 0"))
}

return append(errors, validateStages(vlvc.Stages)...)
errors = append(errors, validateStages(vlvc.Stages)...)
errors = append(errors, validateTargetShifts(vlvc.StartVUs.Int64, vlvc.Stages)...)

return errors
}

// getRawExecutionSteps calculates and returns as execution steps the number of
Expand Down
45 changes: 45 additions & 0 deletions lib/executor/ramping_vus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,51 @@ func TestRampingVUsConfigValidation(t *testing.T) {
errs = c.Validate()
require.NotEmpty(t, errs)
assert.Contains(t, errs[0].Error(), "greater than 0")

const maxConcurrentVUs = 100_000_000

t.Run("If startVUs are larger than maxConcurrentVUs, the validation should return an error", func(t *testing.T) {
t.Parallel()

c = NewRampingVUsConfig("stage")
c.StartVUs = null.IntFrom(maxConcurrentVUs + 1)
c.Stages = []Stage{
{Target: null.IntFrom(0), Duration: types.NullDurationFrom(1 * time.Second)},
}

errs = c.Validate()
require.NotEmpty(t, errs)
assert.Contains(t, errs[0].Error(), "the startVUs exceed max limit of")
})

t.Run("For multiple VU values larger than maxConcurrentVUs, multiple errors are returned", func(t *testing.T) {
t.Parallel()

c = NewRampingVUsConfig("stage")
c.StartVUs = null.IntFrom(maxConcurrentVUs + 1)
c.Stages = []Stage{
{Target: null.IntFrom(maxConcurrentVUs + 2), Duration: types.NullDurationFrom(1 * time.Second)},
}

errs = c.Validate()
require.Equal(t, 2, len(errs))
assert.Contains(t, errs[0].Error(), "the startVUs exceed max limit of")

assert.Contains(t, errs[1].Error(), "target for stage 1 exceeds max limit of")
})

t.Run("VU values below maxConcurrentVUs will pass validation", func(t *testing.T) {
t.Parallel()

c = NewRampingVUsConfig("stage")
c.StartVUs = null.IntFrom(0)
c.Stages = []Stage{
{Target: null.IntFrom(maxConcurrentVUs - 1), Duration: types.NullDurationFrom(1 * time.Second)},
}

errs = c.Validate()
require.Empty(t, errs)
})
}

func TestRampingVUsRun(t *testing.T) {
Expand Down