diff --git a/README.md b/README.md index cf90300..5b50631 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ This repo contains resources for: Consider running a workshop for k6. Below is an outline of what that workshop could look like, as well as modules you could use for each topic. Feel free to take these and include the parts most relevant to you! -We have also created built-in slides for you, which you are free to edit. Please fork this repo and make the edits accordingly how you want the workshop to be structure. +We have also created built-in slides for you, which you are free to edit. For a more custom slides, please fork this repo and make the edits accordingly how you want the workshop to be structured. -The slides are created using [reveal.js](https://revealjs.com/). +The slides are created using [reveal.js](https://revealjs.com/) and are all found in the [slides](./slides/) folder. ### Running the slides diff --git a/images/bert-relaxing.png b/images/bert-relaxing.png new file mode 100644 index 0000000..cd16198 Binary files /dev/null and b/images/bert-relaxing.png differ diff --git a/images/grafana-cloud-k6.png b/images/grafana-cloud-k6.png new file mode 100644 index 0000000..782e518 Binary files /dev/null and b/images/grafana-cloud-k6.png differ diff --git a/images/k6-end-of-summary.png b/images/k6-end-of-summary.png new file mode 100644 index 0000000..2d14852 Binary files /dev/null and b/images/k6-end-of-summary.png differ diff --git a/images/k6-order-of-preference-settings.png b/images/k6-order-of-preference-settings.png new file mode 100644 index 0000000..b97ee96 Binary files /dev/null and b/images/k6-order-of-preference-settings.png differ diff --git a/images/k6-output-options.png b/images/k6-output-options.png new file mode 100644 index 0000000..75467d5 Binary files /dev/null and b/images/k6-output-options.png differ diff --git a/slides/05-the-k6-cli/slides.md b/slides/05-the-k6-cli/slides.md index 61eede1..8d28b7a 100644 --- a/slides/05-the-k6-cli/slides.md +++ b/slides/05-the-k6-cli/slides.md @@ -143,11 +143,8 @@ k6 run test.js -e DOMAIN=test.k6.io k6 always [prioritizes settings](https://k6.io/docs/using-k6/k6-options/how-to/#order-of-precedence) in this order: -1. Command-line flags -1. Environment variables -1. Exported k6 script options -1. Config file -1. Defaults +![Priority of configurations and settings in k6](../../images/k6-order-of-preference-settings.png) + --- diff --git a/slides/06-understanding-k6-results/slides.md b/slides/06-understanding-k6-results/slides.md index e7b85cb..6c35c12 100644 --- a/slides/06-understanding-k6-results/slides.md +++ b/slides/06-understanding-k6-results/slides.md @@ -56,7 +56,9 @@ http_req_duration..............: avg=130.19ms min=130.19ms med=130.19ms max=130. --- -`http_req_duration` is the value for *all* requests, whether or not they passed. +### http_req_duration + +> 💡 `http_req_duration` is the value for *all* requests. The line below reports the response time for *only* the successful requests. @@ -104,4 +106,8 @@ The number of iterations describes how many times k6 looped through your script iterations.....................: 1 1.525116/s ``` ---- \ No newline at end of file +--- + +## Adding checks to your k6 script + +- Move to: [07-adding-checks-to-your-script](?p=07-adding-checks-to-your-script) \ No newline at end of file diff --git a/slides/07-adding-checks-to-your-script/slides.md b/slides/07-adding-checks-to-your-script/slides.md new file mode 100644 index 0000000..8f0e3f8 --- /dev/null +++ b/slides/07-adding-checks-to-your-script/slides.md @@ -0,0 +1,75 @@ +# k6 checks + +--- + +## Our script + +```js +import http from 'k6/http'; + +export default function() { + let url = 'https://httpbin.test.k6.io/post'; + let response = http.post(url, 'Hello world!'); + + console.log(response.json().data); +} +``` + +--- + +## Add checks to your script + +```js [1|3-6] +import { check } from 'k6'; + +check(response, { + 'Application says hello': (r) => r.body.includes('Hello world!') + }); +} +``` + +--- + +## Let's run our test again! + +Do you remember the command to run the test? 👀 + +```shell + ✓ Application says hello + + checks.........................: 100.00% ✓ 1 ✗ 0 +``` + +--- + +## Failed checks + +```js +check(response, { + 'Application says hello': (r) => r.body.includes('Bonjour!') + }); +} +``` + +--- + +## Let's run our test again! + +```shell + ✗ Application says hello + ↳ 0% — ✓ 0 / ✗ 1 + + checks.........................: 0.00% ✓ 0 ✗ 1 +``` + +--- + +## Failed checks are not errors! + +> 💡 To make failing checks stop your test, you can [combine them with thresholds](https://k6.io/docs/using-k6/thresholds/#failing-a-load-test-using-checks). + +--- + +## Making your scripts realistic with think time + +- Move to: [08-adding-think-time](?p=08-adding-think-time) \ No newline at end of file diff --git a/slides/08-adding-think-time/slides.md b/slides/08-adding-think-time/slides.md new file mode 100644 index 0000000..74b9d66 --- /dev/null +++ b/slides/08-adding-think-time/slides.md @@ -0,0 +1,84 @@ +# Think time + +--- + +## What is think time? + +The amount of time that a script pauses during test execution to simulate delays that real users have in the course of using an application. + +--- + +## When should you use think time? + +- If your test follows a user flow +- If you want to simulate actions that take some time to carry out +- Your load generator, or the machine you're running k6 from, displays high (> 80%) CPU utilization during test execution. + +--- + +## When should you **NOT** use think time? + +- You want to do a [stress test](https://k6.io/docs/test-types/stress-testing/) +- The API endpoint you're testing experiences a high amount of requests per second in production that occur without delays +- Your load generator can run your test script without crossing the 80% CPU utilization mark. + +--- + +## k6 Sleep + +```js [2|11] +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export default function() { + let url = 'https://httpbin.test.k6.io/post'; + let response = http.post(url, 'Hello world!'); + check(response, { + 'Application says hello': (r) => r.body.includes('Hello world!') + }); + + sleep(1); +} +``` + +--- + +**Sleep does not affect the response time (`http_req_duration`); the response time is always reported with sleep removed. Sleep is, however, included in the `iteration_duration`.** + +--- + +## Dynamic think time + +A dynamic think time is more realistic, and simulates real users more accurately. + +--- + +### Random sleep + +One way to implement dynamic think time is to use the JavaScript `Math.random()` function: + +```js +sleep(Math.random() * 5); +``` + +--- + +### Random sleep between + +```js [1|3] +import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.0.0/index.js"; + +sleep(randomIntBetween(1,5)); +``` + +--- + +## How much think time should you add? + +The real answer is: it depends. + +--- + +## Let's move to scaling up your test + +- Move to: [09-load-test-options](?p=09-load-test-options) \ No newline at end of file diff --git a/slides/09-load-test-options/slides.md b/slides/09-load-test-options/slides.md new file mode 100644 index 0000000..5242d77 --- /dev/null +++ b/slides/09-load-test-options/slides.md @@ -0,0 +1,130 @@ +# k6 Load Test Options + +--- + +## Script options + +```js +export let options = { + vus: 10, + iterations: 40, +}; +``` + +--- + +> 💡 If you only define VUs and no other test options, you may get the following error: + +```shell + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + +WARN[0000] the `vus=10` option will be ignored, it only works in conjunction with `iterations`, `duration`, or `stages` + execution: local + script: test.js + output: - +``` + +--- + +## Iterations + +```js + vus: 10, + iterations: 40, +``` + +> Setting the number of iterations in test options defines it for **all** users. + +--- + +## Duration + +```js + vus: 10, + duration: '2m' +``` + +> Setting the duration instructs k6 to repeat the script for each of the VUs until the duration is reached. + +--- + +## Iterations and durations + +```js + vus: 10, + duration: '5m', + iterations: 40, +``` + +> If you set the duration in conjunction with setting the number of iterations, the value that ends earlier is used. + +--- + +## Stages + +Defining iterations and duration creates a _simple load profile_ + +![A simple load profile](../../images/load_profile-no_ramp-up_or_ramp-down.png) + + +--- + +## Constand load profile + +What if you want to add a ramp-up or ramp-down, so that the profile looks more like this? + +![Constant load profile, with ramps](../../images/load_profile-constant.png.png) + + +--- + +In that case, you may want to use [stages](https://k6.io/docs/using-k6/options/#stages). + +```js +export let options = { + stages: [ + { duration: '30m', target: 100 }, + { duration: '1h', target: 100 }, + { duration: '5m', target: 0 }, + ], +}; +``` + +--- + +## The full script so far + +If you're using stages, here's what your script should look like so far: + +```js +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export let options = { + stages: [ + { duration: '30m', target: 100 }, + { duration: '1h', target: 100 }, + { duration: '5m', target: 0 }, + ], +}; + +export default function() { + let url = 'https://httpbin.test.k6.io/post'; + let response = http.post(url, 'Hello world!'); + check(response, { + 'Application says hello': (r) => r.body.includes('Hello world!') + }); + + sleep(Math.random() * 5); +} +``` + +--- + +## Let's set some thresholds + +- Move to [10-setting-test-criteria-with-thresholds](?p=10-setting-test-criteria-with-thresholds) \ No newline at end of file diff --git a/slides/10-setting-test-criteria-with-thresholds/slides.md b/slides/10-setting-test-criteria-with-thresholds/slides.md new file mode 100644 index 0000000..bb3ea9c --- /dev/null +++ b/slides/10-setting-test-criteria-with-thresholds/slides.md @@ -0,0 +1,129 @@ +# k6 Thresholds + +--- + +## Add thresholds to your script + +```js [7-10] +export let options = { + stages: [ + { duration: '30m', target: 100 }, + { duration: '1h', target: 100 }, + { duration: '5m', target: 0 }, + ], + thresholds: { + http_req_failed: ['rate<=0.05'], + http_req_duration: ['p(95)<=5000'], + }, +}; +``` + +--- + +> 💡 Thresholds are **always** based on metrics. + +--- + +## Types of thresholds + +- Error rate +- Response time +- Checks + +> 💡 Recommended: Use error rate, response time, and checks thresholds in your tests where possible. + +--- + +### Error rate + +```js +thresholds: { + http_req_failed: ['rate<=0.05'], +}, +``` + +--- + +### Response time + +```js +thresholds: { + http_req_duration: ['p(95)<=5000'], +}, +``` + +> 💡 Recommended: Start with the 95th percentile response time. + +--- + +#### Using multiple response time thresholds + +```js +thresholds: { + http_req_duration: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000'], +}, +``` + +--- + +### Checks + +```js +thresholds: { + checks: ['rate>=0.9'], +}, +``` + +--- + +## Aborting test on fail + +```js +thresholds: { + http_req_failed: [{ + threshold: 'rate<=0.05', + abortOnFail: true, + }], +}, +``` + +--- + +## The full script + +```js +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export let options = { + stages: [ + { duration: '30m', target: 100 }, + { duration: '1h', target: 100 }, + { duration: '5m', target: 0 }, + ], + thresholds: { + http_req_failed: [{ + threshold: 'rate<=0.05', + abortOnFail: true, + }], + http_req_duration: ['p(95)<=100'], + checks: ['rate>=0.99'], + }, +}; + +export default function() { + let url = 'https://httpbin.test.k6.io/post'; + let response = http.post(url, 'Hello world!'); + check(response, { + 'Application says hello': (r) => r.body.includes('Hello world!') + }); + + sleep(Math.random() * 5); +} +``` + +--- + +## Output k6 results in different ways + +- Move to [11-k6-results-output-options](?p=11-k6-results-output-options) \ No newline at end of file diff --git a/slides/11-k6-results-output-options/slides.md b/slides/11-k6-results-output-options/slides.md new file mode 100644 index 0000000..53b1180 --- /dev/null +++ b/slides/11-k6-results-output-options/slides.md @@ -0,0 +1,74 @@ +# k6 results + +--- + +## What we have done so far + +![k6 end of summary report](../../images/k6-end-of-summary.png) + +--- + +## The output option + +![k6 output options](../../images/k6-output-options.png) + +--- + +### Saving k6 results as a CSV + +```shell +k6 run test.js -o csv=results.csv +``` + +> 💡 You can also use `--out` instead of `-o`. + +--- + +### CSV results output format + +```csv +metric_name,timestamp,metric_value,check,error,error_code,expected_response,group,method,name,proto,scenario,service,status,subproto,tls_version,url,extra_tags +http_reqs,1641298536,1.000000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_duration,1641298536,114.365000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_blocked,1641298536,589.667000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_connecting,1641298536,117.517000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_tls_handshaking,1641298536,415.043000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_sending,1641298536,0.251000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_waiting,1641298536,114.010000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_receiving,1641298536,0.104000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +http_req_failed,1641298536,0.000000,,,,true,,POST,https://httpbin.test.k6.io/post,HTTP/1.1,default,,200,,tls1.2,https://httpbin.test.k6.io/post, +checks,1641298536,1.000000,Application says hello,,,,,,,,default,,,,,, +vus,1641298536,2.000000,,,,,,,,,,,,,,, +vus_max,1641298536,100.000000,,,,,,,,,,,,,,, +``` + +--- + +### Saving k6 results as a JSON + +```shell +k6 run test.js -o json=results.json +``` + +### JSON results output format + +```JSON +{"type":"Metric","data":{"name":"http_reqs","type":"counter","contains":"default","tainted":null,"thresholds":[],"submetrics":null,"sub":{"name":"","parent":"","suffix":"","tags":null}},"metric":"http_reqs"} +{"type":"Point","data":{"time":"2022-01-05T12:46:23.603474+01:00","value":1,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://httpbin.test.k6.io/post","proto":"HTTP/1.1","scenario":"default","status":"200","tls_version":"tls1.2","url":"https://httpbin.test.k6.io/post"}},"metric":"http_reqs"} +{"type":"Metric","data":{"name":"http_req_duration","type":"trend","contains":"time","tainted":null,"thresholds":["p(95)<=100"],"submetrics":null,"sub":{"name":"","parent":"","suffix":"","tags":null}},"metric":"http_req_duration"} +{"type":"Point","data":{"time":"2022-01-05T12:46:23.603474+01:00","value":118.96,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://httpbin.test.k6.io/post","proto":"HTTP/1.1","scenario":"default","status":"200","tls_version":"tls1.2","url":"https://httpbin.test.k6.io/post"}},"metric":"http_req_duration"} +{"type":"Metric","data":{"name":"http_req_blocked","type":"trend","contains":"time","tainted":null,"thresholds":[],"submetrics":null,"sub":{"name":"","parent":"","suffix":"","tags":null}},"metric":"http_req_blocked"} +``` + +--- + +## Grafana Cloud k6 + +![Grafana Cloud k6](../../images/grafana-cloud-k6.png) + + +--- + +## Let's wrap this up! + +- Move to [end](?p=end) diff --git a/slides/end/slides.md b/slides/end/slides.md new file mode 100644 index 0000000..368d21f --- /dev/null +++ b/slides/end/slides.md @@ -0,0 +1,16 @@ +# The End 🎉 + +--- + +## Further learning + +- [k6.io](https://k6.io/) +- [k6.io/docs](https://k6.io/docs/) +- [youtube.com/k6test](https://www.youtube.com/k6test) +- [github.com/grafana/quickpizza](https://github.com/grafana/quickpizza) + +--- + +## Thank you! + +![Bert relaxing](../../images/bert-relaxing.png) \ No newline at end of file diff --git a/slides/intro/slides.md b/slides/intro/slides.md index b1ffcd0..bd081d0 100644 --- a/slides/intro/slides.md +++ b/slides/intro/slides.md @@ -13,24 +13,23 @@ Performance testing and k6! 💜 ## Structure (Beginner) I. Performance Testing Principles - - Introduction to performance testing - - Frontend vs backend performance testing - - Load testing - - High level overview of the load testing process + - [Introduction to performance testing](?p=01-introduction-to-performance-testing) + - [Frontend vs backend performance testing](?p=02-frontend-vs-backend-performance-testing) + - [Load testing and overview of the load testing process](?p=03-load-testing) --- ## Structure (Beginner) II. k6 Foundations - - Getting started with k6 OSS - - The k6 CLI - - Understanding k6 results - - Adding checks to your script - - Adding think time using sleep - - k6 load test options - - Setting test criteria with thresholds - - k6 results output options + - [Getting started with k6 OSS](?p=04-getting-started-with-k6-oss) + - [The k6 CLI](?p=05-the-k6-cli) + - [Understanding k6 results](?p=06-understanding-k6-results) + - [Adding checks to your script](?p=07-adding-checks-to-your-script) + - [Adding think time using sleep](?p=08-adding-think-time) + - [k6 load test options](?p=09-load-test-options) + - [Setting test criteria with thresholds](?p=10-setting-test-criteria-with-thresholds) + - [k6 results output options](?p=11-k6-results-output-options) ---