How to use executor: 'shared-iterations' and stages together o simulate ramp-up scenario?

Hi all, i want to know how to use executor: ‘shared-iterations’ and stages together to simulate ramp-up scenario? Can anyone provide an example?


Hi @sunnini

Thanks for reaching out!

It sounds like the Ramping arrival rate executor would achieve exactly what you’re looking for? Depending on what your specific goal is, the Ramping VUs might also prove useful?

If that’s not the case, could you please elaborate on why you specifically don’t use them, and what your use case/scenario is, so I can try to support you.

Cheers :bowing_man:

I want to simulate a real continuous users increasing scenario, instead of starting constant VUS. But my script also must use executor: ‘shared-iterations’ to retrieve unique data from csv. How to use executor: ‘shared-iterations’ and stages together, any solution?

My understanding of what you’re trying to achieve, from your description, is: to simulate a ramping number of users interacting with your target, while using a predefined set of users to do that. Your initial approach was to try to achieve that by mixing ramping (modifying linearly the number of VUs at play), and shared-iterations (to make sure to execute exactly N iterations for a data set with N entries) executors. Is that correct?

Based on the assumption this is what you’re trying to achieve, I came up with the following script demonstrating how to use the Ramping VU executor hand in hand with the execution module’s ability to give you the current iteration number, to both having VUs ramping, and being able to have them pick a user from the data set based on the iteration number (to be sure that we go through all of them, sequentially):

// https://community.k6.io/t/how-to-use-executor-shared-iterations-and-stages-together-o-simulate-ramp-up-scenario/4141
import { sleep } from "k6";
import { SharedArray } from "k6/data";
import { scenario } from "k6/execution";

const usersJSON = new SharedArray("users", function () {
  return JSON.parse(
    open("./userdata.json", "r")
  ).users;
});

export const options = {
  discardResponseBodies: true,
  scenarios: {
    users: {
      executor: "ramping-vus",
      startVUs: 0,
      stages: [
        { duration: "20s", target: 10 },
        { duration: "10s", target: 0 },
      ],
      gracefulRampDown: "0s",
    },
  },
};

export default function () {
  // As demonstrated in https://k6.io/docs/examples/data-parameterization#retrieving-unique-data
  // Plus, applying a modulo to the index of the user in the array to stay
  // in bounds of the users array.
  const idx = scenario.iterationInTest % usersJSON.length;
  const user = usersJSON[idx];
  console.log(`${idx}:${user}`);
  sleep(1);
}

From your explanation, I had a hunch you might want to interrupt the execution once all the users from the data set have been used? If that’s the case, you should be able to check if scenario.iterationInTest == dataset.length and stop the execution there.

Let me know if that’s helpful :bowing_man:

1 Like

Thanks a lot @oleiade , I think your example is very helpful to me and can fully satisfy my case, you mentioned when scenario.iterationInTest == dataset.length and stop the execution there, i want to know how to interrupt the execution ?

You’re very welcome!

I was thinking specifically of the execution module’s abort function which will stop the test all in all. The twist is that it will make k6 consider the test aborted, and exit with code 108, as it is originally designed to help exit unrecoverable scenarios, but if you handle that explicitly, you should be good to go :+1:

thanks your pointers @oleiade , very useful to me!

1 Like

Hi! Thanks for helping out! Your answer helped me as well and pointed out to the right direction, but my case/scenario a little bit more complex…
Me case is almost identical to the TS’s (I need to combine SharedArray’s sequential usage (a lot of test data in a file) with stages (for now I just need to generate linear load of rumping-up users during specific amount of time)),
but I have also expect.js/ChaiJS (I think for the sake of simplicity it’s not quite important for now what lib exactly I am using) scenarios, like as such:

  describe(`02. POST /someMethod should return 200 OK and correct data`, (t) => {

    console.log('02. POST /someMethod:');
    let active_user = testdata[0]["user"]
    const randomUser = active_user[Math.floor(Math.random() * active_user.length)];
    console.log('Random user: ', JSON.stringify(randomUser));
    console.log(`02. POST someMethod: active_user: ${randomUser}`)

    session.addHeader("user", `${randomUser}`)
    session.addTags("POST-someMethod")
    let response = session.post("/someMethod", 
    JSON.stringify({
        "field1": `${randomUser}`,
        "field2": "qwerty",
        "field3": "1234"
    }));

    t.expect(response.status).as("response status").toEqual(200)
    someId = response.json("someId");
    anotherId = response.json("map.anotherId");
  })

  &&

  describe(`03. POST /someMethod2`, (t) => {

    console.log('03. POST /someMethod2:');
    let active_user = testdata[0]["user"]
    // console.log('testdata: ', JSON.stringify(testdata));
    // Pick a random username/password pair
    const randomUser = active_user[Math.floor(Math.random() * active_user.length)];
    console.log('Random user: ', JSON.stringify(randomUser));
    console.log(`03. POST /someMethod2: active_user: ${randomUser}`)

    let someMethod2 = {
      method: "POST",
      url: `${__ENV.BASE_URL}/someMethod2`,
      body: JSON.stringify({
          "someId": `${someId}`,  // Here is the place where above values should be put
          "anotherId": `${anotherId}`,  // Here is the place where above values should be put
          "field3": "val"
      }),
      params: {headers: {"accept": "application/json",
                        "token": "sdfsdfssg",
                        "user": `${randomUser}`}}
    };

    let responses = http.batch([someMethod2], 
    {
      tags: {my_tag: "POST-someMethod2"},
    });

    responses.forEach(response => {
      t.expect(response.status).as("response status").toEqual(200)
        .and(response).toHaveValidJson()
    })
    
    &&
    
    ...

and so on…

Tried your’s solution with modulo and length of a SharedArray and standard load scenarios as in your example, but, unfortunately, in logs and stdout I’m seeing that each user from SharedArray going arbitrary through describe-chain…
Though I’m expecting that each user will pass through every step of describe-chain/sequence sequentially:

  describe(`01. GET /someMethod should return 200 OK and correct data`, (t) => {
        active_user_1
  })

  &&

  describe(`02. POST /someMethod should return 200 OK and correct data`, (t) => {
        active_user_1
  })

  &&

  describe(`03. POST /someMethod2`, (t) => {
        active_user_1
    })
    
    &&
    
  describe(`01. GET /someMethod should return 200 OK and correct data`, (t) => {
        active_user_2
  })

  &&

  describe(`02. POST /someMethod should return 200 OK and correct data`, (t) => {
        active_user_2
  })

  &&

  describe(`03. POST /someMethod2`, (t) => {
        active_user_2
    })
    
    &&
    ...

and so on…
duplicated describes for active_user_2 just for visibility and to help understand my case better and to show what expected to see in runtime, of course, in k6-script itself there’s should be just one variable active_user, which should get each user from SharedArray sequentially, but in the mean time there’s should be concurrency: when each VU gets each own user and going through each describe-step sequentially.