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

Core changes for k6/x/execution JS module #1863

Merged
merged 22 commits into from
Jun 22, 2021
Merged

Conversation

imiric
Copy link
Contributor

@imiric imiric commented Feb 17, 2021

These are core changes needed for the k6/x/execution JS module.

Initially the module was part of core as well, but we decided to extract it into an extension to be able to quickly iterate on the API. See the JS usage and example in the extension repo.

This also inadvertently fixes the segmentation of iterations for the ramping-arrival-rate executor. See #1863 (comment).

Part of #1320

@imiric imiric requested review from mstoykov and na-- February 17, 2021 17:10
js/runner_test.go Outdated Show resolved Hide resolved
Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't taken a deeper look or tried the code, but some comments:

  1. IMO we should allow getting the info in all cases where any info is available
  2. The things that call functions should probably call the functions on the spot (without any caching) or be functions in the first place
  3. Given that a lot of the *State objects seem "static" can we have 1 of those and just give it everywhere
  4. given 3. (and even without it) can you check and make certain that a user script can't change any of the values?

js/modules/k6/execution/execution.go Outdated Show resolved Hide resolved
js/modules/k6/execution/execution.go Outdated Show resolved Hide resolved
@imiric
Copy link
Contributor Author

imiric commented Feb 18, 2021

@mstoykov:

IMO we should allow getting the info in all cases where any info is available

Are you referring to being able to call this from init? Most of this information doesn't make sense outside the context of a running VU/scenario. Only vus_max seems relevant in init and we could get it from the config instead of *ExecutionState, but besides complicating things slightly it would be weird to have to document which information is available and which isn't, instead of just disallowing it entirely. If we go with your 2nd suggestion below, it might make sense, so I'll think about it some more. :)

The things that call functions should probably call the functions on the spot (without any caching) or be functions in the first place

Hhmm I'm not sure I prefer the API to be something like getTestStats().duration() or complicating it with DynamicObject like we discussed in #1320. Returning an already evaluated object is easier to understand (users don't expect proxies) and these are small objects so the performance benefit would likely be negligible.

I could give it a shot if you feel strongly about it, though would prefer the DynamicObject approach in that case.

Given that a lot of the *State objects seem "static" can we have 1 of those and just give it everywhere

Hhmm you mean about *ScenarioState specifically and how it's set in the context each time a VU is activated? True, it would be better if it was set on the context without being passed around in VUActivationParams. I'll look into it, thanks.

But *State and *ExecutionState are only set once.

Ah, no, you're referring about the map[string]interface{} being created every time? Indeed, good point, will avoid that too.

given 3. (and even without it) can you check and make certain that a user script can't change any of the values?

Sure. With dumb / already evaluated objects this shouldn't matter, but I'll confirm after your suggested changes.

@na--
Copy link
Member

na-- commented Feb 19, 2021

I have only skimmed the code changes, so I haven't fully grokked the PR yet, but I have a more generic comment here. I think the MVP version of #1320 shouldn't be exposing information users can already mostly get. It should be exposing things that they cannot currently get in any way. Primarily because it would require some refactoring to the executors and may change how the rest of the feature is implemented.

From what I can see, exec.getVUStats() would return exactly __VU and __ITER, right? While it is nice to have a better UX than the current super-global "constants" for that information, it's not necessary to do so initially, users manage just fine without it. exec.getScenarioStats() is new information, so better, but even now you can get most of that information by just having different exec function (or via environment variables in each scenario). The rest of the information from exec.getScenarioStats() and the whole exec.getTestStats() is novel and previously inaccessible information, true, but probably wouldn't actually be useful to users from inside of the script very often...

I think the MVP of this feature should have these two/three pieces of information, with everything else being a nice-to-have bonus:

  • in the current scenario, what is the ID number of the VU currently running the iteration
  • in the current scenario, what is the sequential number of the iteration for this VU that is currently being executed
  • for shared-iterations, constant-arrival-rate and variable-arrival-rate scenarios, a way to get the sequential number of the current iteration among all of the iterations for the current scenario, across all VUs and even instances.

The first 2 are the equivalent of __VU and __ITER, but only in terms of a single scenario. The last one is a very useful feature for data sharding and it should be easy to calculate with the execution segments. But all of these are information that is completely unavailable to user scripts right now. So, yeah, I may be wrong, but I think we should start with the "hard" (i.e. unknown) parts of this issue first, and implement the "easy" (i.e. known) parts last, as a polish to everything.

@imiric
Copy link
Contributor Author

imiric commented Feb 19, 2021

@na-- OK, so to clarify, that would involve adding a few additional executor-specific counters, right? Except for the last global across instances values which could be calculated given the execution segment?

@na--
Copy link
Member

na-- commented Feb 19, 2021

OK, so to clarify, that would involve adding a few additional executor-specific counters, right?

Yeah, basically a local VU number for the scenario (basically immutable after the VU activation in the scenario) and a local iteration number for the specific VU in the scenario (mutable, but the only lock contention here would be between whatever increments it and this new exec method used to retrieve it, so pretty light). Still, as I said, those things require some refactoring of the executors... 😞

@imiric imiric force-pushed the feat/1320-execution-api branch from eed870f to 555e1eb Compare February 24, 2021 11:02
@codecov-io
Copy link

codecov-io commented Feb 24, 2021

Codecov Report

Merging #1863 (a7456c3) into master (51790fc) will increase coverage by 0.20%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1863      +/-   ##
==========================================
+ Coverage   71.50%   71.70%   +0.20%     
==========================================
  Files         180      181       +1     
  Lines       13969    14008      +39     
==========================================
+ Hits         9988    10045      +57     
+ Misses       3344     3327      -17     
+ Partials      637      636       -1     
Flag Coverage Δ
ubuntu 71.64% <100.00%> (+0.17%) ⬆️
windows 71.29% <100.00%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
lib/executors.go 93.10% <ø> (ø)
core/local/local.go 75.82% <100.00%> (+0.11%) ⬆️
js/modules/k6/execution/execution.go 100.00% <100.00%> (ø)
js/runner.go 80.97% <100.00%> (+0.11%) ⬆️
lib/context.go 100.00% <100.00%> (ø)
lib/executor/constant_arrival_rate.go 96.42% <100.00%> (ø)
lib/executor/constant_vus.go 96.20% <100.00%> (ø)
lib/executor/externally_controlled.go 76.22% <100.00%> (+0.45%) ⬆️
lib/executor/helpers.go 96.38% <100.00%> (ø)
lib/executor/per_vu_iterations.go 97.05% <100.00%> (ø)
... and 8 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 51790fc...4be00f3. Read the comment docs.

@imiric imiric force-pushed the feat/1320-execution-api branch from 555e1eb to 4be00f3 Compare February 26, 2021 12:12
@imiric
Copy link
Contributor Author

imiric commented Feb 26, 2021

So it's unfortunately safe to say that this won't make the v0.31.0 release. 😞

I think the functionality described here is implemented now, but it will need thorough testing and actually making the tests pass consistently.

Please give the code an overview, the commits should be fairly isolated, and test it on your machine to ensure it's at least functionally OK.

What's pending:

  • Fixing the flaky tests. I think I managed to find a timing sweet spot for the *GlobalIters tests (they passed for more than 30m with this script), but it's clear from the Windows failures that the others are still flaky. I'm not sure how to refactor them to avoid this, so any ideas are welcome.

  • Addressing @mstoykov's comments above, which I started doing but ran into issues with sharing the object across goja runtimes.

  • Addressing any additional feedback.

@@ -108,6 +108,7 @@ func getIterationRunner(

// TODO: move emission of end-of-iteration metrics here?
executionState.AddFullIterations(1)
incrIter()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're currently only tracking completed/full iterations. Would tracking interrupted iterations too be interesting to users? It would essentially double the amount of counters for everything... :-/

@mstoykov mstoykov added this to the v0.32.0 milestone Apr 6, 2021
@imiric imiric force-pushed the feat/1320-execution-api branch from 4be00f3 to 69d1150 Compare April 12, 2021 15:01
@na-- na-- modified the milestones: v0.32.0, v0.33.0 Apr 14, 2021
@imiric imiric force-pushed the feat/1320-execution-api branch 2 times, most recently from 7d41b47 to 34a0596 Compare April 23, 2021 12:57
@codecov-commenter
Copy link

codecov-commenter commented Apr 23, 2021

Codecov Report

Merging #1863 (739bb55) into master (9f3dd60) will increase coverage by 0.17%.
The diff coverage is 92.83%.

❗ Current head 739bb55 differs from pull request most recent head 0279c67. Consider uploading reports for the commit 0279c67 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1863      +/-   ##
==========================================
+ Coverage   71.99%   72.17%   +0.17%     
==========================================
  Files         181      181              
  Lines       14149    14283     +134     
==========================================
+ Hits        10187    10309     +122     
- Misses       3338     3348      +10     
- Partials      624      626       +2     
Flag Coverage Δ
ubuntu 72.08% <92.83%> (+0.14%) ⬆️
windows 71.80% <92.83%> (+0.17%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
js/bundle.go 93.54% <ø> (ø)
lib/executors.go 93.10% <ø> (ø)
lib/state.go 100.00% <ø> (ø)
lib/context.go 46.66% <20.00%> (-53.34%) ⬇️
js/runner.go 82.40% <86.53%> (-0.42%) ⬇️
lib/testutils/minirunner/minirunner.go 81.15% <87.09%> (+0.72%) ⬆️
lib/executor/base_executor.go 84.00% <87.50%> (+6.22%) ⬆️
core/local/local.go 78.19% <100.00%> (+0.10%) ⬆️
lib/execution.go 92.59% <100.00%> (+0.35%) ⬆️
lib/execution_segment.go 92.73% <100.00%> (+1.57%) ⬆️
... and 13 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9f3dd60...0279c67. Read the comment docs.

@imiric imiric force-pushed the feat/1320-execution-api branch from 34a0596 to 923f494 Compare May 14, 2021 14:04
imiric pushed a commit that referenced this pull request May 18, 2021
@imiric imiric requested a review from mstoykov May 18, 2021 14:58
@imiric
Copy link
Contributor Author

imiric commented May 18, 2021

@mstoykov @na-- Could you take another look and let me know if there's anything pending here?

From what we discussed, allowing the use of this module in the init context is the major thing still missing. After taking another look, I still think it would require substantial refactoring to achieve, and most of the information just doesn't make sense in the init context.

The big (only?) use case that does make sense is retrieving the max test duration before the test starts, as mentioned on the forum and here. The problem with this is that the init context is evaluated before the ExecutionScheduler/ExecutionState is created.
This is a chicken/egg situation since we need to have access to the consolidated options, including the ones exported in JS, to be able to return the correct duration, but these aren't consolidated until after the init context is evaluated for the first time (see cmd/run.go).
I considered adding lib.Options to common.InitEnvironment to precalculate the max duration/VUs and avoid depending on ExecutionScheduler, but we run into the same chicken/egg thing, since options aren't consolidated when InitEnvironment is first initialized.

A possible approach would be to offer a pure JS API to calculate and return just the max duration, but that would duplicate a lot of the same logic we have in Go, and it doesn't seem worth it considering it can be done with the current implementation from the default function, and once we have #1001 to abort the test, it would fulfill the requirement of the person on the forum.

lib/execution_segment.go Outdated Show resolved Hide resolved
lib/execution_segment.go Outdated Show resolved Hide resolved
imiric pushed a commit that referenced this pull request May 20, 2021
Ivan Mirić and others added 7 commits June 21, 2021 16:57
* Use a single segmented index for local and global iterations

This removes the need for a lot of the additional synchronization.

This also makes it so there are global iterations for all executors. For
some of those it means that the global iteration will be *unique* among
instances but doesn't necessary mean that iterations with smaller
numbers *have* run or *will* run in the same test run.

This is somewhat like how it is for the executors for which we already
had it, there is nothing making certain that a shared-iterations
executor will not run out of time to run all the iterations on one of
the instances or a arrival-rate to not have to drop iterations due to
lack VUs to execute them.

* Update lib/executor/base_executor.go

Co-authored-by: Ivan Mirić <[email protected]>

* unexport and comment nextIterationCounters

* Remove synchronization in SegmetnedIndex

* Drop SegmentedIndexResult

* Fix race in TestSharedIterationsGlobalIters

There is nothing stopping 5 VUs starting a shared iterations at the same
time and one of them getting "slower" to the actual execution than it's
iteration counter suggests.

Co-authored-by: Ivan Mirić <[email protected]>
Calling these methods defined on a value receiver copies the BaseExecutor
instance and could cause a data race if another goroutine is writing to it.
In practice this wasn't a problem and unlikely anyone would ever run into it on
master, but it did appear on the feat/1320-execution-api branch (#1863) when
running:

go run -race main.go run --quiet -u 5 -i 5 'github.com/k6io/k6/samples/http_get.js'

Clipped stack trace:

    WARNING: DATA RACE
    Write at 0x00c00050d7a8 by main goroutine:
      go.k6.io/k6/lib/executor.(*SharedIterations).Init()
          /home/ivan/Projects/k6io/k6/lib/executor/shared_iterations.go:179 +0x384
      go.k6.io/k6/core/local.(*ExecutionScheduler).Init()
          /home/ivan/Projects/k6io/k6/core/local/local.go:276 +0xc1c
      go.k6.io/k6/core.(*Engine).Init()
          /home/ivan/Projects/k6io/k6/core/engine.go:190 +0x148
      go.k6.io/k6/cmd.getRunCmd.func1()
          /home/ivan/Projects/k6io/k6/cmd/run.go:248 +0x1ad7
    ...

    Previous read at 0x00c00050d7a8 by goroutine 32:
      go.k6.io/k6/lib/executor.(*SharedIterations).GetProgress()
          <autogenerated>:1 +0x85
      go.k6.io/k6/cmd.getRunCmd.func1.1()
          /home/ivan/Projects/k6io/k6/cmd/run.go:180 +0x13d

Also see #1863 (comment) .
We might want to eventually add this back once we determine the
usefulness of it, possibly with a global (across instances) variant.

Resolves #1863 (comment)
Copy link
Member

@na-- na-- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@na-- na-- changed the title Core changes for k6/x/execution JS module Core changes for k6/x/execution JS module Jun 22, 2021
Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👏

@imiric imiric merged commit 2039c56 into master Jun 22, 2021
@imiric imiric deleted the feat/1320-execution-api branch June 22, 2021 08:20
imiric pushed a commit to grafana/xk6-execution that referenced this pull request Jun 22, 2021
Some copying and renaming required, but it's not bad.
imiric pushed a commit to grafana/xk6-execution that referenced this pull request Jun 22, 2021
Some copying and renaming required, but it's not bad.
imiric pushed a commit to grafana/xk6-execution that referenced this pull request Jun 25, 2021
Based on internal design discussion. Access by property is more idiomatic for JS.

Also, s/stats/info/ based on feedback in grafana/k6#1863.
imiric pushed a commit to grafana/xk6-execution that referenced this pull request Jun 25, 2021
Based on internal design discussion. Access by property is more idiomatic for JS.

Also, s/stats/info/ based on feedback in grafana/k6#1863.
imiric pushed a commit to grafana/xk6-execution that referenced this pull request Jun 25, 2021
Based on internal design discussion. Access by property is more idiomatic for JS.

Also, s/stats/info/ based on feedback in grafana/k6#1863.
imiric pushed a commit to grafana/xk6-execution that referenced this pull request Jun 25, 2021
Based on internal design discussion. Access by property is more idiomatic for JS.

Also, s/stats/info/ based on feedback in grafana/k6#1863.
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
This moves the iteration state from lib.State to js.VU and js.ActiveVU,
and adds synchronization between scenario iteration increments to ensure
the information returned by k6/execution within a single VU iteration is
stable.

Resolves grafana/k6#1863 (comment)
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
Calling these methods defined on a value receiver copies the BaseExecutor
instance and could cause a data race if another goroutine is writing to it.
In practice this wasn't a problem and unlikely anyone would ever run into it on
master, but it did appear on the feat/1320-execution-api branch (#1863) when
running:

go run -race main.go run --quiet -u 5 -i 5 'github.com/k6io/k6/samples/http_get.js'

Clipped stack trace:

    WARNING: DATA RACE
    Write at 0x00c00050d7a8 by main goroutine:
      go.k6.io/k6/lib/executor.(*SharedIterations).Init()
          /home/ivan/Projects/k6io/k6/lib/executor/shared_iterations.go:179 +0x384
      go.k6.io/k6/core/local.(*ExecutionScheduler).Init()
          /home/ivan/Projects/k6io/k6/core/local/local.go:276 +0xc1c
      go.k6.io/k6/core.(*Engine).Init()
          /home/ivan/Projects/k6io/k6/core/engine.go:190 +0x148
      go.k6.io/k6/cmd.getRunCmd.func1()
          /home/ivan/Projects/k6io/k6/cmd/run.go:248 +0x1ad7
    ...

    Previous read at 0x00c00050d7a8 by goroutine 32:
      go.k6.io/k6/lib/executor.(*SharedIterations).GetProgress()
          <autogenerated>:1 +0x85
      go.k6.io/k6/cmd.getRunCmd.func1.1()
          /home/ivan/Projects/k6io/k6/cmd/run.go:180 +0x13d

Also see grafana/k6#1863 (comment) .
harrytwigg pushed a commit to APITeamLimited/globe-test that referenced this pull request Jan 11, 2023
We might want to eventually add this back once we determine the
usefulness of it, possibly with a global (across instances) variant.

Resolves grafana/k6#1863 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants