技術(tech)

k6 – practice of how to set and pass each option values considering scalability

The k6 is one of the popular tools for performance testing.

Just in case of development as a Poc, we may develop and create code without scalability and maintainability.

However, if we need to maintain a repository for the long term, it’s better to consider scalability and maintainability to develop efficiently.

So, in this article, let me introduce a practice of how to pass each option value when we execute k6.

Goal of this article

As a shortly summarize this article, the following requirements will be achieved.

  • Manage option values as scenario settings per scenario by JSON file
  • Manage default option values which is shared among all scenario
  • Pass each option values to k6 after merge the above two option settings
    • Priority of option is here
    • default value < JSON value

The following PR is the code that can achive the avobe requirements.

PR: https://github.com/gonkunkun/k6-template/pull/1/files

 

How to pass the option values

Pass option values to directlly

The following example is popular settings as introduced on the k6 official page.

import http from 'k6/http';

export const options = {
  hosts: { 'test.k6.io': '1.2.3.4' },
  stages: [
    { duration: '1m', target: 10 },
    { duration: '1m', target: 20 },
    { duration: '1m', target: 0 },
  ],
  thresholds: { http_req_duration: ['avg<100', 'p(95)<200'] },
  noConnectionReuse: true,
  userAgent: 'MyK6UserAgentString/1.0',
};

export default function () {
  http.get('http://test.k6.io/');
}

https://k6.io/docs/using-k6/k6-options/how-to/#set-options-in-the-script

Problem of the above code is here.

  • Only one scenario set can be managed
    • When we prepare and proceed a load testing, there are some kinds of tests like load test, spike test, etc…
    • So we need to prepare and manage each scenario set
    • But, in case of the above example, we need to fix code when any one scenario set will be executed

So we’ll come to want to manage option values outside of program code like JSON file.

Manage option using JSON

Also, there is a sample on the k6 official page.

https://k6.io/docs/using-k6/k6-options/how-to/#set-options-with-the-config-flag

As the sample of the official page, it seems a good way to pass the option value with the execution parameter value.

k6 run --config options.json script.js
{
  "hosts": {
    "test.k6.io": "1.2.3.4"
  },
  "stages": [
    {
      "duration": "1m",
      "target": 10
    },
    {
      "duration": "1m",
      "target": 30
    },
    {
      "duration": "1m",
      "target": 0
    }
  ],
  "thresholds": {
    "http_req_duration": ["avg<100", "p(95)<200"]
  },
  "noConnectionReuse": true,
  "userAgent": "MyK6UserAgentString/1.0"
}

 

As a result, we can freely pass each the scenario set option values like loadTest.json, spikeTest.json, etc…

But, this implementation has the following issue.

  • It’s difficult to have a option values that shared among scenario set

Set and use default value which can share

We can set default value which can share among scenario sets like this.

import exec from 'k6/execution';

export const options = {
  stages: [
    { duration: '5s', target: 100 },
    { duration: '5s', target: 50 },
  ],
};

export default function () {
  console.log(exec.test.options.scenarios.default.stages[0].target); // 100
}

 

If you wanna set individual values for a specific scenario set, the "–config" parameter can be used to read option values from JSON.

k6 run --config options.json script.js
{
  "hosts": {
    "test.k6.io": "1.2.3.4"
  },
  "stages": [
    {
      "duration": "1m",
      "target": 10
    },
    {
      "duration": "1m",
      "target": 30
    },
    {
      "duration": "1m",
      "target": 0
    }
  ],
  "thresholds": {
    "http_req_duration": ["avg<100", "p(95)<200"]
  },
  "noConnectionReuse": true,
  "userAgent": "MyK6UserAgentString/1.0"
}

 

Yeah, this looks like a good way to go.

However, this was has one problem.

That is default value can’t be overridden by the option value that will be passed from JSON files.

The following picture shows the priority of option values.

https://k6.io/docs/using-k6/k6-options/how-to/#order-of-precedence

Default option values is "Script options" and options values that pass from JSON file is "–config".

※ Sorry for a little bit of complexity. "Default" is the default value if the option value isn’t set. It’s different from the word "Default" in this article.

So, "Script options" is higher priority than "–config".

I’m sure it just popped into your mind right now that we wanna overwride default option values by JSON file.

I’m sure it just popped into your mind right now that we wanna override default option values by JSON file.

Prioritize option values of JSON file

Finally, we come back to the main point I wanna convey this article.

Again, what I wanna do is here.

  • Manage option values as scenario settings per scenario by JSON file
  • Manage default option values which is shared among all scenario
  • Pass each option values to k6 after merge the above two option settings
    • Priority of option is here
    • default value < JSON value

To achive the above requirements is difficult if you pass the JSON file using "–config" option flag.

By the way, about "–config" option flag and position is also discussed in the following community.

https://github.com/grafana/k6-docs/issues/688

Someone says it’s better to directly read the JSON file and pass it to options than use the "–config" option flag.

In 99%, we should recommend modularizing functionality/options using ES5 modules like the k6-purina example. Not using the --config option.

// load test config, used to populate exported options object:
const testConfig = JSON.parse(open('./config/test.json'));

// combine the above with options set directly:
export const options = testConfig;

 

Let’s modify the above example to give the options a merged value of the default value and the value read from the JSON file.

Sample code is here.

https://github.com/gonkunkun/k6-template/pull/1/files

Image of execution for k6 is the following command.

smoke:sample-product": "./k6 run ./dist/loadTest.js -e CONFIG_PATH=./src/sample-product/configs/smoke.json

 

According to the "CONFIG_PATH" that passed from cli command, read option values.

 

Put the env.ts in between.

// @ts-ignore
import { parse } from "k6/x/dotenv"

// eslint-disable-next-line @typescript-eslint/naming-convention
const _ENV = parse(open("../.env"))
export const ENV = _ENV.ENV || 'local'
export const CONFIG_PATH = _ENV.CONFIG_PATH || ENV.CONFIG_PATH || '../src/sample-product/configs/smoke.json'

 

Next, prepare the config.ts.

import * as env from '../common/env'

export const OPTIONS_CONFIG = (): Record<string, unknown> => {
  const defaultConfigs = {
    blockHostnames: ['*.hogehoge.com'],
    insecureSkipTLSVerify: true,
  }

  const configs = JSON.parse(open(env.CONFIG_PATH))

  return Object.assign({}, defaultConfigs, configs)
}

 

Seconds, receiving the "CONFIG_PATH" that passed from the env.ts, read the JSON file using open().

It then merges defaultConfigs and configs and returns their values.

Finally, let’s call the above function from scenario file.

import { OPTIONS_CONFIG } from './configs/config'

export const options = OPTIONS_CONFIG()

export function setup(): void {
略

 

This way, when you wanna temporarily change the "blockHostnames" setting, you may change the JSON file and add the above option to overwrite it.

In other words, we separated logic and options from code.

 

Conclusion

I showed a good way to pass option values to k6.

I would like to know if there are other good examples.

If you have any suggestions, please let me know.