k6 Load Testing - Bug Reaper

                  Bug Reaper

Lean about Automation Testing,Selenium WebDriver,RestAssured,Appium,Jenkins,JAVA,API Automation,TestNG,Maven, Rest API, SOAP API,Linux,Maven,Security Testing,Interview Questions

Thursday 22 December 2022

k6 Load Testing

k6 is written in the goja programming language, which is an implementation of ES2015(ES6) JavaScript on pure Golang language. That means you can use JavaScript to write k6 scripts,

Install  Chocolatey package manager


Open PowerShell 

Set-ExecutionPolicy Bypass -Scope Process


Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) 


Open Cmd

choco install k6

Now k6 is installed in machine, you can run the script from wherever you want and your code is written

//Options

The option object holds all the information related to your test:

How many users to simulate

How to ramp up

The duration of the test

How to ramp down


Sample script (you can create this script anywhere)

import http from "k6/http";

export let options = {

    insecureSkipTLSVerify: true, //InsecureSkipTLSVerify disables TLS certificate verification when //communicating with this server. This is strongly discouraged. You should use the CABundle instead.

//TLS certificates are a type of digital certificate, issued by a Certificate Authority (CA). The CA signs //the certificate, certifying that they have verified that it belongs to the owners of the domain name //which is the subject of the certificate

//noConnectionReuse determines whether a connection is reused throughout different actions of the //same virtual user and in the same iteration.

    noConnectionReuse: false,

    vus:60, // vus is virtual users

    duration: '10s' //for how much time test will run


};

export default function() {

    let response = http.get("https://test-api.k6.io");

};


Below Command to run the script

k6 run test.js



Observe that 10 users for 60 seconds are run as we have given the same in above script


We can also mention number of vus and duration we need from terminal
Example

k6 run --vus 10 --duration 30s test.js

Here (Running a 30-second, 10-VU load test )

VUs are essentially parallel



Options (load testing)
For load testing, you should ramp up the VU to a good amount and maintain it for a fixed period of time before ramping it down to 0. Have a look at the following example, which uses 100 VUs.


import http from "k6/http";
import { check } from 'k6';
export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
// simulate ramp-up traffic from 1 to 100 users over the period of 40 seconds.
// we use ramp up as we want to see if our load balancers are working fine when we ramp up //users
    { duration: '40s', target: 100 },
// Stay at 100 users for 20 seconds
    { duration: '20s', target: 100 },
// ramp down to 0 users
//we use ramp down to make sure the resources that we were using earlier to handle load are getting //released or not
    { duration: '1s', target: 0 },
  ],

};
export default function() {
//Checks
//k6 provides a way for you to assert the returned response. It’s called check. Please note that check //doesn’t halt the execution.
    let response = http.get("https://test-api.k6.io");
check(response, {
    'is status 200': (r) => r.status === 200,
  });
};





Options (stress testing)
On the other hand, stress testing involves the constant ramping up of VUs gradually over a period of time. You can start with 100 VUs and then increment it by 100 VUs each time. Then, you ramp it down as part of the recovery phase.

Sample Script

import http from "k6/http";
import { check } from 'k6';
export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
{ target: 100, duration: "40s" }, //below normal load
        { target: 100, duration: "40s" },
        { target: 200, duration: "50s" }, //normal load
        { target: 200, duration: "50s" },
        { target: 300, duration: "50s" }, // around the breaking load
        { target: 300, duration: "50s" },
        { target: 400, duration: "50s" }, //beyond the breaking point
        { target: 400, duration: "50s" },
        { target: 0, duration: "10s" },  //scale down. Recovery stage.
  ],

};
export default function() {
    let response = http.get("https://test-api.k6.io");
check(response, {
    'is status 200': (r) => r.status === 200,
  });
};
























HTTP-specific built-in metric:

METRIC NAME DESCRIPTION
http_reqs --> How many HTTP requests has k6 generated, in total.
http_req_blocked --> Time spent blocked (waiting for a free TCP connection slot) before initiating the request. float
http_req_connecting --> Time spent establishing TCP connection to the remote host. float
http_req_tls_handshaking --> Time spent handshaking TLS session with remote host
http_req_sending --> Time spent sending data to the remote host. float
http_req_waiting --> Time spent waiting for response from remote host (a.k.a. \”time to first byte\”, or \”TTFB\”). float
http_req_receiving  --> Time spent receiving response data from the remote host. float
http_req_duration --> Total time for the request. It’s equal to http_req_sending + http_req_waiting + http_req_receiving. float


What k6 does not do
By default, k6 does not render web pages the same way a browser does. Browsers can consume significant system resources. Skipping the browser allows running more load within a single machine.

However, with xk6-browser, you can interact with real browsers and collect frontend metrics as part of your k6 tests.

Options (spike testing)
Spike testing aims to overwhelm your system with a sudden surge of a load within a short period of time.

Sample Script
import http from "k6/http";
import { check } from 'k6';
export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
        { target: 100, duration: "10s" }, //below normal load
        { target: 100, duration: "1m" },
        { target: 1400, duration: "10s" }, //spike to 1400 users
        { target: 1400, duration: "1m" }, // stay at 1400 users for 3 minutes
        { target: 100, duration: "10s" }, // scale down. recovery stage.
        { target: 100, duration: "1m" },
        { target: 0, duration: "10s" },
    ],

};
export default function() {
    let response = http.get("https://test-api.k6.io");
check(response, {
    'is status 200': (r) => r.status === 200,
  });
};



Thresholds
Thresholds are global pass/fail criteria that you can configure k6 to use 

k6 allows you to define targets of tests by defining thresholds on statistics. For example:

Sample Script
import http from "k6/http";
import { check } from 'k6';
export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
        { target: 100, duration: "10s" }, //below normal load
        { target: 1400, duration: "10s" }, //spike to 1400 users
        { target: 0, duration: "10s" },
    ],
thresholds: {
 http_req_failed: ['rate<0.0001'], // http errors should be less than 1%
 http_req_duration: ['p(100)<1'], // 100% of requests should be below 1ms
 },

};
export default function() {
    let response = http.get("https://test-api.k6.io");
check(response, {
    'is status 200': (r) => r.status === 200,
  });
};






























We can get the result as json format

k6 run --out json=my_test_result.json --vus 10 --duration 30s test.js

report will be saved
















The end-of-test summary shows aggregated statistical values for your result metrics, including:

Median and average values
Minimum and maximum values
p90, p95, and p99 values


You can configure the statistics to report with the --summary-trend-stats option. For example, this command displays only median, p95, and p99.9 values.

k6 run --iterations=100 --vus=10 \ --summary-trend-stats="med,p(95),p(99.9)" script.js






Sample Post Script Request


/**
 * @author NeerajBakhtani
 */
import http from 'k6/http';
import {check} from 'k6';

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
        {target: 3, duration: "10s"}, //below normal load
    ],
    thresholds: {
        http_req_failed: ['rate<0.0001'], // http errors should be less than 1%
        http_req_duration: ['p(100)<4'], // 95% of requests should be below 200ms
    },
};

export default function () {
    const url = 'https://dummyjson.com/auth/login';
    const payload = JSON.stringify({
        username: 'kminchelle',
        password: '0lelplR',
    });

    const params = {
            headers: {'Content-Type': 'application/json'},

        };
    const response=http.post(url,payload,params); // Note: while making post request we need to pass //url, payload and headers

    check(response,{
        'is status': (r)=>r.status===200,
        'is response body has username': (r)=>r.body.includes('kminchelle')
    })

}


Shared & Per VUs Iterations With K6 


Note that iterations aren't fairly distributed with this executor, and a VU that executes faster will complete more iterations than others. 

If you want guarantees that every VU will complete a specific, fixed number of iterations, use the per-VU iterations executor.





















In shared iteration there is no guarantee that all VU will get equal chance to perform iteration so chances are there that 1 VU can perform more iteration that other VU thats the reason there is another option to use per VU

For above feature we have one option available named executor

we need to pass this option in our script


Sample Script with shared-iterations(shared-iterations
are used when we are not sure if the user will perform same operation again and again, example among 200 users we can't be sure that 1st user will perform search operation exactly 200 times and may be it can be like that 1st user will perform operation for 100 times and 2nd user is more aggressive he can perform search operation 200 times, so in short we are not sure which user will perform a particular operation how many times 

so for such cases we use shared iterations so that total iterations are done without worrying about how many operation a particular user performs)

/**
 * @author NeerajBakhtani
 */
import http from 'k6/http';
import {check} from 'k6';

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    executor: 'shared-iterations',
    vus: 10,
    iterations: 200,
    maxDuration: '30s',
    thresholds: {
        http_req_failed: ['rate<0.1'], // http errors should be less than 1%
        http_req_duration: ['p(100)<4'], // 95% of requests should be below 200ms
    },
};

export default function () {
    const url = 'https://dummyjson.com/auth/login';
    const payload = JSON.stringify({
        username: 'kminchelle',
        password: '0lelplR',
    });

    const params = {
        headers: {'Content-Type': 'application/json'},

    };
    const response = http.post(url, payload, params);
    check(response, {
        'is status': (r) => r.status === 200,
        'is response body has username': (r) => r.body.includes('kminchelle')
    })

}



Sample Script with per-vu-iterations
  executor: 'per-vu-iterations',

for certain use cases we are concerned about how many operations a particular user performs, so in that case we use   executor: 'per-vu-iterations',



This above report is with shared-iterations, duration is 266 ms

















This above report is with per-vu-iterations, duration is 281ms

Note: Observe that since threads are not equally distributed strictly in shared-iterations. So 
execution is faster as no thread has to wait for anyone

But in  per-vu-iterations each thread has specific iteration to execute so they have to run in a discipline which is why they take more time

90 and 95 percentile
90% percentile is a statistical measurement, in case of JMeter it means that 90% of the sampler response times were smaller than or equal to this time

means lets suppose 90% pf all request during load were services by the server within 500 ms and rest 10% of the requests were served which took more than 500 seconds





You can install plugin for k6 in Webstorm
https://plugins.jetbrains.com/plugin/16141-k6


you would be able to run test cases directly, no need of command prompt

To Generate report locally
/**
 * @author NeerajBakhtani
 */
import http from 'k6/http';
import {check} from 'k6';
import {sleep} from 'k6';
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    vus: 10,
    iterations: 300,
   
};

export default function () {
    const url = 'https://dummyjson.com/auth/login';
    const payload = JSON.stringify({
        username: 'kminchelle',
        password: '0lelplR',
    });

    const params = {
        headers: {'Content-Type': 'application/json'},

    };
    const response = http.post(url, payload, params);
    const response1=http.post
    //sleep(2);
    check(response, {
        'is status': (r) => r.status === 200,
        'is response body has username': (r) => r.body.includes('kminchelle')
    })

}

export function handleSummary(data) {
    return {
        "scriptReport.html": htmlReport(data),
        stdout: textSummary(data, { indent: " ", enableColors: true })
    };
}


Report


















Download docker
https://docs.docker.com/desktop/install/windows-install/


create a docker-compose.yml file

version: '3.4'

networks:
  k6:
  grafana:

services:
  influxdb:
    image: influxdb:1.8
    networks:
      - k6
      - grafana
    ports:
      - "8086:8086"
    environment:
      - INFLUXDB_DB=k6

  grafana:
    image: grafana/grafana:latest
    networks:
      - grafana
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_BASIC_ENABLED=false
    volumes:
      - ./grafana:/etc/grafana/provisioning/

  k6:
    image: grafana/k6:latest
    networks:
      - k6
    ports:
      - "6565:6565"
    environment:
    - K6_OUT=influxdb=http://localhost:8086/k6
    volumes:
      - ./scripts:/scripts



Now run the below command

docker-compose up -d // this command will look for docker-compose.yml file and install the services //mentioned

Understand how docker file works

docker compose file helps developer to setup env quickly by pulling up the images

networks: tell containers we need to talk to each other, we can segregate networks

services: tell how 2 services will talk to each other



After docker setup the environment it looks like below

+] Running 5/5
 - Network k6loadtest_k6            Created                                                                                                                                           0.9s
 - Network k6loadtest_grafana       Created                                                                                                                                           1.0s
 - Container k6loadtest-k6-1        Started                                                                                                                                          20.2s
 - Container k6loadtest-grafana-1   Started                                                                                                                                          20.2s
 - Container k6loadtest-influxdb-1  Started  

























You can see docker images installed in above screenshot



Run below command
cat postapi.js | docker run -i loadimpact/k6 run -

Output

k6 run --out influxdb=http://localhost:8086/k6 postapi.js

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: postapi.js
     output: InfluxDBv1 (http://localhost:8086)

  scenarios: (100.00%) 1 scenario, 10 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 300 iterations shared among 10 VUs (maxDuration: 10m0s, gracefulStop: 30s)


running (00m08.9s), 00/10 VUs, 300 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  00m08.9s/10m0s  300/300 shared iters
INFO[0012] [k6-reporter v2.3.0] Generating HTML summary report  source=console
     ✗ is status
      ↳  40% — ✓ 120 / ✗ 180
     ✗ is response body has username
      ↳  40% — ✓ 120 / ✗ 180

     checks.........................: 40.00% ✓ 240       ✗ 360
     data_received..................: 305 kB 34 kB/s
     data_sent......................: 66 kB  7.5 kB/s
     http_req_blocked...............: avg=25.21ms  min=0s       med=0s       max=768.15ms p(90)=0s       p(95)=0s
     http_req_connecting............: avg=9.38ms   min=0s       med=0s       max=290.79ms p(90)=0s       p(95)=0s
     http_req_duration..............: avg=270.27ms min=223.96ms med=265.66ms max=424.42ms p(90)=324.98ms p(95)=336.47ms
       { expected_response:true }...: avg=256.34ms min=223.96ms med=258.43ms max=315.5ms  p(90)=281.61ms p(95)=284.27ms
     http_req_failed................: 60.00% ✓ 180       ✗ 120
     http_req_receiving.............: avg=148.07µs min=0s       med=0s       max=4.94ms   p(90)=543.4µs  p(95)=659.5µs
     http_req_sending...............: avg=18.16µs  min=0s       med=0s       max=523.2µs  p(90)=0s       p(95)=22.23µs
     http_req_tls_handshaking.......: avg=9.37ms   min=0s       med=0s       max=289.6ms  p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=270.11ms min=221.75ms med=265.66ms max=424.42ms p(90)=324.47ms p(95)=336.12ms
     http_reqs......................: 300    33.805613/s
     iteration_duration.............: avg=295.7ms  min=223.96ms med=268.36ms max=1.05s    p(90)=331.67ms p(95)=362.65ms
     iterations.....................: 300    33.805613/s
     vus............................: 10     min=10      max=10
     vus_max........................: 10     min=10      max=10



Create Dashboard and Data source in Grafana
Data Source








Grafana Results

Create Dashboard

















Add Panel


















Add Panel with Data Source  (Select Measurement)

















Select Measurement from drop down, it should contain all the fields that k6 produced in the results













































No comments:

Post a Comment