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

New executors #1007

Merged
merged 387 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
387 commits
Select commit Hold shift + click to select a range
0b14828
Add enumer generation for ExecutionStatus
na-- Mar 22, 2020
298cf6b
Improve setup() and teardown() errors
na-- Mar 22, 2020
94c73a9
Make TestVariableLoopingVUsRun more deterministic
Mar 23, 2020
277e31b
Make TestVariableLoopingVUsRampDownNoWobble more deterministic
Mar 24, 2020
8841d3a
Simplify TestVariableLoopingVUsRampDownNoWobble
Mar 25, 2020
6507287
Merge pull request #1373 from loadimpact/fix/1357-flaky-tests
Mar 26, 2020
355ac25
Add test for ExecutionSegment.Scale consistency
Mar 26, 2020
0a2bc9c
Replace random ExecutionSegmentSequence generation, log random seed
Mar 26, 2020
d2af11c
Add responsive progress bars
Jan 17, 2020
b1dfe9c
Make UI mode configurable via CLI/env
Jan 23, 2020
242d955
Refactor terminal resize handling, simplify?
Mar 5, 2020
4ea5cd2
Fix scrollback when rendering progress bars with limited window width
Mar 5, 2020
ce07a70
Bring back PersistentText for avoiding stdout flicker
Mar 9, 2020
ff5eab2
Remove ui-mode option
Mar 27, 2020
17c9255
More compact updateTermWidth init
Mar 27, 2020
b14db8e
Merge pull request #1374 from loadimpact/test/1007-1296-segment-scale
Mar 30, 2020
1affab2
WIP Implement GetStripedOffsets
mstoykov Jan 30, 2020
cac2e09
two more test cases
mstoykov Jan 30, 2020
c7133d0
Use sort.SliceStable and copy ExecutionSegmentSequence
mstoykov Feb 3, 2020
cee0602
Add benchmarks
mstoykov Feb 3, 2020
ce94b52
Precalculate the numerator change
mstoykov Feb 3, 2020
b45e60b
Return correct offsets and return the LCD as well
mstoykov Feb 4, 2020
72c7023
Preemptivly break the loop if we know we have enough offsets
mstoykov Feb 4, 2020
6bdc4c9
Optimize the calculation of LCD as na-- proposed
mstoykov Feb 4, 2020
32fdd3d
Don't use rat.Cmp in order to save a lot memory and CPU
mstoykov Feb 4, 2020
91bb4ad
Sort by the numerator array
mstoykov Feb 4, 2020
037db77
Refactor the GetStrippedOffsets
mstoykov Feb 20, 2020
56248a7
Add ExecutionSegmentSequence cli flag
mstoykov Feb 17, 2020
08aacad
Use GetStrippedOffsets in constant-arrival-rate
mstoykov Feb 20, 2020
f219a1b
Considerable speedup of strippedOffsetFor by using less big.Rat
mstoykov Feb 21, 2020
cdf8ead
Rewrite lcd calculation to not use big.Rat as well
mstoykov Feb 21, 2020
005b855
Refactor and use the striping algorithm
mstoykov Mar 4, 2020
886859a
Use single select for rendering progress bars
Mar 30, 2020
363ed9c
Remove redundant logging in non-interactive mode
Mar 30, 2020
1d34063
Silence warning retrieving terminal size in non-interactive mode
Mar 30, 2020
dc46c03
Merge pull request #1332 from loadimpact/feat/1007-1279-responsive-pr…
Mar 30, 2020
73e6f5b
refactor some panics away
mstoykov Mar 31, 2020
67e3738
more errors
mstoykov Mar 31, 2020
8cd6f5f
Add test and benchmarks for the new ScaleInt64
mstoykov Mar 31, 2020
6a6cb41
Move precalculation ExecutionTuple in ConstantArrivalRate Init
mstoykov Mar 31, 2020
06a231e
Optimize case with only 1(full)segment
mstoykov Apr 1, 2020
c2f3163
Add/remove TODOs
mstoykov Apr 1, 2020
4af3174
typo fix
mstoykov Apr 1, 2020
6758233
Merge pull request #1323 from loadimpact/GetStripedOffsetsImplementation
mstoykov Apr 1, 2020
22e8ab0
fix golangci issues
mstoykov Apr 1, 2020
4f355f6
Refactor the Engine to process metrics independently from the test Run
na-- Apr 9, 2020
05f5408
Fix a racy test
na-- Apr 9, 2020
de645da
Fix more data races in tests
na-- Apr 9, 2020
f2ca5b0
Address code review comments
na-- Apr 14, 2020
85d8826
Add a test that ensures teardown() is ran even on aborted test runs
na-- Apr 14, 2020
3f95a23
Fix typos and wrong comments
na-- Apr 14, 2020
a8d1085
Fix VariableArrivalRate not working well (#1285)
mstoykov Apr 14, 2020
43f65ba
Merge pull request #1390 from loadimpact/engine-refactor
na-- Apr 14, 2020
1840d08
Fix typos
Mar 11, 2020
20b69f3
Refactor VU context handling, introduce VU activation
Mar 16, 2020
03ddf43
Refactor per-vu-iterations executor for new VU activation
Mar 16, 2020
5b48445
Refactor variable-looping-vus executor for new VU activation
Mar 16, 2020
c529e1e
Refactor shared-iterations executor for new VU activation
Mar 16, 2020
25c8ee9
Refactor constant-looping-vus executor for new VU activation
Mar 16, 2020
2a2b4b6
Refactor externally-controlled executor for new VU activation
Mar 16, 2020
721295a
Refactor constant-arrival-rate executor for new VU activation
Mar 16, 2020
f5968ae
Refactor variable-arrival-rate executor for new VU activation
Mar 17, 2020
6b88d93
Fix tests
Mar 17, 2020
b963308
Defer cancel all RunContexts in tests to release deactivation goroutines
Apr 7, 2020
2e1ef94
Use a separate ActiveVU struct
Mar 31, 2020
8fb23c3
Synchronize ActiveVU.RunOnce calls
Apr 1, 2020
5ab0d34
Fix active VU tracking in variable-looping-vus
Apr 3, 2020
e66f7e6
Handle VU ID counter in tests with ExecutionState
Apr 7, 2020
476ccca
Synchronize VU deactivation with executors
Apr 14, 2020
d6f5dd0
CI updates
na-- Apr 16, 2020
6ee9e3e
Fix a minor bug
na-- Apr 16, 2020
b3b2a6e
Unexport runMutex, move to ActiveVU
Apr 16, 2020
eb13303
Fix constant-arrival-rate executor hanging
na-- Apr 23, 2020
d24094c
Merge branch 'master' into feat/merge-master
Apr 24, 2020
d01e2c1
Change defer order in variable-arrival-rate executor
Apr 24, 2020
aac9d81
Bring back context cancelled TODO
Apr 24, 2020
a8ac0e5
Skip TestCDNJS
Apr 24, 2020
a52c502
Appease the godot linter
Apr 24, 2020
b0ed4a0
Merge pull request #1411 from loadimpact/feat/merge-master
Apr 24, 2020
5762401
Merge branch 'new-schedulers' into fix/1007-1283-context-cancelled
Apr 27, 2020
91bb3fb
Move runMutex to js.VU
Apr 27, 2020
82443a3
Fix lint CI step
Apr 27, 2020
05aad3a
Merge branch 'master' into feat/merge-master
Apr 27, 2020
27e4f0e
Silence nestif linter
Apr 27, 2020
248139b
Merge pull request #1416 from loadimpact/feat/merge-master
Apr 28, 2020
af18a3a
use ExecutionTuple
mstoykov Apr 10, 2020
4c59d5c
Add test with execution segmeng 0:1/3
mstoykov Apr 10, 2020
721a47e
Fix not adding steps with the same amount of VUs
mstoykov Apr 10, 2020
44512c7
Simplify and probably stabilize the calculation of the steps
mstoykov Apr 10, 2020
7402ee0
drop unneded variable
mstoykov Apr 10, 2020
e4607a1
Add one test with executionTuples for looping vus
mstoykov Apr 10, 2020
42f0475
delete preveScaledVUs and 'optimize' addStep ;)
mstoykov Apr 13, 2020
71c088f
add benchmark
mstoykov Apr 14, 2020
96e503f
Rewrite the varriable looping vus to fix a bug and speed it up
mstoykov Apr 14, 2020
89d5b73
Rename variable looping vus benchmark
mstoykov Apr 15, 2020
1bce1fe
Add a bunch of corner case tests for variable looping vus
mstoykov Apr 15, 2020
b75355c
Varriable->Variable
mstoykov Apr 15, 2020
8faff96
Fix going back if the offsets are different
mstoykov Apr 16, 2020
eea43fc
don't use switch use ifs
mstoykov Apr 16, 2020
35d4a5e
Refactor getRawExecutionSteps to be more logical
mstoykov Apr 23, 2020
d0a8ac5
Optimize getRawExecutionSteps by preallocating
mstoykov Apr 23, 2020
df30a71
fix TestVariableLoopingVUsRampDownNoWobble
mstoykov Apr 23, 2020
c60f01e
Don't stop VUs right away when stepping down but instead on the "next…
mstoykov Apr 24, 2020
70275a0
drop two ifs that are no longer needed
mstoykov Apr 24, 2020
964b477
rename local and global to scaled and unscaled
mstoykov Apr 24, 2020
37492ac
misc changes and comment fixes/additions
mstoykov Apr 24, 2020
c03b5e0
Steal some documentation and implementation ideas from the more reada…
mstoykov Apr 25, 2020
fb145c2
Add a randomized test for the variable-looping-vus requirements (#1413)
na-- Apr 27, 2020
6e86861
Fix wrongly preallocating the offsetsCache by 1 smaller
mstoykov Apr 25, 2020
edb8239
Merge pull request #1392 from loadimpact/varriableLoopingVUsStepping
mstoykov Apr 28, 2020
ad190b2
Fix TestSumRandomSegmentSequenceMatchesNoSegment
na-- Apr 28, 2020
5e7457b
Fix checking the wrong error when initing collectors
mstoykov Apr 28, 2020
79e1438
GetNewExecutionTupleBasedOnValue should return the same ExecutionTup……
mstoykov Apr 29, 2020
4892443
Properly synchronize VU deactivation and fix some tests (#1424)
na-- Apr 29, 2020
684520e
Merge branch 'new-schedulers' into fix/1007-1283-context-cancelled
Apr 29, 2020
d2a6f8a
Ignore funlen linter for SharedIterations.Run
Apr 29, 2020
62b519e
Fix TestVariableLoopingVUsRampDownNoWobble
Apr 29, 2020
f7cf5a6
Fix TestVURunInterrupt by creating the context just before it's used
mstoykov Apr 30, 2020
30bd863
Add VU deactivation synchronization to MiniRunner
Apr 30, 2020
a644c13
Use the striping algorithm for shared iterations
mstoykov Apr 28, 2020
7dcbfe1
Merge pull request #1368 from loadimpact/fix/1007-1283-context-cancelled
Apr 30, 2020
e693715
Add support for non-default function execution
Apr 17, 2020
4913ad0
Add support for executor-specific env vars
Apr 17, 2020
71a7180
Add support for executor-specific tags
Apr 21, 2020
702f80d
Ignore funlen linter for PerVUIterations.Run
Apr 30, 2020
f7bc0fa
Move script function names to lib/consts package
May 4, 2020
a185924
Validate executor config earlier
May 4, 2020
f7e0214
Use helper function for VUActivationParams, simplify by passing BaseC…
May 4, 2020
1eafd11
Refactor exec fallback
May 6, 2020
d90078d
Check exports with lambda instead of passing object
May 7, 2020
ae5ef34
Fix custom exec tags not leaking between executors
May 11, 2020
60c3c09
Revert moving executor init before VU init
May 11, 2020
25a26ab
Get new VUActivationParams instance per VU in some executors
May 11, 2020
80bff4d
Add custom executor tags to WebSocket metrics
May 11, 2020
06aa32d
Add custom executor tags to checks and group metrics
May 11, 2020
d25c71a
Remove superfluous use of fmt.Sprintf
May 11, 2020
8c5d8df
Define httpbin.local host in NoCrossover test
May 11, 2020
28032b6
Cleanup NoCrossover test a bit
May 11, 2020
ba076f6
Revert "Get new VUActivationParams instance per VU in some executors"
May 12, 2020
4cf716c
Merge RunTags into state.Tags and reuse that everywhere
May 12, 2020
438b422
Move newManualVUHandle function to EC RunState
May 12, 2020
56c0a89
Minor simplification of VU activation in vuHandle
May 12, 2020
60b2ffe
Change defer order in VLV Run
May 12, 2020
62b9c0b
Limit parallel VU initialization by GOMAXPROCS not CPU count
mstoykov May 13, 2020
0f665e1
Refactor ExecutionSegment sequences, wrappers and tuples
na-- May 12, 2020
e09562d
Resolve PR comments
na-- May 13, 2020
a44509d
Fix a bunch of minor issues and repetitions in #1428
na-- May 18, 2020
e1a5c0a
Fix __ENV polution
na-- May 18, 2020
b2b8648
Tweak NoCrossover test to better cover VU reuse scenarios
May 18, 2020
270fd91
Merge pull request #1428 from loadimpact/feat/1007-1300-env-tags-exec…
May 18, 2020
d1aeda5
Prevent panics from multiple calls to stop the engine
na-- May 20, 2020
2180b19
doc: add copyright
thinkerou May 19, 2020
6aadc03
dry getSimpleRunner the same way as getSimpleBundle
mstoykov Apr 30, 2020
252319a
optimize js package test by using compatibility mode base by default
mstoykov Apr 30, 2020
923f886
Introduce RunES6String and replace RunString to not support ES6
mstoykov Apr 30, 2020
6e4ea20
Fix a broken test
na-- May 21, 2020
473f934
Update the linter and fix linter issues
na-- May 21, 2020
e0ddf5d
Try to fix docker builds...
na-- May 21, 2020
cb8c731
Remove an unnecesary else statement (#1461)
thinkerou May 22, 2020
d3a1546
Fix metrics processing after test run (#1460)
na-- May 22, 2020
366a008
Bump version in preparation for the v0.27.0 release
na-- May 26, 2020
dd88859
Avoid overwriting options when grabbing exports from archive
May 26, 2020
91b8023
Format import statements (#1464)
thinkerou May 27, 2020
bf8a28c
Merge branch 'master' into feat/merge-master
May 28, 2020
83d0fcf
Merge pull request #1472 from loadimpact/feat/merge-master
May 28, 2020
c1687e0
Make certain that even sneakily open can't be used outside the init c…
mstoykov May 29, 2020
33d65e9
Dont reset startTime, as it can be racy and it wouldn't really matter
mstoykov Jun 1, 2020
0e5e354
Fix potential data races in Engine tests
na-- Jun 2, 2020
7e636c8
Fix another data race...
na-- Jun 2, 2020
5974049
refactor TestIntegrationConvertCmd to be more stable (#1484)
mstoykov Jun 4, 2020
bceeb6a
Prevent a cloud test from blocking and make it less likely it happens
mstoykov Jun 4, 2020
24b0671
Increase TestEmittedMetricsWhenScalingDown timeout a bit
mstoykov Jun 4, 2020
61cd643
increase timeout and better reporting for TestExecutionSchedulerRunCu…
mstoykov Jun 4, 2020
ac4ab4e
Increase setup/teardown timeouts in some failing tests
mstoykov Jun 3, 2020
830ebf6
Merge pull request #1486 from loadimpact/fixRacyTests
mstoykov Jun 4, 2020
96cd417
Make TestExecutionSchedulerRunCustomConfigNoCrossover more stable
mstoykov Jun 5, 2020
1f2528c
Make TestConstantLoopingVUsRun more stable
mstoykov Jun 5, 2020
e798653
Make TestConstantArrivalRateRunCorrectTiming faster and stabler
mstoykov Jun 8, 2020
6ee1625
Prevent lock in cloud tests
mstoykov Jun 8, 2020
ae18141
Merge pull request #1491 from loadimpact/fixRacyTests2
mstoykov Jun 9, 2020
d932446
Fix externally controlled executor not pausing (#1479)
mstoykov Jun 9, 2020
0839be8
make JSON output logging less noisy
michiboo May 28, 2020
08070ca
Rename the user-facing config options
na-- Jun 9, 2020
dcf2be3
Remove mentions of Load Impact Insights
na-- Jun 9, 2020
594b141
Remove CleanUpWrongMetadataJSON() now that we don't need it
na-- Jun 9, 2020
0bc0470
Rename Go names from Execution to Scenarios
na-- Jun 9, 2020
3fc886e
Rename more Go names from Execution to Scenarios
na-- Jun 9, 2020
5020f21
Rename ConstantLoopingVUs to ConstantVUs
na-- Jun 9, 2020
f95969a
Rename VariableArrivalRate to RampingArrivalRate
na-- Jun 9, 2020
05524b6
Rename VariableLoopingVUs to RampingVUs
na-- Jun 9, 2020
54a1a45
Rename executor files to reflect their new names
na-- Jun 9, 2020
0486c11
merge steps in VLV executors for more stability (#1496)
mstoykov Jun 10, 2020
c8bfbcb
fix js package http benchmark and add one with base compatibility-mode
mstoykov Jun 12, 2020
1841d35
Skip bridge benchmarks atleast one of which is broken since 4cfbe1612…
mstoykov Jun 12, 2020
4e4d5d8
Skip BenchmarkResponseJson which was apperantly broken when spliting …
mstoykov Jun 12, 2020
40d5c1a
Make the version a constant
na-- Jun 11, 2020
1ff05a4
Update the used Go version in appveyor
na-- Jun 11, 2020
06c82e2
Fix config validation messages after the renames
na-- Jun 11, 2020
987c903
Make errors starting the API server fatal, if adddress was explicitly…
na-- Jun 16, 2020
56a6285
Rename configType to executorType
na-- Jun 17, 2020
e0eab8f
rewrite vuHandle as a state machine and fix some more races (#1506)
mstoykov Jun 17, 2020
2f150f2
Merge pull request #1503 from loadimpact/fixBenchmarks
mstoykov Jun 17, 2020
56a03ae
remove space from logrus Field
mstoykov Jun 23, 2020
986f67d
Don't leak goroutines everywhere in the ws code
mstoykov Jun 24, 2020
a6bd751
update github.com/gorilla/websocket to 1.4.2
mstoykov Jun 24, 2020
fc34f91
Fix ws_ping not being formatted as time
mstoykov Jun 24, 2020
a9903e0
Merge pull request #1515 from loadimpact/updateWSLib
mstoykov Jun 25, 2020
33ca3af
Fix externally-controlled maxVUs scaling
Jun 24, 2020
1dc8222
Fix double increment of initializedVUs in executor tests
Jun 25, 2020
06f66c5
Update TestExternallyControlledRun to check for maxVUs and scaling down
Jun 25, 2020
fc48c46
Rewrite TestExternallyControlledRun to hopefully fix flakiness
Jun 25, 2020
b9939c7
Remove old ExecutionSchedulerSetVUs test, integrate in TestExternally…
Jun 29, 2020
d3894cb
Update goja
mstoykov Jun 29, 2020
bea60a4
Update outdated comment
Jun 29, 2020
0411270
Add "scenario" as default system tag
Jun 29, 2020
27a699e
Merge pull request #1520 from loadimpact/updateGoja
mstoykov Jun 30, 2020
64f30fc
Decrement initialized VUs only when not returning VUs to the global pool
Jun 30, 2020
d84fe0e
Minor type cleanup in TestExternallyControlledRun
Jun 30, 2020
61158c8
Reuse the context and state in js.VU as much as possible (#1478)
mstoykov Jun 30, 2020
890270d
Merge pull request #1517 from loadimpact/fix/1511-external-scale
Jun 30, 2020
e2dd282
Fix startTime>0 messing up the externally-controlled executor
na-- Jun 30, 2020
15c1000
chore: drop workaround for pre go1.13 fixes #1165
mstoykov Jun 30, 2020
ecf5e1a
Make MaxVUs equal to preallocatedVUs when missing for arrival rate ex…
mstoykov Jun 30, 2020
447f892
Tag the scenario logs properly
na-- Jul 1, 2020
94b88a0
VariableArrivalRate: don't block on unplannedVU init if possible
mstoykov May 5, 2020
6a23e86
Make certain no unitilized VU will be left unreturned
mstoykov May 7, 2020
502a550
Fix ramping VU initialization
na-- Jun 29, 2020
223ed80
Fix tests and constant-arrival-rate VU init
na-- Jul 2, 2020
9780e43
Fix a typo and a test
na-- Jul 2, 2020
07dea12
Handle unplanned VU init errors better
na-- Jul 3, 2020
89361f6
Merge pull request #1500 from loadimpact/fixRampingArrivalRateUnplann…
mstoykov Jul 3, 2020
e0c93e8
Add a new dropped_iterations metric
na-- Jul 2, 2020
29168ee
Fix a context bug and add tests
na-- Jul 3, 2020
9d6cffb
Fix a flaky test
na-- Jul 3, 2020
6b308c5
Fix a typo by simplifying the code
na-- Jul 3, 2020
41a4f73
Consistently use parentCtx in the executors' Run() methods
na-- Jul 3, 2020
ba12f04
Fix cloud test progress status in CLI
Jul 6, 2020
089e416
Move cloud status output to right side of progressbar, show time duri…
Jul 6, 2020
74cf031
Fix: show stages and new executor duration during cloud test run
Jun 9, 2020
f69cdeb
Ensure every progress bar change is printed, better align run and clo…
Jun 16, 2020
20b988a
Revert progressbar status prefix whitespace in local execution
Jul 6, 2020
41f89e7
Add missing printBar call in cloud execution
Jul 6, 2020
028c46e
Add helper function to modify and print the progressbar
Jul 6, 2020
1c7f2ce
Unify execution description for cloud/local execution
Jul 6, 2020
cc01c16
Revert executor.Stage / lib.Stage merge
Jul 6, 2020
83ae172
Reorder some var declarations for better legibility
Jul 6, 2020
f57e189
Remove duplicate execution plan calculations
Jul 6, 2020
c5e62ff
Merge pull request #1490 from loadimpact/fix/1488-cli-cloud-status
Jul 6, 2020
182297b
Display user-friendly times and correct VU numbers
na-- Jul 6, 2020
488a1fb
Fix a variable name typo
na-- Jul 6, 2020
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
207 changes: 207 additions & 0 deletions lib/execution_segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
*
* 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 <http://www.gnu.org/licenses/>.
*
*/

package lib

import (
"encoding"
"fmt"
"math/big"
"strings"
)

// ExecutionSegment represents a (start, end] partition of the total execution
// work for a specific test. For example, if we want the split the execution of a
// test in 2 different parts, we can split it in two segments (0, 0.5] and (0,5, 1].
//
// We use rational numbers so it's easier to verify the correctness and easier to
// reason about portions of indivisible things, like VUs. This way, we can easily
// split a test in thirds (i.e. (0, 1/3], (1/3, 2/3], (2/3, 1]), without fearing
// that we'll lose a VU along the way...
//
// The most important part is that if work is split between multiple k6 instances,
// each k6 instance can precisely and reproducably calculate its share of the work,
// just by knowing its own segment. There won't be a need to schedule the
// execution from a master node, or to even know how many other k6 instances are
// running!
type ExecutionSegment struct {
// 0 <= from < to <= 1
from *big.Rat
to *big.Rat

// derived, equals to-from, but pre-calculated here for speed
length *big.Rat
}

// Ensure we implement those interfaces
var _ encoding.TextUnmarshaler = &ExecutionSegment{}
var _ fmt.Stringer = &ExecutionSegment{}

// Helpful "constants" so we don't initialize them in every function call
var zeroRat, oneRat = big.NewRat(0, 1), big.NewRat(1, 1) //nolint:gochecknoglobals
var oneBigInt, twoBigInt = big.NewInt(1), big.NewInt(2) //nolint:gochecknoglobals

// NewExecutionSegment validates the supplied arguments (basically, that 0 <=
// from < to <= 1) and either returns an error, or it returns a
// fully-initialized and usable execution segment.
func NewExecutionSegment(from, to *big.Rat) (*ExecutionSegment, error) {
if from.Cmp(zeroRat) < 0 {
return nil, fmt.Errorf("segment start value should be at least 0 but was %s", from.FloatString(2))
}
if from.Cmp(to) >= 0 {
return nil, fmt.Errorf("segment start(%s) should be less than its end(%s)", from.FloatString(2), to.FloatString(2))
}
if to.Cmp(oneRat) > 0 {
return nil, fmt.Errorf("segment end value shouldn't be more than 1 but was %s", to.FloatString(2))
}
return &ExecutionSegment{
from: from,
to: to,
length: new(big.Rat).Sub(to, from),
}, nil
}

// stringToRat is a helper function that tries to convert a string to a rational
// number while allowing percentage, decimal, and fraction values.
func stringToRat(s string) (*big.Rat, error) {
if strings.HasSuffix(s, "%") {
num, ok := new(big.Int).SetString(strings.TrimSuffix(s, "%"), 10)
if !ok {
return nil, fmt.Errorf("'%s' is not a valid percentage", s)
}
return new(big.Rat).SetFrac(num, big.NewInt(100)), nil
}
rat, ok := new(big.Rat).SetString(s)
if !ok {
return nil, fmt.Errorf("'%s' is not a valid percentage, decimal, fraction or interval value", s)
}
return rat, nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface, so that
// execution segments can be specified as CLI flags, environment variables, and
// JSON strings.
//
// We are able to parse both single percentage/float/fraction values, and actual
// (from; to] segments. For the single values, we just treat them as the
// beginning segment - thus the execution segment can be used as a shortcut for
// quickly running an arbitrarily scaled-down version of a test.
//
// The parsing logic is that values with a colon, i.e. ':', are full segments:
// `1/2:3/4`, `0.5:0.75`, `50%:75%`, and even `2/4:75%` should be (1/2, 3/4]
// And values without a hyphen are the end of a first segment:
// `20%`, `0.2`, and `1/5` should be converted to (0, 1/5]
// empty values should probably be treated as "1", i.e. the whole execution
na-- marked this conversation as resolved.
Show resolved Hide resolved
func (es *ExecutionSegment) UnmarshalText(text []byte) (err error) {
from := zeroRat
toStr := string(text)
if strings.ContainsRune(toStr, ':') {
fromToStr := strings.SplitN(toStr, ":", 2)
toStr = fromToStr[1]
if from, err = stringToRat(fromToStr[0]); err != nil {
return err
}
}

to, err := stringToRat(toStr)
if err != nil {
return err
}

segment, err := NewExecutionSegment(from, to)
if err != nil {
return err
}
*es = *segment
return nil
}

func (es *ExecutionSegment) String() string {
if es == nil {
return "0:1"
}
return es.from.RatString() + ":" + es.to.RatString()
}

// FloatLength is a helper method for getting some more human-readable
// information about the execution segment.
func (es *ExecutionSegment) FloatLength() float64 {
if es == nil {
return 1.0
}
res, _ := es.length.Float64()
return res
}

//TODO: add a NewFromString() and Split() methods

// 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).
func (es *ExecutionSegment) InPlaceScaleRat(value *big.Rat) *big.Rat {
if es == nil { // no execution segment, i.e. 100%
return value
}
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)
}
14 changes: 2 additions & 12 deletions lib/scheduler/interfaces.go → lib/execution_segment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@
*
*/

package scheduler
package lib

import "time"

// Config is an interface that should be implemented by all scheduler config types
type Config interface {
GetBaseConfig() BaseConfig
Validate() []error
GetMaxVUs() int64
GetMaxDuration() time.Duration // includes max timeouts, to allow us to share VUs between schedulers in the future
//TODO: Split(percentages []float64) ([]Config, error)
//TODO: String() method that could be used for priting descriptions of the currently running schedulers for the UI?
}
//TODO
na-- marked this conversation as resolved.
Show resolved Hide resolved
153 changes: 153 additions & 0 deletions lib/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
*
* 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 <http://www.gnu.org/licenses/>.
*
*/

package lib

import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"time"
)

// StrictJSONUnmarshal decodes a JSON in a strict manner, emitting an error if there
// are unknown fields or unexpected data
func StrictJSONUnmarshal(data []byte, v interface{}) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
dec.UseNumber()

if err := dec.Decode(&v); err != nil {
return err
}
if dec.More() {
//TODO: use a custom error?
return fmt.Errorf("unexpected data after the JSON object")
}
return nil
}

// GetMaxPlannedVUs returns the maximum number of planned VUs at any stage of
// the scheduler execution plan.
func GetMaxPlannedVUs(steps []ExecutionStep) (result uint64) {
for _, s := range steps {
stepMaxPlannedVUs := s.PlannedVUs
if stepMaxPlannedVUs > result {
result = stepMaxPlannedVUs
}
}
return result
}

// GetMaxPossibleVUs returns the maximum number of planned + unplanned (i.e.
// initialized mid-test) VUs at any stage of the scheduler execution plan.
// Unplanned VUs are possible in some schedulers, like the arrival-rate ones, as
// a way to have a low number of pre-allocated VUs, but be able to initialize
// new ones in the middle of the test, if needed. For example, if the remote
// system starts responding very slowly and all of the pre-allocated VUs are
// waiting for it.
//
// IMPORTANT 1: Getting planned and unplanned VUs separately for the whole
// duration of a test can often lead to mistakes. That's why this function is
// called GetMaxPossibleVUs() and why there is no GetMaxUnplannedVUs() function.
//
// As an example, imagine that you have a scheduler with MaxPlannedVUs=20 and
// MaxUnaplannedVUs=0, followed immediately after by another scheduler with
// MaxPlannedVUs=10 and MaxUnaplannedVUs=10. If you MaxPlannedVUs for the whole
// test is 20, and MaxUnaplannedVUs, but since those schedulers won't run
// concurrently, MaxVUs for the whole test is not 30, rather it's 20, since 20
// VUs will be sufficient to run the test.
//
// IMPORTANT 2: this has one very important exception. The manual execution
// scheduler doesn't use the MaxUnplannedVUs (i.e. this function will return 0),
// since their initialization and usage is directly controlled by the user and
// is effectively bounded only by the resources of the machine k6 is running on.
func GetMaxPossibleVUs(steps []ExecutionStep) (result uint64) {
for _, s := range steps {
stepMaxPossibleVUs := s.PlannedVUs + s.MaxUnplannedVUs
if stepMaxPossibleVUs > result {
result = stepMaxPossibleVUs
}
}
return result
}

// GetEndOffset returns the time offset of the last step of the execution plan,
// and whether that step is a final one, i.e. whether the number of planned or
// unplanned
na-- marked this conversation as resolved.
Show resolved Hide resolved
func GetEndOffset(steps []ExecutionStep) (lastStepOffset time.Duration, isFinal bool) {
if len(steps) == 0 {
return 0, true
}
lastStep := steps[len(steps)-1]
return lastStep.TimeOffset, (lastStep.PlannedVUs == 0 && lastStep.MaxUnplannedVUs == 0)
na-- marked this conversation as resolved.
Show resolved Hide resolved
}

// A helper function for joining error messages into a single string
func concatErrors(errors []error, separator string) string {
errStrings := make([]string, len(errors))
for i, e := range errors {
errStrings[i] = e.Error()
}
return strings.Join(errStrings, separator)
}

// StreamExecutionSteps launches a new goroutine and emits all execution steps
// at their appropriate time offsets over the returned unbuffered channel. If
// closeChanWhenDone is specified, it will close the channel after it sends the
// last step. If it isn't, or if the context is cancelled, the internal
// goroutine will be stopped, *but the channel will remain open*!
//
// As usual, steps in the supplied slice have to be sorted by their TimeOffset
// values in an ascending order. Of course, multiple events can have the same
// time offset (incl. 0).
func StreamExecutionSteps(
ctx context.Context, startTime time.Time, steps []ExecutionStep, closeChanWhenDone bool,
) <-chan ExecutionStep {

ch := make(chan ExecutionStep)
go func() {
for _, step := range steps {
offsetDiff := step.TimeOffset - time.Since(startTime)
if offsetDiff > 0 { // wait until time of event arrives
select {
case <-ctx.Done():
return // exit if context is cancelled
case <-time.After(offsetDiff): //TODO: reuse a timer?
// do nothing
}
}
select {
case <-ctx.Done():
na-- marked this conversation as resolved.
Show resolved Hide resolved
return // exit if context is cancelled
case ch <- step: // send the step
}
}

// Close the channel only if all steps were sent successfully (i.e. the
// parent context didn't die) and we were instructed to do so.
if closeChanWhenDone {
close(ch)
}
}()
return ch
}
Loading