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
31 changes: 31 additions & 0 deletions lib/executor/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import (
"go.k6.io/k6/ui/pb"
)

const (
// this value is an arbitrary limit. It pervenets VU shifts from being too large.
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved
maxTargetShift int = 100_000_000
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved
)

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

// Validates target up and down shifts. For any shift that is larger than maxTargetShift, it will append an error.
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved
// This includes the shift from 0 to the startTarget and the shift from the last stage to 0.
// Targets can be Vus or iteration rates.
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved
// Each Stage needs a Target value. The stages array can be empty. The Targes could be negative.
func validateTargetShifts(startTarget int64, stages []Stage) []error {
var errors []error
targets := []int64{0, startTarget}

for _, s := range stages {
targets = append(targets, s.Target.Int64)
}
targets = append(targets, 0)

for i := 0; i < len(targets)-1; i++ {
currentTarget := targets[i]
nextTarget := targets[i+1]

if absInt64(currentTarget-nextTarget) > int64(maxTargetShift) {
errors = append(errors, fmt.Errorf(
"the target shifts from %d to %d is larger than the maximum allowed %d", currentTarget, nextTarget, maxTargetShift))
}
}

return errors
}

// A helper function to avoid code duplication
func validateStages(stages []Stage) []error {
var errors []error
Expand Down
1 change: 1 addition & 0 deletions lib/executor/ramping_arrival_rate.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func (varc *RampingArrivalRateConfig) Validate() []error {
}

errors = append(errors, validateStages(varc.Stages)...)
errors = append(errors, validateTargetShifts(varc.StartRate.Int64, varc.Stages)...)
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved

if !varc.PreAllocatedVUs.Valid {
errors = append(errors, fmt.Errorf("the number of preAllocatedVUs isn't specified"))
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
64 changes: 64 additions & 0 deletions lib/executor/ramping_vus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,70 @@ func TestRampingVUsConfigValidation(t *testing.T) {
errs = c.Validate()
require.NotEmpty(t, errs)
assert.Contains(t, errs[0].Error(), "greater than 0")

const maxTargetShift = 100_000_000

t.Run("VU up shift larger than maxVUShift should fail", func(t *testing.T) {
t.Parallel()

c = NewRampingVUsConfig("stage")
// the largest upshift will be from 0 to maxVUShift + 1 which is larger than maxVUShift
c.StartVUs = null.IntFrom(maxTargetShift + 1)
c.Stages = []Stage{
// the largest downshift will be just below maxVUShift
{Target: null.IntFrom(100_000), Duration: types.NullDurationFrom(1 * time.Second)},
}

errs = c.Validate()
require.NotEmpty(t, errs)
assert.Contains(t, errs[0].Error(), fmt.Sprintf("shifts from %d to %d is larger than", 0, maxTargetShift+1))
})

t.Run("VU down shift larger than maxVUShift should fail", func(t *testing.T) {
VincentSchmid marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()

c = NewRampingVUsConfig("stage")
c.StartVUs = null.IntFrom(100_000)
c.Stages = []Stage{
// the largest upshift will be just below maxVUShift
{Target: null.IntFrom(maxTargetShift + 2), Duration: types.NullDurationFrom(1 * time.Second)},
// the largest downshift will be from maxVUShift + 1 to 0 which is larger than maxVUShift
}

errs = c.Validate()
require.NotEmpty(t, errs)
assert.Contains(t, errs[0].Error(), fmt.Sprintf("shifts from %d to %d is larger than", maxTargetShift+2, 0))
})

t.Run("Multiple VU shifts are larger than maxVUShift, multiple errors are returned", func(t *testing.T) {
t.Parallel()

c = NewRampingVUsConfig("stage")
c.StartVUs = null.IntFrom(0)
c.Stages = []Stage{
// up and down shifts are larger than maxVUShift. Multiple errors are returned
{Target: null.IntFrom(maxTargetShift + 3), Duration: types.NullDurationFrom(1 * time.Second)},
}

errs = c.Validate()
require.Equal(t, 2, len(errs))
assert.Contains(t, errs[0].Error(), fmt.Sprintf("shifts from %d to %d is larger than", 0, maxTargetShift+3))
assert.Contains(t, errs[1].Error(), fmt.Sprintf("shifts from %d to %d is larger than", maxTargetShift+3, 0))
})

t.Run("VU shifts smaller than maxVUShift will pass", func(t *testing.T) {
t.Parallel()

c = NewRampingVUsConfig("stage")
c.StartVUs = null.IntFrom(0)
c.Stages = []Stage{
// up and down shifts are smaller than maxVUShift
{Target: null.IntFrom(maxTargetShift - 1), Duration: types.NullDurationFrom(1 * time.Second)},
}

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

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