diff --git a/cmd/run.go b/cmd/run.go index 8dae15a52eec..39cf9fa94cd6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -228,17 +228,17 @@ a commandline interface for interacting with it.`, fprintf(stdout, "\n") plan := execScheduler.GetExecutionPlan() - executors := execScheduler.GetExecutors() + executorConfigs := execScheduler.GetExecutorConfigs() maxDuration, _ := lib.GetEndOffset(plan) fprintf(stdout, " execution: %s\n", ui.ValueColor.Sprintf( - "(%.2f%%) %d executors, %d max VUs, %s max duration (incl. graceful stop):", - conf.ExecutionSegment.FloatLength()*100, len(executors), + "(%.2f%%) %d executorConfigs, %d max VUs, %s max duration (incl. graceful stop):", + conf.ExecutionSegment.FloatLength()*100, len(executorConfigs), lib.GetMaxPossibleVUs(plan), maxDuration), ) - for _, sched := range executors { + for _, ec := range executorConfigs { fprintf(stdout, " * %s: %s\n", - sched.GetConfig().GetName(), sched.GetConfig().GetDescription(conf.ExecutionSegment)) + ec.GetName(), ec.GetDescription(conf.ExecutionSegment)) } fprintf(stdout, "\n") } diff --git a/core/local/local.go b/core/local/local.go index 58b513b7f40e..b43424b474e4 100644 --- a/core/local/local.go +++ b/core/local/local.go @@ -40,12 +40,13 @@ type ExecutionScheduler struct { options lib.Options logger *logrus.Logger - initProgress *pb.ProgressBar - executors []lib.Executor // sorted by (startTime, ID) - executionPlan []lib.ExecutionStep - maxDuration time.Duration // cached value derived from the execution plan - maxPossibleVUs uint64 // cached value derived from the execution plan - state *lib.ExecutionState + initProgress *pb.ProgressBar + executors []lib.Executor // sorted by (startTime, ID) + executorConfigs []lib.ExecutorConfig + executionPlan []lib.ExecutionStep + maxDuration time.Duration // cached value derived from the execution plan + maxPossibleVUs uint64 // cached value derived from the execution plan + state *lib.ExecutionState } // Check to see if we implement the lib.ExecutionScheduler interface @@ -67,13 +68,20 @@ func NewExecutionScheduler(runner lib.Runner, logger *logrus.Logger) (*Execution executorConfigs := options.Execution.GetSortedConfigs() executors := make([]lib.Executor, len(executorConfigs)) - for i, sc := range executorConfigs { + // Only take executor which has works. + n := 0 + for _, sc := range executorConfigs { + if !sc.HasWork(executionState.Options.ExecutionSegment) { + continue + } s, err := sc.NewExecutor(executionState, logger.WithField("executor", sc.GetName())) if err != nil { return nil, err } - executors[i] = s + executors[n] = s + n++ } + executors = executors[:n] if options.Paused.Bool { if err := executionState.Pause(); err != nil { @@ -86,12 +94,13 @@ func NewExecutionScheduler(runner lib.Runner, logger *logrus.Logger) (*Execution logger: logger, options: options, - initProgress: pb.New(pb.WithConstLeft("Init")), - executors: executors, - executionPlan: executionPlan, - maxDuration: maxDuration, - maxPossibleVUs: maxPossibleVUs, - state: executionState, + initProgress: pb.New(pb.WithConstLeft("Init")), + executors: executors, + executorConfigs: executorConfigs, + executionPlan: executionPlan, + maxDuration: maxDuration, + maxPossibleVUs: maxPossibleVUs, + state: executionState, }, nil } @@ -115,6 +124,11 @@ func (e *ExecutionScheduler) GetExecutors() []lib.Executor { return e.executors } +// GetExecutorConfigs returns the slice of executor configs. +func (e *ExecutionScheduler) GetExecutorConfigs() []lib.ExecutorConfig { + return e.executorConfigs +} + // GetInitProgressBar returns the progress bar associated with the Init // function. After the Init is done, it is "hijacked" to display real-time // execution statistics as a text bar. diff --git a/core/local/local_test.go b/core/local/local_test.go index 1a06d1f22148..ae78cd0754de 100644 --- a/core/local/local_test.go +++ b/core/local/local_test.go @@ -728,3 +728,56 @@ func TestSetPaused(t *testing.T) { require.Contains(t, err.Error(), "doesn't support pause and resume operations after its start") }) } + +func TestNewExecutionSchedulerHasWork(t *testing.T) { + t.Parallel() + script := []byte(` +import http from 'k6/http'; + +export let options = { + executionSegment: "2/4:3/4", + execution: { + shared_iters1: { + type: "shared-iterations", + vus: 3, + iterations: 3, + }, + shared_iters2: { + type: "shared-iterations", + vus: 4, + iterations: 4, + }, + constant_arr_rate: { + type: "constant-arrival-rate", + rate: 3, + timeUnit: "1s", + duration: "20s", + preAllocatedVUs: 4, + maxVUs: 4, + }, + }, +}; + +export default function() { + const response = http.get("http://test.loadimpact.com"); +};`) + + runner, err := js.New( + &loader.SourceData{ + URL: &url.URL{Path: "/script.js"}, + Data: script, + }, + nil, + lib.RuntimeOptions{}, + ) + require.NoError(t, err) + + logger := logrus.New() + logger.SetOutput(testutils.NewTestOutput(t)) + + execScheduler, err := NewExecutionScheduler(runner, logger) + require.NoError(t, err) + + assert.Len(t, execScheduler.executors, 2) + assert.Len(t, execScheduler.executorConfigs, 3) +} diff --git a/lib/executor/constant_arrival_rate.go b/lib/executor/constant_arrival_rate.go index 4e7857fc6c70..0bdba8efdbc5 100644 --- a/lib/executor/constant_arrival_rate.go +++ b/lib/executor/constant_arrival_rate.go @@ -164,6 +164,11 @@ func (carc ConstantArrivalRateConfig) NewExecutor( }, nil } +// HasWork reports whether there is any works for give execution segment. +func (carc ConstantArrivalRateConfig) HasWork(es *lib.ExecutionSegment) bool { + return carc.GetMaxVUs(es) > 0 +} + // ConstantArrivalRate tries to execute a specific number of iterations for a // specific period. type ConstantArrivalRate struct { diff --git a/lib/executor/constant_looping_vus.go b/lib/executor/constant_looping_vus.go index 4ab79b2b7ef1..45b4a273c18b 100644 --- a/lib/executor/constant_looping_vus.go +++ b/lib/executor/constant_looping_vus.go @@ -116,6 +116,11 @@ func (clvc ConstantLoopingVUsConfig) GetExecutionRequirements(es *lib.ExecutionS } } +// HasWork reports whether there is any works for give execution segment. +func (clvc ConstantLoopingVUsConfig) HasWork(es *lib.ExecutionSegment) bool { + return clvc.GetVUs(es) > 0 +} + // NewExecutor creates a new ConstantLoopingVUs executor func (clvc ConstantLoopingVUsConfig) NewExecutor(es *lib.ExecutionState, logger *logrus.Entry) (lib.Executor, error) { return ConstantLoopingVUs{ diff --git a/lib/executor/externally_controlled.go b/lib/executor/externally_controlled.go index baa3c216a71a..9efbd691fae0 100644 --- a/lib/executor/externally_controlled.go +++ b/lib/executor/externally_controlled.go @@ -180,6 +180,11 @@ func (mec ExternallyControlledConfig) NewExecutor(es *lib.ExecutionState, logger }, nil } +// HasWork reports whether there is any works for give execution segment. +func (mec ExternallyControlledConfig) HasWork(es *lib.ExecutionSegment) bool { + return es.Scale(mec.MaxVUs.Int64) > 0 +} + type pauseEvent struct { isPaused bool err chan error diff --git a/lib/executor/per_vu_iterations.go b/lib/executor/per_vu_iterations.go index e13c1bc40a22..2eed09c078b0 100644 --- a/lib/executor/per_vu_iterations.go +++ b/lib/executor/per_vu_iterations.go @@ -134,6 +134,11 @@ func (pvic PerVUIterationsConfig) NewExecutor( }, nil } +// HasWork reports whether there is any works for give execution segment. +func (pvic PerVUIterationsConfig) HasWork(es *lib.ExecutionSegment) bool { + return pvic.GetVUs(es) > 0 && pvic.GetIterations() > 0 +} + // PerVUIterations executes a specific number of iterations with each VU. type PerVUIterations struct { *BaseExecutor diff --git a/lib/executor/shared_iterations.go b/lib/executor/shared_iterations.go index 9ca50f6a98c9..01bf9c6fa82d 100644 --- a/lib/executor/shared_iterations.go +++ b/lib/executor/shared_iterations.go @@ -148,6 +148,11 @@ type SharedIterations struct { // Make sure we implement the lib.Executor interface. var _ lib.Executor = &SharedIterations{} +// HasWork reports whether there is any works for give execution segment. +func (sic SharedIterationsConfig) HasWork(es *lib.ExecutionSegment) bool { + return sic.GetVUs(es) > 0 && sic.GetIterations(es) > 0 +} + // Run executes a specific total number of iterations, which are all shared by // the configured VUs. func (si SharedIterations) Run(ctx context.Context, out chan<- stats.SampleContainer) (err error) { diff --git a/lib/executor/variable_arrival_rate.go b/lib/executor/variable_arrival_rate.go index 75938aadb88b..e6918f29b360 100644 --- a/lib/executor/variable_arrival_rate.go +++ b/lib/executor/variable_arrival_rate.go @@ -260,6 +260,11 @@ func (varc VariableArrivalRateConfig) NewExecutor( }, nil } +// HasWork reports whether there is any works for give execution segment. +func (varc VariableArrivalRateConfig) HasWork(es *lib.ExecutionSegment) bool { + return varc.GetMaxVUs(es) > 0 +} + // VariableArrivalRate tries to execute a specific number of iterations for a // specific period. //TODO: combine with the ConstantArrivalRate? diff --git a/lib/executor/variable_looping_vus.go b/lib/executor/variable_looping_vus.go index 8fe851030c51..63b2be9ec106 100644 --- a/lib/executor/variable_looping_vus.go +++ b/lib/executor/variable_looping_vus.go @@ -460,6 +460,11 @@ func (vlvc VariableLoopingVUsConfig) NewExecutor(es *lib.ExecutionState, logger }, nil } +// HasWork reports whether there is any works for give execution segment. +func (vlvc VariableLoopingVUsConfig) HasWork(es *lib.ExecutionSegment) bool { + return lib.GetMaxPlannedVUs(vlvc.GetExecutionRequirements(es)) > 0 +} + // VariableLoopingVUs handles the old "stages" execution configuration - it // loops iterations with a variable number of VUs for the sum of all of the // specified stages' duration. diff --git a/lib/executors.go b/lib/executors.go index 77c0450b950d..98456de68269 100644 --- a/lib/executors.go +++ b/lib/executors.go @@ -103,6 +103,9 @@ type ExecutorConfig interface { GetDescription(es *ExecutionSegment) string NewExecutor(*ExecutionState, *logrus.Entry) (Executor, error) + + // HasWork reports whether there is any work to do with given segment. + HasWork(*ExecutionSegment) bool } // InitVUFunc is just a shorthand so we don't have to type the function diff --git a/ui/pb/progressbar_test.go b/ui/pb/progressbar_test.go deleted file mode 100644 index d36af9b0d3dc..000000000000 --- a/ui/pb/progressbar_test.go +++ /dev/null @@ -1,23 +0,0 @@ -/* - * - * k6 - a next-generation load testing tool - * Copyright (C) 2019 Load Impact - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -package pb - -//TODO