Best way to execute a mixed "scenarios" with Stages

Hi,

I am pretty new to K6, I have been playing with the options, stages, checks & thresholds…
Now I have been reading the documentation at k6.io and the community forums, but haven’t been able to find how to do the following:

I’d like to simulate several user’s behaviors - or we can call it different usages of the system, which I believe corresponds to the K6 term, Scenarios. (please tell me if I am wrong).
For example:

  • user A will log in and search some product, then examine it and/or download a file.
  • user B will log in and add a product, and upload some images, maybe a document.
  • user C will register, then log in, see some pages and log out.
    in a distribution of 70% , 25% B and 5% C.

I want to do this with stages like:

  • 100 VU for 10 minutes
  • 200 VU for 10 min
  • 300 VU, 10 min
  • 500 VU, 120 min.

I have seen several entries:

But still not sure what is the best way, I have read that Scenarios seem the way to go but also it seems that they are executed sequentially… or at a concrete moment… or their usage is a bit complex/unclear, mixing in the Executors which I am not at all familiar with…
I have been reading about it at Scenarios (k6.io)

What would you recommend to go with? at the moment the simple Switch with a random function seems to be winning, but I am happy to be proved wrong :slight_smile:

In addition, if anybody could share a working code sample on these two approaches would be great…

Thanks in advance :slight_smile:

1 Like

Prior to k6 0.27.0, using a switch statement and random function was the only good way to do so, but with the introduction of scenarios you can configure very advanced load testing workflows such as the one you describe.

Each scenario has its own workload model (executor), and they can either be run in parallel, sequentially, or a mix of both. The advanced examples in the documentation might be helpful for you to look at.

As for the executors, the ramping-vus executor is the equivalent of using the global stages option.

Here’s a (somewhat simplified) example of what this might look like, executing two scenarios in parallel:

export let options = {
  scenarios: {
    user_a_scenario: {
      exec: userA,
      executor: 'ramping-vus',
      startVUs: 100,
      stages: [
        { duration: '10m', target: 100 },
        { duration: '10m', target: 200 }
      ]
    },
    user_b_scenario: {
      exec: userB,
      executor: 'ramping-vus',
      startVUs: 100,
      stages: [
        { duration: '10m', target: 100 },
        { duration: '10m', target: 200 }
      ]
    }
  }
};

export function userA() {
  //Code executed for the userA workflow
}

export function userB() {
  //Code executed for the userB workflow
}
4 Likes

Thanks Dan!
That seems just what the doctor recommended :slight_smile:
Now it’s clear how this feature works.

Hi @dan_nm,

I have made a simplified script to work against one of the K6 apis and I am getting the issue:
‘ERRO[0000] json: unsupported type: func(goja.FunctionCall) goja.Value’
which seems related to this issue: export options with invalid JSON · Issue #855 · loadimpact/k6 · GitHub

My code is the following one:

// Scenario set-up
// Minutes:      0 5 10 15 20 25
// Scenario 01 : ==============>
// Scenario 02 :   ============>
// Scenario 03 :     ==========>
// Reasoning of this setup: we verify how the system reacts when we add a scenario progressively and that they do not interfere 
// or produce undesired effects.
// Additionally we start with 5 VUs for 1 minute and then ramp up to the expected load for the rest of the test.

import http from 'k6/http';
import {check, group, sleep, fail} from 'k6';

export let options = {
  scenarios: {
    Scenario01_scenario: {
      exec: Scenario01,
      executor: 'ramping-vus',
      startVUs: 5,
      stages: [
        { duration: '1m', target: 5 },
        { duration: '24m', target: 20 }
      ]
    },
    Scenario02_scenario: {
      exec: Scenario02,
      executor: 'ramping-vus',
      startVUs: 5,
      stages: [
        { duration: '1m', target: 5 },
        { duration: '19m', target: 10 }
      ]
    },
    Scenario03_scenario: {
      exec: Scenario03,
      executor: 'ramping-vus',
      startVUs: 5,
      stages: [
        { duration: '1m', target: 5 },
        { duration: '14m', target: 10 }
      ]
    }
  }
};

export function Scenario01() {
  //Code executed for the Scenario01 workflow
   let response = http.get('https://test-api.k6.io/public/crocodiles/');
  
  sleep(1);
}

export function Scenario02() {
  //Code executed for the Scenario02 workflow
   let response = http.get('https://test-api.k6.io/public/crocodiles/');
  
  sleep(1);
}

export function Scenario03() {
  //Code executed for the Scenario03 workflow
  let response = http.get('https://test-api.k6.io/public/crocodiles/');
  
  sleep(1);
}

Wondering what I am doing wrong, essentially I used your code and built upon it…
Side Note: to fit the defined time offset, I had startTime: ‘0s’, 300s and 600s, but I removed it to try to resolve the issue, without success…

Hi again, I adapted your code to make it run (adding imports, making a call and reducing the target VUs, and the same thing happens:

import http from 'k6/http';
import {sleep} from 'k6';

export let options = {
  scenarios: {
    user_a_scenario: {
      exec: userA,
      executor: 'ramping-vus',
      startVUs: 10,
      stages: [
        { duration: '1m', target: 10 },
        { duration: '1m', target: 20 }
      ]
    },
    user_b_scenario: {
      exec: userB,
      executor: 'ramping-vus',
      startVUs: 10,
      stages: [
        { duration: '1m', target: 10 },
        { duration: '1m', target: 20 }
      ]
    }
  }
};

export function userA() {
  //Code executed for the userA workflow
  http.get('https://test-api.k6.io/public/crocodiles/');
  sleep(1);
}

export function userB() {
  //Code executed for the userB workflow
  http.get('https://test-api.k6.io/public/crocodiles/');
}
1 Like

Update: It’s the “exec= UserA,” code.
That’s weird, as on the documentation Ramping VUs (k6.io) it states that " In addition to the common configuration options this executor also adds the following options:" and exec is one of the “common configuration options” so the idea is that either order matters or we are using it wrongly…

And it needs to be enclosed in simple Apostrophes… so it should be
exec: 'userA',
instead of
exec: userA,
Glad I could figure it out myself LOL

1 Like

@joslat, you have to specify the exec values as string instead of directly passing the function reference. So, exec: "userB" instead of exec: userB.

The reasons for this are a bit complex. Essentially, k6 has to be able to serialize the exported script options to a JSON file for validation and for the cloud/distributed execution, see Archive Command for details.

2 Likes

Ah yeah. My bad for missing that in the example code I wrote up the other day. Glad you were able to get it working!

1 Like