-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Per-VU init lifecycle function #785
Comments
(this is a copy of the long explanation I wrote in https://community.k6.io/t/setup-and-teardown-running-by-virtual-user/720/3, since the workaround possible in the new k6 v0.27.0 will probably be useful to people who are waiting on this issue) In a lot of cases, you can easily log in and log out (or create and destroy) with multiple user accounts in export default function(setupData) {
let myCredentials = setupData[__VU % options.vus];
// ...
} That said, the recently released k6 v0.27.0 also adds another option - the import http from 'k6/http';
import { sleep, check } from 'k6';
import { Counter } from 'k6/metrics';
import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.0.0/index.js';
let VUsCount = __ENV.VUS ? __ENV.VUS : 5;
const vuInitTimeoutSecs = 5; // adjust this if you have a longer init!
const loadTestDurationSecs = 30;
const loadTestGracefulStopSecs = 5;
let vuSetupsDone = new Counter('vu_setups_done');
export let options = {
scenarios: {
// This is the per-VU setup/init equivalent:
vu_setup: {
executor: 'per-vu-iterations',
vus: VUsCount,
iterations: 1,
maxDuration: `${vuInitTimeoutSecs}s`,
gracefulStop: '0s',
exec: 'vuSetup',
},
// You can have any type of executor here, or multiple ones, as long as
// the number of VUs is the pre-initialized VUsCount above.
my_api_test: {
executor: 'constant-arrival-rate',
startTime: `${vuInitTimeoutSecs}s`, // start only after the init is done
preAllocatedVUs: VUsCount,
rate: 5,
timeUnit: '1s',
duration: `${loadTestDurationSecs}s`,
gracefulStop: `${loadTestGracefulStopSecs}s`,
// Add extra tags to emitted metrics from this scenario. This way
// our thresholds below can only be for them. We can also filter by
// the `scenario:my_api_test` tag for that, but setting a custom tag
// here allows us to set common thresholds for multi-scenario tests.
tags: { type: 'loadtest' },
exec: 'apiTest',
},
// This is the per-VU teardown/cleanup equivalent:
vu_teardown: {
executor: 'per-vu-iterations',
startTime: `${vuInitTimeoutSecs + loadTestDurationSecs + loadTestGracefulStopSecs}s`,
vus: VUsCount,
iterations: 1,
maxDuration: `${vuInitTimeoutSecs}s`,
exec: 'vuTeardown',
},
},
thresholds: {
// Make sure all of the VUs finished their setup successfully, so we can
// ensure that the load test won't continue with broken VU "setup" data
'vu_setups_done': [{
threshold: `count==${VUsCount}`,
abortOnFail: true,
delayAbortEval: `${vuInitTimeoutSecs}s`,
}],
// Also make sure all of the VU teardown calls finished uninterrupted:
'iterations{scenario:vu_teardown}': [`count==${VUsCount}`],
// Ignore HTTP requests from the VU setup or teardown here
'http_req_duration{type:loadtest}': ['p(99)<300', 'p(99.9)<500', 'max<1000'],
},
summaryTrendStats: ['min', 'med', 'avg', 'p(90)', 'p(95)', 'p(99)', 'p(99.9)', 'max'],
};
let vuCrocName = uuidv4();
let httpReqParams = { headers: {} }; // token is set in init()
export function vuSetup() {
vuSetupsDone.add(0); // workaround for https://github.com/loadimpact/k6/issues/1346
let user = `croco${vuCrocName}`
let pass = `pass${__VU}`
let res = http.post('https://test-api.k6.io/user/register/', {
first_name: 'Crocodile',
last_name: vuCrocName,
username: user,
password: pass,
});
check(res, { 'Created user': (r) => r.status === 201 });
// Add some bogus wait time to see how VU setup "timeouts" are handled, and
// how these requests are not included in the http_req_duration threshold.
let randDelay = Math.floor(Math.random() * 4)
http.get(`https://httpbin.test.k6.io/delay/${randDelay}`);
let loginRes = http.post(`https://test-api.k6.io/auth/token/login/`, {
username: user,
password: pass
});
let vuAuthToken = loginRes.json('access');
if (check(vuAuthToken, { 'Logged in user': (t) => t !== '' })) {
console.log(`VU ${__VU} was logged in with username ${user} and token ${vuAuthToken}`);
// Set the data back in the global VU context:
httpReqParams.headers['Authorization'] = `Bearer ${vuAuthToken}`;
vuSetupsDone.add(1);
}
}
export function apiTest() {
const url = 'https://test-api.k6.io/my/crocodiles/';
const payload = {
name: `Name ${uuidv4()}`,
sex: 'M',
date_of_birth: '2001-01-01',
};
let newCrocResp = http.post(url, payload, httpReqParams);
if (check(newCrocResp, { 'Croc created correctly': (r) => r.status === 201 })) {
console.log(`[${__VU}] Created a new croc with id ${newCrocResp.json('id')}`);
}
let resp = http.get(url, httpReqParams);
if (resp.status == 200 && resp.json().length > 3) {
let data = resp.json();
if (data.length > 3) {
let id = data[0].id;
console.log(`[${__VU}] We have ${data.length} crocs, so deleting the oldest one ${id}`);
let r = http.del(`${url}/${id}/`, null, httpReqParams);
check(newCrocResp, { 'Croc deleted correctly': (r) => r.status === 201 })
}
}
}
export function vuTeardown() {
console.log(`VU ${__VU} (${vuCrocName}) is tearing itself down...`);
// In the real world, that will be actual clean up code and fancy error
// catching like in vuSetup() above. For the demo, you can increase the
// bogus wait time below to see how VU teardown "timeouts" are handled.
sleep(Math.random() * 5);
console.log(`VU ${__VU} (${vuCrocName}) was torn down!`);
} |
I've come here from #1638, and found this a nice workaround for one scenario but if I need to setup different data per scenario I'm in a bind. So I then went back to using one setup function for my scenarios, and creating arrays of what I needed as mentioned above. Is there a way to configure |
Yes, you just configure |
Added the
In any case, #1320 is probably more important, since it will partially solve this issue by giving us the following workaround: if (getVuIterationInCurrentScenario() == 1) {
// ... initialize VU for this scenario ...
} Of course, that has the cost of likely making the |
From #785 (comment):
I like this approah / workaround. Is there a way to know, in the setup() method, how many VU's are going to be invoked? |
Hi @badeball, I would recommend using
This is what is configured in the options. Since v0.38.0 as you also can have the final But again in most cases you will have configured it so it shouldn't be a thing you can't have inside your script easily. p.s. Fairly hackish way that works for not distributed/cloud test will be to use |
Hi. I get to this point because I want to make a big number of concurrent persistent connections, but not all at the same time to avoid saturating the server and the network stacks. I was thinking about using the Anyway, a per-VU setup with ramp-up function will be more than desired for this kind of use cases. |
This workaround does not seem to work at the moment of writing. |
I literally copy-pasted the script and it worked. Sometimes it fails because the default Could you bring more details of what did fail for you, please? Thanks! 🙇🏻 |
As mentioned in the comments of #784, there isn't a very good way for users to implement per-VU initialization, since we don't support making HTTP and websocket calls in the init script phase (for good reasons, also mentioned in that issue).
The two current workarounds are:
__ITER
execution context variable and initializing things in the first iteration of each VU. The problem with this is that the longer first iteration could then potentially skew theiteration_duration
metric by quite a lot and it would also eat up the script executionduration
for initialization purposes.setup()
to initialize things for all VUs, return the data in an array, and then use the__VU
execution context variable so each VU uses its own data. That's a bit awkward, but it won't skew theiteration_duration
metric. Another potential drawback is that in the distributed execution context, all requests would be made from a single IP address, which isn't ideal if we want the init code to do login or sign-up or something like that.One potential solution to this issue is to add a new per-VU lifecycle function called
init(setupData)
/initVU(setupData)
/setupVU(setupData)
or something like that. It will be called once in each VU, after the globalsetup()
is done, so it will receive the data thatsetup()
returned. It will also be called before the actual load test (looping execution of thedefault
function) starts, so it won't be counted as part of the script executionduration
orstages
.That per-VU function will be executed in the actual VU runtimes, unlike
setup()
andteardown()
, so it can save any state as global variables in the VU for later access by thedefault
function. And having it as a separate function won't interfere with the actual init phase when we get the script options, won't skew theiteration_duration
metric for the first iteration and won't take up some of the configured script execution duration. We can even tag any resulting metrics appropriately, like we do with the ones fromsetup()
andteardown()
now.Details to be considered:
setupTimeout
andteardownTimeout
.SlotLimiter
we use inhttp.batch()
requests or use something like it.The text was updated successfully, but these errors were encountered: