├── README.md ├── config ├── grafana │ ├── dashboards │ │ ├── dashboards.yaml │ │ └── definitions │ │ │ └── 19665_rev2.json │ └── datasources │ │ └── datasource.yaml └── prometheus.yaml ├── docker-compose.yaml ├── media ├── k6-prometheus-dashboard.png ├── menu-icon.svg ├── quickpizza-websocket-ui.png ├── vus.png └── web-dashboard-overview.png └── test.js /README.md: -------------------------------------------------------------------------------- 1 | # k6 OSS Workshop 2 | 3 | A 2-3 hour k6 workshop with practical k6 examples using the QuickPizza demo app. 4 | 5 | This workshop is intented to be used as a starting point for anyone who wants to learn or teach k6 to others. 6 | 7 | You are encouraged to copy and modify the workshop for your particular case or audience. If you need some slides, check out the [k6 starter deck](https://docs.google.com/presentation/d/1gviRg7RTzT0Y2_5WPBADyn5xpa96PIqWivGAThNW6pM/edit?usp=sharing). 8 | 9 | **Table of contents** 10 | 11 | You can view the table of contents clicking the menu icon ![](./media/menu-icon.svg) at the top left of this readme page. 12 | 13 | - [Before we start](#before-we-start) 14 | * [Requirements](#requirements) 15 | * [Run the playground](#run-the-playground) 16 | - [Fundamentals](#fundamentals) 17 | * [What is load (performance) testing](#what-is-load-performance-testing) 18 | * [Run your first k6 test](#run-your-first-k6-test) 19 | * [Model the test load (workload)](#model-the-test-load-workload) 20 | * [`vus` and `iterations`](#vus-and-iterations) 21 | * [`vus` and `duration`](#vus-and-duration) 22 | * [`stages`](#stages) 23 | * [`scenarios`](#scenarios) 24 | * [Think time](#think-time) 25 | - [Define Pass/Fail criteria](#define-passfail-criteria) 26 | * [Checks](#checks) 27 | * [Thresholds](#thresholds) 28 | - [Performance test results](#performance-test-results) 29 | * [Custom summary](#custom-summary) 30 | * [Web dashboard](#web-dashboard) 31 | * [Visualize results with Prometheus and Grafana](#visualize-results-with-prometheus-and-grafana) 32 | - [More examples](#more-examples) 33 | * [CLI overrides and environment variables](#cli-overrides-and-environment-variables) 34 | * [Test lifecycle hooks](#test-lifecycle-hooks) 35 | * [Test data parameterization](#test-data-parameterization) 36 | * [Custom metrics](#custom-metrics) 37 | * [WebSockets](#websockets) 38 | * [Browser testing](#browser-testing) 39 | * [Additional resources](#additional-resources) 40 | 41 | ## Before we start 42 | 43 | ### Requirements 44 | 45 | - Docker & Docker Compose 46 | - git 47 | - k6 48 | - (Recommended) [Install the k6 binary for your OS](https://grafana.com/docs/k6/latest/get-started/installation/). You can easily remove the k6 binary afterward if you don't want it. 49 | - Alternatively, you can also run k6 inside Docker. 50 | - However, the experience using the k6 binary is better as it requires fewer commands and the results in the terminal are nicer. 51 | - If you plan to run k6 with Docker, pre-pull the k6 images: 52 | - `docker pull grafana/k6` 53 | - `docker pull grafana/k6:latest-with-browser` 54 | 55 | 56 | ### Run the playground 57 | 58 | First of all, clone the repository: 59 | 60 | ```bash 61 | git clone https://github.com/grafana/k6-oss-workshop.git 62 | ``` 63 | 64 | Then, go to the project directory and run the playground environment with Docker compose: 65 | 66 | ```bash 67 | cd k6-oss-workshop && docker compose up -d 68 | ``` 69 | 70 | 71 | To verify everything is working, go to http://localhost:3333 and click the big button to get a Pizza recommendation! 72 | 73 | And, open http://localhost:3000 and verify that you can see a Grafana instance. 74 | 75 | 76 | ## Fundamentals 77 | 78 | ### What is load (performance) testing 79 | 80 | Performance testing is known as the testing practice that verifies how our system behaves and performs, often regarding speed and reliability. Performance testing and load testing often refer to the same practice. 81 | 82 | Before writing our first test, let's briefly cover the load testing fundamentals: 83 | 84 | - What is load testing 85 | - Types of load tests 86 | - Usual goals 87 | 88 | ### Run your first k6 test 89 | 90 | To run your first test, we'll use the [`test.js`](./test.js) file in this repo: 91 | 92 | ```js 93 | import http from "k6/http"; 94 | 95 | const BASE_URL = __ENV.BASE_URL || "http://localhost:3333"; 96 | 97 | export default function () { 98 | let restrictions = { 99 | maxCaloriesPerSlice: 500, 100 | mustBeVegetarian: false, 101 | excludedIngredients: ["pepperoni"], 102 | excludedTools: ["knife"], 103 | maxNumberOfToppings: 6, 104 | minNumberOfToppings: 2, 105 | }; 106 | let res = http.post(`${BASE_URL}/api/pizza`, JSON.stringify(restrictions), { 107 | headers: { 108 | "Content-Type": "application/json", 109 | "X-User-ID": 23423, 110 | }, 111 | }); 112 | console.log(`${res.json().pizza.name} (${res.json().pizza.ingredients.length} ingredients)`); 113 | } 114 | ``` 115 | 116 | Then, run it with: 117 | 118 | ```bash 119 | k6 run test.js 120 | ``` 121 | 122 |
123 | Docker run 124 | 125 | ```bash 126 | docker run -i --network=default_network grafana/k6:latest run -e BASE_URL=http://quickpizza:3333 - 130 | 131 |
132 | 133 | That's it! You have successfully run your first test. 134 | 135 | Now, you have a lot of things in the output. Let's go through them. 136 | k6's output has three main sections: 137 | 1. Information about the test and its configuration 138 | 2. Runtime information (e.g., logs, debug) 139 | 3. Summary of the test result 140 | 141 | Knowing k6's output is essential, as you will be using it a lot. 142 | In the output's second section, you should see a log line with the pizza name and the number of ingredients. This happens once because k6 has run your default function once. 143 | 144 | ```bash 145 | INFO[0000] A Pink Americana (7 ingredients) source=console 146 | ``` 147 | 148 | You can also see in the third section lots of metrics that k6 has generated and aggregated for you. These metrics are helpful to understand how the test went (e.g., the number of requests, errors, response time, etc.), also known as built-in k6 metrics. 149 | 150 | ```bash 151 | data_received..................: 745 B 2.9 kB/s 152 | data_sent......................: 323 B 1.3 kB/s 153 | http_req_blocked...............: avg=5.21ms min=5.21ms med=5.21ms max=5.21ms p(90)=5.21ms p(95)=5.21ms 154 | http_req_connecting............: avg=1.52ms min=1.52ms med=1.52ms max=1.52ms p(90)=1.52ms p(95)=1.52ms 155 | http_req_duration..............: avg=238.21ms min=238.21ms med=238.21ms max=238.21ms p(90)=238.21ms p(95)=238.21ms 156 | { expected_response:true }...: avg=238.21ms min=238.21ms med=238.21ms max=238.21ms p(90)=238.21ms p(95)=238.21ms 157 | http_req_failed................: 0.00% ✓ 0 ✗ 1 158 | http_req_receiving.............: avg=5.86ms min=5.86ms med=5.86ms max=5.86ms p(90)=5.86ms p(95)=5.86ms 159 | http_req_sending...............: avg=3.74ms min=3.74ms med=3.74ms max=3.74ms p(90)=3.74ms p(95)=3.74ms 160 | http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s 161 | http_req_waiting...............: avg=228.6ms min=228.6ms med=228.6ms max=228.6ms p(90)=228.6ms p(95)=228.6ms 162 | http_reqs......................: 1 3.90657/s 163 | iteration_duration.............: avg=250.42ms min=250.42ms med=250.42ms max=250.42ms p(90)=250.42ms p(95)=250.42ms 164 | iterations.....................: 1 3.90657/s 165 | ``` 166 | 167 | Some of the most important metrics are: 168 | - `http_reqs`, to measure the number of requests (request rate). 169 | - `http_req_failed`, to measure the error rate (errors). 170 | - `http_req_duration`, to measure response times (latency). 171 | - `vus`, to measure the number of virtual users (traffic). 172 | 173 | You can learn more about these metrics in the [built-in k6 metrics documentation](https://grafana.com/docs/k6/latest/using-k6/metrics/reference/), and find other metrics such as gRPC, WebSocket, or Browser metrics. These metrics will be reported when using their respective [k6 APIs](https://grafana.com/docs/k6/latest/javascript-api/) in our test. 174 | 175 | ### Model the test load (workload) 176 | 177 | Two concepts are key to understanding how to model the load in k6: Virtual Users (VUs) and iterations. 178 | 179 | 1. An iteration is a single execution of your script. In the example above, it's a single run of your default function. 180 | 181 | 2. A virtual user is a thread that runs your script for a specific period or number of iterations. 182 | 183 | You can think of it like a for-loop. You can have many VUs; each one will be continuously running your script function. 184 | 185 | ![How k6 schedules VUs](./media/vus.png) 186 | 187 | Basically, k6 acts as a scheduler of VUs and iterations based on the test load configuration. 188 | 189 | When you run a k6 test, by default, it will run your script once, with a single VU. This is useful for debugging your script. However, it's not very useful for understanding how your system behaves under load for some time. To set the test load, k6 provides multiple options: 190 | 191 | ### `vus` and `iterations` 192 | 193 | [`vus`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#vus) and [`iterations`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#iterations) - Specifying the number of virtual users and total number of executions of the default function. 194 | 195 | Let's try it out! After the imports, add a configuration block to your script: 196 | 197 | ```js 198 | export const options = { 199 | iterations: 30, // the total number of executions of the default function 200 | vus: 10, // 10 virtual users 201 | }; 202 | ``` 203 | 204 | Run the test again. 205 | 206 | k6 schedules 10 virtual users and runs 30 iterations in total (between all VUs). After the test finishes, find these results in the `iterations` and `vus` metric output. 207 | 208 | ### `vus` and `duration` 209 | 210 | [`vus`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#vus) and [`duration`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#duration) - Specifying the number of virtual users and the total test duration. 211 | 212 | ```js 213 | export const options = { 214 | vus: 10, // virtual users 215 | duration: '10s', // total test duration 216 | }; 217 | ``` 218 | 219 | Replace the options settings in the previous script and run the test again. 220 | 221 | k6 schedules 10 virtual users that execute the default function continuously for 10 seconds. 222 | 223 | 224 | ### `stages` 225 | 226 | [`stages`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#stages) - Specifying a list of periods that must reach a specific VU target. 227 | 228 | ```js 229 | export const options = { 230 | stages: [ 231 | // ramp up from 0 to 20 VUs over the next 5 seconds 232 | { duration: '5s', target: 20 }, 233 | // run 20 VUs over the next 10 seconds 234 | { duration: '10s', target: 20 }, 235 | // ramp down from 20 to 0 VUs over the next 5 seconds 236 | { duration: '5s', target: 0 }, 237 | ], 238 | }; 239 | ``` 240 | 241 | Replace the options settings in the previous script and run the test again. 242 | What's k6 doing? 243 | 1. During the first 5 seconds, k6 will ramp up from 0 to 20 VUs. 244 | 2. Then, it will stay at 20 VUs for 10 seconds. 245 | 3. Finally, it will ramp down from 20 to 0 VUs in 5 seconds. 246 | 247 |
248 | Docker run 249 | 250 | > The output will be messy if you use Docker to run this test. You can fix that by adding the [--quiet flag](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#quiet) to the k6 command. Why? B/c while using the CLI, you would get a very nice progress bar, telling you how many VUs are running and how your test behaves in real-time. However, when using Docker, k6 will output the progress bar in the logs. 251 | 252 |
253 | 254 | 255 | ### `scenarios` 256 | 257 | This workshop won't cover all the different scenario options, so please refer to the [Scenario documentation for further details](https://grafana.com/docs/k6/latest/using-k6/scenarios/). 258 | 259 | The Scenarios API allows modeling the workload for different use cases and execute multiple functions, each with distinct workloads. 260 | 261 | In this example, we’ll use scenarios to demo how to set the workload in terms of requests per second, specifically, 20 requests per second for 10 seconds. 262 | 263 | ```js 264 | export const options = { 265 | scenarios: { 266 | default: { 267 | executor: 'constant-arrival-rate', 268 | 269 | // How long the test lasts 270 | duration: '10s', 271 | 272 | // Iterations rate. By default, the `timeUnit` rate is 1s. 273 | rate: 20, 274 | 275 | 276 | // Required. The value can be the same as the rate. 277 | // k6 warns during execution if more VUs are needed. 278 | preAllocatedVUs: 20, 279 | }, 280 | }, 281 | }; 282 | ``` 283 | 284 | Replace the options settings in the previous script. 285 | 286 | This setting uses the [`constant-arrival-rate` scenario](https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/constant-arrival-rate/) to schedule a rate of iterations, telling k6 to schedule the execution of **20 iterations** (`rate`: 20) **per second** (the default `timeUnit`). 287 | 288 | 289 | Given that each iteration executes only one Pizza request, the test will run 20 requests per second. Mathematically speaking: 290 | - 20 iterations per second x 1 request per iteration = 20 requests per second 291 | - 20 requests per second x 20 second = 200 requests 292 | 293 | Run the test. After completion, you can see the test request rate by looking at the `http_reqs` metric, which reports the number of http requests and the request rate. In our example, it is close to our goal of 20 requests per second. 294 | 295 | ```bash 296 | http_reqs......................: 201 19.680544/s 297 | ``` 298 | 299 | ### Think time 300 | 301 | We are hammering the service a bit too much as there is no wait/think time between iterations. Let's add sleep to be a bit more gentle. 302 | 303 | Add this import at the top of the file: 304 | 305 | ```js 306 | import { sleep } from 'k6'; 307 | ``` 308 | 309 | Replace the scenario workload with `stages` or `vus/duration`. 310 | 311 | ```js 312 | export const options = { 313 | vus: 10, 314 | duration: '10s', 315 | }; 316 | ``` 317 | 318 | Finally, add this line at the end of the default function to pause the VU execution for 1 second: 319 | 320 | ```js 321 | sleep(1); 322 | ``` 323 | 324 | Then, rerun the script. 325 | 326 | Now, we are getting a pizza recommendation every second. That is more realistic! 327 | 328 | 329 | You can use think time ([`sleep`](https://grafana.com/docs/k6/latest/javascript-api/k6/sleep/)) to accurately simulate end users' behavior, making a load testing script more realistic. You should consider adding think time if: 330 | - Your test follows a user flow, like accessing different parts of the application in a certain order. 331 | - You want to simulate actions that take some time to carry out, like reading text on a page or filling out a form. 332 | 333 | Note that using think time ([`sleep`](https://grafana.com/docs/k6/latest/javascript-api/k6/sleep/)) reduces how quickly requests are sent. Think time is unnecessary when: 334 | - You want to do a stress test to find out how many requests per second your application can handle. 335 | - The API endpoint you're testing experiences a high amount of requests per second in production that occur without delays. 336 | 337 | ## Define Pass/Fail criteria 338 | 339 | Another problem with our script is that we don't validate whether the service is working as expected. We are just blindly sending requests and hoping for the best. Then, we should include assertions in our test to verify the behavior or performance of the responses. 340 | 341 | In testing, an assertion typically refers to verifying a particular condition in the test: Is this true or false? For most testing tools, if an assertion evaluates as false, the test fails. 342 | 343 | k6 works slightly differently in this regard, having two different APIs for defining assertions: 344 | - **Thresholds**: used to establish the Pass/Fail criteria of the test. 345 | - **Checks**: used to create assertions and inform about their status without affecting the Pass/Fail outcome. 346 | 347 | ### Checks 348 | 349 | Checks are like assertions—they verify whether a condition is met. Checks inform about the assertion result; however, they do not affect the Pass/Fail test result. 350 | 351 | But why does a check failure not fail the test? It’s a design choice. Production systems typically don’t aim for 100% reliability; instead, we define error budgets and 'nines of availability,' – our systems often accept a certain percentage of errors. 352 | 353 | Let's see them in action by adding a check to validate that the status of the HTTP response is 200. 354 | 355 | First, we need to import the check function: 356 | 357 | ```js 358 | import { check } from "k6"; 359 | ``` 360 | 361 | Then, we can add a check to our script. You just need to add the following lines after the request: 362 | 363 | ```js 364 | check(res, { 365 | "is status 200": (r) => r.status === 200, 366 | }); 367 | ``` 368 | 369 | Then, rerun the script. 370 | 371 | Now, you should see a new section in the output with the results of the checks! You should see that all the checks have passed. That's good! 372 | 373 | If you want to see what happens when a check fails, you can change the check to: 374 | 375 | ```js 376 | check(res, { 377 | "is status 200": (r) => r.status === 500, 378 | }); 379 | ``` 380 | 381 | For more information, refer to the [Checks documentation](https://grafana.com/docs/k6/latest/using-k6/checks/). 382 | 383 | 384 | ### Thresholds 385 | 386 | Thresholds are the pass/fail criteria you define for your test metrics. If the system under test (SUT) does not meet the conditions of your threshold, the test finishes with a failed status. That means that k6 will exit with a non-zero exit code. You can leverage standard metrics that k6 generates or custom metrics that you define in your script. 387 | 388 | 389 | Let's add a threshold to our script. You can do that by changing the options block to: 390 | 391 | ```js 392 | export let options = { 393 | stages: [ 394 | { duration: "5s", target: 10 }, 395 | { duration: "10s", target: 10 }, 396 | { duration: "5s", target: 0 }, 397 | ], 398 | thresholds: { 399 | "http_req_duration": ["p(95)<5000"], 400 | }, 401 | }; 402 | ``` 403 | 404 | Then, rerun the script. 405 | 406 | When the test ends, k6 reports the threshold status with a green checkmark ✅ or a red cross ❌ near the metric name. 407 | 408 | This threshold is saying that 95% of the requests should be faster than 5 seconds ✅. If that's not the case, the threshold fails ❌. 409 | 410 | To see what happens when a threshold fails, you can change the threshold to: 411 | 412 | ```js 413 | export let options = { 414 | stages: [ 415 | { duration: "5s", target: 10 }, 416 | { duration: "10s", target: 10 }, 417 | { duration: "5s", target: 0 }, 418 | ], 419 | thresholds: { 420 | "http_req_duration": ["p(95)<10"], 421 | }, 422 | }; 423 | ``` 424 | 425 | You can also inspect the status code of the test with: 426 | 427 | ```bash 428 | echo $? 429 | ``` 430 | 431 |
432 | Docker run 433 | 434 | ```bash 435 | docker run -i --network=default_network grafana/k6 run -e BASE_URL=http://quickpizza:3333 - 439 | 440 |
441 | 442 | Then, rerun the script. 443 | 444 | There is also the option to abort the test if a threshold fails using this syntax: 445 | 446 | ```js 447 | thresholds: { 448 | "http_req_duration": [ 449 | { threshold: "p(95)<10", abortOnFail: true }, 450 | ], 451 | }, 452 | ``` 453 | 454 | This was a simple Threshold example. The Thresholds API allows for more advanced cases like: 455 | - Defining multiple thresholds. 456 | - Implementing thresholds on custom metrics. 457 | - Setting multiple thresholds for the same metric. 458 | - Querying tags in metrics. 459 | 460 | For more information, refer to the [Thresholds documentation](https://grafana.com/docs/k6/latest/using-k6/thresholds/). 461 | 462 | ## Performance test results 463 | 464 | ### Custom summary 465 | 466 | You have already seen the end-of-test summary in your terminal multiple times. You can customize it! 467 | 468 | To customize it, you need to implement the `handleSummary` function. 469 | 470 | After your test runs, k6 aggregates your metrics into a JavaScript object. The `handleSummary()` function takes this object as an argument (called `data` in the following example). 471 | 472 | Now, let's define a `handleSummary()` that creates a JSON file with the default summary object by adding this snippet at the end of your script: 473 | 474 | ```js 475 | export function handleSummary(data) { 476 | return { 477 | 'summary.json': JSON.stringify(data), // the default data object 478 | }; 479 | } 480 | ``` 481 | 482 | If you run the test again, you should see a new file named `summary.json` with all the data the summary would have. 483 | 484 |
485 | Docker run 486 | 487 | ```bash 488 | docker run -i --network=default_network grafana/k6:latest run -e BASE_URL=http://quickpizza:3333 - :/home/k6/summary.json . 495 | 496 | # In MacBook M1/M2, make sure Docker has rosetta installed and virtualization enabled 497 | ``` 498 |
499 | 500 |
501 | 502 | Fundamentally, `handleSummary()` is just a function that can access a data object. As such, you can transform the summary data into any text format: JSON, HTML, console, XML, and so on. You can pipe your custom summary to standard output or standard error, write it to a file, or share it with any remote service. 503 | 504 | For more information, refer to the [Custom summary documentation](https://grafana.com/docs/k6/latest/results-output/end-of-test/custom-summary/). 505 | 506 | ### Web dashboard 507 | 508 | The test result summary is useful; however aggregated results hide a lot of information as they don’t show how the metrics change during the test execution. 509 | 510 | It is more useful to visualize time-series graphs to understand what happened at different stages of the test. To solve this, k6 has different `Outputs`. These let you export your metrics/logs to some other place in real-time. 511 | 512 | Note that you should set a test duration to at least 30 seconds to receive enough data to visualize a graph. Then, enable the web dashboard by setting the `K6_WEB_DASHBOARD` environment variable to `true` when running your test script as follows: 513 | 514 | ```bash 515 | K6_WEB_DASHBOARD=true k6 run test.js 516 | ``` 517 | 518 |
519 | Docker run 520 | 521 | ```bash 522 | docker run -p 5665:5665 -i --network=default_network grafana/k6:latest run -e BASE_URL=http://quickpizza:3333 -e K6_WEB_DASHBOARD=true - 526 | 527 |
528 | 529 | By default, the web dashboard is available at http://127.0.0.1:5665. 530 | 531 | ![Web dashboard](./media/web-dashboard-overview.png) 532 | 533 | To learn about the distinct dashboard options, refer to the [Web dashboard documentation](https://grafana.com/docs/k6/latest/results-output/web-dashboard/). 534 | 535 | 536 | ### Visualize results with Prometheus and Grafana 537 | 538 | The web dashboard is nice, but it does not store your test results. k6 has [many outputs](https://grafana.com/docs/k6/latest/results-output/real-time/) to store your test results. You may want to store test results to visualize them at another moment or compare them with the results of other tests. 539 | 540 | One of the most popular outputs is the Prometheus output. This output will export your metrics to a Prometheus instance, so you can use Grafana to visualize the metrics in real time. 541 | The Docker Compose playground already has a Prometheus and Grafana instance running. So, let's use that! 542 | You just need to change how you invoke k6, and add the Prometheus output: 543 | 544 | ```sh 545 | k6 run --out=experimental-prometheus-rw test.js 546 | ``` 547 | 548 |
549 | Docker run 550 | 551 | ```bash 552 | docker run -i --network=default_network grafana/k6:latest run --out=experimental-prometheus-rw -e BASE_URL=http://quickpizza:3333 -e K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write - 556 | 557 |
558 | 559 | Then, run the script again. After that, open http://localhost:3000. 560 | 561 | Then, go to dashboards and click on the k6 dashboard ([this URL should get you there](http://localhost:3000/d/ccbb2351-2ae2-462f-ae0e-f2c893ad1028/k6-prometheus)). 562 | 563 | ![k6 OSS Grafana dashboard](./media/k6-prometheus-dashboard.png) 564 | 565 | > Why is this feature experimental? Only because it has been added quite recently to k6. But it is already stable and used by lots of people. 566 | 567 | 568 | Optionally, you can run the test and set the `testid` tag to identify the metrics of specific test runs. `testid` can be any unique string to identify the test run to query the results. 569 | 570 | ```bash 571 | k6 run --out=experimental-prometheus-rw --tag testid=1 test.js 572 | ``` 573 | 574 | Then, you can filter specific test results in PromQL queries. The previous k6 Grafana dashboard includes a `Test ID` drop-down menu at the top to filter test results by `testid`. 575 | 576 | ## More examples 577 | 578 | In case you have time (now or later), here are some common cases that you can learn and try out. Pick the example(s) you are most interested in. 579 | 580 | ### CLI overrides and environment variables 581 | 582 | Most things you can configure in the options block can also be overridden via the CLI. If you are curious, check out the [full list of k6 options](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/). 583 | 584 | 585 | For example, you can override the number of VUs with the `--vus` flag and the duration with the `--duration` flag, or run 1 iteration to quickly debug your script. 586 | 587 | 588 | ```bash 589 | k6 run test.js -i 1 590 | ``` 591 | 592 | Alternatively, you can control the test configuration via environment variables. k6 uses the option from the highest order of preference, as shown on the following diagram: 593 | 594 | ```mermaid 595 | flowchart LR 596 | DefaultOption --> 597 | ScriptOption --> 598 | EnvironmentVariable --> 599 | CLIFlag 600 | ``` 601 | 602 | 603 | 604 | 605 | Let's see how options are overridden. 606 | 607 | First, replace the options with some small values like: 608 | 609 | ```js 610 | export const options = { 611 | vus: 2, 612 | duration: '30s' 613 | }; 614 | ``` 615 | 616 | Run the test and check the `vus` and `duration` value in the terminal output. 617 | 618 | Then, try to: 619 | 620 | 1. Overwrite the `vus` or `duration` options with their respective environment variables. 621 | 2. Overwrite the `vus` or `duration` options with their CLI flags. 622 | 3. You can do the same with any other k6 option. 623 | 624 | 625 | 626 | 627 | Also, you can use environment variables to pass secrets to your script (e.g., API keys). 628 | 629 | You can pass environment variables to k6 in multiple ways: 630 | 631 | ```bash 632 | k6 run -e MY_ENV_VAR=123 example.js 633 | # or 634 | MY_ENV_VAR=123 k6 run example.js 635 | ``` 636 | 637 | Then, you can use it in your script with the `__ENV` object: 638 | 639 | ```js 640 | console.log(__ENV.MY_ENV_VAR); 641 | ``` 642 | 643 | If you noticed, we have been using the BASE_URL environment variable to pass the URL of the QuickPizza service to our script. That's why we can run the same script locally and in Docker without changing anything! 644 | 645 | 646 | For further information, you can learn about how options work and environment variables in the documentation. 647 | 648 | 649 | ### Test lifecycle hooks 650 | 651 | In k6, there are four lifecycle stages. 652 | 653 | ```js 654 | // 1. init code 655 | 656 | export function setup() { 657 | // 2. setup code 658 | } 659 | 660 | export default function (data) { 661 | // 3. VU code 662 | } 663 | 664 | export function teardown(data) { 665 | // 4. teardown code 666 | } 667 | ``` 668 | 669 | - Code in the `init` context prepares the script, loads files, imports modules, and defines the test lifecycle functions. Required. 670 | - The `setup` function runs, setting up the test environment and generating data. Optional. 671 | - `VU code` runs in the default (or scenario) function, running for as long and as many times as the options define. Required. 672 | - The `teardown` function runs, post-processing data and closing the test environment. Optional. 673 | 674 | 675 | You can use the lifecycle functions to do things like, not running the test if the service is down! 676 | 677 | You could do something like that by adding the following code to your script: 678 | 679 | ```js 680 | export function setup() { 681 | let res = http.get(BASE_URL) 682 | if (res.status !== 200) { 683 | throw new Error(`Got unexpected status code ${res.status} when trying to setup. Exiting.`) 684 | } 685 | } 686 | ``` 687 | 688 | Then, run the test. 689 | 690 | Finally, try adding some logging to the `teardown` function and run the test again. 691 | 692 | For further information, refer to the [Test lifecycle documentation](https://grafana.com/docs/k6/latest/using-k6/test-lifecycle/). 693 | 694 | ### Test data parameterization 695 | 696 | So far, we have been using some hard-coded data. Let's change that! 697 | 698 | We will make the customerID we add to every request dynamic, with data from a JSON file. 699 | To accomplish that, we first need to create a file named `customers.json` with some customer IDs: 700 | 701 | ```json 702 | { 703 | "customers": [ 704 | 12351, 705 | 12352, 706 | 12353, 707 | 12354, 708 | 12355, 709 | 12356 710 | ] 711 | } 712 | ``` 713 | 714 | Then, we need to load this data in k6. There are multiple ways to do that, but one of the easiest and most optimal is using our SharedArray library. This library will load the data once and let all the VUs read it. 715 | To use it, we need to import it: 716 | 717 | ```js 718 | import { SharedArray } from "k6/data"; 719 | ``` 720 | 721 | Then, we need to load the data: 722 | 723 | ```js 724 | // Add this line after the options block, outside of the default function, as we want to load the data only once. 725 | const customers = new SharedArray('all my customers', function () { 726 | return JSON.parse(open('./customers.json')).customers; 727 | }); 728 | ``` 729 | 730 | Finally, we can use it in our script. Let's replace the HTTP request with: 731 | 732 | ```js 733 | let res = http.post(`${BASE_URL}/api/pizza`, JSON.stringify(restrictions), { 734 | headers: { 735 | "Content-Type": 'application/json', 736 | "X-User-ID": customers[Math.floor(Math.random() * customers.length)], 737 | }, 738 | }); 739 | ``` 740 | 741 |
742 | Docker run 743 | 744 | In Docker, you need to mount the `customers.json` file. You can do that by adding the `-v` flag to the docker command as follows: 745 | 746 | 747 | ```bash 748 | docker run -v $(pwd)/customers.json:/customers.json -i --network=default_network grafana/k6:latest run -e BASE_URL=http://quickpizza:3333 - 752 | 753 |
754 | 755 | 756 | 757 | That way, we will pick a random customer from the list of customers. Then, rerun the script. 758 | If you check the logs of the QuickPizza service, you should see that the customer ID that we attach to every log line is changing all the time. You can verify this by running: 759 | 760 | ```bash 761 | docker compose logs quickpizza 762 | ``` 763 | 764 | 765 | In this previous example, each VU uses a random value from the list. Alternatively, we can make sure each Virtual User uses the same ID by reading the internal ID that k6 uses to identify each VU. 766 | 767 | First, we need to import the `k6/execution` module which provides variables with information during the test execution. 768 | 769 | ```js 770 | import exec from 'k6/execution'; 771 | ``` 772 | 773 | 774 | Replace the `X-User-ID` header as follows: 775 | 776 | ```js 777 | "X-User-ID": customers[ (exec.vu.idInTest - 1) % customers.length], 778 | ``` 779 | 780 | With this change, the same VU will always use the same customerID value. To verify this, you can add the following log statement: 781 | 782 | ```js 783 | console.log(exec.vu.idInTest, customers[ (exec.vu.idInTest - 1) % customers.length]); 784 | ``` 785 | 786 | And if you want to see all the execution data in action, copy the log statement below into the default function and run the test again. 787 | 788 |
789 | Log all execution context variables 790 | 791 | ```js 792 | console.log(`Execution context 793 | 794 | Instance info 795 | ------------- 796 | Vus active: ${exec.instance.vusActive} 797 | Iterations completed: ${exec.instance.iterationsCompleted} 798 | Iterations interrupted: ${exec.instance.iterationsInterrupted} 799 | Iterations completed: ${exec.instance.iterationsCompleted} 800 | Iterations active: ${exec.instance.vusActive} 801 | Initialized vus: ${exec.instance.vusInitialized} 802 | Time passed from start of run(ms): ${exec.instance.currentTestRunDuration} 803 | 804 | Scenario info 805 | ------------- 806 | Name of the running scenario: ${exec.scenario.name} 807 | Executor type: ${exec.scenario.executor} 808 | Scenario start timestamp: ${exec.scenario.startTime} 809 | Percentage complete: ${exec.scenario.progress} 810 | Iteration in instance: ${exec.scenario.iterationInInstance} 811 | Iteration in test: ${exec.scenario.iterationInTest} 812 | 813 | Test info 814 | --------- 815 | All test options: ${exec.test.options} 816 | 817 | VU info 818 | ------- 819 | Iteration id: ${exec.vu.iterationInInstance} 820 | Iteration in scenario: ${exec.vu.iterationInScenario} 821 | VU ID in instance: ${exec.vu.idInInstance} 822 | VU ID in test: ${exec.vu.idInTest} 823 | VU tags: ${exec.vu.tags}`); 824 | ``` 825 | 826 | 827 |
828 | 829 |
830 | 831 | 832 | 833 | 834 | 835 | For more information about this topic, refer to the [Execution context variables documentation](https://grafana.com/docs/k6/latest/using-k6/execution-context-variables/) and [data parameterization examples](https://grafana.com/docs/k6/latest/examples/data-parameterization/). 836 | 837 | ### Custom metrics 838 | 839 | By default, k6 automatically collects built-in metrics. Besides built-ins, you can also create custom metrics. 840 | 841 | Metrics fall into four broad types: 842 | - **Counters**: sum values. 843 | - **Gauges**: track the smallest, largest, and latest values. 844 | - **Rates**: track how frequently a non-zero value occurs. 845 | - **Trends**: calculates statistics for multiple values (like mean, mode, or percentile). 846 | - 847 | You can create them by using the `k6/metrics` module. Let's try it out! 848 | 849 | For QuickPizza, let's create a custom metric to track the number of pizzas that have been recommended and another one to track the number of ingredients each pizza has. You first need to import the metrics module: 850 | 851 | ```js 852 | import { Trend, Counter } from "k6/metrics"; 853 | ``` 854 | 855 | Then, you can create the metrics: 856 | 857 | ```js 858 | // Put this after the options block, outside of the default function 859 | const pizzas = new Counter('quickpizza_number_of_pizzas'); 860 | const ingredients = new Trend('quickpizza_ingredients'); 861 | ``` 862 | 863 | Later, in the default function, you can use your metrics after the HTTP request is made: 864 | 865 | ```js 866 | // We increment the number of pizzas by 1 867 | pizzas.add(1); 868 | 869 | // We add the number of ingredients of the pizza to the trend 870 | ingredients.add(res.json().pizza.ingredients.length); 871 | ``` 872 | 873 | After the test ends, you should be able to see these new metrics in the summary section of the output. 874 | 875 | ```bash 876 | quickpizza_ingredients.........: avg=5 min=5 med=5 max=5 p(90)=5 p(95)=5 877 | quickpizza_number_of_pizzas....: 1 3.816182/s 878 | ``` 879 | 880 | 881 | It's now your turn; go ahead and add [one or more thresholds](#thresholds) based on any of the new custom metrics! 🛠️ 882 | 883 | 884 | For further information, refer to the [Create custom metrics documentation](https://grafana.com/docs/k6/latest/using-k6/metrics/create-custom-metrics/). 885 | 886 | ### WebSockets 887 | 888 | Before we show the k6 WebSocket example, note that QuickPizza also has a WebSocket endpoint! 889 | 890 | If multiple people have the QuickPizza site open, and one asks for a recommendation, the other people will get a little nudge telling them that someone already got a recommendation. Let’s see this by opening two browser instances in Incognito mode and clicking the fancy Pizza button. Then, you should see a message like: 891 | 892 | ![QuickPizza real-time UI with WebSockets](./media/quickpizza-websocket-ui.png) 893 | 894 | Let's now replicate this in our k6 test. 895 | 896 | You could use the script you already have, but for simplicity, we will create a new one. Create a new file named `websockets.js` and copy this script: 897 | 898 | ```js 899 | import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.1.0/index.js'; 900 | import { WebSocket } from 'k6/experimental/websockets'; 901 | import { setInterval } from 'k6/experimental/timers'; 902 | 903 | 904 | const BASE_URL = __ENV.BASE_URL || 'ws://localhost:3333'; 905 | 906 | 907 | export default function () { 908 | const ws = new WebSocket(`${BASE_URL}/ws`); 909 | ws.addEventListener('open', () => { 910 | // new recommendation every 2-8s 911 | const t = setInterval(() => { 912 | ws.send(JSON.stringify({ user: `VU ${__VU}`, msg: "new_pizza" })); 913 | }, randomIntBetween(2000, 8000)); 914 | 915 | 916 | // listen for messages/errors and log them into console 917 | ws.addEventListener('message', (e) => { 918 | const msg = JSON.parse(e.data); 919 | console.log(`VU ${__VU} received: ${msg.user} msg: ${msg.msg}`); 920 | }); 921 | }); 922 | } 923 | ``` 924 | 925 | Then, run the test. 926 | 927 | ```bash 928 | k6 run websockets.js 929 | ``` 930 | 931 |
932 | Docker run 933 | 934 | ```bash 935 | docker run -i --network=default_network grafana/k6:latest run -e BASE_URL=ws://quickpizza:3333 - 939 | 940 |
941 | 942 | The example above will run forever. Not good. In a real situation, you would use sessions/timers and more sophisticated stuff, but this should at least give you a basic idea of how you can use WebSockets in k6. 943 | 944 | If you have time, here is another [WebSocket example](https://github.com/grafana/quickpizza/blob/main/k6/foundations/13.basic.websockets.js) using Scenarios to try with Quickpizza. 945 | 946 | For more examples and details about the k6 WebSocket API, refer to the [k6 WebSocket documentation](https://grafana.com/docs/k6/latest/javascript-api/k6-experimental/websockets/). 947 | 948 | ### Browser testing 949 | 950 | Even though we have been using HTTP requests so far, k6 is not limited to that. You can use it to test all kinds of things! It natively supports other protocols like gRPC, WebSockets, and Redis - and you can extend k6 to test other protocols using k6 Extensions! 951 | 952 | 953 | You can also use it to interact with and test your web apps, as with Playwright/Puppeteer. This will be the focus of this section. We will use the Browser testing APIs of k6 to get a pizza recommendation, take a screenshot of the result, and verify the page's performance (by checking its web vitals metrics). 954 | 955 | 956 | To do that, let's create a new script named `browser.js` with the following content: 957 | 958 | ```js 959 | import { browser } from "k6/experimental/browser"; 960 | import { check } from "k6"; 961 | 962 | const BASE_URL = __ENV.BASE_URL || "http://localhost:3333"; 963 | 964 | export const options = { 965 | scenarios: { 966 | ui: { 967 | executor: "shared-iterations", 968 | options: { 969 | browser: { 970 | type: "chromium", 971 | }, 972 | }, 973 | }, 974 | }, 975 | }; 976 | 977 | export default async function () { 978 | const page = browser.newPage(); 979 | 980 | try { 981 | await page.goto(BASE_URL); 982 | check(page, { 983 | header: 984 | page.locator("h1").textContent() == 985 | "Looking to break out of your pizza routine?", 986 | }); 987 | 988 | await page.locator('//button[. = "Pizza, Please!"]').click(); 989 | page.waitForTimeout(500); 990 | page.screenshot({ path: "screenshot.png" }); 991 | check(page, { 992 | recommendation: page.locator("div#recommendations").textContent() != "", 993 | }); 994 | } finally { 995 | page.close(); 996 | } 997 | } 998 | ``` 999 | 1000 | There are things in the script that we have already talked about, like Checks and Scenarios. But there are also new things, like the `browser` import and the `page` object. These APIs will drive a real browser under the hood. In this script, we go to the QuickPizza page, click the big button, take a screenshot of the result, and verify that the recommendation is not empty. 1001 | 1002 | ```bash 1003 | k6 run browser.js 1004 | ``` 1005 | 1006 |
1007 | Docker run (k6:latest-with-browser) 1008 | 1009 | ```bash 1010 | docker run -i --network=default_network --cap-add=SYS_ADMIN grafana/k6:latest-with-browser run -e BASE_URL=http://quickpizza:3333 - :/home/k6/screenshot.png . 1017 | 1018 | # In MacBook M1/M2, make sure Docker has rosetta installed and virtualization enabled 1019 | ``` 1020 | 1021 |
1022 | 1023 |
1024 | 1025 | Then, open the `screenshot.png` file. You should see a screenshot of the QuickPizza page with a pizza recommendation. 1026 | 1027 | Also, you should be able to see the Checks we have defined in the output. Plus, lots of new metrics! These metrics are related to the performance of the page. You can use them to understand how your page is performing and how it is affecting your users. Many of them are [Web Vitals metrics](https://grafana.com/docs/k6/latest/using-k6-browser/metrics/), which are a set of metrics that Google recommends to measure the user experience on the web. 1028 | 1029 | 1030 | For more information about how to use the browser module, refer to the [k6 browser documentation](https://grafana.com/docs/k6/latest/using-k6-browser/). 1031 | 1032 | 1033 | ### Additional resources 1034 | 1035 | Wow, if you have reached this point, you have learned a lot about k6. But there is more! 1036 | 1037 | The Grafana k6 documentation is the best place to learn about all the things we missed and more: https://grafana.com/docs/k6. 1038 | 1039 | Still, the [Quickpizza repository](https://github.com/grafana/quickpizza) includes a few things that you might find interesting: 1040 | 1041 | - Additional getting-started examples 1042 | - Examples using local or public extensions 1043 | - One example using the `k6-operator` to distribute the test load among Kubernetes Pods 1044 | - Other integrations with Grafana and the Grafana o11y stack 1045 | - And more! -------------------------------------------------------------------------------- /config/grafana/dashboards/dashboards.yaml: -------------------------------------------------------------------------------- 1 | # For configuration options, see 2 | # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards 3 | 4 | apiVersion: 1 5 | 6 | providers: 7 | - name: 'Default' 8 | type: file 9 | disableDeletion: false 10 | updateIntervalSeconds: 10 11 | editable: true 12 | options: 13 | path: /etc/grafana/provisioning/dashboards/definitions -------------------------------------------------------------------------------- /config/grafana/dashboards/definitions/19665_rev2.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__elements": {}, 13 | "__requires": [ 14 | { 15 | "type": "grafana", 16 | "id": "grafana", 17 | "name": "Grafana", 18 | "version": "10.1.2" 19 | }, 20 | { 21 | "type": "datasource", 22 | "id": "prometheus", 23 | "name": "Prometheus", 24 | "version": "1.0.0" 25 | }, 26 | { 27 | "type": "panel", 28 | "id": "stat", 29 | "name": "Stat", 30 | "version": "" 31 | }, 32 | { 33 | "type": "panel", 34 | "id": "table", 35 | "name": "Table", 36 | "version": "" 37 | }, 38 | { 39 | "type": "panel", 40 | "id": "text", 41 | "name": "Text", 42 | "version": "" 43 | }, 44 | { 45 | "type": "panel", 46 | "id": "timeseries", 47 | "name": "Time series", 48 | "version": "" 49 | } 50 | ], 51 | "annotations": { 52 | "list": [ 53 | { 54 | "builtIn": 1, 55 | "datasource": { 56 | "type": "grafana", 57 | "uid": "-- Grafana --" 58 | }, 59 | "enable": true, 60 | "hide": true, 61 | "iconColor": "rgba(0, 211, 255, 1)", 62 | "name": "Annotations & Alerts", 63 | "type": "dashboard" 64 | } 65 | ] 66 | }, 67 | "description": "Visualize k6 OSS results stored in Prometheus", 68 | "editable": true, 69 | "fiscalYearStartMonth": 0, 70 | "graphTooltip": 0, 71 | "id": null, 72 | "links": [ 73 | { 74 | "asDropdown": false, 75 | "icon": "external link", 76 | "includeVars": false, 77 | "keepTime": false, 78 | "tags": [], 79 | "targetBlank": true, 80 | "title": "Grafana k6 OSS Docs: Prometheus Remote Write", 81 | "tooltip": "Open docs in a new tab", 82 | "type": "link", 83 | "url": "https://k6.io/docs/results-output/real-time/prometheus-remote-write/" 84 | } 85 | ], 86 | "liveNow": false, 87 | "panels": [ 88 | { 89 | "datasource": { 90 | "type": "prometheus", 91 | "uid": "${DS_PROMETHEUS}" 92 | }, 93 | "fieldConfig": { 94 | "defaults": { 95 | "color": { 96 | "mode": "palette-classic" 97 | }, 98 | "custom": { 99 | "axisCenteredZero": false, 100 | "axisColorMode": "text", 101 | "axisLabel": "", 102 | "axisPlacement": "auto", 103 | "barAlignment": 0, 104 | "drawStyle": "line", 105 | "fillOpacity": 0, 106 | "gradientMode": "none", 107 | "hideFrom": { 108 | "legend": false, 109 | "tooltip": false, 110 | "viz": false 111 | }, 112 | "insertNulls": false, 113 | "lineInterpolation": "linear", 114 | "lineWidth": 1, 115 | "pointSize": 5, 116 | "scaleDistribution": { 117 | "type": "linear" 118 | }, 119 | "showPoints": "auto", 120 | "spanNulls": false, 121 | "stacking": { 122 | "group": "A", 123 | "mode": "none" 124 | }, 125 | "thresholdsStyle": { 126 | "mode": "off" 127 | } 128 | }, 129 | "mappings": [], 130 | "thresholds": { 131 | "mode": "absolute", 132 | "steps": [ 133 | { 134 | "color": "green", 135 | "value": null 136 | } 137 | ] 138 | } 139 | }, 140 | "overrides": [ 141 | { 142 | "matcher": { 143 | "id": "byName", 144 | "options": "http_req_s_errors" 145 | }, 146 | "properties": [ 147 | { 148 | "id": "color", 149 | "value": { 150 | "fixedColor": "red", 151 | "mode": "fixed" 152 | } 153 | }, 154 | { 155 | "id": "custom.lineStyle", 156 | "value": { 157 | "dash": [ 158 | 10, 159 | 10 160 | ], 161 | "fill": "dash" 162 | } 163 | }, 164 | { 165 | "id": "custom.axisPlacement", 166 | "value": "right" 167 | }, 168 | { 169 | "id": "unit", 170 | "value": "reqps" 171 | } 172 | ] 173 | }, 174 | { 175 | "matcher": { 176 | "id": "byName", 177 | "options": "http_req_s" 178 | }, 179 | "properties": [ 180 | { 181 | "id": "unit", 182 | "value": "reqps" 183 | }, 184 | { 185 | "id": "custom.axisPlacement", 186 | "value": "right" 187 | }, 188 | { 189 | "id": "color", 190 | "value": { 191 | "fixedColor": "yellow", 192 | "mode": "fixed" 193 | } 194 | }, 195 | { 196 | "id": "custom.lineStyle", 197 | "value": { 198 | "dash": [ 199 | 10, 200 | 10 201 | ], 202 | "fill": "dash" 203 | } 204 | } 205 | ] 206 | }, 207 | { 208 | "matcher": { 209 | "id": "byName", 210 | "options": "vus" 211 | }, 212 | "properties": [ 213 | { 214 | "id": "color", 215 | "value": { 216 | "mode": "fixed" 217 | } 218 | }, 219 | { 220 | "id": "unit", 221 | "value": "VUs" 222 | } 223 | ] 224 | }, 225 | { 226 | "matcher": { 227 | "id": "byRegexp", 228 | "options": "http_req_duration_[a-zA-Z0-9_]+" 229 | }, 230 | "properties": [ 231 | { 232 | "id": "unit", 233 | "value": "s" 234 | }, 235 | { 236 | "id": "custom.axisPlacement", 237 | "value": "right" 238 | }, 239 | { 240 | "id": "color", 241 | "value": { 242 | "fixedColor": "blue", 243 | "mode": "fixed" 244 | } 245 | } 246 | ] 247 | } 248 | ] 249 | }, 250 | "gridPos": { 251 | "h": 11, 252 | "w": 24, 253 | "x": 0, 254 | "y": 0 255 | }, 256 | "id": 10, 257 | "options": { 258 | "legend": { 259 | "calcs": [], 260 | "displayMode": "list", 261 | "placement": "bottom", 262 | "showLegend": true 263 | }, 264 | "tooltip": { 265 | "mode": "single", 266 | "sort": "none" 267 | } 268 | }, 269 | "targets": [ 270 | { 271 | "datasource": { 272 | "type": "prometheus", 273 | "uid": "${DS_PROMETHEUS}" 274 | }, 275 | "editorMode": "code", 276 | "expr": "avg(k6_vus{testid=~\"$testid\"})", 277 | "instant": false, 278 | "legendFormat": "vus", 279 | "range": true, 280 | "refId": "A" 281 | }, 282 | { 283 | "datasource": { 284 | "type": "prometheus", 285 | "uid": "${DS_PROMETHEUS}" 286 | }, 287 | "editorMode": "code", 288 | "expr": "avg(k6_http_req_duration_$quantile_stat{testid=~\"$testid\"})", 289 | "hide": false, 290 | "instant": false, 291 | "legendFormat": "http_req_duration_$quantile_stat", 292 | "range": true, 293 | "refId": "C" 294 | }, 295 | { 296 | "datasource": { 297 | "type": "prometheus", 298 | "uid": "${DS_PROMETHEUS}" 299 | }, 300 | "editorMode": "code", 301 | "expr": "sum(irate(k6_http_reqs_total{testid=~\"$testid\"}[$__rate_interval]))", 302 | "hide": false, 303 | "instant": false, 304 | "legendFormat": "http_req_s", 305 | "range": true, 306 | "refId": "B" 307 | }, 308 | { 309 | "datasource": { 310 | "type": "prometheus", 311 | "uid": "${DS_PROMETHEUS}" 312 | }, 313 | "editorMode": "code", 314 | "expr": "avg(round(k6_http_req_failed_rate{testid=~\"$testid\"}, 0.1)*100)", 315 | "hide": true, 316 | "instant": false, 317 | "legendFormat": "http_req_failed", 318 | "range": true, 319 | "refId": "E" 320 | }, 321 | { 322 | "datasource": { 323 | "type": "prometheus", 324 | "uid": "${DS_PROMETHEUS}" 325 | }, 326 | "editorMode": "code", 327 | "expr": "sum(irate(k6_http_reqs_total{testid=~\"$testid\", expected_response=\"false\"}[$__rate_interval]))", 328 | "hide": false, 329 | "instant": false, 330 | "legendFormat": "http_req_s_errors", 331 | "range": true, 332 | "refId": "D" 333 | } 334 | ], 335 | "title": "Performance Overview", 336 | "type": "timeseries" 337 | }, 338 | { 339 | "collapsed": false, 340 | "gridPos": { 341 | "h": 1, 342 | "w": 24, 343 | "x": 0, 344 | "y": 11 345 | }, 346 | "id": 1, 347 | "panels": [], 348 | "title": "Performance Overview", 349 | "type": "row" 350 | }, 351 | { 352 | "datasource": { 353 | "type": "prometheus", 354 | "uid": "${DS_PROMETHEUS}" 355 | }, 356 | "fieldConfig": { 357 | "defaults": { 358 | "color": { 359 | "mode": "thresholds" 360 | }, 361 | "mappings": [], 362 | "thresholds": { 363 | "mode": "absolute", 364 | "steps": [ 365 | { 366 | "color": "green", 367 | "value": null 368 | } 369 | ] 370 | }, 371 | "unit": "none" 372 | }, 373 | "overrides": [] 374 | }, 375 | "gridPos": { 376 | "h": 3, 377 | "w": 6, 378 | "x": 0, 379 | "y": 12 380 | }, 381 | "id": 4, 382 | "options": { 383 | "colorMode": "value", 384 | "graphMode": "none", 385 | "justifyMode": "auto", 386 | "orientation": "auto", 387 | "reduceOptions": { 388 | "calcs": [], 389 | "fields": "", 390 | "values": false 391 | }, 392 | "textMode": "auto" 393 | }, 394 | "pluginVersion": "10.1.2", 395 | "targets": [ 396 | { 397 | "datasource": { 398 | "type": "prometheus", 399 | "uid": "${DS_PROMETHEUS}" 400 | }, 401 | "editorMode": "code", 402 | "expr": "sum(k6_http_reqs_total{testid=~\"$testid\"})", 403 | "instant": false, 404 | "legendFormat": "__auto", 405 | "range": true, 406 | "refId": "A" 407 | } 408 | ], 409 | "title": "HTTP requests", 410 | "type": "stat" 411 | }, 412 | { 413 | "datasource": { 414 | "type": "prometheus", 415 | "uid": "${DS_PROMETHEUS}" 416 | }, 417 | "fieldConfig": { 418 | "defaults": { 419 | "color": { 420 | "fixedColor": "red", 421 | "mode": "fixed" 422 | }, 423 | "mappings": [], 424 | "thresholds": { 425 | "mode": "absolute", 426 | "steps": [ 427 | { 428 | "color": "green", 429 | "value": null 430 | } 431 | ] 432 | }, 433 | "unit": "none" 434 | }, 435 | "overrides": [] 436 | }, 437 | "gridPos": { 438 | "h": 3, 439 | "w": 6, 440 | "x": 6, 441 | "y": 12 442 | }, 443 | "id": 22, 444 | "options": { 445 | "colorMode": "value", 446 | "graphMode": "none", 447 | "justifyMode": "auto", 448 | "orientation": "auto", 449 | "reduceOptions": { 450 | "calcs": [], 451 | "fields": "", 452 | "values": false 453 | }, 454 | "textMode": "auto" 455 | }, 456 | "pluginVersion": "10.1.2", 457 | "targets": [ 458 | { 459 | "datasource": { 460 | "type": "prometheus", 461 | "uid": "${DS_PROMETHEUS}" 462 | }, 463 | "editorMode": "code", 464 | "expr": "sum(k6_http_reqs_total{testid=~\"$testid\", expected_response=\"false\"})", 465 | "instant": false, 466 | "legendFormat": "__auto", 467 | "range": true, 468 | "refId": "A" 469 | } 470 | ], 471 | "title": "HTTP request failures", 472 | "type": "stat" 473 | }, 474 | { 475 | "datasource": { 476 | "type": "prometheus", 477 | "uid": "${DS_PROMETHEUS}" 478 | }, 479 | "fieldConfig": { 480 | "defaults": { 481 | "color": { 482 | "mode": "thresholds" 483 | }, 484 | "mappings": [], 485 | "thresholds": { 486 | "mode": "absolute", 487 | "steps": [ 488 | { 489 | "color": "green", 490 | "value": null 491 | } 492 | ] 493 | }, 494 | "unit": "reqps" 495 | }, 496 | "overrides": [] 497 | }, 498 | "gridPos": { 499 | "h": 3, 500 | "w": 6, 501 | "x": 12, 502 | "y": 12 503 | }, 504 | "id": 20, 505 | "options": { 506 | "colorMode": "value", 507 | "graphMode": "none", 508 | "justifyMode": "auto", 509 | "orientation": "auto", 510 | "reduceOptions": { 511 | "calcs": [], 512 | "fields": "", 513 | "values": false 514 | }, 515 | "textMode": "auto" 516 | }, 517 | "pluginVersion": "10.1.2", 518 | "targets": [ 519 | { 520 | "datasource": { 521 | "type": "prometheus", 522 | "uid": "${DS_PROMETHEUS}" 523 | }, 524 | "editorMode": "code", 525 | "expr": "sum(irate(k6_http_reqs_total{testid=~\"$testid\"}[$__rate_interval]))", 526 | "instant": false, 527 | "legendFormat": "__auto", 528 | "range": true, 529 | "refId": "A" 530 | } 531 | ], 532 | "title": "Peak RPS", 533 | "type": "stat" 534 | }, 535 | { 536 | "datasource": { 537 | "type": "prometheus", 538 | "uid": "${DS_PROMETHEUS}" 539 | }, 540 | "description": "Select a different Stat to change the query", 541 | "fieldConfig": { 542 | "defaults": { 543 | "color": { 544 | "mode": "thresholds" 545 | }, 546 | "mappings": [], 547 | "thresholds": { 548 | "mode": "absolute", 549 | "steps": [ 550 | { 551 | "color": "green", 552 | "value": null 553 | } 554 | ] 555 | }, 556 | "unit": "s" 557 | }, 558 | "overrides": [] 559 | }, 560 | "gridPos": { 561 | "h": 3, 562 | "w": 6, 563 | "x": 18, 564 | "y": 12 565 | }, 566 | "id": 21, 567 | "options": { 568 | "colorMode": "value", 569 | "graphMode": "none", 570 | "justifyMode": "auto", 571 | "orientation": "auto", 572 | "reduceOptions": { 573 | "calcs": [], 574 | "fields": "", 575 | "values": false 576 | }, 577 | "textMode": "auto" 578 | }, 579 | "pluginVersion": "10.1.2", 580 | "targets": [ 581 | { 582 | "datasource": { 583 | "type": "prometheus", 584 | "uid": "${DS_PROMETHEUS}" 585 | }, 586 | "editorMode": "code", 587 | "expr": "avg(k6_http_req_duration_$quantile_stat{testid=~\"$testid\"})", 588 | "instant": false, 589 | "legendFormat": "__auto", 590 | "range": true, 591 | "refId": "A" 592 | } 593 | ], 594 | "title": "HTTP Request Duration", 595 | "type": "stat" 596 | }, 597 | { 598 | "datasource": { 599 | "type": "prometheus", 600 | "uid": "${DS_PROMETHEUS}" 601 | }, 602 | "fieldConfig": { 603 | "defaults": { 604 | "color": { 605 | "mode": "palette-classic" 606 | }, 607 | "custom": { 608 | "axisCenteredZero": false, 609 | "axisColorMode": "text", 610 | "axisLabel": "", 611 | "axisPlacement": "auto", 612 | "barAlignment": 0, 613 | "drawStyle": "line", 614 | "fillOpacity": 0, 615 | "gradientMode": "none", 616 | "hideFrom": { 617 | "legend": false, 618 | "tooltip": false, 619 | "viz": false 620 | }, 621 | "insertNulls": false, 622 | "lineInterpolation": "linear", 623 | "lineWidth": 1, 624 | "pointSize": 5, 625 | "scaleDistribution": { 626 | "type": "linear" 627 | }, 628 | "showPoints": "auto", 629 | "spanNulls": false, 630 | "stacking": { 631 | "group": "A", 632 | "mode": "none" 633 | }, 634 | "thresholdsStyle": { 635 | "mode": "off" 636 | } 637 | }, 638 | "mappings": [], 639 | "thresholds": { 640 | "mode": "absolute", 641 | "steps": [ 642 | { 643 | "color": "green", 644 | "value": null 645 | } 646 | ] 647 | }, 648 | "unit": "bytes" 649 | }, 650 | "overrides": [] 651 | }, 652 | "gridPos": { 653 | "h": 8, 654 | "w": 12, 655 | "x": 0, 656 | "y": 15 657 | }, 658 | "id": 8, 659 | "options": { 660 | "legend": { 661 | "calcs": [], 662 | "displayMode": "list", 663 | "placement": "bottom", 664 | "showLegend": true 665 | }, 666 | "tooltip": { 667 | "mode": "single", 668 | "sort": "none" 669 | } 670 | }, 671 | "targets": [ 672 | { 673 | "datasource": { 674 | "type": "prometheus", 675 | "uid": "${DS_PROMETHEUS}" 676 | }, 677 | "editorMode": "code", 678 | "expr": "avg(irate(k6_data_sent_total{testid=~\"$testid\"}[$__rate_interval]))", 679 | "instant": false, 680 | "legendFormat": "data_sent", 681 | "range": true, 682 | "refId": "A" 683 | }, 684 | { 685 | "datasource": { 686 | "type": "prometheus", 687 | "uid": "${DS_PROMETHEUS}" 688 | }, 689 | "editorMode": "code", 690 | "exemplar": false, 691 | "expr": "avg(irate(k6_data_received_total{testid=~\"$testid\"}[$__rate_interval]))", 692 | "hide": false, 693 | "instant": false, 694 | "legendFormat": "data_received", 695 | "range": true, 696 | "refId": "B" 697 | } 698 | ], 699 | "title": "Transfer Rate", 700 | "type": "timeseries" 701 | }, 702 | { 703 | "datasource": { 704 | "type": "prometheus", 705 | "uid": "${DS_PROMETHEUS}" 706 | }, 707 | "fieldConfig": { 708 | "defaults": { 709 | "color": { 710 | "mode": "palette-classic" 711 | }, 712 | "custom": { 713 | "axisCenteredZero": false, 714 | "axisColorMode": "text", 715 | "axisLabel": "", 716 | "axisPlacement": "auto", 717 | "barAlignment": 0, 718 | "drawStyle": "line", 719 | "fillOpacity": 0, 720 | "gradientMode": "none", 721 | "hideFrom": { 722 | "legend": false, 723 | "tooltip": false, 724 | "viz": false 725 | }, 726 | "insertNulls": false, 727 | "lineInterpolation": "linear", 728 | "lineWidth": 1, 729 | "pointSize": 5, 730 | "scaleDistribution": { 731 | "type": "linear" 732 | }, 733 | "showPoints": "auto", 734 | "spanNulls": false, 735 | "stacking": { 736 | "group": "A", 737 | "mode": "none" 738 | }, 739 | "thresholdsStyle": { 740 | "mode": "off" 741 | } 742 | }, 743 | "mappings": [], 744 | "thresholds": { 745 | "mode": "absolute", 746 | "steps": [ 747 | { 748 | "color": "green", 749 | "value": null 750 | } 751 | ] 752 | }, 753 | "unit": "s" 754 | }, 755 | "overrides": [ 756 | { 757 | "matcher": { 758 | "id": "byName", 759 | "options": "dropped_iterations" 760 | }, 761 | "properties": [ 762 | { 763 | "id": "unit", 764 | "value": "none" 765 | } 766 | ] 767 | } 768 | ] 769 | }, 770 | "gridPos": { 771 | "h": 8, 772 | "w": 12, 773 | "x": 12, 774 | "y": 15 775 | }, 776 | "id": 9, 777 | "options": { 778 | "legend": { 779 | "calcs": [], 780 | "displayMode": "list", 781 | "placement": "bottom", 782 | "showLegend": true 783 | }, 784 | "tooltip": { 785 | "mode": "single", 786 | "sort": "none" 787 | } 788 | }, 789 | "targets": [ 790 | { 791 | "datasource": { 792 | "type": "prometheus", 793 | "uid": "${DS_PROMETHEUS}" 794 | }, 795 | "editorMode": "code", 796 | "expr": "avg(k6_iteration_duration_$quantile_stat{testid=~\"$testid\"})", 797 | "instant": false, 798 | "legendFormat": "iteration_duration_$quantile_stat", 799 | "range": true, 800 | "refId": "A" 801 | }, 802 | { 803 | "datasource": { 804 | "type": "prometheus", 805 | "uid": "${DS_PROMETHEUS}" 806 | }, 807 | "editorMode": "code", 808 | "exemplar": false, 809 | "expr": "avg(k6_dropped_iterations_total{testid=~\"$testid\"})", 810 | "hide": false, 811 | "instant": false, 812 | "legendFormat": "dropped_iterations", 813 | "range": true, 814 | "refId": "B" 815 | } 816 | ], 817 | "title": "Iterations", 818 | "type": "timeseries" 819 | }, 820 | { 821 | "collapsed": false, 822 | "gridPos": { 823 | "h": 1, 824 | "w": 24, 825 | "x": 0, 826 | "y": 23 827 | }, 828 | "id": 16, 829 | "panels": [], 830 | "title": "HTTP", 831 | "type": "row" 832 | }, 833 | { 834 | "datasource": { 835 | "type": "prometheus", 836 | "uid": "${DS_PROMETHEUS}" 837 | }, 838 | "description": "Select a different Stat to change the query\n\nHTTP-specific built-in metrics", 839 | "fieldConfig": { 840 | "defaults": { 841 | "color": { 842 | "mode": "palette-classic" 843 | }, 844 | "custom": { 845 | "axisCenteredZero": false, 846 | "axisColorMode": "text", 847 | "axisLabel": "", 848 | "axisPlacement": "auto", 849 | "barAlignment": 0, 850 | "drawStyle": "line", 851 | "fillOpacity": 0, 852 | "gradientMode": "none", 853 | "hideFrom": { 854 | "legend": false, 855 | "tooltip": false, 856 | "viz": false 857 | }, 858 | "insertNulls": false, 859 | "lineInterpolation": "linear", 860 | "lineWidth": 1, 861 | "pointSize": 5, 862 | "scaleDistribution": { 863 | "type": "linear" 864 | }, 865 | "showPoints": "auto", 866 | "spanNulls": false, 867 | "stacking": { 868 | "group": "A", 869 | "mode": "none" 870 | }, 871 | "thresholdsStyle": { 872 | "mode": "off" 873 | } 874 | }, 875 | "mappings": [], 876 | "thresholds": { 877 | "mode": "absolute", 878 | "steps": [ 879 | { 880 | "color": "green", 881 | "value": null 882 | } 883 | ] 884 | }, 885 | "unit": "s" 886 | }, 887 | "overrides": [ 888 | { 889 | "matcher": { 890 | "id": "byRegexp", 891 | "options": "http_req_duration_[a-zA-Z0-9_]+" 892 | }, 893 | "properties": [ 894 | { 895 | "id": "color", 896 | "value": { 897 | "fixedColor": "blue", 898 | "mode": "fixed" 899 | } 900 | } 901 | ] 902 | } 903 | ] 904 | }, 905 | "gridPos": { 906 | "h": 8, 907 | "w": 8, 908 | "x": 0, 909 | "y": 24 910 | }, 911 | "id": 14, 912 | "options": { 913 | "legend": { 914 | "calcs": [], 915 | "displayMode": "list", 916 | "placement": "bottom", 917 | "showLegend": true 918 | }, 919 | "tooltip": { 920 | "mode": "single", 921 | "sort": "none" 922 | } 923 | }, 924 | "targets": [ 925 | { 926 | "datasource": { 927 | "type": "prometheus", 928 | "uid": "${DS_PROMETHEUS}" 929 | }, 930 | "editorMode": "code", 931 | "expr": "avg(k6_http_req_blocked_$quantile_stat{testid=~\"$testid\"})", 932 | "hide": false, 933 | "instant": false, 934 | "legendFormat": "http_req_blocked_$quantile_stat", 935 | "range": true, 936 | "refId": "B" 937 | }, 938 | { 939 | "datasource": { 940 | "type": "prometheus", 941 | "uid": "${DS_PROMETHEUS}" 942 | }, 943 | "editorMode": "code", 944 | "expr": "avg(k6_http_req_tls_handshaking_$quantile_stat{testid=~\"$testid\"})", 945 | "hide": false, 946 | "instant": false, 947 | "legendFormat": "http_req_tls_handshaking_$quantile_stat", 948 | "range": true, 949 | "refId": "C" 950 | }, 951 | { 952 | "datasource": { 953 | "type": "prometheus", 954 | "uid": "${DS_PROMETHEUS}" 955 | }, 956 | "editorMode": "code", 957 | "expr": "avg(k6_http_req_sending_$quantile_stat{testid=~\"$testid\"})", 958 | "hide": false, 959 | "instant": false, 960 | "legendFormat": "http_req_sending_$quantile_stat", 961 | "range": true, 962 | "refId": "D" 963 | }, 964 | { 965 | "datasource": { 966 | "type": "prometheus", 967 | "uid": "${DS_PROMETHEUS}" 968 | }, 969 | "editorMode": "code", 970 | "expr": "avg(k6_http_req_waiting_$quantile_stat{testid=~\"$testid\"})", 971 | "hide": false, 972 | "instant": false, 973 | "legendFormat": "http_req_waiting_$quantile_stat", 974 | "range": true, 975 | "refId": "E" 976 | }, 977 | { 978 | "datasource": { 979 | "type": "prometheus", 980 | "uid": "${DS_PROMETHEUS}" 981 | }, 982 | "editorMode": "code", 983 | "expr": "avg(k6_http_req_receiving_$quantile_stat{testid=~\"$testid\"})", 984 | "hide": false, 985 | "instant": false, 986 | "legendFormat": "http_req_receiving_$quantile_stat", 987 | "range": true, 988 | "refId": "F" 989 | }, 990 | { 991 | "datasource": { 992 | "type": "prometheus", 993 | "uid": "${DS_PROMETHEUS}" 994 | }, 995 | "editorMode": "code", 996 | "expr": "avg(k6_http_req_duration_$quantile_stat{testid=~\"$testid\"})", 997 | "hide": false, 998 | "instant": false, 999 | "legendFormat": "http_req_duration_$quantile_stat", 1000 | "range": true, 1001 | "refId": "A" 1002 | } 1003 | ], 1004 | "title": "HTTP Latency Timings", 1005 | "type": "timeseries" 1006 | }, 1007 | { 1008 | "datasource": { 1009 | "type": "prometheus", 1010 | "uid": "${DS_PROMETHEUS}" 1011 | }, 1012 | "description": "Select a different Stat to change the query", 1013 | "fieldConfig": { 1014 | "defaults": { 1015 | "color": { 1016 | "mode": "palette-classic" 1017 | }, 1018 | "custom": { 1019 | "axisCenteredZero": false, 1020 | "axisColorMode": "text", 1021 | "axisLabel": "", 1022 | "axisPlacement": "auto", 1023 | "axisSoftMin": 0, 1024 | "barAlignment": 0, 1025 | "drawStyle": "line", 1026 | "fillOpacity": 0, 1027 | "gradientMode": "none", 1028 | "hideFrom": { 1029 | "legend": false, 1030 | "tooltip": false, 1031 | "viz": false 1032 | }, 1033 | "insertNulls": false, 1034 | "lineInterpolation": "linear", 1035 | "lineWidth": 1, 1036 | "pointSize": 5, 1037 | "scaleDistribution": { 1038 | "type": "linear" 1039 | }, 1040 | "showPoints": "auto", 1041 | "spanNulls": false, 1042 | "stacking": { 1043 | "group": "A", 1044 | "mode": "none" 1045 | }, 1046 | "thresholdsStyle": { 1047 | "mode": "off" 1048 | } 1049 | }, 1050 | "mappings": [], 1051 | "thresholds": { 1052 | "mode": "absolute", 1053 | "steps": [ 1054 | { 1055 | "color": "green", 1056 | "value": null 1057 | } 1058 | ] 1059 | }, 1060 | "unit": "s" 1061 | }, 1062 | "overrides": [ 1063 | { 1064 | "matcher": { 1065 | "id": "byRegexp", 1066 | "options": "errors_http_req_duration_[a-zA-Z0-9_]+" 1067 | }, 1068 | "properties": [ 1069 | { 1070 | "id": "color", 1071 | "value": { 1072 | "fixedColor": "red", 1073 | "mode": "fixed" 1074 | } 1075 | } 1076 | ] 1077 | }, 1078 | { 1079 | "matcher": { 1080 | "id": "byRegexp", 1081 | "options": "success_http_req_duration_[a-zA-Z0-9_]+" 1082 | }, 1083 | "properties": [ 1084 | { 1085 | "id": "color", 1086 | "value": { 1087 | "fixedColor": "green", 1088 | "mode": "fixed" 1089 | } 1090 | } 1091 | ] 1092 | }, 1093 | { 1094 | "matcher": { 1095 | "id": "byRegexp", 1096 | "options": "http_req_duration_[a-zA-Z0-9_]+" 1097 | }, 1098 | "properties": [ 1099 | { 1100 | "id": "color", 1101 | "value": { 1102 | "fixedColor": "yellow", 1103 | "mode": "fixed" 1104 | } 1105 | } 1106 | ] 1107 | }, 1108 | { 1109 | "matcher": { 1110 | "id": "byRegexp", 1111 | "options": "http_req_duration_[a-zA-Z0-9_]+" 1112 | }, 1113 | "properties": [ 1114 | { 1115 | "id": "color", 1116 | "value": { 1117 | "fixedColor": "blue", 1118 | "mode": "fixed" 1119 | } 1120 | } 1121 | ] 1122 | } 1123 | ] 1124 | }, 1125 | "gridPos": { 1126 | "h": 8, 1127 | "w": 8, 1128 | "x": 8, 1129 | "y": 24 1130 | }, 1131 | "id": 15, 1132 | "options": { 1133 | "legend": { 1134 | "calcs": [], 1135 | "displayMode": "list", 1136 | "placement": "bottom", 1137 | "showLegend": true 1138 | }, 1139 | "tooltip": { 1140 | "mode": "single", 1141 | "sort": "none" 1142 | } 1143 | }, 1144 | "targets": [ 1145 | { 1146 | "datasource": { 1147 | "type": "prometheus", 1148 | "uid": "${DS_PROMETHEUS}" 1149 | }, 1150 | "editorMode": "code", 1151 | "expr": "avg(k6_http_req_duration_$quantile_stat{testid=~\"$testid\"})", 1152 | "hide": false, 1153 | "instant": false, 1154 | "legendFormat": "http_req_duration_$quantile_stat", 1155 | "range": true, 1156 | "refId": "A" 1157 | }, 1158 | { 1159 | "datasource": { 1160 | "type": "prometheus", 1161 | "uid": "${DS_PROMETHEUS}" 1162 | }, 1163 | "editorMode": "code", 1164 | "expr": "avg(k6_http_req_duration_$quantile_stat{testid=~\"$testid\", expected_response=\"true\"})", 1165 | "instant": false, 1166 | "legendFormat": "success_http_req_duration_$quantile_stat", 1167 | "range": true, 1168 | "refId": "C" 1169 | }, 1170 | { 1171 | "datasource": { 1172 | "type": "prometheus", 1173 | "uid": "${DS_PROMETHEUS}" 1174 | }, 1175 | "editorMode": "code", 1176 | "expr": "avg(k6_http_req_duration_$quantile_stat{testid=~\"$testid\", expected_response=\"false\"})", 1177 | "hide": false, 1178 | "instant": false, 1179 | "legendFormat": "errors_http_req_duration_$quantile_stat", 1180 | "range": true, 1181 | "refId": "B" 1182 | } 1183 | ], 1184 | "title": "HTTP Latency Stats", 1185 | "type": "timeseries" 1186 | }, 1187 | { 1188 | "datasource": { 1189 | "type": "prometheus", 1190 | "uid": "${DS_PROMETHEUS}" 1191 | }, 1192 | "description": "", 1193 | "fieldConfig": { 1194 | "defaults": { 1195 | "color": { 1196 | "mode": "palette-classic" 1197 | }, 1198 | "custom": { 1199 | "axisCenteredZero": false, 1200 | "axisColorMode": "text", 1201 | "axisLabel": "", 1202 | "axisPlacement": "auto", 1203 | "axisSoftMin": 0, 1204 | "barAlignment": 0, 1205 | "drawStyle": "line", 1206 | "fillOpacity": 0, 1207 | "gradientMode": "none", 1208 | "hideFrom": { 1209 | "legend": false, 1210 | "tooltip": false, 1211 | "viz": false 1212 | }, 1213 | "insertNulls": false, 1214 | "lineInterpolation": "linear", 1215 | "lineWidth": 1, 1216 | "pointSize": 5, 1217 | "scaleDistribution": { 1218 | "type": "linear" 1219 | }, 1220 | "showPoints": "auto", 1221 | "spanNulls": false, 1222 | "stacking": { 1223 | "group": "A", 1224 | "mode": "none" 1225 | }, 1226 | "thresholdsStyle": { 1227 | "mode": "off" 1228 | } 1229 | }, 1230 | "mappings": [], 1231 | "thresholds": { 1232 | "mode": "absolute", 1233 | "steps": [ 1234 | { 1235 | "color": "green", 1236 | "value": null 1237 | } 1238 | ] 1239 | }, 1240 | "unit": "reqps" 1241 | }, 1242 | "overrides": [ 1243 | { 1244 | "matcher": { 1245 | "id": "byName", 1246 | "options": "http_req_s_errors" 1247 | }, 1248 | "properties": [ 1249 | { 1250 | "id": "color", 1251 | "value": { 1252 | "fixedColor": "red", 1253 | "mode": "fixed" 1254 | } 1255 | }, 1256 | { 1257 | "id": "custom.lineStyle", 1258 | "value": { 1259 | "dash": [ 1260 | 10, 1261 | 10 1262 | ], 1263 | "fill": "dash" 1264 | } 1265 | } 1266 | ] 1267 | }, 1268 | { 1269 | "matcher": { 1270 | "id": "byName", 1271 | "options": "http_req_s" 1272 | }, 1273 | "properties": [ 1274 | { 1275 | "id": "color", 1276 | "value": { 1277 | "fixedColor": "yellow", 1278 | "mode": "fixed" 1279 | } 1280 | }, 1281 | { 1282 | "id": "custom.lineStyle", 1283 | "value": { 1284 | "dash": [ 1285 | 10, 1286 | 10 1287 | ], 1288 | "fill": "dash" 1289 | } 1290 | } 1291 | ] 1292 | }, 1293 | { 1294 | "matcher": { 1295 | "id": "byName", 1296 | "options": "http_req_s_success" 1297 | }, 1298 | "properties": [ 1299 | { 1300 | "id": "custom.lineStyle", 1301 | "value": { 1302 | "dash": [ 1303 | 10, 1304 | 10 1305 | ], 1306 | "fill": "dash" 1307 | } 1308 | }, 1309 | { 1310 | "id": "color", 1311 | "value": { 1312 | "fixedColor": "green", 1313 | "mode": "fixed" 1314 | } 1315 | } 1316 | ] 1317 | } 1318 | ] 1319 | }, 1320 | "gridPos": { 1321 | "h": 8, 1322 | "w": 8, 1323 | "x": 16, 1324 | "y": 24 1325 | }, 1326 | "id": 18, 1327 | "options": { 1328 | "legend": { 1329 | "calcs": [], 1330 | "displayMode": "list", 1331 | "placement": "bottom", 1332 | "showLegend": true 1333 | }, 1334 | "tooltip": { 1335 | "mode": "single", 1336 | "sort": "none" 1337 | } 1338 | }, 1339 | "targets": [ 1340 | { 1341 | "datasource": { 1342 | "type": "prometheus", 1343 | "uid": "${DS_PROMETHEUS}" 1344 | }, 1345 | "editorMode": "code", 1346 | "expr": "sum(irate(k6_http_reqs_total{testid=~\"$testid\"}[$__rate_interval]))", 1347 | "instant": false, 1348 | "legendFormat": "http_req_s", 1349 | "range": true, 1350 | "refId": "A" 1351 | }, 1352 | { 1353 | "datasource": { 1354 | "type": "prometheus", 1355 | "uid": "${DS_PROMETHEUS}" 1356 | }, 1357 | "editorMode": "code", 1358 | "expr": "sum(irate(k6_http_reqs_total{testid=~\"$testid\", expected_response=\"false\"}[$__rate_interval]))", 1359 | "hide": false, 1360 | "instant": false, 1361 | "legendFormat": "http_req_s_errors", 1362 | "range": true, 1363 | "refId": "B" 1364 | }, 1365 | { 1366 | "datasource": { 1367 | "type": "prometheus", 1368 | "uid": "${DS_PROMETHEUS}" 1369 | }, 1370 | "editorMode": "code", 1371 | "expr": "sum(irate(k6_http_reqs_total{testid=~\"$testid\", expected_response=\"true\"}[$__rate_interval]))", 1372 | "hide": false, 1373 | "instant": false, 1374 | "legendFormat": "http_req_s_success", 1375 | "range": true, 1376 | "refId": "C" 1377 | } 1378 | ], 1379 | "title": "HTTP Request Rate", 1380 | "type": "timeseries" 1381 | }, 1382 | { 1383 | "datasource": { 1384 | "type": "prometheus", 1385 | "uid": "${DS_PROMETHEUS}" 1386 | }, 1387 | "description": "min/max/p95/p99 depends on the available Quantile Stats", 1388 | "fieldConfig": { 1389 | "defaults": { 1390 | "color": { 1391 | "mode": "thresholds" 1392 | }, 1393 | "custom": { 1394 | "align": "auto", 1395 | "cellOptions": { 1396 | "type": "auto" 1397 | }, 1398 | "inspect": false 1399 | }, 1400 | "mappings": [], 1401 | "thresholds": { 1402 | "mode": "absolute", 1403 | "steps": [ 1404 | { 1405 | "color": "green", 1406 | "value": null 1407 | } 1408 | ] 1409 | } 1410 | }, 1411 | "overrides": [ 1412 | { 1413 | "matcher": { 1414 | "id": "byName", 1415 | "options": "name" 1416 | }, 1417 | "properties": [ 1418 | { 1419 | "id": "filterable", 1420 | "value": false 1421 | } 1422 | ] 1423 | }, 1424 | { 1425 | "matcher": { 1426 | "id": "byName", 1427 | "options": "method" 1428 | }, 1429 | "properties": [ 1430 | { 1431 | "id": "filterable", 1432 | "value": false 1433 | } 1434 | ] 1435 | }, 1436 | { 1437 | "matcher": { 1438 | "id": "byName", 1439 | "options": "status" 1440 | }, 1441 | "properties": [ 1442 | { 1443 | "id": "filterable", 1444 | "value": false 1445 | } 1446 | ] 1447 | }, 1448 | { 1449 | "matcher": { 1450 | "id": "byName", 1451 | "options": "min" 1452 | }, 1453 | "properties": [ 1454 | { 1455 | "id": "unit", 1456 | "value": "s" 1457 | } 1458 | ] 1459 | }, 1460 | { 1461 | "matcher": { 1462 | "id": "byName", 1463 | "options": "max" 1464 | }, 1465 | "properties": [ 1466 | { 1467 | "id": "unit", 1468 | "value": "s" 1469 | } 1470 | ] 1471 | }, 1472 | { 1473 | "matcher": { 1474 | "id": "byName", 1475 | "options": "p95" 1476 | }, 1477 | "properties": [ 1478 | { 1479 | "id": "unit", 1480 | "value": "s" 1481 | } 1482 | ] 1483 | }, 1484 | { 1485 | "matcher": { 1486 | "id": "byName", 1487 | "options": "p99" 1488 | }, 1489 | "properties": [ 1490 | { 1491 | "id": "unit", 1492 | "value": "s" 1493 | } 1494 | ] 1495 | } 1496 | ] 1497 | }, 1498 | "gridPos": { 1499 | "h": 7, 1500 | "w": 24, 1501 | "x": 0, 1502 | "y": 32 1503 | }, 1504 | "id": 17, 1505 | "options": { 1506 | "cellHeight": "sm", 1507 | "footer": { 1508 | "countRows": false, 1509 | "enablePagination": true, 1510 | "fields": "", 1511 | "reducer": [ 1512 | "sum" 1513 | ], 1514 | "show": false 1515 | }, 1516 | "frameIndex": 2, 1517 | "showHeader": true 1518 | }, 1519 | "pluginVersion": "10.1.2", 1520 | "targets": [ 1521 | { 1522 | "datasource": { 1523 | "type": "prometheus", 1524 | "uid": "${DS_PROMETHEUS}" 1525 | }, 1526 | "editorMode": "code", 1527 | "expr": "avg by(name, method, status) (k6_http_req_duration_min{testid=~\"$testid\"})", 1528 | "format": "table", 1529 | "hide": false, 1530 | "instant": false, 1531 | "legendFormat": "min", 1532 | "range": true, 1533 | "refId": "B" 1534 | }, 1535 | { 1536 | "datasource": { 1537 | "type": "prometheus", 1538 | "uid": "${DS_PROMETHEUS}" 1539 | }, 1540 | "editorMode": "code", 1541 | "expr": "avg by(name, method, status) (k6_http_req_duration_max{testid=~\"$testid\"})", 1542 | "format": "table", 1543 | "hide": false, 1544 | "instant": false, 1545 | "legendFormat": "max", 1546 | "range": true, 1547 | "refId": "C" 1548 | }, 1549 | { 1550 | "datasource": { 1551 | "type": "prometheus", 1552 | "uid": "${DS_PROMETHEUS}" 1553 | }, 1554 | "editorMode": "code", 1555 | "expr": "avg by(name, method, status) (k6_http_req_duration_p95{testid=~\"$testid\"})", 1556 | "format": "table", 1557 | "hide": false, 1558 | "instant": false, 1559 | "legendFormat": "p95", 1560 | "range": true, 1561 | "refId": "D" 1562 | }, 1563 | { 1564 | "datasource": { 1565 | "type": "prometheus", 1566 | "uid": "${DS_PROMETHEUS}" 1567 | }, 1568 | "editorMode": "code", 1569 | "expr": "avg by(name, method, status) (k6_http_req_duration_p99{testid=~\"$testid\"})", 1570 | "format": "table", 1571 | "hide": false, 1572 | "instant": false, 1573 | "legendFormat": "__auto", 1574 | "range": true, 1575 | "refId": "E" 1576 | } 1577 | ], 1578 | "title": "Requests by URL", 1579 | "transformations": [ 1580 | { 1581 | "id": "merge", 1582 | "options": {} 1583 | }, 1584 | { 1585 | "id": "groupBy", 1586 | "options": { 1587 | "fields": { 1588 | "Value #B": { 1589 | "aggregations": [ 1590 | "min" 1591 | ], 1592 | "operation": "aggregate" 1593 | }, 1594 | "Value #C": { 1595 | "aggregations": [ 1596 | "max" 1597 | ], 1598 | "operation": "aggregate" 1599 | }, 1600 | "Value #D": { 1601 | "aggregations": [ 1602 | "mean" 1603 | ], 1604 | "operation": "aggregate" 1605 | }, 1606 | "Value #E": { 1607 | "aggregations": [ 1608 | "mean" 1609 | ], 1610 | "operation": "aggregate" 1611 | }, 1612 | "method": { 1613 | "aggregations": [], 1614 | "operation": "groupby" 1615 | }, 1616 | "name": { 1617 | "aggregations": [], 1618 | "operation": "groupby" 1619 | }, 1620 | "status": { 1621 | "aggregations": [], 1622 | "operation": "groupby" 1623 | } 1624 | } 1625 | } 1626 | }, 1627 | { 1628 | "id": "organize", 1629 | "options": { 1630 | "excludeByName": { 1631 | "Time": true 1632 | }, 1633 | "indexByName": { 1634 | "Time": 0, 1635 | "Value #B": 4, 1636 | "Value #C": 5, 1637 | "Value #D": 6, 1638 | "Value #E": 7, 1639 | "method": 2, 1640 | "name": 1, 1641 | "status": 3 1642 | }, 1643 | "renameByName": { 1644 | "Value #B": "min", 1645 | "Value #B (min)": "min", 1646 | "Value #C": "max", 1647 | "Value #C (max)": "max", 1648 | "Value #D": "p95", 1649 | "Value #D (mean)": "p95", 1650 | "Value #E": "p99", 1651 | "Value #E (mean)": "p99" 1652 | } 1653 | } 1654 | } 1655 | ], 1656 | "type": "table" 1657 | }, 1658 | { 1659 | "collapsed": false, 1660 | "gridPos": { 1661 | "h": 1, 1662 | "w": 24, 1663 | "x": 0, 1664 | "y": 39 1665 | }, 1666 | "id": 11, 1667 | "panels": [], 1668 | "title": "Checks", 1669 | "type": "row" 1670 | }, 1671 | { 1672 | "datasource": { 1673 | "type": "prometheus", 1674 | "uid": "${DS_PROMETHEUS}" 1675 | }, 1676 | "fieldConfig": { 1677 | "defaults": { 1678 | "color": { 1679 | "mode": "thresholds" 1680 | }, 1681 | "custom": { 1682 | "align": "auto", 1683 | "cellOptions": { 1684 | "type": "auto" 1685 | }, 1686 | "inspect": false 1687 | }, 1688 | "mappings": [], 1689 | "thresholds": { 1690 | "mode": "absolute", 1691 | "steps": [ 1692 | { 1693 | "color": "green" 1694 | } 1695 | ] 1696 | } 1697 | }, 1698 | "overrides": [ 1699 | { 1700 | "matcher": { 1701 | "id": "byName", 1702 | "options": "Success Rate" 1703 | }, 1704 | "properties": [ 1705 | { 1706 | "id": "custom.hidden", 1707 | "value": false 1708 | }, 1709 | { 1710 | "id": "unit", 1711 | "value": "%" 1712 | } 1713 | ] 1714 | }, 1715 | { 1716 | "matcher": { 1717 | "id": "byName", 1718 | "options": "Value (mean)" 1719 | }, 1720 | "properties": [ 1721 | { 1722 | "id": "custom.hidden", 1723 | "value": true 1724 | } 1725 | ] 1726 | }, 1727 | { 1728 | "matcher": { 1729 | "id": "byName", 1730 | "options": "check" 1731 | }, 1732 | "properties": [ 1733 | { 1734 | "id": "filterable", 1735 | "value": false 1736 | } 1737 | ] 1738 | } 1739 | ] 1740 | }, 1741 | "gridPos": { 1742 | "h": 8, 1743 | "w": 12, 1744 | "x": 0, 1745 | "y": 40 1746 | }, 1747 | "id": 12, 1748 | "options": { 1749 | "cellHeight": "sm", 1750 | "footer": { 1751 | "countRows": false, 1752 | "enablePagination": true, 1753 | "fields": "", 1754 | "reducer": [ 1755 | "sum" 1756 | ], 1757 | "show": false 1758 | }, 1759 | "frameIndex": 2, 1760 | "showHeader": true, 1761 | "sortBy": [ 1762 | { 1763 | "desc": true, 1764 | "displayName": "Value (count)" 1765 | } 1766 | ] 1767 | }, 1768 | "pluginVersion": "10.1.2", 1769 | "targets": [ 1770 | { 1771 | "datasource": { 1772 | "type": "prometheus", 1773 | "uid": "${DS_PROMETHEUS}" 1774 | }, 1775 | "editorMode": "code", 1776 | "exemplar": false, 1777 | "expr": "round(k6_checks_rate{testid=~\"$testid\"}, 0.1)", 1778 | "format": "table", 1779 | "instant": false, 1780 | "legendFormat": "__auto", 1781 | "range": true, 1782 | "refId": "A" 1783 | } 1784 | ], 1785 | "title": "Checks list", 1786 | "transformations": [ 1787 | { 1788 | "id": "labelsToFields", 1789 | "options": { 1790 | "keepLabels": [ 1791 | "__name__", 1792 | "check" 1793 | ], 1794 | "mode": "columns" 1795 | } 1796 | }, 1797 | { 1798 | "id": "groupBy", 1799 | "options": { 1800 | "fields": { 1801 | "Value": { 1802 | "aggregations": [ 1803 | "mean" 1804 | ], 1805 | "operation": "aggregate" 1806 | }, 1807 | "check": { 1808 | "aggregations": [], 1809 | "operation": "groupby" 1810 | }, 1811 | "k6_checks_rate": { 1812 | "aggregations": [ 1813 | "sum", 1814 | "count" 1815 | ], 1816 | "operation": "aggregate" 1817 | } 1818 | } 1819 | } 1820 | }, 1821 | { 1822 | "id": "calculateField", 1823 | "options": { 1824 | "alias": "Success Rate", 1825 | "binary": { 1826 | "left": "Value (mean)", 1827 | "operator": "*", 1828 | "reducer": "sum", 1829 | "right": "100" 1830 | }, 1831 | "mode": "binary", 1832 | "reduce": { 1833 | "reducer": "sum" 1834 | } 1835 | } 1836 | }, 1837 | { 1838 | "id": "convertFieldType", 1839 | "options": { 1840 | "conversions": [], 1841 | "fields": {} 1842 | } 1843 | } 1844 | ], 1845 | "type": "table" 1846 | }, 1847 | { 1848 | "datasource": { 1849 | "type": "prometheus", 1850 | "uid": "${DS_PROMETHEUS}" 1851 | }, 1852 | "description": "Filter by check name to query a particular check", 1853 | "fieldConfig": { 1854 | "defaults": { 1855 | "color": { 1856 | "mode": "palette-classic" 1857 | }, 1858 | "custom": { 1859 | "axisCenteredZero": false, 1860 | "axisColorMode": "text", 1861 | "axisLabel": "", 1862 | "axisPlacement": "auto", 1863 | "axisSoftMax": 100, 1864 | "axisSoftMin": 0, 1865 | "barAlignment": -1, 1866 | "drawStyle": "line", 1867 | "fillOpacity": 0, 1868 | "gradientMode": "none", 1869 | "hideFrom": { 1870 | "legend": false, 1871 | "tooltip": false, 1872 | "viz": false 1873 | }, 1874 | "insertNulls": false, 1875 | "lineInterpolation": "linear", 1876 | "lineWidth": 2, 1877 | "pointSize": 5, 1878 | "scaleDistribution": { 1879 | "type": "linear" 1880 | }, 1881 | "showPoints": "auto", 1882 | "spanNulls": false, 1883 | "stacking": { 1884 | "group": "A", 1885 | "mode": "none" 1886 | }, 1887 | "thresholdsStyle": { 1888 | "mode": "off" 1889 | } 1890 | }, 1891 | "mappings": [], 1892 | "thresholds": { 1893 | "mode": "absolute", 1894 | "steps": [ 1895 | { 1896 | "color": "green" 1897 | } 1898 | ] 1899 | }, 1900 | "unit": "%" 1901 | }, 1902 | "overrides": [] 1903 | }, 1904 | "gridPos": { 1905 | "h": 8, 1906 | "w": 12, 1907 | "x": 12, 1908 | "y": 40 1909 | }, 1910 | "id": 13, 1911 | "options": { 1912 | "legend": { 1913 | "calcs": [], 1914 | "displayMode": "list", 1915 | "placement": "bottom", 1916 | "showLegend": true 1917 | }, 1918 | "tooltip": { 1919 | "mode": "single", 1920 | "sort": "none" 1921 | } 1922 | }, 1923 | "targets": [ 1924 | { 1925 | "datasource": { 1926 | "type": "prometheus", 1927 | "uid": "${DS_PROMETHEUS}" 1928 | }, 1929 | "editorMode": "code", 1930 | "expr": "avg(round(k6_checks_rate{testid=~\"$testid\"}, 0.1)*100)", 1931 | "instant": false, 1932 | "legendFormat": "k6_checks_rate", 1933 | "range": true, 1934 | "refId": "A" 1935 | } 1936 | ], 1937 | "title": "Checks Success Rate (aggregate individual checks)", 1938 | "type": "timeseries" 1939 | }, 1940 | { 1941 | "datasource": { 1942 | "type": "prometheus", 1943 | "uid": "${DS_PROMETHEUS}" 1944 | }, 1945 | "gridPos": { 1946 | "h": 5, 1947 | "w": 24, 1948 | "x": 0, 1949 | "y": 48 1950 | }, 1951 | "id": 23, 1952 | "options": { 1953 | "code": { 1954 | "language": "plaintext", 1955 | "showLineNumbers": false, 1956 | "showMiniMap": false 1957 | }, 1958 | "content": "### Visualize other k6 results \n\nAt the top of the dashboard, click `Add` and select `Visualization` from the dropdown menu. Choose the visualization type and input the PromQL queries for the `k6_` metric(s).\n\nAlternatively, click on the `Explore` icon on the menu bar and input the queries for the `k6_` metric(s). From `Explore`, you can add new Panels to this dashboard. \n\nNote that all k6 metrics are prefixed with the `k6_` namespace when sent to Prometheus.", 1959 | "mode": "markdown" 1960 | }, 1961 | "pluginVersion": "10.1.2", 1962 | "type": "text" 1963 | } 1964 | ], 1965 | "refresh": "", 1966 | "schemaVersion": 38, 1967 | "style": "dark", 1968 | "tags": [ 1969 | "prometheus", 1970 | "k6" 1971 | ], 1972 | "templating": { 1973 | "list": [ 1974 | { 1975 | "current": { 1976 | "selected": false, 1977 | "text": "prometheus", 1978 | "value": "${DS_PROMETHEUS}" 1979 | }, 1980 | "description": "Choose a Prometheus Data Source", 1981 | "hide": 0, 1982 | "includeAll": false, 1983 | "label": "Prometheus DS", 1984 | "multi": false, 1985 | "name": "DS_PROMETHEUS", 1986 | "options": [], 1987 | "query": "prometheus", 1988 | "queryValue": "", 1989 | "refresh": 1, 1990 | "regex": "", 1991 | "skipUrlSync": false, 1992 | "type": "datasource" 1993 | }, 1994 | { 1995 | "current": {}, 1996 | "datasource": { 1997 | "type": "prometheus", 1998 | "uid": "${DS_PROMETHEUS}" 1999 | }, 2000 | "definition": "label_values(testid)", 2001 | "description": "Filter by \"testid\" tag. Define it by tagging: k6 run --tag testid=xyz", 2002 | "hide": 0, 2003 | "includeAll": true, 2004 | "label": "Test ID", 2005 | "multi": true, 2006 | "name": "testid", 2007 | "options": [], 2008 | "query": { 2009 | "query": "label_values(testid)", 2010 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 2011 | }, 2012 | "refresh": 2, 2013 | "regex": "", 2014 | "skipUrlSync": false, 2015 | "sort": 0, 2016 | "type": "query" 2017 | }, 2018 | { 2019 | "current": {}, 2020 | "datasource": { 2021 | "type": "prometheus", 2022 | "uid": "${DS_PROMETHEUS}" 2023 | }, 2024 | "definition": "metrics(k6_http_req_duration_)", 2025 | "description": "Statistic for Trend Metrics Queries. The available options depend on the values of the K6_PROMETHEUS_RW_TREND_STATS setting.", 2026 | "hide": 0, 2027 | "includeAll": false, 2028 | "label": "Trend Metrics Query", 2029 | "multi": false, 2030 | "name": "quantile_stat", 2031 | "options": [], 2032 | "query": { 2033 | "query": "metrics(k6_http_req_duration_)", 2034 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 2035 | }, 2036 | "refresh": 2, 2037 | "regex": "/http_req_duration_(min|max|count|sum|avg|med|p[0-9]+)/g", 2038 | "skipUrlSync": false, 2039 | "sort": 2, 2040 | "type": "query" 2041 | }, 2042 | { 2043 | "datasource": { 2044 | "type": "prometheus", 2045 | "uid": "${DS_PROMETHEUS}" 2046 | }, 2047 | "description": "Adhoc filters are applied to all panels. To enable it, go to Dashboard Settings / Variables / adhoc_filter and select the target Prometheus data source.", 2048 | "filters": [], 2049 | "hide": 0, 2050 | "label": "AdhocFilter", 2051 | "name": "adhoc_filter", 2052 | "skipUrlSync": false, 2053 | "type": "adhoc" 2054 | } 2055 | ] 2056 | }, 2057 | "time": { 2058 | "from": "now-5m", 2059 | "to": "now" 2060 | }, 2061 | "timepicker": {}, 2062 | "timezone": "", 2063 | "title": "k6 Prometheus", 2064 | "uid": "ccbb2351-2ae2-462f-ae0e-f2c893ad1028", 2065 | "version": 1, 2066 | "weekStart": "", 2067 | "gnetId": 19665 2068 | } -------------------------------------------------------------------------------- /config/grafana/datasources/datasource.yaml: -------------------------------------------------------------------------------- 1 | # For configuration options, see 2 | # https://grafana.com/docs/grafana/latest/administration/provisioning/#example-data-source-config-file 3 | 4 | apiVersion: 1 5 | 6 | datasources: 7 | - name: prometheus 8 | type: prometheus 9 | access: proxy 10 | orgId: 1 11 | url: http://prometheus:9090 12 | basicAuth: false 13 | isDefault: true 14 | jsonData: 15 | tlsAuth: false 16 | tlsAuthWithCACert: false 17 | editable: false -------------------------------------------------------------------------------- /config/prometheus.yaml: -------------------------------------------------------------------------------- 1 | scrape_configs: 2 | - job_name: quickpizza 3 | scrape_interval: 15s 4 | static_configs: 5 | - targets: 6 | - quickpizza:3333 -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | quickpizza: 5 | image: ghcr.io/grafana/quickpizza-local:latest 6 | ports: 7 | - 3333:3333 8 | 9 | prometheus: 10 | image: prom/prometheus:latest 11 | ports: 12 | - 9090:9090 13 | command: 14 | - --web.enable-remote-write-receiver 15 | - --enable-feature=native-histograms 16 | - --config.file=/etc/prometheus/prometheus.yaml 17 | 18 | volumes: 19 | - ./config/prometheus.yaml:/etc/prometheus/prometheus.yaml 20 | 21 | grafana: 22 | image: grafana/grafana:latest 23 | restart: always 24 | ports: 25 | - 3000:3000 26 | environment: 27 | - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin 28 | - GF_AUTH_ANONYMOUS_ENABLED=true 29 | - GF_AUTH_BASIC_ENABLED=false 30 | volumes: 31 | - ./config/grafana:/etc/grafana/provisioning/ 32 | 33 | networks: 34 | default: 35 | name: default_network -------------------------------------------------------------------------------- /media/k6-prometheus-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/k6-oss-workshop/6a2343fecd5e632706e6e8faa27cf292d105241b/media/k6-prometheus-dashboard.png -------------------------------------------------------------------------------- /media/menu-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /media/quickpizza-websocket-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/k6-oss-workshop/6a2343fecd5e632706e6e8faa27cf292d105241b/media/quickpizza-websocket-ui.png -------------------------------------------------------------------------------- /media/vus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/k6-oss-workshop/6a2343fecd5e632706e6e8faa27cf292d105241b/media/vus.png -------------------------------------------------------------------------------- /media/web-dashboard-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/k6-oss-workshop/6a2343fecd5e632706e6e8faa27cf292d105241b/media/web-dashboard-overview.png -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | 3 | const BASE_URL = __ENV.BASE_URL || "http://localhost:3333"; 4 | 5 | export default function () { 6 | let restrictions = { 7 | maxCaloriesPerSlice: 500, 8 | mustBeVegetarian: false, 9 | excludedIngredients: ["pepperoni"], 10 | excludedTools: ["knife"], 11 | maxNumberOfToppings: 6, 12 | minNumberOfToppings: 2, 13 | }; 14 | let res = http.post(`${BASE_URL}/api/pizza`, JSON.stringify(restrictions), { 15 | headers: { 16 | "Content-Type": "application/json", 17 | "X-User-ID": 23423, 18 | }, 19 | }); 20 | console.log(`${res.json().pizza.name} (${res.json().pizza.ingredients.length} ingredients)`); 21 | } --------------------------------------------------------------------------------