-
Notifications
You must be signed in to change notification settings - Fork 220
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
Reworks "life cycle" explanation page #624
Conversation
There's a version of the docs published here: https://mdr-ci.staging.k6.io/docs/refs/pull/624/merge It will be deleted automatically in 30 days. |
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
|
||
| Test stage | Used to | Example | Called | Required? | | ||
|-----------------|------------------------------------------------------------|-----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|-----------| | ||
| **1. init** | Load local files, import modules, declare global variables | Open JSON file, Import module | Once per VU\* | Optional | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Init is optional, but a test without importing any k6 API is useless. Should Init be required ??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Init isn't optional. You always need init code as this is the place that defines which function is the default ;).
All the code that is outside of a function will be executed always at the begining, including if a function is called
function one() {
// this code is not init context and isn't called so it won't be executed
}
function two() {
// this code is not the init context but is called
}
two() // this is in the init context and calles the function `two` so it's code will also be executed
// export default function() {} // if this isn't uncommented k6 will give you error because nothing was defined as the function to be executed
export function setup() { // this line that is in the "init code" is what defines that there is `setup` to be called
// some setup code
}
I hope this makes it clear that "init code" is totally not optional ;).
This actually is one of the reason (IMO) that we don't call it init code, but init context. As that moves the thinking away from code and to where the code is being executed (in the init context).
I would expect there is probably even a better way to explain this using words that are more specific and don't let so much for interpretation.
WDYT @na--
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you completely @mstoykov, though I don't have any better suggestions than "init context" 😞
Not sure if we should (or even can) coin some better terms here, but an explanation of how k6 executes scripts and why init code is executed by every VU is probably in order here, not just a slight re-wording of the existing docs 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more comments
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
This is a good question, and the answer is: we do. | ||
But code *inside* and *outside* your `default` function do different things. | ||
|
||
Code inside `default` is called *VU code*. | ||
VU code runs over and over through the test duration. | ||
Besides setup and teardown, code outside of `default` is *init code*. | ||
It runs only once per VU. | ||
|
||
VU code can make HTTP requests, emit metrics, and generally do everything you'd expect a load test to do. | ||
There are a few important exceptions. VU code: | ||
* *Does not* load files from your local filesystem, | ||
* *Does not* import any other modules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this explains what you can do and where, but not why which arguably is what the question above asks.
The short answer is that "we need the script to be executed in order to know what k6 should do". That includes:
- getting hte configuration
- knowing which functions to be called on
setup
/teardown
and iterations - all the things above that you have mentioned above are also separated in order to
- on one side not generate metrics whenever we have to run the "init code" and to be able to do it fairly repeatable
- getting an
archive
with everything needed to run the code somewhere else (like in the cloud)
And that last two points is where a lot of the restrictions come from. But ultimately we just use the fact that we need to be able to run "init code" to get the options
and the definitions to do that.
Also, ultimately running
let b = 5;
twice in the same javascript context will give you an error as b
has already been defined. Putting it in the function and calling the function is what makes a new context and let you do
function f () {
let res = http.request()
// something with res
}
f();
f();
So there is also kind of another technical reason for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for these explanations! I think you are getting at something very essential to keep in mind when explaining these internals.
We always must ask: what does this information mean to the user? What bad thing will happen if they don't know this information?
It's tricky sometimes to separate what information matters for implementers from what matters for end users.
Co-authored-by: Mihail Stoykov <[email protected]>
I'm unthreading the discussions for these , because I think the conversation is converging on the same essential point. As @na-- writes
I think separating "How" and "Why" makes a lot of sense. I will try: How does k6 execute scripts?
Why does k6 work this way?There are multiple reasons to separate the code into these stages.
What do you think about this rewrite? It's simpler, and I hope more accurate. Perhaps this could replace the current intro and the sections called "Init and VU context" and "Benefits of Separation". |
How does k6 execute scripts? great. Why k6 works this way is very advanced and too detailed. I think it should not be shown upfront. I suggest using either an internal page or the "collapsible component".
|
This comment made me think that the whole article could be restructured and simplified. Basically, give the very TL,DR info in the intro. Then, use the table to go into a bit more detail. After that, explain each stage. Finally, provide the reasons why this works this way at the end. This would give the whole document a structure of progressive disclosure. Generally it's good to leave the most technical details at the end.
|
|
||
To support these modes, you can pass only data (i.e. JSON) between `setup()` and the | ||
other stages. | ||
You can pass only data (i.e. JSON) between `setup()` and the other stages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a note that it's a copy of what setup
returns. So that it:
- uses more memory if it's big
- you can't actually change it in the VU code and see the changes in the teardown. Although any VU will see it's changes to the
data
. This though is likely to change as we might freeze the whole object just to make it less obvious that it won't work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although any VU will see it's changes to the data. This though is likely to change as we might freeze the whole object just to make it less obvious that it won't work
Sorry, I'm a bit confused here. So, VU code can manipulate data from setup, but it can't pass that data to teardown. Is that correct?
And, in the table, I note that setup code can be used to " share data among VUs". How is this data shared. Is it from one VU to the next VU? Or does each VU grab "clean" data from the setup
code?
However it works, it seems like, if VU code can manipulate data that doesn't end up in the teardown stage, then the data is not really "passed" from Setup to VU to Default.
Rather, setup passes data in two places. Once, directly to teardown
. And once (or many times??) directly to default
.
I've made a small diagram to illustrate how I conceive this. Excuse me, I'm sure said some inaccurate things here, but I thought it may be easier to correct than to explain :-) .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more or less -yes to all of those :).
In practice implementing data
being modified and all that modifications being visible by :
- all VUs
- teardown
Will be ... extremely complicated and (computationally) expensive especially in distributed execution. Which is why we haven't even discussed it ever. But long long time ago I made fixes so that they have separate copies - previous to that this was sometimes panicking as multiple VUs were writing to the same not thread safe structure.
edit: the data is passed to each VU separately - but that shouldn't really be depended upon or documented. We do in general really want if it's a singel copy that is just unmodifiable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, this sounds pretty complicated. I think I'm just going to just suggest that it's a "copy," and leave it at that. Especially since it sounds like this data may get "frozen" in the VU stage in some later k6 version anyway.
For the people who are motivated enough to actually use this implementation in some advanced way, well they can look at the source code (and find this discussion). 🙂
src/data/markdown/translated-guides/en/02 Using k6/06 Test life cycle.md
Outdated
Show resolved
Hide resolved
With 534db8c , this page is almost at a complete rewrite now. I tried to add progressively more detail. Hopefully, the page gives readers a solid mental model of how a k6 script runs, without adding too much technical details or suggesting complex/impossible operations (e.g passing and manipulating data from setup to VU to teardown). I'd like the diagram to be more beautiful, and I'd prefer not having the "description list" in pure HTML, but fixing those two issues would require some time investment. If you all think it looks alright, I'd like to merge this. |
See: |
This is a first attempt to clarify the very important explanation page "Stages of a test life cycle."
There is still much room to improve and expand, but I hope this current edit makes it a little easier for first-time users to conceptualize.
Since I added some content, I'd appreciate a second pair of eyes.
Cleaned up and merged in
#638