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

Remove segment scale #2457

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
44 changes: 0 additions & 44 deletions lib/execution_segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,41 +253,6 @@ func (es *ExecutionSegment) SubSegment(child *ExecutionSegment) *ExecutionSegmen
}
}

// helper function for rounding (up) of rational numbers to big.Int values
func roundUp(rat *big.Rat) *big.Int {
quo, rem := new(big.Int).QuoRem(rat.Num(), rat.Denom(), new(big.Int))

if rem.Mul(rem, twoBigInt).Cmp(rat.Denom()) >= 0 {
return quo.Add(quo, oneBigInt)
}
return quo
}

// Scale proportionally scales the supplied value, according to the execution
// segment's position and size of the work.
func (es *ExecutionSegment) Scale(value int64) int64 {
if es == nil { // no execution segment, i.e. 100%
return value
}
// Instead of the first proposal that used remainders and floor:
// floor( (value * from) % 1 + value * length )
// We're using an alternative approach with rounding that (hopefully) has
// the same properties, but it's simpler and has better precision:
// round( (value * from) - round(value * from) + (value * (to - from)) )?
// which reduces to:
// round( (value * to) - round(value * from) )?

toValue := big.NewRat(value, 1)
toValue.Mul(toValue, es.to)

fromValue := big.NewRat(value, 1)
fromValue.Mul(fromValue, es.from)

toValue.Sub(toValue, new(big.Rat).SetFrac(roundUp(fromValue), oneBigInt))

return roundUp(toValue).Int64()
}

// InPlaceScaleRat scales rational numbers in-place - it changes the passed
// argument (and also returns it, to allow for chaining, like many other big.Rat
// methods).
Expand All @@ -298,15 +263,6 @@ func (es *ExecutionSegment) InPlaceScaleRat(value *big.Rat) *big.Rat {
return value.Mul(value, es.length)
}

// CopyScaleRat scales rational numbers without changing them - creates a new
// bit.Rat object and uses it for the calculation.
func (es *ExecutionSegment) CopyScaleRat(value *big.Rat) *big.Rat {
if es == nil { // no execution segment, i.e. 100%
return value
}
return new(big.Rat).Mul(value, es.length)
}

// ExecutionSegmentSequence represents an ordered chain of execution segments,
// where the end of one segment is the beginning of the next. It can serialized
// as a comma-separated string of rational numbers "r1,r2,r3,...,rn", which
Expand Down
67 changes: 0 additions & 67 deletions lib/execution_segment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,26 +188,6 @@ func TestExecutionSegmentSplit(t *testing.T) {
assert.Equal(t, "7/16:1/2", segments[3].String())
}

func TestExecutionSegmentFailures(t *testing.T) {
t.Parallel()
es := new(ExecutionSegment)
require.NoError(t, es.UnmarshalText([]byte("0:0.25")))
require.Equal(t, int64(1), es.Scale(2))
require.Equal(t, int64(1), es.Scale(3))

require.NoError(t, es.UnmarshalText([]byte("0.25:0.5")))
require.Equal(t, int64(0), es.Scale(2))
require.Equal(t, int64(1), es.Scale(3))

require.NoError(t, es.UnmarshalText([]byte("0.5:0.75")))
require.Equal(t, int64(1), es.Scale(2))
require.Equal(t, int64(0), es.Scale(3))

require.NoError(t, es.UnmarshalText([]byte("0.75:1")))
require.Equal(t, int64(0), es.Scale(2))
require.Equal(t, int64(1), es.Scale(3))
}

func TestExecutionTupleScale(t *testing.T) {
t.Parallel()
es := new(ExecutionSegment)
Expand Down Expand Up @@ -248,24 +228,6 @@ func TestBigScale(t *testing.T) {
require.Equal(t, int64(18), et.ScaleInt64(50))
}

func TestExecutionSegmentCopyScaleRat(t *testing.T) {
t.Parallel()
es := new(ExecutionSegment)
twoRat := big.NewRat(2, 1)
threeRat := big.NewRat(3, 1)
require.NoError(t, es.UnmarshalText([]byte("0.5")))
require.Equal(t, oneRat, es.CopyScaleRat(twoRat))
require.Equal(t, big.NewRat(3, 2), es.CopyScaleRat(threeRat))

require.NoError(t, es.UnmarshalText([]byte("0.5:1.0")))
require.Equal(t, oneRat, es.CopyScaleRat(twoRat))
require.Equal(t, big.NewRat(3, 2), es.CopyScaleRat(threeRat))

var nilEs *ExecutionSegment
require.Equal(t, twoRat, nilEs.CopyScaleRat(twoRat))
require.Equal(t, threeRat, nilEs.CopyScaleRat(threeRat))
}

func TestExecutionSegmentInPlaceScaleRat(t *testing.T) {
t.Parallel()
es := new(ExecutionSegment)
Expand Down Expand Up @@ -452,30 +414,6 @@ func generateRandomSequence(t testing.TB, n, m int64, r *rand.Rand) ExecutionSeg
return ess
}

// Ensure that the sum of scaling all execution segments in
// the same sequence with scaling factor M results in M itself.
func TestExecutionSegmentScaleConsistency(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
for i := 0; i < numTests; i++ {
scale := rand.Int31n(99) + 2
seq := generateRandomSequence(t, r.Int63n(9)+2, 100, r)

t.Run(fmt.Sprintf("%d_%s", scale, seq), func(t *testing.T) {
var total int64
for _, segment := range seq {
total += segment.Scale(int64(scale))
}
assert.Equal(t, int64(scale), total)
})
}
}

// Ensure that the sum of scaling all execution segments in
// the same sequence with scaling factor M results in M itself.
func TestExecutionTupleScaleConsistency(t *testing.T) {
Expand Down Expand Up @@ -898,11 +836,6 @@ func BenchmarkExecutionSegmentScale(b *testing.B) {
require.NoError(b, err)
for _, value := range []int64{5, 5523, 5000000, 67280421310721} {
value := value
b.Run(fmt.Sprintf("segment.Scale(%d)", value), func(b *testing.B) {
for i := 0; i < b.N; i++ {
segment.Scale(value)
}
})

b.Run(fmt.Sprintf("et.Scale(%d)", value), func(b *testing.B) {
for i := 0; i < b.N; i++ {
Expand Down
2 changes: 1 addition & 1 deletion lib/executor/constant_vus.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var _ lib.ExecutorConfig = &ConstantVUsConfig{}

// GetVUs returns the scaled VUs for the executor.
func (clvc ConstantVUsConfig) GetVUs(et *lib.ExecutionTuple) int64 {
return et.Segment.Scale(clvc.VUs.Int64)
return et.ScaleInt64(clvc.VUs.Int64)
}

// GetDescription returns a human-readable description of the executor options
Expand Down
16 changes: 8 additions & 8 deletions lib/executor/externally_controlled.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ func (mec ExternallyControlledConfig) Validate() []error {
func (mec ExternallyControlledConfig) GetExecutionRequirements(et *lib.ExecutionTuple) []lib.ExecutionStep {
startVUs := lib.ExecutionStep{
TimeOffset: 0,
PlannedVUs: uint64(et.Segment.Scale(mec.MaxVUs.Int64)), // user-configured, VUs to be pre-initialized
MaxUnplannedVUs: 0, // intentional, see function comment
PlannedVUs: uint64(et.ScaleInt64(mec.MaxVUs.Int64)), // user-configured, VUs to be pre-initialized
MaxUnplannedVUs: 0, // intentional, see function comment
}

maxDuration := mec.Duration.TimeDuration()
Expand Down Expand Up @@ -434,11 +434,11 @@ func (rs *externallyControlledRunState) progressFn() (float64, []string) {

func (rs *externallyControlledRunState) handleConfigChange(oldCfg, newCfg ExternallyControlledConfigParams) error {
executionState := rs.executor.executionState
segment := executionState.Options.ExecutionSegment
oldActiveVUs := segment.Scale(oldCfg.VUs.Int64)
oldMaxVUs := segment.Scale(oldCfg.MaxVUs.Int64)
newActiveVUs := segment.Scale(newCfg.VUs.Int64)
newMaxVUs := segment.Scale(newCfg.MaxVUs.Int64)
et := executionState.ExecutionTuple
oldActiveVUs := et.ScaleInt64(oldCfg.VUs.Int64)
oldMaxVUs := et.ScaleInt64(oldCfg.MaxVUs.Int64)
newActiveVUs := et.ScaleInt64(newCfg.VUs.Int64)
newMaxVUs := et.ScaleInt64(newCfg.MaxVUs.Int64)

rs.executor.logger.WithFields(logrus.Fields{
"oldActiveVUs": oldActiveVUs, "oldMaxVUs": oldMaxVUs,
Expand Down Expand Up @@ -523,7 +523,7 @@ func (mex *ExternallyControlled) Run(
logrus.Fields{"type": externallyControlledType, "duration": duration},
).Debug("Starting executor run...")

startMaxVUs := mex.executionState.Options.ExecutionSegment.Scale(mex.config.MaxVUs.Int64)
startMaxVUs := mex.executionState.ExecutionTuple.ScaleInt64(mex.config.MaxVUs.Int64)

ss := &lib.ScenarioState{
Name: mex.config.Name,
Expand Down
2 changes: 1 addition & 1 deletion lib/executor/per_vu_iterations.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var _ lib.ExecutorConfig = &PerVUIterationsConfig{}

// GetVUs returns the scaled VUs for the executor.
func (pvic PerVUIterationsConfig) GetVUs(et *lib.ExecutionTuple) int64 {
return et.Segment.Scale(pvic.VUs.Int64)
return et.ScaleInt64(pvic.VUs.Int64)
}

// GetIterations returns the UNSCALED iteration count for the executor. It's
Expand Down
10 changes: 5 additions & 5 deletions lib/executor/ramping_arrival_rate.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ var _ lib.ExecutorConfig = &RampingArrivalRateConfig{}

// GetPreAllocatedVUs is just a helper method that returns the scaled pre-allocated VUs.
func (varc RampingArrivalRateConfig) GetPreAllocatedVUs(et *lib.ExecutionTuple) int64 {
return et.Segment.Scale(varc.PreAllocatedVUs.Int64)
return et.ScaleInt64(varc.PreAllocatedVUs.Int64)
}

// GetMaxVUs is just a helper method that returns the scaled max VUs.
Expand All @@ -90,9 +90,9 @@ func (varc RampingArrivalRateConfig) GetMaxVUs(et *lib.ExecutionTuple) int64 {
// GetDescription returns a human-readable description of the executor options
func (varc RampingArrivalRateConfig) GetDescription(et *lib.ExecutionTuple) string {
// TODO: something better? always show iterations per second?
maxVUsRange := fmt.Sprintf("maxVUs: %d", et.Segment.Scale(varc.PreAllocatedVUs.Int64))
maxVUsRange := fmt.Sprintf("maxVUs: %d", et.ScaleInt64(varc.PreAllocatedVUs.Int64))
if varc.MaxVUs.Int64 > varc.PreAllocatedVUs.Int64 {
maxVUsRange += fmt.Sprintf("-%d", et.Segment.Scale(varc.MaxVUs.Int64))
maxVUsRange += fmt.Sprintf("-%d", et.ScaleInt64(varc.MaxVUs.Int64))
}
maxUnscaledRate := getStagesUnscaledMaxTarget(varc.StartRate.Int64, varc.Stages)
maxArrRatePerSec, _ := getArrivalRatePerSec(
Expand Down Expand Up @@ -143,8 +143,8 @@ func (varc RampingArrivalRateConfig) GetExecutionRequirements(et *lib.ExecutionT
return []lib.ExecutionStep{
{
TimeOffset: 0,
PlannedVUs: uint64(et.Segment.Scale(varc.PreAllocatedVUs.Int64)),
MaxUnplannedVUs: uint64(et.Segment.Scale(varc.MaxVUs.Int64 - varc.PreAllocatedVUs.Int64)),
PlannedVUs: uint64(et.ScaleInt64(varc.PreAllocatedVUs.Int64)),
MaxUnplannedVUs: uint64(et.ScaleInt64(varc.MaxVUs.Int64 - varc.PreAllocatedVUs.Int64)),
},
{
TimeOffset: sumStagesDuration(varc.Stages) + varc.GracefulStop.TimeDuration(),
Expand Down