Skip to content

Commit

Permalink
Add a minIterationDuration option (grafana#821)
Browse files Browse the repository at this point in the history
* Add a minIterationDuration option

* Fix a gofmt issue

* Fix minIterationDuration issues from code review
  • Loading branch information
na-- authored Nov 13, 2018
1 parent 329194b commit 86aa3f6
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 5 deletions.
7 changes: 4 additions & 3 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ import (
"strings"
"time"

"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/types"
"github.com/loadimpact/k6/stats"
null "gopkg.in/guregu/null.v3"

"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/ui"
"github.com/pkg/errors"
"github.com/spf13/pflag"
null "gopkg.in/guregu/null.v3"
)

var (
Expand Down Expand Up @@ -61,6 +60,7 @@ func optionFlagSet() *pflag.FlagSet {
flags.Bool("insecure-skip-tls-verify", false, "skip verification of TLS certificates")
flags.Bool("no-connection-reuse", false, "disable keep-alive connections")
flags.Bool("no-vu-connection-reuse", false, "don't reuse connections between iterations")
flags.Duration("min-iteration-duration", 0, "minimum amount of time k6 will take executing a single iteration")
flags.BoolP("throw", "w", false, "throw warnings (like failed http requests) as errors")
flags.StringSlice("blacklist-ip", nil, "blacklist an `ip range` from being called")
flags.StringSlice("summary-trend-stats", nil, "define `stats` for trend metrics (response times), one or more as 'avg,p(95),...'")
Expand All @@ -86,6 +86,7 @@ func getOptions(flags *pflag.FlagSet) (lib.Options, error) {
InsecureSkipTLSVerify: getNullBool(flags, "insecure-skip-tls-verify"),
NoConnectionReuse: getNullBool(flags, "no-connection-reuse"),
NoVUConnectionReuse: getNullBool(flags, "no-vu-connection-reuse"),
MinIterationDuration: getNullDuration(flags, "min-iteration-duration"),
Throw: getNullBool(flags, "throw"),
DiscardResponseBodies: getNullBool(flags, "discard-response-bodies"),
// Default values for options without CLI flags:
Expand Down
51 changes: 51 additions & 0 deletions core/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -915,3 +915,54 @@ func TestEmittedMetricsWhenScalingDown(t *testing.T) {
durationSum := getMetricSum(collector, metrics.IterationDuration.Name)
assert.InDelta(t, 1.7, durationSum/(1000*durationCount), 0.1)
}

func TestMinIterationDuration(t *testing.T) {
t.Parallel()

runner, err := js.New(
&lib.SourceData{Filename: "/script.js", Data: []byte(`
import { Counter } from "k6/metrics";
let testCounter = new Counter("testcounter");
export let options = {
minIterationDuration: "1s",
vus: 2,
vusMax: 2,
duration: "1.9s",
};
export default function () {
testCounter.add(1);
};`)},
afero.NewMemMapFs(),
lib.RuntimeOptions{},
)
require.NoError(t, err)

engine, err := NewEngine(local.New(runner), runner.GetOptions())
require.NoError(t, err)

collector := &dummy.Collector{}
engine.Collectors = []lib.Collector{collector}

ctx, cancel := context.WithCancel(context.Background())
errC := make(chan error)
go func() { errC <- engine.Run(ctx) }()

select {
case <-time.After(10 * time.Second):
cancel()
t.Fatal("Test timed out")
case err := <-errC:
cancel()
require.NoError(t, err)
require.False(t, engine.IsTainted())
}

// Only 2 full iterations are expected to be completed due to the 1 second minIterationDuration
assert.Equal(t, 2.0, getMetricSum(collector, metrics.Iterations.Name))

// But we expect the custom counter to be added to 4 times
assert.Equal(t, 4.0, getMetricSum(collector, "testcounter"))
}
9 changes: 9 additions & 0 deletions js/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,14 @@ func (u *VU) runFn(ctx context.Context, group *lib.Group, fn goja.Callable, args

state.Samples <- u.Dialer.GetTrail(startTime, endTime, isFullIteration, stats.IntoSampleTags(&tags))

// If MinIterationDuration is specified and the iteration wasn't cancelled
// and was less than it, sleep for the remainder
if isFullIteration && state.Options.MinIterationDuration.Valid {
durationDiff := time.Duration(state.Options.MinIterationDuration.Duration) - endTime.Sub(startTime)
if durationDiff > 0 {
time.Sleep(durationDiff)
}
}

return v, state, err
}
2 changes: 1 addition & 1 deletion js/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,7 @@ func TestHTTPRequestInInitContext(t *testing.T) {
import http from "k6/http";
let res = http.get("HTTPBIN_URL/");
export default function() {
console.log(test);ci
console.log(test);
}
`)),
}, afero.NewMemMapFs(), lib.RuntimeOptions{})
Expand Down
7 changes: 7 additions & 0 deletions lib/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ type Options struct {
// errors about running out of file handles or sockets, or being unable to bind addresses.
NoVUConnectionReuse null.Bool `json:"noVUConnectionReuse" envconfig:"no_vu_connection_reuse"`

// MinIterationDuration can be used to force VUs to pause between iterations if a specific
// iteration is shorter than the specified value.
MinIterationDuration types.NullDuration `json:"minIterationDuration" envconfig:"min_iteration_duration"`

// These values are for third party collectors' benefit.
// Can't be set through env vars.
External map[string]json.RawMessage `json:"ext" ignored:"true"`
Expand Down Expand Up @@ -354,6 +358,9 @@ func (o Options) Apply(opts Options) Options {
if opts.NoVUConnectionReuse.Valid {
o.NoVUConnectionReuse = opts.NoVUConnectionReuse
}
if opts.MinIterationDuration.Valid {
o.MinIterationDuration = opts.MinIterationDuration
}
if opts.NoCookiesReset.Valid {
o.NoCookiesReset = opts.NoCookiesReset
}
Expand Down
6 changes: 5 additions & 1 deletion release notes/upcoming.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ export default function () {

Thanks to @AndriiChuzhynov for implementing this! (#766)

### New option to disable the summary at the end of a test (#729)
### New option: disable the summary at the end of a test (#729)

A new option that disables the end-of-test summary has been added. That summary is often superfluous when k6 tests are run in a distributed execution mode, or when the generated metrics are piped to an external output like InfluxDB or Load Impact Insights. The option can be enabled with the `--no-summary` CLI flag or the `K6_NO_SUMMARY` environment variable. When both it and the and the `--no-thresholds` option are enabled, k6 won't store any generated metrics in-memory, making the test execution a bit more efficient.

### New option: set a minimum iteration duration (#821)

You can now specify the minimum amount of time a single iteration should take via the new `minIterationDuration` option. It's also configurable via the `--min-iteration-duration` CLI flag and `K6_MIN_ITERATION_DURATION` environment variable. This setting only applies for full iterations, so any interrupted iterations due to ramping down of VUs from a stage or at the end of the tests can still be shorter.

## UX

* Added a warning when the maximum number of VUs is more than the total number of iterations (#802)
Expand Down

0 comments on commit 86aa3f6

Please sign in to comment.