<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: k6</title>
    <description>The latest articles on Forem by k6 (@k6).</description>
    <link>https://forem.com/k6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1854%2F7061cda4-6e39-409b-a9e6-d3a308a1ce43.png</url>
      <title>Forem: k6</title>
      <link>https://forem.com/k6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/k6"/>
    <language>en</language>
    <item>
      <title>Get Started with k6 browser</title>
      <dc:creator>Marie Cruz</dc:creator>
      <pubDate>Wed, 11 Jan 2023 11:28:57 +0000</pubDate>
      <link>https://forem.com/k6/get-started-with-xk6-browser-2bpe</link>
      <guid>https://forem.com/k6/get-started-with-xk6-browser-2bpe</guid>
      <description>&lt;p&gt;This post explains how to get started with &lt;a href="https://github.com/grafana/xk6-browser" rel="noopener noreferrer"&gt;k6 browser&lt;/a&gt;, a &lt;a href="https://k6.io/docs/javascript-api/#k6-experimental" rel="noopener noreferrer"&gt;k6 experimental module&lt;/a&gt; that adds browser-level APIs to interact with browsers and collect web performance metrics as part of your k6 tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Beyond Protocol Level
&lt;/h2&gt;

&lt;p&gt;Over the years, k6 has become known as a performance testing tool that provides the best developer experience. Most of our efforts have focused on providing a tool that helps test your servers or backend systems. Our &lt;a href="https://k6.io/docs/testing-guides/load-testing-websites/" rel="noopener noreferrer"&gt;comprehensive load testing guide&lt;/a&gt; recommends keeping users in mind, and backend performance testing only addresses half of your performance testing efforts. &lt;/p&gt;

&lt;p&gt;Suppose you test the user experience of your website and verify that there are no performance issues on a specific user journey. In that case, &lt;strong&gt;you need to drive some of your performance testing efforts from a browser perspective and consider a more realistic end-to-end test of the user flow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most load-testing tools focus on testing API endpoints, but it's different from what your users normally interact with. Your users interact with the browser, so it's also vital to test the browser's performance to get an end-to-end perspective of what's happening when interacting with your web applications.&lt;/p&gt;

&lt;p&gt;Both frontend and backend performance testing has its pros and cons when done in isolation, which we discussed in more detail as part of the video below. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xVACRP5qIJI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here at k6, we want to start expanding our performance testing use case and also test beyond the protocol level.&lt;/p&gt;

&lt;p&gt;This is where the k6 browser module comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is k6 browser module?
&lt;/h2&gt;

&lt;p&gt;The browser module brings browser automation and end-to-end web testing to k6 while supporting core k6 features. It enables you to get insights from your front-end application during performance testing.&lt;/p&gt;

&lt;p&gt;You can now mix browser-level and protocol-level tests in a single and unified script using k6. This can simulate the bulk of your traffic from your protocol-level tests and run one or two virtual users on a browser level to mimic how a user interacts with your website, thus leveraging a hybrid approach to performance testing.&lt;/p&gt;

&lt;p&gt;The browser module offers a unique solution as you don’t have to use separate tools to test your frontend and backend systems. It also offers a simplified experience and aggregated view of performance metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing the test
&lt;/h3&gt;

&lt;p&gt;You can copy one of our example scripts to get started as part of our &lt;a href="https://k6.io/docs/javascript-api/k6-browser/get-started/running-browser-tests/" rel="noopener noreferrer"&gt;k6 browser module documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/experimental/browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down what’s happening with the preceding code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are importing &lt;code&gt;chromium&lt;/code&gt; from the &lt;code&gt;k6/experimental/browser&lt;/code&gt; module. &lt;code&gt;chromium&lt;/code&gt; is of type &lt;a href="https://k6.io/docs/javascript-api/xk6-browser/api/browsertype/" rel="noopener noreferrer"&gt;&lt;code&gt;BrowserType&lt;/code&gt;&lt;/a&gt;, which is k6 browser’s entry point into launching a browser process.&lt;/li&gt;
&lt;li&gt;Next, we use the &lt;a href="https://k6.io/docs/using-k6/test-lifecycle/#the-vu-stage" rel="noopener noreferrer"&gt;scenario function&lt;/a&gt;, an existing k6 functionality, to define our VU (virtual user) code.&lt;/li&gt;
&lt;li&gt;To create a new browser instance, we use the &lt;a href="https://k6.io/docs/javascript-api/k6-browser/api/browsertype/launch/" rel="noopener noreferrer"&gt;&lt;code&gt;launch&lt;/code&gt;&lt;/a&gt; method of &lt;code&gt;chromium&lt;/code&gt;, which returns a &lt;a href="https://k6.io/docs/javascript-api/k6-browser/api/browser/" rel="noopener noreferrer"&gt;&lt;code&gt;Browser&lt;/code&gt;&lt;/a&gt; object. You can pass different parameters within &lt;code&gt;launch&lt;/code&gt; and one of the parameters you can pass is &lt;code&gt;headless&lt;/code&gt;, which you can use to show the browser or not.&lt;/li&gt;
&lt;li&gt;To create a new page in your browser instance, we use the &lt;code&gt;browser.newPage()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, on to the fun part! Let’s simulate a user visiting a &lt;a href="https://test.k6.io/" rel="noopener noreferrer"&gt;test application&lt;/a&gt; and logging in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/experimental/browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io/my_messages.php&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="login"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome, admin!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a lot of things happening in the preceding code especially the introduction of &lt;a href="https://k6.io/docs/javascript-api/k6-browser/get-started/running-browser-tests/#asynchronous-operations" rel="noopener noreferrer"&gt;asynchronous operations&lt;/a&gt; so let’s break it down again.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We visit the page by using &lt;code&gt;page.goto&lt;/code&gt; and pass the test application URL. We are also waiting for the network to be idle, which will succeed if there are no network connections for at least 500 ms. &lt;code&gt;page.goto&lt;/code&gt; is also an asynchronous operation, so we need to wait for this to finish and use the &lt;code&gt;await&lt;/code&gt; keyword.&lt;/li&gt;
&lt;li&gt;Once the operation completes, we use &lt;a href="https://k6.io/docs/javascript-api/k6-browser/api/locator/" rel="noopener noreferrer"&gt;&lt;code&gt;page.locator&lt;/code&gt;&lt;/a&gt; to interact with the elements we want. In the example, we are creating two locators. One for the login name and another one for the login password. We use the &lt;a href="https://k6.io/docs/javascript-api/k6-browser/api/locator/type/" rel="noopener noreferrer"&gt;&lt;code&gt;type&lt;/code&gt;&lt;/a&gt; method to type the name and password into the fields.&lt;/li&gt;
&lt;li&gt;To click the login button, we use the &lt;a href="https://k6.io/docs/javascript-api/k6-browser/api/locator/click/" rel="noopener noreferrer"&gt;&lt;code&gt;click&lt;/code&gt;&lt;/a&gt; method, which is an asynchronous operation. Clicking the submit button also causes page navigation which we need to wait to load, so &lt;code&gt;page.waitForNavigation()&lt;/code&gt;, another asynchronous operation, is needed because the page won't be ready until the navigation completes.&lt;/li&gt;
&lt;li&gt;Since there are two asynchronous operations, we need to use &lt;code&gt;Promise.all([])&lt;/code&gt; to wait for the two promises to be resolved before continuing to avoid any race conditions.&lt;/li&gt;
&lt;li&gt;Next, we use the &lt;a href="https://k6.io/docs/javascript-api/k6/check/" rel="noopener noreferrer"&gt;check&lt;/a&gt; feature from k6 to assert the text content of a specific element.&lt;/li&gt;
&lt;li&gt;Finally, we close the page and the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Running the Test
&lt;/h3&gt;

&lt;p&gt;To run the test, simply use the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;K6_BROWSER_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;k6 run script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you face any issues running the command, please check out our &lt;a href="https://k6.io/docs/javascript-api/k6-browser/get-started/running-browser-tests/#run-a-test" rel="noopener noreferrer"&gt;documentation for running the test&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You should see a similar test run as this &lt;a href="https://user-images.githubusercontent.com/10811379/221546009-1292553c-1eec-4982-a97f-6e9d26531484.mp4" rel="noopener noreferrer"&gt;video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the browser launching, this provides a more visual experience as to what your users actually see so you can also find blind spots and catch issues related to browsers that won't be detected on a protocol-level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser Metrics
&lt;/h3&gt;

&lt;p&gt;When it's finished running, apart from the metrics that k6 already tracks, additional &lt;a href="https://k6.io/docs/javascript-api/k6-browser/get-started/browser-metrics/" rel="noopener noreferrer"&gt;browser metrics&lt;/a&gt; are tracked as part of the k6 summary output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  browser_dom_content_loaded.......: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;36.72ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;544µs   &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22.61ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;87.02ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;74.14ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;80.58ms
  browser_first_contentful_paint...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47.52ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22.01ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47.52ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;73.02ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;67.92ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;70.47ms
  browser_first_meaningful_paint...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75.22ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75.22ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75.22ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75.22ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;75.22ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;75.22ms
  browser_first_paint..............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45.72ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21.96ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45.72ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;69.49ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;64.73ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;67.11ms
  browser_loaded...................: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;38.14ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5.28ms  &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22.45ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;86.68ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;73.83ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;80.26ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metrics above are still subject to change. Our goal is to be able to report the &lt;a href="https://web.dev/vitals/#core-web-vitals" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt; as well as &lt;a href="https://web.dev/vitals/#other-web-vitals" rel="noopener noreferrer"&gt;Other Web Vitals&lt;/a&gt; for a better understanding of user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  k6 Browser API
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://k6.io/docs/javascript-api/k6-browser/api/" rel="noopener noreferrer"&gt;k6 Browser API&lt;/a&gt; aims to provide a rough compatibility with the &lt;a href="https://playwright.dev/docs/api/class-playwright" rel="noopener noreferrer"&gt;Playwright API&lt;/a&gt; for NodeJS, meaning k6 users don't have to learn an entirely new API.&lt;/p&gt;

&lt;p&gt;At the moment, the k6 API is synchronous. However, since many browser operations happen asynchronously, and in order to follow the Playwright API more closely, we are working on migrating most of the browser methods to be asynchronous. This means that while k6 browser is ready to be used, be warned that our API is still undergoing a few changes.&lt;/p&gt;

&lt;p&gt;At the moment, few methods such as &lt;code&gt;page.goto()&lt;/code&gt;, &lt;code&gt;page.waitForNavigation()&lt;/code&gt; and &lt;code&gt;Locator.click()&lt;/code&gt; return &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises" rel="noopener noreferrer"&gt;JavaScript promises&lt;/a&gt;. Our goal is to support async and await syntax for all asynchronous operations, for simplicity.&lt;/p&gt;

&lt;p&gt;For more examples on how to use the k6 browser API, please check out &lt;a href="https://github.com/grafana/xk6-browser/tree/main/examples" rel="noopener noreferrer"&gt;k6 browser examples&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Hybrid Approach to Performance Testing
&lt;/h2&gt;

&lt;p&gt;If you only consider web performance, this can lead to false confidence in overall application performance when the amount of traffic against an application increases.&lt;/p&gt;

&lt;p&gt;It's still highly recommended to also test your backend systems to have a complete picture of your application’s performance, via the protocol-level.&lt;/p&gt;

&lt;p&gt;However, there are problems associated with testing via the protocol level, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not being closer to the user experience since it’s skipping the browser,&lt;/li&gt;
&lt;li&gt;scripts can be lengthy to create and difficult to maintain as the application grows,&lt;/li&gt;
&lt;li&gt;browser performance metrics are ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the other hand, if you perform load testing by spinning up a lot of browsers, this requires significantly more load-generation resources, which can end up quite costly.&lt;/p&gt;

&lt;p&gt;To address the shortcomings of each approach, a recommended practice is to adopt a &lt;a href="https://k6.io/docs/testing-guides/load-testing-websites/#hybrid-load-testing" rel="noopener noreferrer"&gt;hybrid approach to performance testing&lt;/a&gt;, which is a combination of testing both the backend and frontend systems via protocol and browser level. With a hybrid approach, you spin up the majority of your load via the protocol level, then simultaneously have one or two browser-level virtual users, so you can also have a view of what’s happening on the front end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5ougnent4lykfbgsp9r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5ougnent4lykfbgsp9r.png" alt="An illustration of k6 testing frontend system as well as backend systems for a hybrid approach to performance testing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The great thing with k6 browser module is that it can offer you this hybrid approach to performance testing. While you can do this with multiple tools, the beauty of using this module is that it's built on top of k6, which means that you can have a protocol-level and a browser-level test in the same script.&lt;/p&gt;

&lt;p&gt;Let's see how that translates to code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing the test
&lt;/h3&gt;

&lt;p&gt;A common scenario that we recommend is to mix a smaller subset of browser-level tests with a larger protocol-level test. To run a browser-level and protocol-level test concurrently, you can use &lt;a href="https://k6.io/docs/using-k6/scenarios/" rel="noopener noreferrer"&gt;scenarios&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/experimental/browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constant-vus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;news&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constant-vus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;news&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io/browser.php&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#checkbox1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkbox is checked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#checkbox-info-display&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thanks for checking the box&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;news&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io/news.php&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the preceeding code again.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are using &lt;a href="https://k6.io/docs/using-k6/k6-options/" rel="noopener noreferrer"&gt;options&lt;/a&gt; to configure our test-run behaviour. In this particular script, we are declaring two scenarios to configure specific workload, one for the browser-level test called &lt;code&gt;browser&lt;/code&gt; and one for the protocol-level test called &lt;code&gt;news&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Both the &lt;code&gt;browser&lt;/code&gt; and &lt;code&gt;news&lt;/code&gt; scenario are using the &lt;a href="https://k6.io/docs/using-k6/scenarios/executors/constant-vus/" rel="noopener noreferrer"&gt;constant-vu executor&lt;/a&gt; which introduces a constant number of virtual users to execute as many iterations as possible for a specified amount of time.&lt;/li&gt;
&lt;li&gt;Next, there are two JavaScript functions declared, &lt;code&gt;browser()&lt;/code&gt; and &lt;code&gt;news()&lt;/code&gt;. These functions contain the code that will be executed by a virtual user. The &lt;code&gt;browser()&lt;/code&gt; function, represents our browser-level test and simply visits a test URL, clicks a checkbox and verifies if the checkbox has been ticked successfully while the &lt;code&gt;news()&lt;/code&gt; function, which represents our protocol-level test, sends a GET request to a different URL and checks if the status code is returning 200.&lt;/li&gt;
&lt;li&gt;Since we are using scenarios, the two functions are independent from each other and therefore, runs in parallel.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Running the test
&lt;/h3&gt;

&lt;p&gt;Using the same k6 run command as above, you should see a similar test output as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;running &lt;span class="o"&gt;(&lt;/span&gt;1m00.1s&lt;span class="o"&gt;)&lt;/span&gt;, 00/21 VUs, 12953 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
browser ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 1 VUs   10s
news    ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 20 VUs  1m0s

     ✓ status is 200
     ✓ checkbox is checked

     browser_dom_content_loaded.......: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.27ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;68µs    &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11.76ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;26.77ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;25.56ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;26.36ms
     browser_first_contentful_paint...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21.93ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.5ms  &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;25.32ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;26.19ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;25.83ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;26.01ms
     browser_first_paint..............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21.88ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.45ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;25.27ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;26.14ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;25.78ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;25.96ms
     browser_loaded...................: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.18ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;984µs   &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11.74ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;25.65ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;24.37ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;25.19ms
     checks...........................: 100.00% ✓ 12953      ✗ 0
     data_received....................: 21 MB   341 kB/s
     data_sent........................: 1.4 MB  24 kB/s
     http_req_blocked.................: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.14ms   &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1µs     &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2µs     &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;290.37ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;4µs     p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;4µs
     http_req_connecting..............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.09ms   &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s      &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;195ms    p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;0s      p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;0s
     http_req_duration................: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;90.59ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80.93ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;92.8ms  &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;542.92ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;96.89ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;102.83ms
       &lt;span class="o"&gt;{&lt;/span&gt; expected_response:true &lt;span class="o"&gt;}&lt;/span&gt;.....: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;90.49ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80.93ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;92.8ms  &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;542.92ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;96.86ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;102.81ms
     http_req_failed..................: 0.00%   ✓ 0          ✗ 12946
     http_req_receiving...............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;154.41µs &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;17µs    &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;47µs    &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;97ms     p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;67µs    p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;76µs
     http_req_sending.................: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20.36µs  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s      &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;14µs    &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;32.36ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;20µs    p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;21µs
     http_req_tls_handshaking.........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.16ms   &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s      &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;183.1ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;0s      p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;0s
     http_req_waiting.................: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;90.41ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80.85ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;92.72ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;542.86ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;96.77ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;102.74ms
     http_reqs........................: 12960   215.658713/s
     iteration_duration...............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;93.58ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;81.05ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;92.95ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.83s    p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;97.54ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;103.37ms
     iterations.......................: 12953   215.542231/s
     vus..............................: 20      &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21
     vus_max..........................: 21      &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since it's all in one script, this allows for greater collaboration amongst teams and a unified view of the performance metrics from a browser-level and protocol-level perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;While k6 browser started off as an &lt;a href="https://k6.io/docs/extensions/" rel="noopener noreferrer"&gt;extension&lt;/a&gt;, as of &lt;a href="https://github.com/grafana/k6/releases/tag/v0.43.0" rel="noopener noreferrer"&gt;k6 version 0.43.0&lt;/a&gt;, it is now bundled in k6 as an experimental module, and usable without a separate binary or compilation step! We also have further plans to integrate k6 browser in k6 cloud as part of a private beta. We consider browser automation an important part of web application testing, and we have big goals for k6 browser. Our &lt;a href="https://github.com/grafana/xk6-browser/blob/main/ROADMAP.md" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; details essential status updates and our short, mid, and long-term goals.&lt;/p&gt;

&lt;p&gt;With that said, we need your help! Since k6 browser is still relatively new, we need help from the community to try out the tool and give us feedback.&lt;/p&gt;

&lt;p&gt;Check our &lt;a href="https://github.com/grafana/xk6-browser" rel="noopener noreferrer"&gt;GitHub project&lt;/a&gt;, read our &lt;a href="https://k6.io/docs/javascript-api/k6-browser/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, and play with the tool. If you find any issues, please raise them on our GitHub project or check out our &lt;a href="https://community.k6.io/c/xk6-browser/14" rel="noopener noreferrer"&gt;community forum&lt;/a&gt; for additional support.&lt;/p&gt;

&lt;h2&gt;
  
  
  See Also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://k6.io/docs/testing-guides/load-testing-websites" rel="noopener noreferrer"&gt;A k6 guide for load testing websites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=xVACRP5qIJI" rel="noopener noreferrer"&gt;Video: Frontend vs. backend performance testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k6.io/docs/javascript-api/k6-browser/" rel="noopener noreferrer"&gt;k6 browser documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>performance</category>
      <category>opensource</category>
      <category>tutorial</category>
      <category>k6</category>
    </item>
    <item>
      <title>Testing the new AWS ARM CPU's with k6</title>
      <dc:creator>Carlos Ruiz Lantero</dc:creator>
      <pubDate>Thu, 25 Nov 2021 11:17:55 +0000</pubDate>
      <link>https://forem.com/k6/testing-the-new-aws-arm-cpus-with-k6-1ffb</link>
      <guid>https://forem.com/k6/testing-the-new-aws-arm-cpus-with-k6-1ffb</guid>
      <description>&lt;p&gt;Recently &lt;a href="https://www.zdnet.com/article/aws-makes-arm-powered-lambda-functions-generally-available/"&gt;AWS made the new ARM processors for Lambda functions generally available&lt;/a&gt;. With that change Serverless functions now use Graviton2, said to offer better performance at lower cost.&lt;/p&gt;

&lt;p&gt;I built a sample API on AWS using API Gateway and Lambda, and I wrote two endpoints, one &lt;a href="https://gist.github.com/Lantero/53409874583c68336bae04c14e6c4986"&gt;CPU-intensive&lt;/a&gt; (calculating Pi using Leibniz's formula), the other a typical &lt;a href="https://gist.github.com/Lantero/00bcde883c014a5a31640f76921a8ec5"&gt;data transfer&lt;/a&gt; endpoint (returning an arbitrary number of bytes). Two very different endpoints for my experiment.&lt;/p&gt;

&lt;p&gt;One hard limit on the size of my experiment was that my personal AWS account allows me no more than 50 unreserved concurrency, which roughly means I cannot have more than 50 Lambda instances running in parallel. However, you don't need big numbers in order to gain insights. &lt;br&gt;
​&lt;/p&gt;
&lt;h2&gt;
  
  
  The tests
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
I deployed both endpoints in both x86_64 (Intel) and arm64 (Graviton2). The tests were largely the same, ramp up type tests. For the CPU-intensive one I used a large number of iterations of the Leibniz formula (half a million to be precise). The data tranfer one requested 100 kilobytes.&lt;br&gt;
​&lt;br&gt;
The CPU-intensive test:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x86&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
            &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x86&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;500000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
            &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;500000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/pi/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iterations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;br&gt;
And the data transfer test:&lt;br&gt;
​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x86&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
            &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x86&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;102400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
            &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;102400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/zerobytes/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Parts omitted&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  Results (data transfer)
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
The test results for the data tranfer endpoint were roughly what I had expected. Little difference between the two architectures, seeing that the endpoint doesn't really push the CPU to do much.&lt;/p&gt;

&lt;p&gt;The one interesting thing to note though is that the response time starts at 250ms before stabilizing at 100ms. This is typical for &lt;a href="https://mikhail.io/serverless/coldstarts/aws/"&gt;Lambda cold start&lt;/a&gt;, Lambda cold start lays within the ~100/200ms range. When you first trigger a Lambda function, AWS needs to spin up the infrastructure underneath, likely a container. Once you have your instances running, all your subsequent requests take much less time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3hSnaZDy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktsijr7por854bdusqra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3hSnaZDy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktsijr7por854bdusqra.png" alt="data-transfer-results" width="880" height="234"&gt;&lt;/a&gt;&lt;br&gt;
​&lt;/p&gt;

&lt;h3&gt;
  
  
  Results (CPU load)
&lt;/h3&gt;

&lt;p&gt;​&lt;br&gt;
For our CPU-intensive endpoint again we see that cold start, although the graph isn't as steep as in the previous example, due to regular response time being much bigger in comparison. What's interesting here though is that x86 is 20% faster than the new chips, which is exactly the opposite of what AWS claims.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OLRlrWSI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi17aruxrxni2e3shfdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OLRlrWSI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zi17aruxrxni2e3shfdh.png" alt="cpu-load-results" width="880" height="239"&gt;&lt;/a&gt;&lt;br&gt;
​&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;​&lt;br&gt;
What I learned from this experience is that while AWS boldly claims arm is 20% faster and 20% cheaper, the results of my experiment say it's rather 20% slower and 20% cheaper. This might vary based on the chip instructions you use, this implementation of the Leibniz formula consists of loops, assigments, additions and other basic mathematical operations. But it turns out others are seeing similar results, like the folks at &lt;a href="https://www.narakeet.com/tech/graviton-lambda-ffmpeg.html"&gt;Narakeet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's not bad news though. Non-CPU workloads still benefit from the price at no speed trade-off. Arm also draws less power, which is better for the planet. &lt;/p&gt;

&lt;p&gt;While the experiment covers Lambda, these findings could be extrapolated to EC2. &lt;/p&gt;

&lt;p&gt;The concurrency (number of lambda instances running at the same time) roughly equals the number of requests per second you're running for your API, multiplied by the response time in seconds. It'd be interesting to monitor how this behaves over time and with higher load.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>arm</category>
      <category>k6</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Finding .NET Memory Leaks through Soak Testing</title>
      <dc:creator>Carlos Schults</dc:creator>
      <pubDate>Tue, 02 Nov 2021 15:25:57 +0000</pubDate>
      <link>https://forem.com/k6/finding-net-memory-leaks-through-soak-testing-2ibe</link>
      <guid>https://forem.com/k6/finding-net-memory-leaks-through-soak-testing-2ibe</guid>
      <description>&lt;p&gt;As you’re probably aware, C# is a modern, garbage collected language, which means you don’t have to take care of object disposal as you would in languages like C++. However, that doesn’t mean .NET engineers don’t face memory problems, such as memory leaks.&lt;/p&gt;

&lt;p&gt;In this post, we’ll walk you through the basics of memory leaks in .NET: what they are, how they occur, and why they matter. We’ll also show you how it’s possible to use techniques and tools already at your disposal — in particular, &lt;a href="https://k6.io/docs/test-types/soak-testing/"&gt;soak testing&lt;/a&gt; — to diagnose and fix memory issues.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET Garbage Collection 101
&lt;/h2&gt;

&lt;p&gt;To start, we’ll briefly explain how the .NET garbage collection process works. To learn about GC in more detail, refer to Microsoft’s &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals"&gt;Fundamentals of garbage collection&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Intro To GC
&lt;/h3&gt;

&lt;p&gt;At a high level, GC works like this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The analysis starts with an object that’s guaranteed to be alive, which are called “root objects.” For instance, local variables. &lt;/li&gt;
&lt;li&gt;From the root object, the GC starts to follow all of its references until it reaches the end of the graph. Every visited object is marked as alive. &lt;/li&gt;
&lt;li&gt;The process starts again for the other root objects.&lt;/li&gt;
&lt;li&gt;By the end of the process, unvisited objects are deemed unreachable and marked for deletion.&lt;/li&gt;
&lt;li&gt;Dead objects are cleaned from memory, but that leaves a lot of “holes” in memory. It’s better if the free areas in memory are kept together, so memory is compacted after the cleaning process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we’ve just described is an oversimplified view of GC; we left a lot of details out on purpose. However, you can already see that the process is a lot of work. It takes a while to complete, and thread execution is halted during the process.&lt;/p&gt;

&lt;p&gt;Thus, garbage collection, despite being valuable, is costly for your app’s performance. How to get out of this conundrum?&lt;/p&gt;

&lt;h3&gt;
  
  
  Talking ‘Bout My Generation: Making GC More Efficient
&lt;/h3&gt;

&lt;p&gt;To solve this dilemma, we need to run the collection process as infrequently as we can get away with it. Since every time counts, we’ve got to make sure GC is as efficient as possible.&lt;br&gt;
.NET achieves this by using an approach that understands that not all objects are created equal: it organizes them in different spaces in memory called “generations.”&lt;/p&gt;

&lt;p&gt;Generation 0 (G0) is the first generation, where newly created objects go by default. This generation is the one where collection happens more often, based on the assumption that young objects are less likely to have relationships with other objects and are more likely to be eligible for collection as a result.&lt;/p&gt;

&lt;p&gt;Generation 1 (G1) is the next generation. Objects from G0 who survive a collection are promoted to G1. Here, collection happens less frequently.&lt;/p&gt;

&lt;p&gt;Finally, you have Generation 2 (G2), which is the generation for long lived objects. Here, collection happens very rarely. Objects that make it to this generation are likely to be alive for as long as the application is in use.&lt;/p&gt;

&lt;p&gt;GC operates on some assumptions. One of them is that large objects live longer. &lt;/p&gt;

&lt;p&gt;If a large object were to be allocated in G0, that would create performance problems. It’d have to progress until G2, and the process of compacting it would be costly.&lt;/p&gt;

&lt;p&gt;But that’s not what happens. Large objects (&amp;gt; 85 KB in size) are automatically promoted to an area called the Large Object Heap (LOH.) Unlike the Small Object Heap (SOH), the LOH doesn’t have generations, it’s collected along with G2.&lt;/p&gt;
&lt;h2&gt;
  
  
  .NET Memory Leaks 101
&lt;/h2&gt;

&lt;p&gt;Having covered the fundamentals of the garbage collection process, let’s move on to memory leaks.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Are Memory Leaks In .NET and Why Do They Happen?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Memory_leak"&gt;Wikipedia&lt;/a&gt; defines memory leaks as follows:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;[...] a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory which is no longer needed is not released. A memory leak may also happen when an object is stored in memory but cannot be accessed by the running code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It might seem counterintuitive that .NET can suffer from memory leaks from the definition above. After all, aren’t we talking about a managed environment?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/"&gt;Michael Shpilt&lt;/a&gt; argues that are two types of memory leaks in .NET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;managed memory leaks&lt;/strong&gt;, which happen when you have obsolete objects that don’t get collected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;unmanaged memory leaks&lt;/strong&gt;, which happen if you allocate unmanaged memory and don’t free it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Why Should You Care About Memory Leaks?
&lt;/h3&gt;

&lt;p&gt;Memory leaks can give you a severe headache if you don’t have them on your radar.&lt;/p&gt;

&lt;p&gt;Suppose you have a number of short-lived objects which are no longer needed, but they can’t be collected, because they’re being referenced by other objects. The fact that they’re unnecessarily kept alive will impact performance: besides the unnecessary memory they use, they will cause collections, which are costly.&lt;/p&gt;

&lt;p&gt;Besides degrading performance, severe memory leaks can cause an OutOfMemory exception which will crash your application.&lt;/p&gt;
&lt;h2&gt;
  
  
  Seeing a Memory Leak In Action
&lt;/h2&gt;

&lt;p&gt;Now, I’ll use soak testing to allow us to see a memory leak in action. This will be a simulated example; I’ll use an application I deliberately modified to introduce a memory problem (described in &lt;a href="https://offering.solutions/blog/articles/2016/07/29/how-to-create-an-asp.net-core-webapi/"&gt;this blog post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;You can download the modified version &lt;a href="https://github.com/carlosschults/ASPNETCore-WebAPI-Sample"&gt;from this GitHub repo&lt;/a&gt;. Clone it using Git or download everything as a .zip file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{id:int}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GetSingleFood&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
        &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;ActionResult&lt;/span&gt; &lt;span class="nx"&gt;GetSingleFood&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApiVersion&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// this is the offending line&lt;/span&gt;
            &lt;span class="nx"&gt;FoodEntity&lt;/span&gt; &lt;span class="nx"&gt;foodItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_foodRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetSingle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strings&lt;/code&gt; in the code above refer to a static property of type ConcurrentBag. To sum it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;with each request to this particular endpoint—which is the endpoint we’ll be testing during our endurance test—I create a useless big string.&lt;/li&gt;
&lt;li&gt;the created string is added to a static collection, which ensures its reference will live on and not be collected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, with just two lines of code we ensure the allocation of unnecessary memory and its artificially induced longevity.&lt;/p&gt;

&lt;p&gt;Now, I’ll go to my terminal, access the project’s folder and run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;SampleWebApiAspNetCore
dotnet run &lt;span class="nt"&gt;--configuration&lt;/span&gt; Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, I can open my browser at &lt;a href="https://localhost:5001/API/v1/Foods"&gt;https://localhost:5001/API/v1/Foods&lt;/a&gt; to see the API returning an array of foods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// &lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hamburger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;calories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2021-08-24T08:28:57.6607216-03:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;links&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://localhost:5001/api/v1/foods/2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;self&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://localhost:5001/api/v1/foods/2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete_food&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://localhost:5001/api/v1/foods/2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_food&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running Soak Testing With k6
&lt;/h3&gt;

&lt;p&gt;Earlier, I’ve mentioned there are techniques and tools you can use to fight memory problems. It’s now time to meet the technique (soak testing) and the tool (k6) I’ll use in this tutorial.&lt;/p&gt;

&lt;h4&gt;
  
  
  What is Soak Testing?
&lt;/h4&gt;

&lt;p&gt;There are many kinds of performance testing, and soak testing is one of them. &lt;/p&gt;

&lt;p&gt;Unlike &lt;a href="https://dev.to/k6/intro-to-testing-asp-net-apis-with-k6-when-unit-tests-meet-load-testing-5b5h"&gt;load testing&lt;/a&gt;, which is targeted at evaluating the app’s performance at a given point in time, soak testing determines how reliable the application is over an extended period of time. That’s why soak testing is also called endurance testing.&lt;/p&gt;

&lt;p&gt;How does soak testing work in practice? You basically put a lot of pressure on your system for a few hours. That way, you simulate what would’ve been days of traffic in way less time, potentially uncovering memory leaks, poor configuration, bugs or edge cases that would only surface after an extended period of time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Getting Started With k6
&lt;/h4&gt;

&lt;p&gt;For our tests, we’ll use k6, which is an open-source tool for scriptable performance testing.&lt;/p&gt;

&lt;p&gt;If you want to follow along, please refer to &lt;a href="https://k6.io/docs/getting-started/installation/"&gt;the installation guide&lt;/a&gt;. To test your installation, try to follow the &lt;a href="https://k6.io/docs/getting-started/running-k6/"&gt;minimum test example from k6’s documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating The Test Script
&lt;/h4&gt;

&lt;p&gt;k6 is a scripted tool. That means you write a script that details the steps that will be taken during the test. In the case of k6, you write scripts in JavaScript, which is great: JavaScript is the language of the web, so it’s very likely you and your coworkers have at least some familiarity with it.&lt;/p&gt;

&lt;p&gt;I created a file called soak.js, with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;56m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://localhost:5001/API/v1/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Foods/1/`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Foods/2/`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Foods/3/`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Foods/4/`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the explanations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The script starts with the necessary imports;&lt;/li&gt;
&lt;li&gt;Then, it declares an object named options, that represents the configuration for the test, detailing three different stages:

&lt;ul&gt;
&lt;li&gt;the first will last for 2 minutes, ramping from 0 to 200 VUs (virtual users)&lt;/li&gt;
&lt;li&gt;the second will last for 56 minutes, staying at 200 users&lt;/li&gt;
&lt;li&gt;the last one will go down to zero users again, lasting for 2 minutes&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;After that, we declare the URL for the calls, and the endpoints of the API that will be hit during the test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, with k6 you only need a few lines of code to create a test that will put the application under a heavy pressure for an extended period of time. More specifically, it’s this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;56m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that’s responsible for making this test a “soak”, or endurance test. It ensures k6 will hit the API simulating 200 users for almost an hour.&lt;/p&gt;

&lt;p&gt;Of course, the configuration we’re using is just an example. In real usage scenarios, you implement a k6 script simulating a realistic user flow with the expected frequent traffic.&lt;/p&gt;

&lt;h4&gt;
  
  
  Running The Test Script
&lt;/h4&gt;

&lt;p&gt;Now, I’ll run the test. I go to my terminal and run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k6 run soak.js &lt;span class="nt"&gt;--out&lt;/span&gt; cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before this, I logged in my k6 Cloud account using my account token. The &lt;code&gt;--out cloud&lt;/code&gt; option means that the test itself is run locally, but the results get sent to k6 Cloud, where I can analyze them afterward. &lt;/p&gt;

&lt;h4&gt;
  
  
  Analyzing The Results
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m_aNaWQ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qz4h5bcg4z8su582xoq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m_aNaWQ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qz4h5bcg4z8su582xoq.png" alt="analysing results k6 Cloud" width="880" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above displays the results of the first test. Here’s the overview for the test run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;514676 requests were made.&lt;/li&gt;
&lt;li&gt;From those, 37511 resulted in failure (or 7.29%)—these are tracked by the red line.&lt;/li&gt;
&lt;li&gt;The average request rate was 687 requests per second, peaking at 797.71—this value is tracked in the chart with the purple line.&lt;/li&gt;
&lt;li&gt;The average response time was 34 milliseconds, tracked with the sky blue line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the graph above, we can see that, after some 13 minutes of testing, the application simply couldn’t handle the pressure and crashed, and after that point, all requests resulted in failure until the end of the test run.&lt;/p&gt;

&lt;p&gt;Now, take a look at the following image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HCwBgq2o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/otggicny9kem94jnbue4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HCwBgq2o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/otggicny9kem94jnbue4.png" alt="Visual Studio profiler" width="685" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a screenshot from Visual Studio’s profiler. As you can see, even before reaching the 2 minute mark, the program’s memory consumption skyrocketed and two garbage collections had already taken place!&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing The Application’s Code
&lt;/h3&gt;

&lt;p&gt;“Fixing” the memory issue of this application would be the easiest task ever, since I was the one who introduced it in the first place! Here’s what the correct version of the method should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{id:int}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GetSingleFood&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
        &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;ActionResult&lt;/span&gt; &lt;span class="nx"&gt;GetSingleFood&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ApiVersion&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;FoodEntity&lt;/span&gt; &lt;span class="nx"&gt;foodItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_foodRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetSingle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foodItem&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ExpandSingleFoodItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foodItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Were this a real situation, though, how could the results from this test run help us in detecting the underlying issue?&lt;/p&gt;

&lt;p&gt;Since we’re testing the Foods endpoint on this API, a good place to start the investigation would be the controller action responsible for that specific GET endpoint.&lt;/p&gt;

&lt;p&gt;In our example, we would quickly find the code responsible for creating a lot of memory leakage. In a real scenario, that would be the starting point of a debugging session. With the help of a profiler, you’d be more likely to find the culprit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Soak Testing With k6, Again
&lt;/h3&gt;

&lt;p&gt;After “fixing” the code, I’ll now run the tests again, so we can compare the results of both test runs.&lt;/p&gt;

&lt;p&gt;Here are the results:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZYYBd8v6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8ifolp92wl86ayhnwhx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZYYBd8v6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8ifolp92wl86ayhnwhx.png" alt="Results fixed code k6 Cloud" width="880" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This time 2473732 requests were made.&lt;/li&gt;
&lt;li&gt;From those, only 764 resulted in failure, which corresponds to 0.03%, as you can see in the red line in the chart.&lt;/li&gt;
&lt;li&gt;This time, the purple line, tracking the successful requests, remains stable for virtually the entire session, just taking a deep dive about midway through the tests, but then making a full recovery. &lt;/li&gt;
&lt;li&gt;At the same spot, you can see the request time—tracked by the sky blue line—skyrocketed.&lt;/li&gt;
&lt;li&gt;The average request rate was 143 requests per second, peaking at 800.&lt;/li&gt;
&lt;li&gt;The average response time was 21 milliseconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;.NET isn’t immune to memory issues, among which memory leaks are probably the most insidious ones. Memory leaks are often silent; if you don’t watch out for them, they can sneak in your application and corrode its performance until it’s too late.&lt;/p&gt;

&lt;p&gt;The good news is that it’s possible to fight back. By understanding the fundamentals of GC and memory management, you can write code in such a way that minimizes the likelihood of problems.&lt;/p&gt;

&lt;p&gt;Education can only take you so far, though. At some point, you have to leverage the tools and techniques available to you.That includes profilers, such as CodeTrack.&lt;/p&gt;

&lt;p&gt;Another great tool for your arsenal is k6. Although more widely known as a load testing tool, k6 also enables you to perform soak/endurance testing to your applications, which can help you uncover memory issues.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>memory</category>
      <category>performance</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to Load Test Your Kafka Producers and Consumers using k6</title>
      <dc:creator>Ng Wai Foong</dc:creator>
      <pubDate>Wed, 22 Sep 2021 01:34:25 +0000</pubDate>
      <link>https://forem.com/k6/how-to-load-test-your-kafka-producers-and-consumers-using-k6-2l8k</link>
      <guid>https://forem.com/k6/how-to-load-test-your-kafka-producers-and-consumers-using-k6-2l8k</guid>
      <description>&lt;p&gt;Recently, k6 started supporting &lt;a href="https://github.com/grafana/xk6"&gt;k6 extensions&lt;/a&gt; to extend k6 capabilities for other cases required by the community. The community has already built &lt;a href="https://k6.io/docs/ecosystem/"&gt;plenty of extensions&lt;/a&gt;. k6 extensions are written in Go, and many of them are reusing existing Go libraries. &lt;/p&gt;

&lt;p&gt;This makes k6 to be a versatile tool to test different protocols and adapt to multiple cases. This post is the third part of my series of articles testing various systems using k6:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/k6/load-testing-sql-databases-with-k6-30ci"&gt;Load Testing SQL Databases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/k6/benchmarking-redis-with-k6-33kf"&gt;Benchmarking Redis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look in this post how we test the popular Kafka project. &lt;a href="https://kafka.apache.org/intro"&gt;Apache Kafka&lt;/a&gt; is a powerful event streaming platform that provides the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write and read streams of events&lt;/li&gt;
&lt;li&gt;Store streams of events for as long as you want to&lt;/li&gt;
&lt;li&gt;Process streams of events in parallel retrospectively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works by having client applications write events to the Kafka server. We term this type of application as &lt;strong&gt;Producers&lt;/strong&gt;. Client applications which read and process events from the Kafka server are called &lt;strong&gt;Consumers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Kafka itself is capable of handling hundreds to millions of events per second seamlessly on simple setup.  But what if you wanted to test and observe how your Kafka service behaves before going live?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/mostafa/xk6-kafka"&gt;xk6-kafka extension&lt;/a&gt; provides some convenient functions for interacting with Kafka Producers and Consumers. It serves as a producer that can send a high volume of messages per second, allowing you to monitor the system under test (SUT) and test how the application will keep up with the load.&lt;/p&gt;

&lt;h2&gt;
  
  
  xk6-kafka
&lt;/h2&gt;

&lt;p&gt;At the time of this writing, the xk6-kafka extension provides the following APIs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;consume(reader, limit)&lt;/td&gt;
&lt;td&gt;Consume messages from the Kafka server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;createTopic(address, topic)&lt;/td&gt;
&lt;td&gt;Create a new topic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;listTopics(address)&lt;/td&gt;
&lt;td&gt;Return a unique set of topics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;produce(writer, messages)&lt;/td&gt;
&lt;td&gt;Produce messages to the Kafka server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reader(brokers, topic)&lt;/td&gt;
&lt;td&gt;Instantiate a new Reader instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;writer(brokers, topic)&lt;/td&gt;
&lt;td&gt;Instantiate a new Writer instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Some of the APIs mentioned above do accept additional optional parameters meant for authentication and message compression. Refer to more examples for additional information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building k6 with the kafka extension
&lt;/h3&gt;

&lt;p&gt;By default, k6 does not support testing Kafka. Building k6 with the &lt;a href="https://github.com/mostafa/xk6-kafka"&gt;xk6-kafka extension&lt;/a&gt; creates a k6 version with the capabilities to test Kafka producers and consumers.&lt;/p&gt;

&lt;p&gt;Make sure you have the following installed and ready before you continue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go (&amp;gt;=1.7)&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, continue the installation by running the following command in your terminal to install the xk6 module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;go.k6.io/xk6/cmd/xk6@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the command finishes successfully, you can start making your own custom k6 binary for Kafka as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xk6 build &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/mostafa/xk6-kafka@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will take some time for the process to create a new k6 binary in your working directory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Troubleshooting: If you experienced issues installing xk6 extension, kindly head over to the &lt;a href="https://github.com/grafana/xk6/releases/"&gt;Github repository&lt;/a&gt; to download the pre-compiled binaries instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Running Kafka
&lt;/h2&gt;

&lt;p&gt;The recommended approach is to use docker as manual installation is quite complicated and prone to errors. You can pull the &lt;a href="https://hub.docker.com/r/lensesio/fast-data-dev/tags?page=1&amp;amp;ordering=last_updated"&gt;following image by lensesio&lt;/a&gt; from DockerHub. It contains the complete Kafka setup for development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull lensesio/fast-data-dev:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, run the following to start the docker in detached mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; lenseio &lt;span class="nt"&gt;-p&lt;/span&gt; 2181:2181 &lt;span class="nt"&gt;-p&lt;/span&gt; 3030:3030 &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-p&lt;/span&gt; 8081-8083:8081-8083 &lt;span class="nt"&gt;-p&lt;/span&gt; 9581-9585:9581-9585 &lt;span class="nt"&gt;-p&lt;/span&gt; 9092:9092  &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ADV_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 lensesio/fast-data-dev

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; lenseio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;a href="http://localhost:3030"&gt;&lt;code&gt;http://localhost:3030&lt;/code&gt;&lt;/a&gt; to get into the fast-data-dev environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  k6 test
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Import
&lt;/h3&gt;

&lt;p&gt;Now, let’s create a new JavaScript file called test_script.js in the same directory as your k6 binary. Then, add the following import statement at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createTopic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;k6/x/kafka&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Initialization
&lt;/h3&gt;

&lt;p&gt;Continue by appending the following initialization code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bootstrapServers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:9092&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kafkaTopic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xk6_kafka_json_topic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bootstrapServers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kafkaTopic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bootstrapServers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kafkaTopic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code will initialize both the writer and reader instances based on the configuration specified. If you are using a different IP/host address and port for your Kafka server, kindly modify it accordingly.&lt;/p&gt;

&lt;p&gt;Next, call the createTopic function to create a new topic. Rest assured that this function will do nothing if the topic already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;createTopic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bootstrapServers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;kafkaTopic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s create a function which generates a random integer as a unique identifier for each message later on. Please be noted that this is optional and not a mandatory requirement to do load testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Default function
&lt;/h3&gt;

&lt;p&gt;As for the default function, define it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-id-sql-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Load Testing SQL Databases with k6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://k6.io/blog/load-testing-sql-databases-with-k6/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-id-redis-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;getRandomInt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Benchmarking Redis with k6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://k6.io/blog/benchmarking-redis-with-k6/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}];&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is sent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code block above works as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialize a list of messages&lt;/li&gt;
&lt;li&gt;Call produce function to publish the messages&lt;/li&gt;
&lt;li&gt;Check if messages are successfully sent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Teardown
&lt;/h3&gt;

&lt;p&gt;Once you are done with it, create a teardown function and close the connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;teardown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run the test
&lt;/h3&gt;

&lt;p&gt;Save the file and run the following command on your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./k6 run &lt;span class="nt"&gt;--vus&lt;/span&gt; 50 &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s test_script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;running &lt;span class="o"&gt;(&lt;/span&gt;05.0s&lt;span class="o"&gt;)&lt;/span&gt;, 00/50 VUs, 15136 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
default ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 50 VUs  5s

    ✓ is sent

    █ teardown

    checks.........................: 100.00% ✓ 15136  ✗ 0
    data_received..................: 0 B    0 B/s
    data_sent......................: 0 B    0 B/s
    iteration_duration.............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;16.49ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;31.9µs &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;13.52ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.14s p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;28.55ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;36.46ms
    iterations.....................: 15136   3017.4609/s
    kafka.writer.dial.count........: 151    30.102841/s
    kafka.writer.error.count.......: 0      0/s
    kafka.writer.message.bytes.....: 5.2 MB  1.0 MB/s
    kafka.writer.message.count.....: 30272   6034.9218/s
    kafka.writer.rebalance.count...: 0      0/s
    kafka.writer.write.count.......: 30272   6034.9218/s
    vus............................: 5      &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50
    vus_max........................: 50     &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scale the load
&lt;/h3&gt;

&lt;p&gt;You can easily scale the load by increasing the number of vus. For example, the following command uses 500 vus to load test for a minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./k6 run &lt;span class="nt"&gt;--vus&lt;/span&gt; 500 &lt;span class="nt"&gt;--duration&lt;/span&gt; 1m test_script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you are new to k6, check out how to configure the &lt;a href="https://k6.io/docs/getting-started/running-k6/#using-options"&gt;load options&lt;/a&gt; in the script or run a &lt;a href="https://k6.io/docs/test-types/stress-testing/"&gt;stress test&lt;/a&gt; with k6.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Extend the test
&lt;/h3&gt;

&lt;p&gt;The script above is all about producing messages to your Kafka server. In fact, you can easily modify the code into a test which produces and consumes messages.&lt;/p&gt;

&lt;p&gt;Simply add the following code below the for loop code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;let &lt;/span&gt;result &lt;span class="o"&gt;=&lt;/span&gt; consume&lt;span class="o"&gt;(&lt;/span&gt;consumer, 10&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
check&lt;span class="o"&gt;(&lt;/span&gt;result, &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"10 messages returned"&lt;/span&gt;: &lt;span class="o"&gt;(&lt;/span&gt;msgs&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; msgs.length &lt;span class="o"&gt;==&lt;/span&gt; 10,
&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code will read 10 messages each time. Simply modify the value to something higher if you wish to consume more messages.&lt;/p&gt;

&lt;p&gt;The output is as follows when you run it with the same command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;running &lt;span class="o"&gt;(&lt;/span&gt;05.0s&lt;span class="o"&gt;)&lt;/span&gt;, 00/50 VUs, 9778 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
default ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 50 VUs  5s

    ✓ is sent
    ✓ 10 messages returned

    █ teardown

    checks.........................: 100.00% ✓ 19556      ✗ 0
    data_received..................: 0 B    0 B/s
    data_sent......................: 0 B    0 B/s
    iteration_duration.............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;25.53ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;41.4µs &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;18ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.41s p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;37.73ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;52.37ms
    iterations.....................: 9778   1946.80798/s
    kafka.reader.dial.count........: 50     9.955042/s
    kafka.reader.error.count.......: 0      0/s
    kafka.reader.fetches.count.....: 101    20.109184/s
    kafka.reader.message.bytes.....: 15 MB   2.9 MB/s
    kafka.reader.message.count.....: 97830   19478.034846/s
    kafka.reader.rebalance.count...: 0      0/s
    kafka.reader.timeouts.count....: 46     9.158638/s
    kafka.writer.dial.count........: 152    30.263327/s
    kafka.writer.error.count.......: 0      0/s
    kafka.writer.message.bytes.....: 3.4 MB  669 kB/s
    kafka.writer.message.count.....: 19556   3893.615961/s
    kafka.writer.rebalance.count...: 0      0/s
    kafka.writer.write.count.......: 19556   3893.615961/s
    vus............................: 50     &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50
    vus_max........................: 50     &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kafka metrics in k6
&lt;/h2&gt;

&lt;p&gt;By default, k6 has its own &lt;a href="https://k6.io/docs/using-k6/metrics/"&gt;built-in metrics&lt;/a&gt; which are collected automatically. Apart from that, you can create your &lt;a href="https://k6.io/docs/using-k6/metrics/#custom-metrics"&gt;own custom metrics&lt;/a&gt;. Custom metrics can be categorized into the following types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://k6.io/docs/javascript-api/k6-metrics/counter"&gt;Counter&lt;/a&gt;: A metric that cumulatively sums added values.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://k6.io/docs/javascript-api/k6-metrics/gauge"&gt;Gauge&lt;/a&gt;: A metric that stores the min, max and last values added to it. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://k6.io/docs/javascript-api/k6-metrics/rate"&gt;Rate&lt;/a&gt;: A metric that tracks the percentage of added values that are non-zero.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://k6.io/docs/javascript-api/k6-metrics/trend"&gt;Trend&lt;/a&gt;: A metric that allows for calculating statistics on the added values (min, max, average and percentiles). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides k6, k6 extensions can collect metrics and report them as part of the &lt;a href="https://k6.io/docs/getting-started/results-output/"&gt;k6 results output&lt;/a&gt;. In this case, xk6-kafka collects individual stats for both reader and writer. &lt;/p&gt;

&lt;h3&gt;
  
  
  Reader
&lt;/h3&gt;

&lt;p&gt;Let’s have a look at the metrics meant for the &lt;a href="https://github.com/mostafa/xk6-kafka/blob/697dde73c0979eab0187dd81addbfd11ca8da448/consumer.go#L138"&gt;reader&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metrics&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.dial.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of times the reader tries to connect to Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.error.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of errors occurred when reading from Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.fetches.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of times the reader fetches batches of messages from Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.message.bytes&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total bytes consumed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.message.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of messages consumed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.rebalance.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of rebalances of a topic in a consumer group (&lt;em&gt;deprecated&lt;/em&gt;) .&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.reader.timeouts.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of timeouts occurred when reading from Kafka&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Writer
&lt;/h3&gt;

&lt;p&gt;As for the &lt;a href="https://github.com/mostafa/xk6-kafka/blob/697dde73c0979eab0187dd81addbfd11ca8da448/producer.go#L135"&gt;writer&lt;/a&gt;, the metrics are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metrics&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;kafka.writer.dial.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of times the writer tries to connect to Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.writer.error.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of errors occurred when writing to Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.writer.message.bytes&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total bytes produced.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.writer.message.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of messages produced.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.writer.rebalance.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of rebalances of a topic (&lt;em&gt;deprecated&lt;/em&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kafka.writer.write.count&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;Total number of times the writer writes batches of messages to Kafka.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are more available kafka metrics, as you can find them &lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/stats.go"&gt;here&lt;/a&gt;. However, the extension does not collect all metrics yet. You can follow this &lt;a href="https://github.com/mostafa/xk6-kafka/issues/23"&gt;GitHub issue&lt;/a&gt; to track the progress of their additions.&lt;/p&gt;

&lt;h2&gt;
  
  
  More examples
&lt;/h2&gt;

&lt;p&gt;Moreover, the xk6-kafka repository provides a &lt;a href="https://github.com/mostafa/xk6-kafka/tree/master/scripts"&gt;few test scripts&lt;/a&gt; that work out-of-the-box for new users. At the time of this writing, it comes with the following tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/scripts/test_avro.js"&gt;test_avro.js&lt;/a&gt; : Tests Kafka with 200 Avro messages per iteration.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/scripts/test_avro_with_schema_registry.js"&gt;test_avro_with_schema_registry.js&lt;/a&gt; : Tests Kafka with 200 Avro messages per iteration using schema registry&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/scripts/test_json.js"&gt;test_json.js&lt;/a&gt; : Tests Kafka with 200 JSON messages per iteration.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/scripts/test_json_with_snappy_compression.js"&gt;test_json_with_snappy_compression.js&lt;/a&gt; : Tests Kafka with 200 JSON messages per iteration using snappy compression&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/scripts/test_sasl_auth.js"&gt;test_sasl_auth.js&lt;/a&gt; : Tests Kafka with 200 JSON messages per iteration and based on SASL authentication.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mostafa/xk6-kafka/blob/master/scripts/test_topics.js"&gt;test_topics.js&lt;/a&gt; : List topics on all Kafka partitions and create random topics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to experiment with them and modify the code accordingly based on your own use cases. If you bump into issues, report them &lt;a href="https://github.com/mostafa/xk6-kafka/issues"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, load testing Apache Kafka is now a lot easier with k6. k6 provides the foundation to create and scale your load tests, and the xk6-kafka extension brings a convenient API to interact with a Kafka server.&lt;/p&gt;

&lt;p&gt;If you wish to find out more on other available k6 extensions, simply head over to the &lt;a href="https://k6.io/docs/ecosystem/bundle-builder/"&gt;bundle builder page&lt;/a&gt;. The page also allows you to generate the corresponding command for building your own custom k6 binary.&lt;/p&gt;

&lt;p&gt;If you have any questions or are interested in building an extension, join the k6 community on Slack.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>javascript</category>
      <category>kafka</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to Perform Load Testing with k6 using AWS CodeBuild Platform</title>
      <dc:creator>Michael Wanyoike</dc:creator>
      <pubDate>Mon, 13 Sep 2021 12:50:07 +0000</pubDate>
      <link>https://forem.com/k6/how-to-perform-load-testing-with-k6-using-aws-codebuild-platform-3pc6</link>
      <guid>https://forem.com/k6/how-to-perform-load-testing-with-k6-using-aws-codebuild-platform-3pc6</guid>
      <description>&lt;p&gt;&lt;em&gt;You can find a collection of k6 scripts and build specification for CodeBuild in this tutorial &lt;a href="https://github.com/grafana/k6-example-aws-codebuild"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  📖What you will learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How to integrate load testing with k6 into AWS CodeBuild Platform.&lt;/li&gt;
&lt;li&gt;Different implementation paths, and when to use each.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we will look into how to integrate performance testing in your development process with &lt;strong&gt;AWS CodeBuild&lt;/strong&gt; and &lt;strong&gt;k6&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/open-source"&gt;k6&lt;/a&gt; is an open-source load testing tool for testing the performance of   APIs, microservices, and websites. Developers use k6 to test a system's  performance under a particular load to catch performance regressions or  errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/codebuild/"&gt;AWS CodeBuild&lt;/a&gt; is a fully managed continuous integration service hat compiles source code, runs tests, and produces software packages  that are ready to deploy. It uses a pay-as-you-go pricing model with 100 free build minutes per month on the AWS Free Tier plan.&lt;/p&gt;

&lt;p&gt;Documentation for AWS CodeBuild is tightly bound with other AWS services which makes  it difficult to understand AWS CodeBuild as a standalone product. To put it simply, AWS has multiple products in it's CI/CD offering which  includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/codebuild/"&gt;AWS CodeBuild&lt;/a&gt;&lt;/strong&gt; : continuos integration service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/codedeploy/"&gt;AWS CodeDeploy&lt;/a&gt;&lt;/strong&gt; : managed deployment service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/codepipeline"&gt;AWS CodePipeline&lt;/a&gt;&lt;/strong&gt; : continuos deployment service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/codecommit/"&gt;AWS CodeCommit&lt;/a&gt;&lt;/strong&gt; : managed source control service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read this &lt;a href="https://aws.amazon.com/blogs/devops/complete-ci-cd-with-aws-codecommit-aws-codebuild-aws-codedeploy-and-aws-codepipeline/"&gt;blog post&lt;/a&gt; to understand how all these services are integrated to form a complete  CI/CD pipeline. Fortunately for us, we only need to concern ourselves  with &lt;strong&gt;AWS CodeBuild&lt;/strong&gt; and &lt;strong&gt;Amazon EventBridge&lt;/strong&gt; in order to automate k6 load testing. We recommend having a look at the following links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html"&gt;Build Configuration file syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-codebuild-tutorial.html"&gt;Schedule automated builds using AWS CodeBuild&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing your performance test
&lt;/h2&gt;

&lt;p&gt;In order to learn how to write a performance test, we'll work in small  steps and improving the existing script as our knowledge develops. The  test will be made up of the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An HTTP request against our system under test.&lt;/li&gt;
&lt;li&gt;A load configuration controlling the test duration and amount of virtual users.&lt;/li&gt;
&lt;li&gt;A performance goal, or service level objective, expressed as a threshold.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating the test script
&lt;/h3&gt;

&lt;p&gt;The test script we will write first will simulate a virtual user accessing a specified website. To ensure we don't overload the system under test,  we'll put the virtual user to sleep for one second before continuing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring the load
&lt;/h3&gt;

&lt;p&gt;In this step, we'll look at how we can define parameters. For this  example, we'll have 50 virtual users accessing the specified website for the duration of one minute. Due to the sleep we specified earlier, the  script will generate 50 interactions per second, resulting in a total of about 2900 iterations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have installed k6 in your local machine, you can run your test locally in your terminal using the command: k6 run test.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring our thresholds
&lt;/h2&gt;

&lt;p&gt;Next, we need to define our &lt;strong&gt;Service Level Objectives&lt;/strong&gt;, also known as &lt;strong&gt;SLOs&lt;/strong&gt;. This is basically a measurement that specifies the desired performance levels at a given load. With k6, this is done using &lt;a href="https://k6.io/docs/using-k6/thresholds"&gt;Thresholds&lt;/a&gt; where you can define SLOs as &lt;strong&gt;Pass/Fail criteria&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If any of the thresholds you define fail your test, k6 will halt and  return a non-zero exit code, communicating to the CI tool that &lt;strong&gt;the step has failed&lt;/strong&gt;. Let's see how we can add a Threshold to our previous script. We'll  ensure that the 95th percentile response time remains below 500ms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
        &lt;span class="na"&gt;http_req_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thresholds are powerful features that allow us to define different types of  Pass/Fail criteria in the same test run. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 99th percentile response time must be below 700 ms.&lt;/li&gt;
&lt;li&gt;The 95th percentile response time must be below 400 ms.&lt;/li&gt;
&lt;li&gt;No more than 1% failed requests.&lt;/li&gt;
&lt;li&gt;The content of a response must be correct more than 95% of the time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take a look at our &lt;a href="https://k6.io/docs/using-k6/thresholds"&gt;Thresholds documentation&lt;/a&gt; for additional details on the API and its usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the CodeBuild workflow
&lt;/h2&gt;

&lt;p&gt;We'll use Docker to run load testing in AWS CodeBuild's environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-i&lt;/span&gt; loadimpact/k6 run - &amp;lt;scripts/test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to automate the build process, we'll need to create a buildspec.yml file with the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;

&lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pre_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Pull loadimpact/k6 image..&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker pull loadimpact/k6&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Run performance test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker run -i loadimpact/k6 run - &amp;lt;scripts/test.js&lt;/span&gt;
  &lt;span class="na"&gt;post_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Performance test complete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time of writing AWS CodeBuild supports repositories hosted on GitHub, BitBucket, &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt; and &lt;a href="https://aws.amazon.com/codecommit/"&gt;AWS CodeCommit&lt;/a&gt;. For reference,  we'll use GitHub for this tutorial. Once you've  prepared a project  containing the above files, you'll need to commit  and push the changes  to your remote repository.&lt;/p&gt;

&lt;p&gt;Once your remote repository is ready, you can start working on your &lt;strong&gt;AWS Console&lt;/strong&gt;. Using the &lt;em&gt;Services&lt;/em&gt; menu, locate &lt;em&gt;AWS CodeBuild&lt;/em&gt; and open the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/fa1dd766ef8dfaf13e5311a3d2a6a761/0cbb1/01-aws-codebuild.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O5313qvj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/fa1dd766ef8dfaf13e5311a3d2a6a761/7842b/01-aws-codebuild.png" alt="01-aws-codebuild"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;Click the &lt;em&gt;Create project&lt;/em&gt; button and populate the form as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project Configuration&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Project name&lt;/em&gt;: Load-Test-Example&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Description&lt;/em&gt;: Load testing example with k6&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Source provider&lt;/em&gt;: Github&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Repository:&lt;/em&gt; 'Public' or 'Repository in my GitHub account' for private&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Repository URL&lt;/em&gt;: &lt;a href="https://github.com/brandiqa/k6-example-aws-codebuild"&gt;https://github.com/brandiqa/k6-example-aws-codebuild&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Service Role Permissions&lt;/em&gt;: Set true for 'Allow AWS CodeBuild to modify this service role so it can be used with this build project'&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Operating system&lt;/em&gt;: Ubuntu&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Runtime&lt;/em&gt;: Standard&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Privileged&lt;/em&gt;: true. If you don't enable privilege, the docker command won't have the system level permissions to run.
&lt;a href="https://k6.io/blog/static/fd0d4e7e64044b9b959bc14dd4b69c0a/ab842/02-build-environment.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eH9XjhXo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/fd0d4e7e64044b9b959bc14dd4b69c0a/7842b/02-build-environment.png" alt="02-build-environment"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Leave all other settings in their default state&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buildspec&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Select 'Use a buildspec file'
&lt;a href="https://k6.io/blog/static/f12a1da33c0442ee49b864540da4d07b/43df9/04-buildspec.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GOEmspYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/f12a1da33c0442ee49b864540da4d07b/7842b/04-buildspec.png" alt="04-buildspec"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leave all other settings in the default state. Ensure &lt;em&gt;CloudWatch logs&lt;/em&gt; is enabled. Click the &lt;em&gt;Create build project&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;The next step is to run the project which you can do using the &lt;em&gt;Start build&lt;/em&gt; project. Once it's complete, you should expect the following results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/a77d4a7a66f33275d4c61d3695a12014/05410/05-aws-successful-local-build.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ez8qCA-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/a77d4a7a66f33275d4c61d3695a12014/7842b/05-aws-successful-local-build.png" alt="05-aws-successful-local-build"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Running k6 cloud tests
&lt;/h2&gt;

&lt;p&gt;There are two common execution modes to run k6 tests as part of the CI process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Locally on the CI server.&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://k6.io/cloud"&gt;k6 cloud&lt;/a&gt;, from one or multiple geographic locations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might want to use cloud tests in these common cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're going to run a test from multiple geographic locations (load zones).&lt;/li&gt;
&lt;li&gt;If you're going to run a high-load test, that will need more compute resources than available in the runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those reasons fit your needs, then running k6 cloud tests is the way to go for you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  ⚠️ Try it locally first
&lt;/h3&gt;

&lt;p&gt;Before we start with the configuration, it is good to familiarize ourselves  with how cloud execution works, and we recommend you to test how to  trigger a cloud test from your machine.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://k6.io/docs/using-k6/cloud-execution"&gt;cloud execution guide&lt;/a&gt; to learn how to distribute the test load across multiple geographic locations and more information about the cloud execution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, we will show how to trigger cloud tests using AWS CodeBuild. If you do  not have an account with k6 Cloud already, you should go &lt;a href="https://app.k6.io/account/register"&gt;here&lt;/a&gt; and start your free trial. After that, get your account token from &lt;a href="https://app.k6.io/account/api-token"&gt;the cloud app&lt;/a&gt; and save it somewhere temporarily.&lt;/p&gt;

&lt;p&gt;Next, let's create a modified version of the testing script and call it cloud-test.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;loadimpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloud-test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;http_req_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ext property enables passing of the script filename which is required for cloud execution. The next step is to update buildspec.yml as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;exported-variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;K6_CLOUD_TOKEN&lt;/span&gt;

&lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pre_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Pull loadimpact/k6 image..&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker pull loadimpact/k6&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Run performance test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker run -i -e K6_CLOUD_TOKEN=${K6_CLOUD_TOKEN} loadimpact/k6 cloud - &amp;lt;scripts/cloud-test.js&lt;/span&gt;
  &lt;span class="na"&gt;post_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo Performance test complete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to commit and push the new changes to your remote repository.  Next, we need to modify the current build and specify the K6_CLOUD_TOKEN as an environment variable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/3fc9351ee1719f5d917e9e8765656d8c/ad315/06-aws-environment-variables.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DSB19EzD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/3fc9351ee1719f5d917e9e8765656d8c/7842b/06-aws-environment-variables.png" alt="06-aws-environment-variables"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;After making these changes, run the build. In about a minute or so, you should expect the following output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/69dbd79de3c56117da51252fbd4d323a/499bc/07-aws-k6-cloud-run-build.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yLe9vobH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/69dbd79de3c56117da51252fbd4d323a/7842b/07-aws-k6-cloud-run-build.png" alt="07-aws-k6-cloud-run-build"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;And if we copy the highlighted URL and navigate to it in a new tab&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/a00c2e93083dc60886bf3e681c7c6678/9ca3d/08-aws-k6-cloud-run-results.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zlYPVOvh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/a00c2e93083dc60886bf3e681c7c6678/7842b/08-aws-k6-cloud-run-results.png" alt="08-aws-k6-cloud-run-results"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduling Builds
&lt;/h3&gt;

&lt;p&gt;It is highly recommended to perform load testing at a time when most users  aren't accessing your system. This is because performance testing often  places significant stresses on your infrastructure which may cause user  experience issues.&lt;/p&gt;

&lt;p&gt;To configure scheduled nightly build that runs at a given time of a given day or night, head over to &lt;a href="https://aws.amazon.com/eventbridge/"&gt;Amazon EventBridge&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/c47f1f0940f1efce5de627a2db3773a3/610eb/09-amazon-eventbridge.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SGeLYJzZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/c47f1f0940f1efce5de627a2db3773a3/7842b/09-amazon-eventbridge.png" alt="09-amazon-eventbridge"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;Head over to the &lt;a href="https://console.aws.amazon.com/events/home?p=evtb&amp;amp;cp=bn&amp;amp;ad=c"&gt;Amazon EventBridge console&lt;/a&gt; and click the Create rule button. Fill in the form as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: Schedule-Load-Test-Example&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: Nightly build&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define pattern&lt;/strong&gt;: Schedule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron Expression&lt;/strong&gt;: 0 12 * * ? *&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target&lt;/strong&gt;: CodeBuild project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project ARN&lt;/strong&gt;: arn:aws:codebuild:&lt;em&gt;region-ID&lt;/em&gt;:&lt;em&gt;account-ID&lt;/em&gt;:project/&lt;em&gt;project-name&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the &lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/auth-and-access-control-iam-access-control-identity-based.html#arn-formats"&gt;Project ARN&lt;/a&gt;, simply replace the values in italics with values from your account i.e.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;region-ID&lt;/strong&gt;: found at the top navigation bar, click the drop-down location menu to  see your region id in the format 'us-east-1' or 'us-west-1'&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;account-ID&lt;/strong&gt;: follow this &lt;a href="https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html"&gt;guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;project-name&lt;/strong&gt;: Load-Test-Example&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your fully constructed project ARN should look similar to this:&lt;/p&gt;

&lt;p&gt;arn:aws:codebuild:us-east-2:123456789123:project/Load-Test-Example&lt;/p&gt;

&lt;p&gt;AWS doesn't fully recognize the standard CRON format. Hence you'll need to use the format specified in their &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html"&gt;cron guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/blog/static/f202a3b58e6bd4fb1678df6de0fc932b/fd14d/10-amazon-eventbridge-pattern.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0DeMm0QT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/f202a3b58e6bd4fb1678df6de0fc932b/7842b/10-amazon-eventbridge-pattern.png" alt="10-amazon-eventbridge-pattern"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;After filling the form, click the 'Create' button to save the changes. The Load-Test-Example CodeBuild project will run at the specified time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Building your CI/CD pipeline on AWS platform can be overwhelming at first.  Hopefully, this guide should help alleviate the pain by showing you  exactly what you need you to configure in order to setup an automated  load testing pipeline on AWS.&lt;/p&gt;

&lt;p&gt;As demonstrated in this article, you can execute an AWS build that runs k6 either locally or on the cloud.  Automating performance testing in your CI/CD pipeline will help you  identify issues early before they affect your customers.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>aws</category>
      <category>testing</category>
      <category>k6</category>
    </item>
    <item>
      <title>How to Perform Load Testing with k6 using Google Cloud Build</title>
      <dc:creator>Michael Wanyoike</dc:creator>
      <pubDate>Wed, 01 Sep 2021 10:50:01 +0000</pubDate>
      <link>https://forem.com/k6/how-to-perform-load-testing-with-k6-using-google-cloud-build-1ag5</link>
      <guid>https://forem.com/k6/how-to-perform-load-testing-with-k6-using-google-cloud-build-1ag5</guid>
      <description>&lt;p&gt;&lt;em&gt;You can find a collection of k6 scripts and Google Cloud Build workflows referenced in this tutorial &lt;a href="https://github.com/grafana/k6-example-google-cloud-cicd" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  📖 What you will learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How to integrate load testing with k6 into Google Cloud Build CI/CD Platform.&lt;/li&gt;
&lt;li&gt;Different implementation paths, and when to use each.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we will look into how to integrate performance testing in your development process with &lt;strong&gt;Google Cloud Build&lt;/strong&gt; and &lt;strong&gt;k6&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://k6.io/open-source" rel="noopener noreferrer"&gt;k6&lt;/a&gt; is an open-source load testing tool for testing the performance of   APIs, microservices, and websites. Developers use k6 to test a system's  performance under a particular load to catch performance regressions or  errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/build" rel="noopener noreferrer"&gt;Google Cloud Build&lt;/a&gt; is a serverless CI/CD platform that allows developers to create custom  workflows for their software development lifecycle within the &lt;strong&gt;Google Cloud Platform(GCP)&lt;/strong&gt; ecosystem. It uses a pay-as-you-go pricing model with 120 free minutes given everyday.&lt;/p&gt;

&lt;p&gt;If you've not used Google Cloud Build before, we recommend looking at the following links to get a hold of how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/build/docs/building/build-containers" rel="noopener noreferrer"&gt;Building container images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/build/docs/build-config-file-schema" rel="noopener noreferrer"&gt;Build Configuration file syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/build/docs/automating-builds/create-manage-triggers" rel="noopener noreferrer"&gt;Creating and managing build Triggers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/build/docs/automating-builds/create-scheduled-triggers" rel="noopener noreferrer"&gt;Creating scheduled Triggers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing your performance test
&lt;/h2&gt;

&lt;p&gt;We'll start small by writing a simple test that measure the performance of a  single endpoint. As with most, if not all, development efforts,   performance testing yields the best results if we work in small   increments, iterating and expanding as our knowledge increases.&lt;/p&gt;

&lt;p&gt;Our test will consist of three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An HTTP request against our system under test.&lt;/li&gt;
&lt;li&gt;A load configuration controlling the test duration and amount of virtual users.&lt;/li&gt;
&lt;li&gt;A performance goal, or service level objective, expressed as a threshold.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating the test script
&lt;/h3&gt;

&lt;p&gt;When we execute our test script, each virtual user will execute the default  function as many times as possible until the duration is up. To make   sure we don't flood our system under test, we'll make the virtual user   sleep for a second before it continues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring the load
&lt;/h3&gt;

&lt;p&gt;We'll configure our test to run 50 virtual users continuously for one minute. Because of the sleep we added earlier, this will result in just below   50 iterations per second, giving us a total of about 2900 iterations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have installed k6 in your local machine, you can run your test  locally in your terminal using the command: k6 run test.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring our thresholds
&lt;/h2&gt;

&lt;p&gt;The next step is to define your &lt;strong&gt;service level objectives&lt;/strong&gt;, or &lt;strong&gt;SLOs&lt;/strong&gt; around your application performance. SLOs are a vital aspect of  ensuring the reliability of your systems and applications. If you do   not currently have any defined SLAs or SLOs, now is an excellent time to consider your requirements.&lt;/p&gt;

&lt;p&gt;You can define SLOs as &lt;strong&gt;Pass/Fail criteria&lt;/strong&gt; with &lt;a href="https://k6.io/docs/using-k6/thresholds" rel="noopener noreferrer"&gt;Thresholds&lt;/a&gt; in your k6 script. k6 evaluates them during the test execution and  informs about the Threshold results.  If any of the thresholds in our  test fails, k6 will return with a non-zero exit code, communicating to  the CI tool that &lt;strong&gt;the step has failed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, we will add one Threshold to our previous script to validate than the 95th percentile response time must be below 500ms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
        &lt;span class="na"&gt;http_req_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thresholds are a powerful feature providing a flexible API to define various types of Pass/Fail criteria in the same test run. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 99th percentile response time must be below 700 ms.&lt;/li&gt;
&lt;li&gt;The 95th percentile response time must be below 400 ms.&lt;/li&gt;
&lt;li&gt;No more than 1% failed requests.&lt;/li&gt;
&lt;li&gt;The content of a response must be correct more than 95% of the time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the &lt;a href="https://k6.io/docs/using-k6/thresholds" rel="noopener noreferrer"&gt;Thresholds documentation&lt;/a&gt; for additional details on the API and its usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Cloud Build workflow
&lt;/h2&gt;

&lt;p&gt;Ideally, the easiest way to setup load testing in a CI/CD environment is to use docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-i&lt;/span&gt; loadimpact/k6 run - &amp;lt;scripts/test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, the above command fails to run within the Cloud Build environment. The  solution is to mount the source code files and execute them within a  docker container environment. To ensure a smooth CI/CD workflow in  Google Build's environment, we'll run our load performance test using docker-compose. First, we need to define our docker-compose.yml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;k6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;loadimpact/k6:latest&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run /scripts/test.js&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./scripts:/scripts&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, we have defined a single service called k6 that performs the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pulls the &lt;a href="https://hub.docker.com/r/loadimpact/k6/" rel="noopener noreferrer"&gt;k6 docker image&lt;/a&gt; from Docker Hub registry&lt;/li&gt;
&lt;li&gt;Executes a command that runs the local k6 test&lt;/li&gt;
&lt;li&gt;Mounts the scripts folder in the /script path&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next we need to define the CI/CD steps that will be executed on the Cloud Build platform. We'll do this using cloudbuild.yaml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Load Test&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker'&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-i'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;loadimpact/k6'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;scripts/test.js'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, we would have to define steps for install, test, build and  deploy. Since we are testing with an app that has already been deployed, test.k6.io, we'll only define a single step, &lt;strong&gt;Load Test&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the time of writing, only GitHub, BitBucket and &lt;a href="https://source.cloud.google.com/onboarding/welcome" rel="noopener noreferrer"&gt;Google Cloud Source&lt;/a&gt; repositories are supported by Google Cloud Platform. For reference,  we'll use GitHub for this tutorial. Once you've prepared a project  containing the above files, you'll need to commit and push the changes  to your remote repository.&lt;/p&gt;

&lt;p&gt;Once your remote repository is ready, you can start working on your &lt;strong&gt;Google Cloud Console&lt;/strong&gt;. If you have never used &lt;strong&gt;Cloud Build&lt;/strong&gt; before, you'll need to enable the API first. The setting below is located under &lt;em&gt;API &amp;amp; Services&lt;/em&gt; &amp;gt; &lt;em&gt;Library&lt;/em&gt; &amp;gt; &lt;em&gt;Developer tools&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;​      &lt;a href="https://k6.io/blog/static/29029c1f3bb6f999ffb125b1f340fa68/d2ae1/01-enable-cloud-build-api.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2F29029c1f3bb6f999ffb125b1f340fa68%2F7842b%2F01-enable-cloud-build-api.png" alt="01-enable-cloud-build-api"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;By enabling the API, the appropriate IAM service  account will be created automatically that will allow you to run the  build steps successfully. With Cloud Build, Google provides us with a  selection of &lt;a href="https://cloud.google.com/build/docs/cloud-builders" rel="noopener noreferrer"&gt;&lt;strong&gt;cloud builders&lt;/strong&gt;&lt;/a&gt; a.k.a &lt;em&gt;execution environments&lt;/em&gt; for running our CI/CD steps. These builders include yarn, npm, docker, mvn, go and a few more. Unfortunately, majority of the building tools we need for our projects are not available out of the box.&lt;/p&gt;

&lt;p&gt;To add the tools we need, we have to visit the &lt;a href="https://github.com/GoogleCloudPlatform/cloud-builders-community" rel="noopener noreferrer"&gt;community contributed builders&lt;/a&gt; repository where all the tools needed in a software development lifecycle can be found. In our case, we simply need the docker-compose builder. The steps to install this builder in our own Google Cloud account is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install and configure &lt;a href="https://cloud.google.com/sdk/docs/quickstart" rel="noopener noreferrer"&gt;gcloud CLI&lt;/a&gt; tool to connect with your GCP account from your machine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the update command: gcloud components update&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install docker-compose builder on your GCP account using the following commands:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# Clone the entire cloud builders community repo onto your hard drive&lt;/span&gt;
   git clone https://github.com/GoogleCloudPlatform/cloud-builders-community

   &lt;span class="c"&gt;# Navigate into the docker-compose folder&lt;/span&gt;
   &lt;span class="nb"&gt;cd &lt;/span&gt;cloud-builders-community/docker-compose/

   &lt;span class="c"&gt;# Install docker-compose builder into your account&lt;/span&gt;
   gcloud builds submit &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloudbuild.yaml &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Confirm the docker-compose builder has been installed. See &lt;em&gt;Cloud Build&lt;/em&gt; &amp;gt; &lt;em&gt;Container Registry&lt;/em&gt; &amp;gt; &lt;em&gt;Images&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;​      &lt;a href="https://k6.io/blog/static/c0fc75f186fed371f6b8f6aa695bb1bc/1dbc1/02-docker-compose-builder.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2Fc0fc75f186fed371f6b8f6aa695bb1bc%2F7842b%2F02-docker-compose-builder.png" alt="02-docker-compose-builder"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;Once you have completed this step, you can now proceed with creating a &lt;strong&gt;Trigger&lt;/strong&gt; which is the basis of building a CI/CD pipeline in Google Cloud Build.&lt;/p&gt;

&lt;p&gt;Head over to &lt;em&gt;Cloud Build&lt;/em&gt; &amp;gt; &lt;em&gt;Triggers&lt;/em&gt; page and click the &lt;em&gt;Create Trigger&lt;/em&gt; button. Populate the form as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt; - Run-Performance-Test&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt; - Run k6 load test&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event&lt;/strong&gt; - Push to a branch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt; - &lt;em&gt;link to your GitHub repository&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch&lt;/strong&gt; - &lt;em&gt;main&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration&lt;/strong&gt; - Cloud Build configuration file (yaml or json)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt; - Repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After hitting save, click the Run button. You should expect the following results:&lt;/p&gt;

&lt;p&gt;​      &lt;a href="https://k6.io/blog/static/d0ec3fbc29b320c1a32a878d2d8fac07/88b03/03-local-build-successful-run.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2Fd0ec3fbc29b320c1a32a878d2d8fac07%2F7842b%2F03-local-build-successful-run.png" alt="03-local-build-successful-run"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Running k6 Cloud tests
&lt;/h2&gt;

&lt;p&gt;There are two common execution modes to run k6 tests as part of the CI process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Locally on the CI server.&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://k6.io/cloud" rel="noopener noreferrer"&gt;k6 Cloud&lt;/a&gt;, from one or multiple geographic locations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might want to use cloud tests in these common cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're going to run a test from multiple geographic locations (load zones).&lt;/li&gt;
&lt;li&gt;If you're going to run a high-load test, that will need more compute resources than available in the runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those reasons fit your needs, then running k6 Cloud tests is the way to go for you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  ⚠️ Try it locally first
&lt;/h3&gt;

&lt;p&gt;Before we start with the configuration, it is good to familiarize ourselves  with how cloud execution works, and we recommend you to test how to  trigger a cloud test from your machine.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://k6.io/docs/using-k6/cloud-execution" rel="noopener noreferrer"&gt;cloud execution guide&lt;/a&gt; to learn how to distribute the test load across multiple geographic locations and more information about the cloud execution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, we will show how to run cloud tests on the Google Cloud Build platform. If you do not have an account with k6 Cloud already, you should go &lt;a href="https://app.k6.io/account/register" rel="noopener noreferrer"&gt;here&lt;/a&gt; and start your free trial. After that, get your account token from &lt;a href="https://app.k6.io/account/api-token" rel="noopener noreferrer"&gt;the cloud app&lt;/a&gt; and save it somewhere temporarily.&lt;/p&gt;

&lt;p&gt;Next, we need to update our docker-compose.yml file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.4'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;k6&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;loadimpact/k6:latest&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud /scripts/test.js&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./scripts:/scripts&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;K6_CLOUD_TOKEN=${K6_CLOUD_TOKEN}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are only 2 changes we have made here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the command section, we've changed from run to cloud&lt;/li&gt;
&lt;li&gt;We've added an environment section. The ${K6_CLOUD_TOKEN} environment variable value needs to be defined outside the docker container, in this case, we'll do it in the cloudbuild.yaml config file where execution call is made.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With most platforms, there is a system for securely storing environment  variables. Unfortunately for Google Cloud Build, there isn't. Instead,  what we'll paste the token value in plain format within the &lt;em&gt;Trigger&lt;/em&gt; configuration. We'll use the inline YAML editor. Simply edit the current &lt;em&gt;Trigger&lt;/em&gt; and update the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration Location&lt;/strong&gt;: Inline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Open Editor&lt;/strong&gt; button and copy the following:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/$PROJECT_ID/docker-compose&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;K6_CLOUD_TOKEN=&amp;lt;paste&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;your&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;here&amp;gt;'&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;k6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste in your token key as indicated. Click Done and then Save.&lt;/p&gt;

&lt;p&gt;Once we commit and push the changes we made earlier, k6 will run our test  using the k6 Cloud, and output the URL to our test results as part of  the workflow logs:&lt;/p&gt;

&lt;p&gt;​      &lt;a href="https://k6.io/blog/static/fddfcf2ac20a63feb097bbcdc049a923/48ef6/04-k6-cloud-build.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2Ffddfcf2ac20a63feb097bbcdc049a923%2F7842b%2F04-k6-cloud-build.png" alt="04-k6-cloud-build"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;And if we copy the highlighted URL and navigate to it in a new tab&lt;/p&gt;

&lt;p&gt;​       &lt;a href="https://k6.io/blog/static/4729cb1317e4101d67c4d839c4cf2413/c77e4/05-k6-cloud-run-results.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2F4729cb1317e4101d67c4d839c4cf2413%2F7842b%2F05-k6-cloud-run-results.png" alt="05-k6-cloud-run-results"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;h2&gt;
  
  
  Nightly Builds
&lt;/h2&gt;

&lt;p&gt;Triggering a subset of performance tests at a specific time is a best-practice for automating your performance testing.&lt;/p&gt;

&lt;p&gt;It's common to run some performance tests during the night when users do not access the system under test. For example, to isolate more extensive   tests from other types of testing or to generate a performance report   periodically.&lt;/p&gt;

&lt;p&gt;To configure scheduled nightly build that runs at a  given time of a given day or night, head over to your &lt;em&gt;Trigger&lt;/em&gt; configuration and edit as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event&lt;/strong&gt;: Manual invocation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go back to the main &lt;strong&gt;Triggers&lt;/strong&gt; page and click on the menu (vertical ellipses) located at the right end of the row containing your trigger. You should see a new option called &lt;em&gt;Run on schedule&lt;/em&gt;. Click on it to start setting up the schedule. You'll need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the Schedule API&lt;/li&gt;
&lt;li&gt;Select a service account (accept the default which should start with &lt;a href="mailto:cloud-build-trigger-scheduler@"&gt;cloud-build-trigger-scheduler@&lt;/a&gt;..)&lt;/li&gt;
&lt;li&gt;Provide name, description and frequency of the schedule in &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07" rel="noopener noreferrer"&gt;POSIX cron syntax&lt;/a&gt;. Here is &lt;a href="https://crontab.guru/examples.html" rel="noopener noreferrer"&gt;an interactive tool for creating crontab scheduling expressions&lt;/a&gt;. You can also select your desired time zone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can use the following values for reference:&lt;/p&gt;

&lt;p&gt;​      &lt;a href="https://k6.io/blog/static/f376130f0470a0f6d878f1de43e65e03/25ca4/06-trigger-schedule.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2Ff376130f0470a0f6d878f1de43e65e03%2F7842b%2F06-trigger-schedule.png" alt="06-trigger-schedule"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;Once you save your schedule settings, Google Cloud  Build will take care of running the workflow at the time intervals you  specified.&lt;/p&gt;

&lt;p&gt;You can also visit the &lt;em&gt;Cloud Scheduler&lt;/em&gt; page and trigger the scheduled job manually. In case this fails, edit the &lt;em&gt;Job Schedule&lt;/em&gt; and check the &lt;em&gt;Configure the job's target&lt;/em&gt; &amp;gt; &lt;em&gt;Body&lt;/em&gt; field. By default the branch name is set to master. If you are using a different branch such as main, you need to update as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"branchName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving, you should be able to successfully run the job manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;While there is a learning curve with setting up a full CI/CD pipeline for  your project with Google Cloud, integrating k6 performance tests into a  new or existing Google Cloud Build pipeline is quick and easy as only a  couple of configuration files are involved. &lt;/p&gt;

&lt;p&gt;The configurations  mentioned throughout the article, provide the same flexibility and  capabilities as you're used to from running k6 locally.  By integrating  performance tests into your CI/CD pipelines, you'll be able to identify  and correct performance regressions as they occur.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>googlecloud</category>
      <category>testing</category>
      <category>k6</category>
    </item>
    <item>
      <title>Benchmarking Redis with k6</title>
      <dc:creator>Ng Wai Foong</dc:creator>
      <pubDate>Wed, 18 Aug 2021 01:47:15 +0000</pubDate>
      <link>https://forem.com/k6/benchmarking-redis-with-k6-33kf</link>
      <guid>https://forem.com/k6/benchmarking-redis-with-k6-33kf</guid>
      <description>&lt;p&gt;Previously, I have covered an article on &lt;a href="https://k6.io/blog/load-testing-sql-databases-with-k6/"&gt;Load Testing SQL Databases with k6&lt;/a&gt;. For your information, from k6 version 0.29.0 onwards, you can &lt;a href="https://github.com/grafana/xk6"&gt;write a k6 Go extension&lt;/a&gt; and build your own k6 binaries. This comes in handy as you can use a single framework for load testing different protocols, such as ZMTQ, SQL, Avro, MLLP, etc.&lt;/p&gt;

&lt;p&gt;In this series of k6 extensions, let’s benchmark Redis now. According to &lt;a href="https://redis.io/"&gt;redis.io&lt;/a&gt;, Redis is a type of in-memory data structure store that can be used as database, cache and message broker.&lt;/p&gt;

&lt;p&gt;You might want to evaluate the performance or scalability of Redis instances in given hardware, giving you better insights into the throughput the Redis service can handle. &lt;/p&gt;

&lt;p&gt;This tutorial covers Redis performance testing via two different approaches on a Linux machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;redis-benchmark&lt;/li&gt;
&lt;li&gt;xk6-redis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  redis-benchmark
&lt;/h2&gt;

&lt;p&gt;By default, Redis comes with its own benchmark utility called &lt;a href="https://redis.io/topics/benchmarks"&gt;redis-benchmark&lt;/a&gt;. It is similar to Apache's ab utility and can simulate a number of clients sending a total number of queries simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Options
&lt;/h3&gt;

&lt;p&gt;Make sure that you have Redis installed in your system. If you have not done so, kindly head over to the &lt;a href="https://redis.io/download"&gt;official Redis download page&lt;/a&gt; and install it based on the instructions given.&lt;/p&gt;

&lt;p&gt;Once you are done with it, you should be able to run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Usage: redis-benchmark &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-h&lt;/span&gt; &amp;lt;host&amp;gt;] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;port&amp;gt;] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt; &amp;lt;clients&amp;gt;] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;requests&amp;gt;] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;boolean&amp;gt;]

 &lt;span class="nt"&gt;-h&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;      Server &lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;default 127.0.0.1&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;port&amp;gt;          Server port &lt;span class="o"&gt;(&lt;/span&gt;default 6379&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-s&lt;/span&gt; &amp;lt;socket&amp;gt;        Server socket &lt;span class="o"&gt;(&lt;/span&gt;overrides host and port&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-a&lt;/span&gt; &amp;lt;password&amp;gt;      Password &lt;span class="k"&gt;for &lt;/span&gt;Redis Auth
 &lt;span class="nt"&gt;-c&lt;/span&gt; &amp;lt;clients&amp;gt;       Number of parallel connections &lt;span class="o"&gt;(&lt;/span&gt;default 50&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;requests&amp;gt;      Total number of requests &lt;span class="o"&gt;(&lt;/span&gt;default 100000&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;size&amp;gt;          Data size of SET/GET value &lt;span class="k"&gt;in &lt;/span&gt;bytes &lt;span class="o"&gt;(&lt;/span&gt;default 3&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;--dbnum&lt;/span&gt; &amp;lt;db&amp;gt;       SELECT the specified db number &lt;span class="o"&gt;(&lt;/span&gt;default 0&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;boolean&amp;gt;       &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;keep alive &lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reconnect &lt;span class="o"&gt;(&lt;/span&gt;default 1&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;keyspacelen&amp;gt;   Use random keys &lt;span class="k"&gt;for &lt;/span&gt;SET/GET/INCR, random values &lt;span class="k"&gt;for &lt;/span&gt;SADD
  Using this option the benchmark will &lt;span class="nb"&gt;expand &lt;/span&gt;the string __rand_int__
  inside an argument with a 12 digits number &lt;span class="k"&gt;in &lt;/span&gt;the specified range
  from 0 to keyspacelen-1. The substitution changes every &lt;span class="nb"&gt;time &lt;/span&gt;a &lt;span class="nb"&gt;command
  &lt;/span&gt;is executed. Default tests use this to hit random keys &lt;span class="k"&gt;in &lt;/span&gt;the
  specified range.
 &lt;span class="nt"&gt;-P&lt;/span&gt; &amp;lt;numreq&amp;gt;        Pipeline &amp;lt;numreq&amp;gt; requests. Default 1 &lt;span class="o"&gt;(&lt;/span&gt;no pipeline&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
 &lt;span class="nt"&gt;-e&lt;/span&gt;                 If server replies with errors, show them on stdout.
                    &lt;span class="o"&gt;(&lt;/span&gt;no more than 1 error per second is displayed&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="nt"&gt;-q&lt;/span&gt;                 Quiet. Just show query/sec values
 &lt;span class="nt"&gt;--csv&lt;/span&gt;              Output &lt;span class="k"&gt;in &lt;/span&gt;CSV format
 &lt;span class="nt"&gt;-l&lt;/span&gt;                 Loop. Run the tests forever
 &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;tests&amp;gt;         Only run the comma separated list of tests. The &lt;span class="nb"&gt;test
                    &lt;/span&gt;names are the same as the ones produced as output.
 &lt;span class="nt"&gt;-I&lt;/span&gt;                 Idle mode. Just open N idle connections and wait.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;p&gt;Depending on your needs, a typical example is to just run the benchmark with the default configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a good idea to use the &lt;code&gt;-q&lt;/code&gt; option. Here is an example for running 100k of requests in quiet mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 100000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition, you can run parallel clients via the &lt;code&gt;-c&lt;/code&gt; option. The following example use 20 parallel clients for a total of 100k requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 100000 &lt;span class="nt"&gt;-c&lt;/span&gt; 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can restrict the test to run only a subset of the commands. For example, you can use the following command to test only set and get commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;,get &lt;span class="nt"&gt;-n&lt;/span&gt; 100000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, you can run test on specific commands for benchmarking like the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 100000 script load &lt;span class="s2"&gt;"redis.call('set','key','value')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Redis server is running on a different hostname and port, you can benchmark the server as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-benchmark &lt;span class="nt"&gt;-h&lt;/span&gt; 192.168.1.1 &lt;span class="nt"&gt;-p&lt;/span&gt; 6379 &lt;span class="nt"&gt;-n&lt;/span&gt; 100000 &lt;span class="nt"&gt;-c&lt;/span&gt; 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get the following output indicating the requests per second for each of the test conducted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;PING_INLINE: 43478.26 requests per second
PING_BULK: 41666.67 requests per second
SET: 43478.26 requests per second
GET: 43478.26 requests per second
INCR: 40000.00 requests per second
LPUSH: 43478.26 requests per second
RPUSH: 37037.04 requests per second
LPOP: 45454.55 requests per second
RPOP: 34482.76 requests per second
SADD: 43478.26 requests per second
HSET: 45454.55 requests per second
SPOP: 45454.55 requests per second
LPUSH &lt;span class="o"&gt;(&lt;/span&gt;needed to benchmark LRANGE&lt;span class="o"&gt;)&lt;/span&gt;: 40000.00 requests per second
LRANGE_100 &lt;span class="o"&gt;(&lt;/span&gt;first 100 elements&lt;span class="o"&gt;)&lt;/span&gt;: 45454.55 requests per second
LRANGE_300 &lt;span class="o"&gt;(&lt;/span&gt;first 300 elements&lt;span class="o"&gt;)&lt;/span&gt;: 43478.26 requests per second
LRANGE_500 &lt;span class="o"&gt;(&lt;/span&gt;first 450 elements&lt;span class="o"&gt;)&lt;/span&gt;: 47619.05 requests per second
LRANGE_600 &lt;span class="o"&gt;(&lt;/span&gt;first 600 elements&lt;span class="o"&gt;)&lt;/span&gt;: 38461.54 requests per second
MSET &lt;span class="o"&gt;(&lt;/span&gt;10 keys&lt;span class="o"&gt;)&lt;/span&gt;: 41666.67 requests per second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;Sometimes, you might prefer to analyze the latency instead. There are two types of &lt;a href="https://redis.io/topics/latency"&gt;latency measurement&lt;/a&gt; provided by redis-cli:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;intrinsic latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, we measure latency as the time between sending a request to Redis and receiving a response. On the other hand, intrinsic latency refers to the system latency that is highly dependent on external factors such as operating system kernel or virtualization. Since Redis 2.8.7, you can measure the intrinsic latency independently.&lt;/p&gt;

&lt;p&gt;Please note that you can only run redis-cli in the machine which hosts the Redis server unlike redis-benchmark which is runnable on the client machine. Besides that, this mode is not connected to a Redis server at all and the measurement is based on the largest time in which the kernel does not provide CPU time to run to the redis-cli process itself. As a result, it is not an actual measurement of the latency between client and Redis server.&lt;/p&gt;

&lt;p&gt;Having said that, it does provide a quick analysis if there is something wrong with the machine that hosts the Redis server.&lt;/p&gt;

&lt;p&gt;Run the following command to get the overall latency of your Redis server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli &lt;span class="nt"&gt;--latency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an increase in the sample as time goes by and the average latency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;min: 0, max: 5, avg: 0.22 &lt;span class="o"&gt;(&lt;/span&gt;2406 samples&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;Ctrl+C&lt;/code&gt; to stop it as the process will run indefinitely without stopping.&lt;/p&gt;

&lt;p&gt;For intrinsic latency, you should use the following command instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli &lt;span class="nt"&gt;--intrinsic-latency&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can pass an integer representing the duration of the test. In this case, the test will run for 10 seconds. The output is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Max latency so far: 1 microseconds.
Max latency so far: 15 microseconds.
Max latency so far: 16 microseconds.
Max latency so far: 17 microseconds.
Max latency so far: 18 microseconds.
Max latency so far: 20 microseconds.
Max latency so far: 21 microseconds.
Max latency so far: 24 microseconds.
Max latency so far: 25 microseconds.
Max latency so far: 50 microseconds.
Max latency so far: 74 microseconds.
Max latency so far: 87 microseconds.
Max latency so far: 150 microseconds.
Max latency so far: 1089 microseconds.
Max latency so far: 1715 microseconds.
Max latency so far: 2344 microseconds.
Max latency so far: 7438 microseconds.
Max latency so far: 8002 microseconds.

158645097 total runs &lt;span class="o"&gt;(&lt;/span&gt;avg latency: 0.0630 microseconds / 63.03 nanoseconds per run&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Worst run took 126948x longer than the average latency.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The average latency is about 0.22 milliseconds while the intrinsic latency is 0.063 microseconds.&lt;/p&gt;

&lt;p&gt;Let’s proceed to the next section and start exploring another testing approach using k6.&lt;/p&gt;

&lt;h2&gt;
  
  
  xk6-redis
&lt;/h2&gt;

&lt;p&gt;k6 provides the capabilities to do performance testing with scripting language. This is a big plus to developers and Q&amp;amp;A testers as you will have a better control of the entire workflow of the test. For example, you can ramp up or ramp down the requests at specific intervals of the test which is not achievable when using redis-benchmark.&lt;/p&gt;

&lt;p&gt;Fortunately, k6 provides the &lt;a href="https://github.com/grafana/xk6-redis"&gt;xk6-redis extension&lt;/a&gt; as part of their ecosystem. You can use it directly to build your own custom k6 binaries for testing Redis server.&lt;/p&gt;

&lt;p&gt;This extension comes with the following API:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client(options)&lt;/td&gt;
&lt;td&gt;Represent the Client construtor. Returns a new Redis client object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client.set(key, value, expiration time)&lt;/td&gt;
&lt;td&gt;Set the given key with the given value and expiration time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client.get(key)&lt;/td&gt;
&lt;td&gt;Get returns the value for the given key.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Building k6 with the redis extension
&lt;/h3&gt;

&lt;p&gt;Before that, make sure you have the following installed in your machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have completed the installation, run the following to install xk6 module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/k6io/xk6/cmd/xk6@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have installed xk6 directory to Go module, you can make your Redis k6 build by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xk6 build &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/k6io/xk6-redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a k6 executable in your current working directory.&lt;/p&gt;

&lt;p&gt;Alternatively, you can download the pre-compiled binaries at the &lt;a href="https://github.com/grafana/xk6/releases/"&gt;following Github repository&lt;/a&gt;. The latest version at the time of this writing is v0.4.1. If you have trouble identifying the architecture of your Linux machine, simply run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s say that the command returns the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should download the xk6_0.4.1_linux_amd64.tar.gz asset and extract it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvf&lt;/span&gt; xk6_0.4.1_linux_amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get the following files in your working directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;README.md&lt;/li&gt;
&lt;li&gt;LICENSE&lt;/li&gt;
&lt;li&gt;xk6&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, run the following command to build k6 for Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./xk6 build &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/k6io/xk6-redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should have now a new k6 binary in your working directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  k6 Script
&lt;/h3&gt;

&lt;p&gt;Next, let’s create a new JavaScript file called test_script.js in the same directory as your k6 executable. Append the following import statement at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/x/redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Continue by adding the following code which connect to your Redis server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:6379&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It accepts the following an object with the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;addr: hostname and port of your Redis server denoted as hostname:port.&lt;/li&gt;
&lt;li&gt;password: password of your Redis server.&lt;/li&gt;
&lt;li&gt;db: the db number ranging from 0 to 15.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To keep it simple and short, the test case is going to be as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a new key:value on start of test.&lt;/li&gt;
&lt;li&gt;Running parallel VUs to get the same key repeatedly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The k6 setup function runs only once at the test start, independently of the test load and duration. Let’s set the key: value as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The set function accepts three input parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;key&lt;/li&gt;
&lt;li&gt;value&lt;/li&gt;
&lt;li&gt;expiration time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, define the default function which will be called repeatedly by each VU during the entire test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete code is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/x/redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:6379&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the test
&lt;/h3&gt;

&lt;p&gt;Save the test script and run the following command to the test your Redis server for 5 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./k6 run test_script.js &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, it is using one Virtual User (VU) but you can modify it with the &lt;code&gt;--vus&lt;/code&gt; flag. You should see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;          /&lt;span class="se"&gt;\ &lt;/span&gt;     |‾‾| /‾‾/   /‾‾/   
     /&lt;span class="se"&gt;\ &lt;/span&gt; /  &lt;span class="se"&gt;\ &lt;/span&gt;    |  |/  /   /  /    
    /  &lt;span class="se"&gt;\/&lt;/span&gt;    &lt;span class="se"&gt;\ &lt;/span&gt;   |     &lt;span class="o"&gt;(&lt;/span&gt;   /   ‾‾&lt;span class="se"&gt;\ &lt;/span&gt; 
   /          &lt;span class="se"&gt;\ &lt;/span&gt;  |  |&lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;|  &lt;span class="o"&gt;(&lt;/span&gt;‾&lt;span class="o"&gt;)&lt;/span&gt;  | 
  / __________ &lt;span class="se"&gt;\ &lt;/span&gt; |__| &lt;span class="se"&gt;\_&lt;/span&gt;_&lt;span class="se"&gt;\ \_&lt;/span&gt;____/ .io

  execution: &lt;span class="nb"&gt;local
    &lt;/span&gt;script: test_script.js
    output: -

  scenarios: &lt;span class="o"&gt;(&lt;/span&gt;100.00%&lt;span class="o"&gt;)&lt;/span&gt; 1 scenario, 1 max VUs, 35s max duration &lt;span class="o"&gt;(&lt;/span&gt;incl. graceful stop&lt;span class="o"&gt;)&lt;/span&gt;:
        &lt;span class="k"&gt;*&lt;/span&gt; default: 1 looping VUs &lt;span class="k"&gt;for &lt;/span&gt;5s &lt;span class="o"&gt;(&lt;/span&gt;gracefulStop: 30s&lt;span class="o"&gt;)&lt;/span&gt;


running &lt;span class="o"&gt;(&lt;/span&gt;05.0s&lt;span class="o"&gt;)&lt;/span&gt;, 0/1 VUs, 42037 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
default ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 1 VUs  5s

    █ setup

    data_received........: 0 B   0 B/s
    data_sent............: 0 B   0 B/s
    iteration_duration...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;104.45µs &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;53.7µs &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;88.6µs &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9.32ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;115.4µs p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;129.5µs
    iterations...........: 42037 8401.691798/s
    vus..................: 1    &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    vus_max..............: 1    &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test reports that the Redis server handles 8401 iterations per second.  Because each iteration refers to one execution of the default function and there is one request call in our default function, the server is handling 8401 GET requests per second in this test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale the load
&lt;/h3&gt;

&lt;p&gt;Let’s increase the load gradually until it encounters an error. For a start, set the VUs to 100 as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./k6 run test_script.js &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s &lt;span class="nt"&gt;--vus&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;running &lt;span class="o"&gt;(&lt;/span&gt;05.0s&lt;span class="o"&gt;)&lt;/span&gt;, 000/100 VUs, 111939 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
default ↓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 100 VUs  5s

    █ setup

    data_received........: 0 B  0 B/s
    data_sent............: 0 B  0 B/s
    iteration_duration...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.39ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;46.8µs &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.32ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;87.24ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;9.5ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;12.51ms
    iterations...........: 111939 22304.954101/s
    vus..................: 100  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100     &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
    vus_max..............: 100  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100     &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It indicates that your Redis server can sustain about 22304 iterations per second for 100 users at the same time.&lt;/p&gt;

&lt;p&gt;Continue the test and set the VUs to 1000 this time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./k6 run test_script.js &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s &lt;span class="nt"&gt;--vus&lt;/span&gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the configuration of your Redis, you might encounter the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ERRO[0003] ERR max number of clients reached
running at go.k6.io/k6/js/common.Bind.func1 &lt;span class="o"&gt;(&lt;/span&gt;native&lt;span class="o"&gt;)&lt;/span&gt;
default at file:///home/wfng/test_script.js:14:14&lt;span class="o"&gt;(&lt;/span&gt;4&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="nv"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;constant-vus &lt;span class="nv"&gt;scenario&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;stacktrace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It indicates that you have reached the max number of clients allowed. You can check the number of active connection by running the following command inside redis-cli:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;info clients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will returns the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clients&lt;/span&gt;
connected_clients:7
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the max limit, use the following instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;config get maxclients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"maxclients"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"500"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;Now, let’s have a look at how to get the latency via k6. At the time of this writing, the xk6-redis extension does not report latency as part of its metrics. However, you can easily extend the code in your script and implement your own &lt;a href="https://k6.io/docs/using-k6/metrics/#custom-metrics"&gt;custom metrics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have a look at the following workaround to measure latency. First, let’s add the following import statement at the top of your k6 script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Trend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, initialize a Trend instance as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;RedisLatencyMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis_latency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It accepts two input arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;: the name of the custom metric.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isTime&lt;/code&gt;: a boolean indicating whether the values added to the metric are time values or just untyped values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add the final touch by modifying the default function as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;RedisLatencyMetric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Have a look at the following complete code which initialize the options directly inside the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Trend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/x/redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;RedisLatencyMetric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis_latency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:6379&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;RedisLatencyMetric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to see redis_latency metrics once the test has completed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iteration_duration...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;782.57µs &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;67.35µs &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;732.92µs &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15.86ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;1.1ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;1.3ms
iterations...........: 506755 50660.636169/s
redis_latency........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;764.8µs  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s     &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1ms     &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;16ms    p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;1ms   p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;1ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️  Please note that this workaround of measuring the latency is only indicative, as the JavaScript implementation adds an overhead that might skew the reported latency, especially when the latency is in the sub-microsecond range. &lt;/p&gt;

&lt;p&gt;It would be great if the xk6-redis extension provided its own built-in Redis latency metrics similar to the HTTP request metrics. Measuring Redis latency in Go directly would be much more accurate and avoid the unnecessary RedisLatencyMetric script code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;All in all, redis-benchmark is a good tool that provides you with a quick glimpse of the performance of your Redis server. On the other hand, k6 is scriptable in JavaScript and can provide you with better control over the execution and workflow of your test. A scripting language is more flexible for testing various ways to connect and query your Redis server.&lt;/p&gt;

&lt;p&gt;In fact, you can utilize both of the tools to get the best out of them. For example, you can run redis-benchmark when you install it on your machine for the first time, to get a rough idea of the performance. Subsequently, use k6 for more advanced cases like integrating your test with your existing toolbox or automating your testing.&lt;/p&gt;

</description>
      <category>k6</category>
      <category>redis</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
    <item>
      <title>Load Testing SQL Databases with k6</title>
      <dc:creator>Ng Wai Foong</dc:creator>
      <pubDate>Fri, 16 Jul 2021 03:46:34 +0000</pubDate>
      <link>https://forem.com/k6/load-testing-sql-databases-with-k6-30ci</link>
      <guid>https://forem.com/k6/load-testing-sql-databases-with-k6-30ci</guid>
      <description>&lt;p&gt;This short tutorial shows how to run a k6 test for load testing a database.&lt;/p&gt;

&lt;p&gt;In performance testing, we often trigger load tests that simulate realistic user flows, particularly those that are most commonly seen in production. This type of acceptance testing usually interacts with various parts of our infrastructure: web servers, microservices, databases, etc.&lt;/p&gt;

&lt;p&gt;But what if you want to test the performance or scalability of an infrastructure resource in isolation? &lt;/p&gt;

&lt;p&gt;In many cases, internal components use custom protocols, and the testing tool needs to support those protocols to test the resource individually.  Luckily, with k6, you can use or create &lt;a href="https://k6.io/docs/ecosystem/" rel="noopener noreferrer"&gt;extensions&lt;/a&gt; that allow you to test different protocols, such as ZMTQ, SQL, Avro, MLLP, etc. &lt;/p&gt;

&lt;p&gt;One of the components that you might want to test separately is the database. Databases play an essential role in the performance of our applications, and they can be the bottleneck when experiencing a high volume of users. &lt;/p&gt;

&lt;p&gt;Load testing the database directly could provide you with better insights about the database performance in advance. As a result, you could thoroughly plan out your database architecture and determine how to scale it properly.&lt;/p&gt;

&lt;p&gt;In this tutorial, let’s explore how to load test a database using the &lt;a href="https://github.com/imiric/xk6-sql" rel="noopener noreferrer"&gt;xk6-sql extension&lt;/a&gt;. For simplicity, the tests will be executed against a local SQLite3 server but the extension supports the following RDBMS databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;SQLite3&lt;/li&gt;
&lt;li&gt;MS SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;In this section, you will install all the required components and build a k6 binary for SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the C Compiler for SQLite3
&lt;/h3&gt;

&lt;p&gt;A C compiler is required if you are using SQLite3. Simply install the build-essential package if you are using Debian-based operating system. For Windows users, download the &lt;a href="https://jmeubank.github.io/tdm-gcc/" rel="noopener noreferrer"&gt;tdm-gcc compiler&lt;/a&gt;, extract it and place it in any directory that you prefer. Then, add the path of the bin folder to Environment Variable as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwi9z8bijqli1uukrhhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwi9z8bijqli1uukrhhk.png" alt="Edit System Variable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the Golang Toolchain
&lt;/h3&gt;

&lt;p&gt;Head over to Golang’s installation page and download the installer based on the operating system of your machine. Once you have installed, run the following command to verify the version.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

go version


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should get information related the version number of Go as well as your system architecture:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

go version go1.16.4 windows/amd64


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Build the k6 binary including the SQL extension
&lt;/h3&gt;

&lt;p&gt;For non-SQLite database, run the following command to build the k6 binary:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

xk6 build master &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/imiric/xk6-sql


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You need to set CGO_ENABLED to 1 when building for SQLite3 to ensure that C compiler is used:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 xk6 build master &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/imiric/xk6-sql


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On Windows platform, you need set it explicitly, using set first to call the build command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
xk6 build master &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/imiric/xk6-sql


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see the following output on your console:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

2021/06/17 14:29:43 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Temporary folder: C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\w&lt;/span&gt;fng&lt;span class="se"&gt;\A&lt;/span&gt;ppData&lt;span class="se"&gt;\L&lt;/span&gt;ocal&lt;span class="se"&gt;\T&lt;/span&gt;emp&lt;span class="se"&gt;\b&lt;/span&gt;uildenv_2021-06-17-1429.359000039
2021/06/17 14:29:43 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Writing main module: C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\w&lt;/span&gt;fng&lt;span class="se"&gt;\A&lt;/span&gt;ppData&lt;span class="se"&gt;\L&lt;/span&gt;ocal&lt;span class="se"&gt;\T&lt;/span&gt;emp&lt;span class="se"&gt;\b&lt;/span&gt;uildenv_2021-06-17-1429.359000039&lt;span class="se"&gt;\m&lt;/span&gt;ain.go
2021/06/17 14:29:43 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Initializing Go module
2021/06/17 14:29:43 &lt;span class="o"&gt;[&lt;/span&gt;INFO] &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10s&lt;span class="o"&gt;)&lt;/span&gt;: C:&lt;span class="se"&gt;\P&lt;/span&gt;rogram Files&lt;span class="se"&gt;\G&lt;/span&gt;o&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;o.exe mod init k6
go: creating new go.mod: module k6
go: to add module requirements and sums:
        go mod tidy
2021/06/17 14:29:44 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Pinning versions
2021/06/17 14:29:44 &lt;span class="o"&gt;[&lt;/span&gt;INFO] &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s&lt;span class="o"&gt;)&lt;/span&gt;: C:&lt;span class="se"&gt;\P&lt;/span&gt;rogram Files&lt;span class="se"&gt;\G&lt;/span&gt;o&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;o.exe get &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; go.k6.io/k6@master
go: downloading go.k6.io/k6 v0.32.1-0.20210616133500-9f3dd60fbdc1
go get: added go.k6.io/k6 v0.32.1-0.20210616133500-9f3dd60fbdc1
2021/06/17 14:30:50 &lt;span class="o"&gt;[&lt;/span&gt;INFO] &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s&lt;span class="o"&gt;)&lt;/span&gt;: C:&lt;span class="se"&gt;\P&lt;/span&gt;rogram Files&lt;span class="se"&gt;\G&lt;/span&gt;o&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;o.exe get &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; github.com/imiric/xk6-sql
go get: added github.com/imiric/xk6-sql v0.0.0-20210517160107-d222ad8b93eb
2021/06/17 14:30:52 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Build environment ready
2021/06/17 14:30:52 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Building k6
2021/06/17 14:30:52 &lt;span class="o"&gt;[&lt;/span&gt;INFO] &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s&lt;span class="o"&gt;)&lt;/span&gt;: C:&lt;span class="se"&gt;\P&lt;/span&gt;rogram Files&lt;span class="se"&gt;\G&lt;/span&gt;o&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;o.exe mod tidy
2021/06/17 14:30:56 &lt;span class="o"&gt;[&lt;/span&gt;INFO] &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s&lt;span class="o"&gt;)&lt;/span&gt;: C:&lt;span class="se"&gt;\P&lt;/span&gt;rogram Files&lt;span class="se"&gt;\G&lt;/span&gt;o&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;o.exe build &lt;span class="nt"&gt;-o&lt;/span&gt; C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\w&lt;/span&gt;fng&lt;span class="se"&gt;\D&lt;/span&gt;ocuments&lt;span class="se"&gt;\k&lt;/span&gt;6_test&lt;span class="se"&gt;\k&lt;/span&gt;6.exe &lt;span class="nt"&gt;-ldflags&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-trimpath&lt;/span&gt;
2021/06/17 14:31:15 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Build &lt;span class="nb"&gt;complete&lt;/span&gt;: .&lt;span class="se"&gt;\k&lt;/span&gt;6.exe
2021/06/17 14:31:15 &lt;span class="o"&gt;[&lt;/span&gt;INFO] Cleaning up temporary folder: C:&lt;span class="se"&gt;\U&lt;/span&gt;sers&lt;span class="se"&gt;\w&lt;/span&gt;fng&lt;span class="se"&gt;\A&lt;/span&gt;ppData&lt;span class="se"&gt;\L&lt;/span&gt;ocal&lt;span class="se"&gt;\T&lt;/span&gt;emp&lt;span class="se"&gt;\b&lt;/span&gt;uildenv_2021-06-17-1429.359000039


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After that, you should have a new k6 binary in your working directory. Since I am building on the Windows platform, I got the k6.exe executable file.&lt;/p&gt;

&lt;h2&gt;
  
  
  k6 Script
&lt;/h2&gt;

&lt;p&gt;You need to write a JavaScript file in order to perform load testing with k6. Let’s have a look at an example of a simple test script for load testing API via HTTP:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://test.k6.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each test script requires a default function which will be executed over and over again during testing. The script above makes a GET call to our own k6 test API and sleeps for a second on each execution for a single VU.&lt;/p&gt;

&lt;p&gt;For load testing a database, all you need to do is to import the SQL module that you have created earlier and write the corresponding code inside the default function.&lt;/p&gt;

&lt;p&gt;Create a new JavaScript file called &lt;code&gt;script.js&lt;/code&gt; in the same directory as your k6 binary file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Import SQL module
&lt;/h3&gt;

&lt;p&gt;You can import your newly created SQL module by adding this line to &lt;code&gt;script.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/x/sql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The naming is based on what has been defined in the &lt;a href="https://github.com/imiric/xk6-sql/blob/master/sql.go" rel="noopener noreferrer"&gt;Go file&lt;/a&gt;. In this case, it is defined as &lt;code&gt;k6/x/sql&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect to database
&lt;/h3&gt;

&lt;p&gt;You can easily connect to your database by calling the sql.open function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./test.db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It accepts two input parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type - the type of database (mysql, postgres, sqlite3, sqlserver)&lt;/li&gt;
&lt;li&gt;name - the name of the database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup and Teardown the database
&lt;/h3&gt;

&lt;p&gt;Before executing a SQL command, let’s explore a little more about the &lt;a href="https://k6.io/docs/using-k6/test-life-cycle/" rel="noopener noreferrer"&gt;k6 test life cycle&lt;/a&gt;. It typically follows this structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// 1. init code (call once per VU)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. setup code (call once at the beginning of test)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. VU code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;teardown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. teardown code (call once at the end of test)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can add in any init code right before the setup, default function and teardown. Init code serves as the initialization and will be called once for each virtual user (VU).&lt;/p&gt;

&lt;p&gt;Also, you can specify a setup function which is called once at the beginning of the test where VU is 0. On the other hand, teardown is called once at the end of the test.&lt;/p&gt;

&lt;p&gt;As explained earlier, the default function serves as the VU code which will be executed continuously during testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execute SQL command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After connecting to the database, you can use the &lt;code&gt;db&lt;/code&gt; object and call the exec to run any SQL command. &lt;/p&gt;

&lt;p&gt;For example, as part of the setup process, before the “load” runs,  you can create a new table and insert a few rows of data into the table as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`CREATE TABLE IF NOT EXISTS person (
           id integer PRIMARY KEY AUTOINCREMENT,
           email varchar NOT NULL,
           first_name varchar,
           last_name varchar);`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO person (email, first_name, last_name) VALUES('johndoe@email.com', 'John', 'Doe');&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO person (email, first_name, last_name) VALUES('marysue@email.com', 'Mary', 'Sue');&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO person (email, first_name, last_name) VALUES('dorydoe@email.com', 'Dory', 'Doe');&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And you should not forget to clean up the database at the end of the test with the teardown function. This example deletes the table and closes the database connection:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;teardown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE FROM person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DROP TABLE person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Query data from database
&lt;/h3&gt;

&lt;p&gt;You can easily query the output with the query function. Let’s use it as part of load testing to determine how many iterations you can get when querying the database:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As usual, you can run the check statement to determine the output. Let’s do a simple check on the total rows of data in the database:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is length 3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;complete script code&lt;/strong&gt; is as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/x/sql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./test.db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`CREATE TABLE IF NOT EXISTS person (
           id integer PRIMARY KEY AUTOINCREMENT,
           email varchar NOT NULL,
           first_name varchar,
           last_name varchar);`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO person (email, first_name, last_name) VALUES('johndoe@email.com', 'John', 'Doe');&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO person (email, first_name, last_name) VALUES('marysue@email.com', 'Mary', 'Sue');&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO person (email, first_name, last_name) VALUES('dorydoe@email.com', 'Dory', 'Doe');&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;teardown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE FROM person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DROP TABLE person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM person;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is length 3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Running the test
&lt;/h2&gt;

&lt;p&gt;Once we have the completed script, you can run the test. Let’s start running the load test for 5 seconds:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

k6 run script.js &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By default, it is using just one virtual user (VU) but you can modify it via &lt;code&gt;--vus&lt;/code&gt; flag. You should see the following output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

          /&lt;span class="se"&gt;\ &lt;/span&gt;     |‾‾| /‾‾/   /‾‾/
     /&lt;span class="se"&gt;\ &lt;/span&gt; /  &lt;span class="se"&gt;\ &lt;/span&gt;    |  |/  /   /  /
    /  &lt;span class="se"&gt;\/&lt;/span&gt;    &lt;span class="se"&gt;\ &lt;/span&gt;   |     &lt;span class="o"&gt;(&lt;/span&gt;   /   ‾‾&lt;span class="se"&gt;\&lt;/span&gt;
   /          &lt;span class="se"&gt;\ &lt;/span&gt;  |  |&lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;|  &lt;span class="o"&gt;(&lt;/span&gt;‾&lt;span class="o"&gt;)&lt;/span&gt;  |
  / __________ &lt;span class="se"&gt;\ &lt;/span&gt; |__| &lt;span class="se"&gt;\_&lt;/span&gt;_&lt;span class="se"&gt;\ \_&lt;/span&gt;____/ .io

  execution: &lt;span class="nb"&gt;local
     &lt;/span&gt;script: script.js
     output: -

  scenarios: &lt;span class="o"&gt;(&lt;/span&gt;100.00%&lt;span class="o"&gt;)&lt;/span&gt; 1 scenario, 1 max VUs, 35s max duration &lt;span class="o"&gt;(&lt;/span&gt;incl. graceful stop&lt;span class="o"&gt;)&lt;/span&gt;:
           &lt;span class="k"&gt;*&lt;/span&gt; default: 1 looping VUs &lt;span class="k"&gt;for &lt;/span&gt;5s &lt;span class="o"&gt;(&lt;/span&gt;gracefulStop: 30s&lt;span class="o"&gt;)&lt;/span&gt;


running &lt;span class="o"&gt;(&lt;/span&gt;05.1s&lt;span class="o"&gt;)&lt;/span&gt;, 0/1 VUs, 34467 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
default ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 1 VUs  5s

     ✓ is length 3

     █ setup

     █ teardown

     checks...............: 100.00% ✓ 34467       ✗ 0
     data_received........: 0 B     0 B/s
     data_sent............: 0 B     0 B/s
     iteration_duration...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;143.57µs &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;43.24ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;519.2µs p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;985.47µs
     iterations...........: 34467   6812.032587/s
     vus..................: 1       &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1         &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
     vus_max..............: 1       &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1         &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this case, it shows that the database can handle about 6812 queries per second and average time of 144µs per iteration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale the load
&lt;/h3&gt;

&lt;p&gt;In the previous test, you have specified just a single virtual user. Let’s scale it up to 10 and see how SQLite performs. Run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

k6 run script.js &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s &lt;span class="nt"&gt;--vus&lt;/span&gt; 10


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should get the following result:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

running &lt;span class="o"&gt;(&lt;/span&gt;05.1s&lt;span class="o"&gt;)&lt;/span&gt;, 00/10 VUs, 43228 &lt;span class="nb"&gt;complete &lt;/span&gt;and 0 interrupted iterations
default ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 10 VUs  5s

    ✓ is length 3

    █ setup

    █ teardown

    checks...............: 100.00% ✓ 43228    ✗ 0
    data_received........: 0 B  0 B/s
    data_sent............: 0 B  0 B/s
    iteration_duration...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.16ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;136.03ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;522.5µs p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;570.15µs
    iterations...........: 43228   8446.461494/s
    vus..................: 10   &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
    vus_max..............: 10   &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10      &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s continue the test and set the VU to 100 this time.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

k6 run script.js &lt;span class="nt"&gt;--duration&lt;/span&gt; 5s &lt;span class="nt"&gt;--vus&lt;/span&gt; 100


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output is as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

default ✓ &lt;span class="o"&gt;[======================================]&lt;/span&gt; 100 VUs  5s

    ✓ is length 3

    █ setup

    █ teardown

    checks...............: 100.00% ✓ 97490        ✗ 0
    data_received........: 0 B  0 B/s
    data_sent............: 0 B  0 B/s
    iteration_duration...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5.07ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;506.55µs &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;140.07ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;18.13ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;28.58ms
    iterations...........: 97490   19034.709634/s
    vus..................: 100  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100     &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
    vus_max..............: 100  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100     &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It indicates that SQLite is capable of supporting 100 users with an average duration of 5.07ms per transaction.&lt;/p&gt;

&lt;p&gt;For actual use cases, you should continue to scale it up to the point where it will congest your database and cause it to breakdown. This allows you to have a better idea on the &lt;strong&gt;maximum limit of your database&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are new to k6, check out how to configure the &lt;a href="https://k6.io/docs/getting-started/running-k6/#using-options" rel="noopener noreferrer"&gt;load options&lt;/a&gt; in the script or run a &lt;a href="https://k6.io/docs/test-types/stress-testing/" rel="noopener noreferrer"&gt;stress test&lt;/a&gt; with k6. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  About k6 Extensions
&lt;/h2&gt;

&lt;p&gt;For your information, you can combine multiple extensions and build your own custom k6 binary. For example, you can use the following command to build a k6 binary for both sql and redis:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

xk6 build v0.32.0 &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/dgzlopes/xk6-redis &lt;span class="nt"&gt;--with&lt;/span&gt; github.com/imiric/xk6-sql


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Simply head over to the &lt;a href="https://k6.io/docs/ecosystem/bundle-builder/" rel="noopener noreferrer"&gt;bundle builder page&lt;/a&gt; to generate the corresponding command based on your own use cases.&lt;/p&gt;

&lt;p&gt;With this tutorial, I wanted to show you how easy it is to load test your database or other dependencies separately with k6. If you have any questions or are interested in building an extension, join the k6 community on &lt;a href="https://k6.io/slack" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>sql</category>
      <category>k6</category>
      <category>database</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Intro to Testing ASP.NET APIs with k6 - When Unit Tests Meet Load Testing</title>
      <dc:creator>Carlos Schults</dc:creator>
      <pubDate>Thu, 06 May 2021 08:41:29 +0000</pubDate>
      <link>https://forem.com/k6/intro-to-testing-asp-net-apis-with-k6-when-unit-tests-meet-load-testing-5b5h</link>
      <guid>https://forem.com/k6/intro-to-testing-asp-net-apis-with-k6-when-unit-tests-meet-load-testing-5b5h</guid>
      <description>&lt;p&gt;Software organizations are always on the hunt for ways to satisfy and delight their customers. Application performance is certainly a part of that, and ensuring good performance requires testing. Load testing is one of the types of performance testing and, in this post, you'll see how to use k6 to perform load testing on a .NET RESTful API.&lt;/p&gt;

&lt;p&gt;By the end of the post, you’ll have a more solid knowledge of load testing, why it’s important and how it’s performed. You’ll also have practical experience performing load testing with k6, and you’ll understand some of the unique ways in which this tool sets itself apart from competitors. With all of that being said, let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Load Testing: The What, Why, and How
&lt;/h2&gt;

&lt;p&gt;Before getting our hands dirty, let’s start with some fundamentals. What is load testing?&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining Load Testing
&lt;/h3&gt;

&lt;p&gt;Load testing is one of the many types of web performance testing. It consists of verifying how an application behaves under a heavy load—hence the name. With the help of load testing, you can assess the response of your app to increasing demand and then improve it accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Should You Care About Load Testing?
&lt;/h3&gt;

&lt;p&gt;Why is load testing important? Well, as we’ve explained, when it comes to online services, it’s essential to provide a great user experience to your customers, since the competition is only a few clicks away. &lt;em&gt;And performance is a crucial part of the overall user experience of a website&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But why use load testing specifically? In short, you should adopt load testing when you're &lt;a href="https://k6.io/docs/testing-guides/load-testing-websites/#when-to-load-test-a-website" rel="noopener noreferrer"&gt;concerned about the availability and scalability of your website&lt;/a&gt;. Even though the &lt;a href="https://www.stevesouders.com/blog/2012/02/10/the-performance-golden-rule/" rel="noopener noreferrer"&gt;frontend is responsible for relevant performance issues&lt;/a&gt;, you should focus on the backend to ensure your performance and reliability goals when scaling your site. And load testing is exactly the tool you need to verify how your website reacts when put under load.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Do You Perform Load Testing
&lt;/h3&gt;

&lt;p&gt;How is load testing actually performed? As you’ll see, that can vary, even because load testing tools can come in different categories. However, the gist of it is that load testing simulates multiple users sending multiple requests to the app, concurrently. You choose the number of users and requests based on the estimated load you expect your app to be able to handle.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rolling Up Your Sleeves: Let's Load Test An ASP.NET API
&lt;/h1&gt;

&lt;p&gt;After our brief introduction to load testing, it’s time to learn how to do it in practice. &lt;/p&gt;

&lt;h2&gt;
  
  
  Obtaining The Requirements
&lt;/h2&gt;

&lt;p&gt;The application we’ll be testing throughout this tutorial is a RESTful API written in ASP.Net Core. To be able to run the application, you’ll need the &lt;a href="https://dotnet.microsoft.com/download/dotnet/5.0" rel="noopener noreferrer"&gt;.NET SDK version 5.0 or later&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Presenting the Sample Application
&lt;/h2&gt;

&lt;p&gt;For this tutorial, our sample application will be the sample API developed using ASP.Net Core, demonstrated in &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&amp;amp;tabs=visual-studio-code" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Download or clone the complete application from this &lt;a href="https://github.com/carlosschults/sample-todo-api" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As soon as you get the code, cd into the project’s directory and run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

dotnet run


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, open your browser and go to &lt;code&gt;localhost:&amp;lt;port&amp;gt;/api/TodoItems&lt;/code&gt; to see the resources that are returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Start Testing
&lt;/h2&gt;

&lt;p&gt;Here’s where the fun begins: we’ve gotten ourselves an API, so let’s test it.&lt;/p&gt;

&lt;p&gt;One thing to keep in mind when doing performance testing is that, as we’ve mentioned, it comes in different types. More specifically, we can split load testing tools into two main camps: &lt;a href="https://k6.io/blog/comparing-best-open-source-load-testing-tools/#scriptable-tools-vs-non-scriptable-ones" rel="noopener noreferrer"&gt;scriptable and non-scriptable ones&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Scriptable tools are those that allow you to use a full-fledged scripting language to write your tests. Such tools might offer a steeper learning curve and, in some situations, consume slightly more resources. However, the upside is that they enable users to write flexible and more complex tests.&lt;/p&gt;

&lt;p&gt;On the other end of the spectrum, you have non-scriptable tools. They tend to be simpler and consume fewer resources. But on the other hand, they're way less powerful when it comes to the capabilities they offer.&lt;/p&gt;

&lt;p&gt;This post is about k6, which is a powerful scriptable tool. But we won’t jump into k6 right away. Instead, we’ll start by testing our API with a non-scriptable tool. That way, you’ll be able to appreciate the differences between those two categories of tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing a GET Endpoint With a Non-Scriptable Tool
&lt;/h3&gt;

&lt;p&gt;As I’ve mentioned earlier, it’s possible to divide load testing tools into two broad groups: scriptable and non-scriptable ones. So, to appreciate the qualities of the former group, you need at least a little bit of experience with the latter. That’s why before we get to k6 we’ll take a brief detour to show you how testing an endpoint with a non-scriptable tool looks like.&lt;/p&gt;

&lt;p&gt;The non-scriptable tool we’ll use is &lt;a href="https://github.com/wg/wrk" rel="noopener noreferrer"&gt;wrk&lt;/a&gt;. Feel free to follow along with this part of the tutorial but, since it’s a detour, I won’t provide detailed instructions for it. With that out of the way, let’s begin.&lt;/p&gt;

&lt;p&gt;After &lt;a href="https://github.com/wg/wrk/blob/master/INSTALL" rel="noopener noreferrer"&gt;installing &lt;/a&gt;wrk, I’ll run my sample project again by executing:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

dotnet run


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, I’ll use wrk to test the GET endpoint for the TodoItem resource:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

./wrk -t12 -c400 -d30s https://localhost:5001/api/TodoItems/


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is exactly the same "basic usage" example taken from the project's README. It runs a 30 seconds test that uses 12 threads keeping 400 connections open. What do the results look like? See in the following image: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqfk124rq3eh4gb3aa6p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqfk124rq3eh4gb3aa6p.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's unpack that. After a short text summarizing the executed test (30 seconds, 12 threads, 400 connections) we see statistics regarding the latency and requests per second. For each of those we see, among other info, the average value (1), the standard deviation (2), the maximum value (3).&lt;/p&gt;

&lt;p&gt;We also see the number of requests that were performed, in how much time, and the total volume of data that was read (5). Finally, we see the total amount of requests per second and the volume of data transferred.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing a Get Endpoint with k6
&lt;/h3&gt;

&lt;p&gt;Now it’s time to repeat the previous test, this time using k6.&lt;/p&gt;

&lt;h4&gt;
  
  
  Installing
&lt;/h4&gt;

&lt;p&gt;Let’s start by installing the tool. If you use macOS, install k6 is a breeze using homebrew:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

brew install k6
```

Users of Debian, Ubuntu or other Debian-based distros should run the following commands:
```bash
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 379CE192D401AB61
echo "deb https://dl.bintray.com/loadimpact/deb stable main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update
sudo apt-get install k6
```

If you’re on Windows, you can [download the.msi installer](https://k6.io/docs/getting-started/installation/#windows-msi-installer). Users of other platforms, take a look at the [k6 installation guide](https://k6.io/docs/getting-started/installation/).

After the installation is complete, regardless of OS, you should be able to run {% raw %}`k6 version` to see the current version number and verify the installation was a success.

#### Writing and Running Your First Test Script

To perform k6 tests, you’ll need to create a test script. Test scripts in k6 are written in JavaScript, which is a perfect choice, considering most web developers will already be familiar with the language—as opposed to something like Python, Ruby, or a custom DSL.

Using your favorite editor, create a file called **script.js**. I’ll use Visual Studio Code:

```
code script.js
```

Then, add the following code to the new file:

```javascript
import http from 'k6/http';

export default function () {
  http.get('http://localhost:5000/Api/TodoItems');
}
```

Of course, make sure that you’ve started the API. Also, change the port number if necessary.

After saving the test script, you can test it by running:

```
k6 run script.js
```

The results should look something like this:
```
          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: script.js
     output: -

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


running (00m00.1s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [ 100% ] 1 VUs  00m00.1s/10m0s  1/1 iters, 1 per VU

     data_received..................: 1.8 kB 33 kB/s
     data_sent......................: 816 B  15 kB/s
     http_req_blocked...............: avg=22ms    min=5ms     med=22ms    max=39ms    p(90)=35.6ms   p(95)=37.3ms 
     http_req_connecting............: avg=499.9µs min=0s      med=499.9µs max=999.8µs p(90)=899.82µs p(95)=949.8µs
     http_req_duration..............: avg=3.99ms  min=999.7µs med=3.99ms  max=6.99ms  p(90)=6.39ms   p(95)=6.69ms 
       { expected_response:true }...: avg=3.99ms  min=999.7µs med=3.99ms  max=6.99ms  p(90)=6.39ms   p(95)=6.69ms 
     http_req_failed................: 0.00%  ✓ 0 ✗ 2
     http_req_receiving.............: avg=0s      min=0s      med=0s      max=0s      p(90)=0s       p(95)=0s     
     http_req_sending...............: avg=0s      min=0s      med=0s      max=0s      p(90)=0s       p(95)=0s     
     http_req_tls_handshaking.......: avg=19ms    min=0s      med=19ms    max=38ms    p(90)=34.2ms   p(95)=36.1ms 
     http_req_waiting...............: avg=3.99ms  min=999.7µs med=3.99ms  max=6.99ms  p(90)=6.39ms   p(95)=6.69ms 
     http_reqs......................: 2      37.030248/s
     iteration_duration.............: avg=54ms    min=54ms    med=54ms    max=54ms    p(90)=54ms     p(95)=54ms   
     iterations.....................: 1      18.515124/s
```
#### Improving The Test

You’ve just written and run your first k6 test script. Congrats! However, we need to beef it up a little bit so it compares with the test we’ve done using wrk. Our k6 test is, at the moment, a simple request. It sends a get request to the specified URL and then waits for 1 second.

When running a test with k6, you can pass parameters to the test. Run the following command:

```
k6 --vus 400 --duration 30s run script.js
```

You can see we’ve used two parameters here.  The **--duration** option defines that the test should run for 30 seconds.

The **--vus** option stands for [virtual users](https://k6.io/docs/misc/glossary/#virtual-users), which are used to send the requests to the system under test.To understand the concept of VUs, think of them like parallel infinite loops. If you go back to our script, you'll see there is a `default` function. Scripts in k6 need to have that `default` function as a requirement. That's the entry point for the test script. The code inside such a function is what the VUs execute; it gets executed over and over again, for the whole duration of the tests.

Besides the `default` function, you can have additional code. However, code outside `default` is run only once per VU. You use that code for initialization duties, such as importing different modules or loading something from your filesystem.

That being said, let’s see what the result of the command’s execution looks like:

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

  execution: local
     script: script.js
     output: -

  scenarios: (100.00%) 1 scenario, 400 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 400 looping VUs for 30s (gracefulStop: 30s)


running (0m00.9s), 400/400 VUs, 8 complete and 0 interrupted iterations
default   [   3% ] 400 VUs  00.9s/30s

...

running (0m30.0s), 000/400 VUs, 202254 complete and 0 interrupted iterations
default ✓ [ 100% ] 400 VUs  30s

     data_received..................: 60 MB  2.0 MB/s
     data_sent......................: 36 MB  1.2 MB/s
     http_req_blocked...............: avg=1.66ms   min=0s    med=0s      max=2.68s p(90)=0s      p(95)=0s     
     http_req_connecting............: avg=213.12µs min=0s    med=0s      max=1.32s p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=27.65ms  min=0s    med=24ms    max=1.96s p(90)=40ms    p(95)=54.99ms
       { expected_response:true }...: avg=27.65ms  min=0s    med=24ms    max=1.96s p(90)=40ms    p(95)=54.99ms
     http_req_failed................: 0.00%  ✓ 0     ✗ 404508
     http_req_receiving.............: avg=703.91µs min=0s    med=0s      max=1.35s p(90)=0s      p(95)=999.1µs
     http_req_sending...............: avg=167.24µs min=0s    med=0s      max=1.39s p(90)=0s      p(95)=0s     
     http_req_tls_handshaking.......: avg=1.3ms    min=0s    med=0s      max=2.65s p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=26.77ms  min=0s    med=24ms    max=1.46s p(90)=39.99ms p(95)=51ms   
     http_reqs......................: 404508 13474.167948/s
     iteration_duration.............: avg=59.23ms  min=998µs med=49.99ms max=2.84s p(90)=78.99ms p(95)=98.99ms
     iterations.....................: 202254 6737.083974/s
     vus............................: 380    min=380 max=400 
     vus_max........................: 400    min=400 max=400 
```
What you see above is what the results look like for me after running the latest version of the command. As you can see, there’s a lot going on here. Let’s walk through some of the main pieces of information:

*   `data_received`: that’s the total amount of data received (109 MB) at a rate of 3.4 MB per second;
*   `http_req_duration`: information about the duration of the HTTP requests performed, including the average, median, minimum and maximum values;
*   `http_req_failed`: the rate of requests that failed;
*   `http_req_waiting`: time spent waiting for the server’s response.

To learn more about the metrics displayed in k6’s results, you can [refer to the documentation](https://k6.io/docs/using-k6/metrics/).

### Testing With a Higher Load

We’ll now perform a different test, sending a PUT request instead of a GET one. We’ll also use an additional option of k6 that allows us to simulate an increase in the load on our application.

For that, I’ll create a new test script and name it script2.js. Here’s what its code looks like:

```javascript
import http from 'k6/http';

const url = 'https://localhost:5001/api/TodoItems/';

export let options = {
  stages: [
    { duration: '15s', target: 100 },
    { duration: '30s', target: 500 },
    { duration: '15s', target: 0 },
  ],
};  

export default function () {

  const headers = { 'Accept': '*/*', 'Content-Type': 'application/json', 'Host': 'localhost:5001' };
  const data = {
      name: 'Send e-mail',
      isComplete: false      
  };

  http.put(url, JSON.stringify(data), { headers: headers });
}
```

As you can see, in the script above we’re sending a PUT request to our API, trying to add a new TodoItem resource. Before that, though, we create an `options` variable in which we define three stages for our test. In the first one, which lasts for 15 seconds, k6 will increase the number of VUs from 0 to 100. Then, it will ramp up to 500 for another 30seconds, before ramping down to 0 in the last 15 seconds.

Here’s what the results look like:

```

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

  execution: local
     script: script2.js
     output: -

  scenarios: (100.00%) 1 scenario, 500 max VUs, 1m30s max duration (incl. graceful stop):
           * default: Up to 500 looping VUs for 1m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)


running (0m00.8s), 006/500 VUs, 6743 complete and 0 interrupted iterations
default   [   1% ] 006/500 VUs  0m00.8s/1m00.0s

...

running (1m00.0s), 000/500 VUs, 671850 complete and 0 interrupted iterations
default ✓ [ 100% ] 000/500 VUs  1m0s

     data_received..............: 32 MB   537 kB/s
     data_sent..................: 84 MB   1.4 MB/s
     http_req_blocked...........: avg=73.54µs min=0s med=0s      max=530.01ms p(90)=0s      p(95)=0s     
     http_req_connecting........: avg=16.56µs min=0s med=0s      max=212ms    p(90)=0s      p(95)=0s     
     http_req_duration..........: avg=19.56ms min=0s med=14ms    max=307.99ms p(90)=41.99ms p(95)=55ms   
     http_req_failed............: 100.00% ✓ 671850 ✗ 0    
     http_req_receiving.........: avg=34.6µs  min=0s med=0s      max=229.99ms p(90)=0s      p(95)=0s     
     http_req_sending...........: avg=85.33µs min=0s med=0s      max=157ms    p(90)=0s      p(95)=999µs  
     http_req_tls_handshaking...: avg=56.37µs min=0s med=0s      max=364ms    p(90)=0s      p(95)=0s     
     http_req_waiting...........: avg=19.44ms min=0s med=14ms    max=272.99ms p(90)=41ms    p(95)=54.99ms
     http_reqs..................: 671850  11196.657731/s
     iteration_duration.........: avg=20.07ms min=0s med=14.99ms max=632.99ms p(90)=42.99ms p(95)=56.99ms
     iterations.................: 671850  11196.657731/s
     vus........................: 1       min=1    max=500
     vus_max....................: 500     min=500  max=500
```

## Here's Where a Scriptable Tool Shines: Testing a User Scenario

Up until now, we’ve been testing using fairly simple scripts. You might be wondering what’s all the fuss about scriptable tools after all. Well, here’s the thing: a non-scriptable tool might be enough if you only need to perform basic requests.

However, there are scenarios in which a non-scriptable tool can really make a difference. One of those is where you need to test a user scenario. In other words, you might need to verify a real usage workflow in your app. You simply can’t do that with a non-scriptable tool.

Up until now, we've been testing an endpoint in isolation. Often, when monitoring your services, you might have found a REST API underperforming at a certain load level and want to simulate this behavior again. A non-scriptable tool is often enough for this type of testing.

Such a type of verification, however, isn’t the most realistic. Why? Well, users don’t behave like that. They don’t do things in a completely isolated way. In the real world, your application continuously responds to a flow of real-user interactions, e.g., visit a page, log in, list items, purchase some items, and so on.

Testing real-world scenarios allow you to validate critical business logic or the most frequent user flows. If you want to mimic this type of interaction, a scriptable tool makes this job possible and more manageable.

The following script is a simple example of how testing a user workflow could look like. Using k6, we hit the GET endpoint, retrieving the existing resources. We then get the id of the first object, and use that to send another get  request, obtaining that resource. After that, we set the value of the `completed` attribute and send a PUT request to update the resource on the server. Finally, we use a check to verify whether the response has the expected status code.

```javascript
import http from 'k6/http';
import { check,  sleep } from 'k6';

export let options = {
    vus: 30,
    duration: '40s'
}

export default function () {
  let url = 'https://localhost:5001/api/TodoItems';

  // getting all todo items
  let response = http.get(url);

  // parsing the response body
  let obj = JSON.parse(response.body);

  // retrieving the id from the first resource
  let idFirstItem = obj[0].id;

  // retrieving the resource with the selected id
  response = http.get(`${url}/${idFirstItem}`);
  let item = JSON.parse(response.body);

  // setting the item as complete
  item.complete = true;

  // updating the item
  const headers = { 'Content-Type': 'application/json' };
  let res = http.put(`${url}/${idFirstItem}`, JSON.stringify(item), { headers: headers });

  // checking the response has the 204 (no content) status code
  check(res, {
    'is status 204': (r) =&amp;gt; r.status === 204,
  });

  // random think time between 0 and 5 seconds
  sleep(Math.random() * 5);
}
```

In the previous example, the test runs the same user flow continuously until the test duration ends. But with k6, and you can also simulate different user flows with varying load patterns to run simultaneously. This flexibility allows you to test your application more precisely.

For example, you could run 50 virtual users doing some actions and 100 virtual users doing something different for a different period while generating a constant request rate to one endpoint. 

```javascript
export const options = {

  scenarios: {
    scenario1: {
      executor: 'constant-vus',
      duration: "1m",
      vus: 50,
      exec: "userFlowA"
    },

    scenario2: {
      executor: 'ramping-vus',
      stages: [
        { duration: '1m', target: 100 },
        { duration: '30s', target: 0 },
      ],
      exec: "userFlowB"
    },

    scenario3: {
      executor: 'constant-arrival-rate',
      rate: 200,
      timeUnit: '1s',
      duration: '1m',
      preAllocatedVUs: 50,
      exec: "hammerEndpointA"
    }
  }
}
```

To learn more about configuring advanced traffic patterns, check out the [Scenarios API](https://k6.io/docs/using-k6/scenarios/).


## Here's Where k6 Shines: Setting Performance Goals

The ability to set performance goals for your application is an aspect that differentiates k6 from other load testing tools. With k6, you can verify the performance of your app against expected baselines in a way that’s [not that different from assertions in unit testing](https://k6.io/unit-testing-for-performance/).

How does that work in practice? There are two main options that k6 provides: checks and thresholds. 

[Checks ](https://k6.io/docs/using-k6/checks/) allow you to set expectations in your script and verify those expectations automatically. We’ve used one check in our previous example. Even if the checks fail, the execution of the script doesn’t stop. 

[Thresholds](https://k6.io/docs/using-k6/thresholds/), on the other hand, do interrupt the script’s execution. They’re criteria you can use to fail your load test script when the system under test doesn’t meet the expectations.

Here are some examples of thresholds you could use:

- The system generates at the most 1% errors.
- Response time for 99% of requests should be below 400ms.
- Response time for a specific endpoint must always be below 300ms.

Since k6 allows you to define custom metrics, you can always define thresholds on them as well. Here’s an example of what thresholds look like in an actual script:

```javascript
export let options = {
  thresholds: {
    http_req_failed: ['rate&amp;lt;0.01'],   // http errors should be less than 1% 
    http_req_duration: ['p(99)&amp;lt;400'], // 99% of requests should be below 400ms
  },
};
```

## k6: When Unit Tests Meet Load Testing

In this post, you’ve learned how to get started with load testing using k6. We’ve covered some fundamentals on load testing: you’ve learned the definition of this technique, and the differences between scriptable and non-scriptable load testing tools.

k6 belongs to the latter group. It allows developers to author test scenarios in JavaScript—a language they’re likely to already know. Because of that, k6 is a powerful tool, enabling the performing not only of HTTP benchmarking, but also the verification of realistic usage scenarios.

Perhaps the greatest differentiator of k6 is that it bridges the gap between unit testing and load testing. Developers can use the same workflow they’ve been using for years: creating tests, adding verifications with pass/fail criteria, adding those to the CI/CD pipeline, and then be notified when the tests do fail.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>dotnet</category>
      <category>aspnet</category>
      <category>performance</category>
      <category>testing</category>
    </item>
    <item>
      <title>Running distributed k6 tests on Kubernetes</title>
      <dc:creator>Simme</dc:creator>
      <pubDate>Thu, 11 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://forem.com/k6/running-distributed-k6-tests-on-kubernetes-1fp7</link>
      <guid>https://forem.com/k6/running-distributed-k6-tests-on-kubernetes-1fp7</guid>
      <description>&lt;blockquote&gt;
&lt;h3&gt;
  
  
  📖What you will learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What the operator pattern is and when it is useful&lt;/li&gt;
&lt;li&gt;Deploying the k6 operator in your kubernetes cluster&lt;/li&gt;
&lt;li&gt;Running a distributed k6 test in your own cluster&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  ⚠️ Experimental
&lt;/h4&gt;

&lt;p&gt;The project used in this article is experimental and changes a lot between commits. Use at your own discretion .&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="/blog/static/49d58b70df40a0fa1aa75dd1f6d1f670/acdd1/operator.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2F49d58b70df40a0fa1aa75dd1f6d1f670%2F7842b%2Foperator.png" title="operator" alt="operator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;One of the questions we often get in the forum is how to run distributed k6 tests on your own infrastructure. While we believe that &lt;a href="https://k6.io/docs/testing-guides/running-large-tests" rel="noopener noreferrer"&gt;running large load tests&lt;/a&gt; is possible even when running on a single node, we do appreciate that this is something some of our users might want to do.&lt;/p&gt;

&lt;p&gt;There are at least a couple of reasons why you would want to do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You run everything else in Kubernetes and would like k6 to be executed in the same fashion as all your other infrastructure components. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have access to a couple of high-end nodes and want to pool their resources into a large-scale stress test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have access to multiple low-end or highly utilized nodes and need to pool their resources to be able to reach your target VU count or Requests per Second (RPS).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To be able to follow along in this guide, you’ll need access to a Kubernetes cluster, with enough privileges to apply objects.&lt;/p&gt;

&lt;p&gt;You’ll also need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kubernetes-sigs/kustomize/" rel="noopener noreferrer"&gt;Kustomize&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" rel="noopener noreferrer"&gt;Kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/make/" rel="noopener noreferrer"&gt;Make&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Kubernetes Operator pattern
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/" rel="noopener noreferrer"&gt;operator pattern&lt;/a&gt; is a way of extending Kubernetes so that you may use &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/" rel="noopener noreferrer"&gt;custom resources&lt;/a&gt; to manage applications running in the cluster. The pattern aims to automate the tasks that a human operator would usually do, like provisioning new application components, changing the configuration, or resolving problems that occur.&lt;/p&gt;

&lt;p&gt;This is accomplished using custom resources which, for the scope of this article, could be compared to the traditional service requests that you would file to your system operator to get changes applied to the environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="/blog/static/8bc25b5fb3de365092d17de6121c3280/d9c41/pattern.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2F8bc25b5fb3de365092d17de6121c3280%2F7842b%2Fpattern.png" title="operator pattern" alt="operator pattern"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The operator will listen for changes to, or creation of, K6 custom resource objects. Once a change is detected, it will react by modifying the cluster state, spinning up k6 test jobs as needed. It will then use the parallelism argument to figure out how to split the workload between the jobs using &lt;a href="https://k6.io/docs/using-k6/options#execution-segment" rel="noopener noreferrer"&gt;execution segments&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the k6 operator to run a distributed load test in your Kubernetes cluster
&lt;/h2&gt;

&lt;p&gt;We'll now go through the steps required to deploy, run, and clean up after the k6 operator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloning the repository
&lt;/h3&gt;

&lt;p&gt;Before we get started, we need to clone the operator repository from GitHub and navigate to the repository root:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ git clone https://github.com/k6io/operator &amp;amp;&amp;amp; cd operator



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Deploying the operator
&lt;/h3&gt;

&lt;p&gt;Deploying the operator is done by running the command below, with kubectl configured to use the context of the cluster that you want to deploy it to.&lt;/p&gt;

&lt;p&gt;First, make sure you are using the right context:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl config get-contexts

CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* harley harley harley
          jean jean jean
          ripley ripley ripley



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then deploy the operator bundle using make. This will also apply the roles, namespaces, bindings and services needed to run the operator.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ make deploy

/Users/simme/.go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
cd config/manager &amp;amp;&amp;amp; /Users/simme/.go/bin/kustomize edit set image controller=ghcr.io/k6io/operator:latest
/Users/simme/.go/bin/kustomize build config/default | kubectl apply -f -
namespace/k6-operator-system created
customresourcedefinition.apiextensions.k8s.io/k6s.k6.io created
role.rbac.authorization.k8s.io/k6-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/k6-operator-manager-role created
clusterrole.rbac.authorization.k8s.io/k6-operator-proxy-role created
clusterrole.rbac.authorization.k8s.io/k6-operator-metrics-reader created
rolebinding.rbac.authorization.k8s.io/k6-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/k6-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/k6-operator-proxy-rolebinding created
service/k6-operator-controller-manager-metrics-service created
deployment.apps/k6-operator-controller-manager created



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Writing our test script
&lt;/h3&gt;

&lt;p&gt;Once that is done, we need to create a config map containing the test script. For the operator to pick up our script, we need to name the file &lt;code&gt;test.js&lt;/code&gt;. For this article, we’ll be using the test script below:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

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

export let options = {
  stages: [
    { target: 200, duration: '30s' },
    { target: 0, duration: '30s' },
  ],
};

export default function () {
  const result = http.get('https://test-api.k6.io/public/crocodiles/');
  check(result, {
    'http response status code is 200': result.status === 200,
  });
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Before we continue, we'll run the script once locally to make sure it works:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ k6 run test.js



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you’ve never written a k6 test before, we recommend that you start by reading &lt;a href="https://k6.io/docs/getting-started/running-k6" rel="noopener noreferrer"&gt;this getting started article from the documentation&lt;/a&gt;, just to get a feel for how it works.&lt;/p&gt;

&lt;p&gt;Let’s walk through this script and make sure we understand what is happening: We’ve set up two stages that will run for 30 seconds each. The first one will ramp up to linearly to 200 VUs over 30 seconds. The second one will ramp down to 0 again over 30 seconds.&lt;/p&gt;

&lt;p&gt;In this case the operator will tell each test runner to run only a portion of the total VUs. For instance, if the script calls for 40 VUs, and &lt;code&gt;parallelism&lt;/code&gt; is set to 4, the test runners would have 10 VUs each.&lt;/p&gt;

&lt;p&gt;Each VU will then loop over the default function as many times as possible during the execution. It will execute an HTTP GET request against the URL we’ve configured, and make sure that the responds with HTTP Status 200. In a real test, we'd probably throw in a sleep here to emulate the think time of the user, but as the purpose of this article is to run a distributed test with as much throughput as possible, I've deliberately skipped it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying our test script
&lt;/h3&gt;

&lt;p&gt;Once the test script is done, we have to deploy it to the kubernetes cluster. We’ll use a &lt;code&gt;ConfigMap&lt;/code&gt; to accomplish this. The name of the map can be whatever you like, but for this demo we'll go with &lt;code&gt;crocodile-stress-test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want more than one test script available in your cluster, you just repeat this process for each one, giving the maps different names.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl create configmap crocodile-stress-test --from-file /path/to/our/test.js

configmap/crocodile-stress-test created



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  ⚠️ Namespaces
&lt;/h4&gt;

&lt;p&gt;For this to work, the k6 custom resource and the config map needs to be deployed in the same namespace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s have a look at the result:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl describe configmap crocodile-stress-test

Name: crocodile-stress-test
Namespace: default
Labels: &amp;lt;none&amp;gt;
Annotations: &amp;lt;none&amp;gt;

Data
====
test.js:
----
import http from 'k6/http';
import { check } from 'k6';

export let options = {
  stages: [
    { target: 200, duration: '30s' },
    { target: 0, duration: '30s' },
  ],
};

export default function () {
  const result = http.get('https://test-api.k6.io/public/crocodiles/');
  check(result, {
    'http response status code is 200': result.status === 200,
  });
}

Events: &amp;lt;none&amp;gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The config map contains the content of our test file, labelled as test.js. The operator will later search through our config map for this key, and use its content as the test script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating our custom resource (CR)
&lt;/h3&gt;

&lt;p&gt;To communicate with the operator, we’ll use a custom resource called &lt;code&gt;K6&lt;/code&gt;. Custom resources behave just as native kubernetes objects, while being fully customizable. In this case, the data of the custom resource contains all the information necessary for k6 operator to be able to start a distributed load test:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: k6.io/v1alpha1
kind: K6
metadata:
  name: k6-sample
spec:
  parallelism: 4
  script: crocodile-stress-test



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For Kubernetes to know what to do with this custom resource, we first need to specify what API Version we want to use to interpret its content, in this case &lt;code&gt;k6.io/v1alpha1&lt;/code&gt;. We’ll then set the kind to K6, and give our resource a name.&lt;/p&gt;

&lt;p&gt;As the specification for our custom resource, we now have the option to use a couple of different properties:&lt;/p&gt;

&lt;h4&gt;
  
  
  Parallelism
&lt;/h4&gt;

&lt;p&gt;Configures how many k6 test runner jobs the operator should spawn.&lt;/p&gt;

&lt;h4&gt;
  
  
  Script
&lt;/h4&gt;

&lt;p&gt;The name of the config map containing our &lt;code&gt;script.js&lt;/code&gt; file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Separate
&lt;/h4&gt;

&lt;p&gt;Whether the operator should allow multiple k6 jobs to run concurrently at the same node. The default value for this property is &lt;code&gt;false&lt;/code&gt;, allowing each node to run multiple jobs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Arguments
&lt;/h4&gt;

&lt;p&gt;Allowing you to pass arguments to each k6 job, just as you would from the CLI. For instance &lt;code&gt;--tag testId=crocodile-stress-test-1&lt;/code&gt;,&lt;code&gt;--out cloud&lt;/code&gt;, or &lt;code&gt;—no-connection-reuse&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying our Custom Resource
&lt;/h3&gt;

&lt;p&gt;We will now deploy our custom resource using kubectl, and by that, start the test:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl apply -f /path/to/our/k6/custom-resource.yml

k6.k6.io/k6-sample created



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we do this, the k6 operator will pick up the changes and start the execution of the test. This looks somewhat along the lines of what is shown in this diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="/blog/static/8c12a4c120f2f4feed3d7284df4be089/14945/pattern-k6.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk6.io%2Fblog%2Fstatic%2F8c12a4c120f2f4feed3d7284df4be089%2F14945%2Fpattern-k6.png" title="k6 pattern" alt="k6 pattern"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s make sure everything went as expected:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl get k6 

NAME AGE
k6-sample 2s

$ kubectl get jobs

NAME COMPLETIONS DURATION AGE
k6-sample-1 0/1 12s 12s
k6-sample-2 0/1 12s 12s
k6-sample-3 0/1 12s 12s
k6-sample-4 0/1 12s 12s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
k6-sample-3-s7hdk 1/1 Running 0 20s
k6-sample-4-thnpw 1/1 Running 0 20s
k6-sample-2-f9bbj 1/1 Running 0 20s
k6-sample-1-f7ktl 1/1 Running 0 20s



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The pods have now been created and put in a paused state until the operator has made sure they’re all ready to execute the test. Once that’s the case, the operator deploys another job, k6-sample-starter which is responsible for making sure all our runners start execution at the same time.&lt;/p&gt;

&lt;p&gt;Let’s wait a couple of seconds and then list our pods again:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl get pods

NAME READY STATUS RESTARTS AGE
k6-sample-3-s7hdk 1/1 Running 0 76s
k6-sample-4-thnpw 1/1 Running 0 76s
k6-sample-2-f9bbj 1/1 Running 0 76s
k6-sample-1-f7ktl 1/1 Running 0 76s
k6-sample-starter-scw59 0/1 Completed 0 56s



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;All right! The starter has completed and our tests are hopefully running. To make sure, we may check the logs of one of the jobs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl logs k6-sample-1-f7ktl

[...]

Run [100%] paused
default [0%]

Run [100%] paused
default [0%]

running (0m00.7s), 02/50 VUs, 0 complete and 0 interrupted iterations
default [1%] 02/50 VUs 0m00.7s/1m00.0s

running (0m01.7s), 03/50 VUs, 13 complete and 0 interrupted iterations
default [3%] 03/50 VUs 0m01.7s/1m00.0s

running (0m02.7s), 05/50 VUs, 41 complete and 0 interrupted iterations
default [4%] 05/50 VUs 0m02.7s/1m00.0s

[...]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And with that, our test is running! 🎉 After a couple of minutes, we’re now able to list the jobs again to verify they’ve all completed:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl get jobs

NAME COMPLETIONS DURATION AGE
k6-sample-starter 1/1 8s 6m2s
k6-sample-3 1/1 96s 6m22s
k6-sample-2 1/1 96s 6m22s
k6-sample-1 1/1 97s 6m22s
k6-sample-4 1/1 97s 6m22s



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Cleaning up
&lt;/h3&gt;

&lt;p&gt;To clean up after a test run, we delete all resources using the same yaml file we used to deploy it:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl delete -f /path/to/our/k6/custom-resource.yml

k6.k6.io "k6-sample" deleted



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which deletes all the resources created by the operator as well, as shown below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ kubectl get jobs
No resources found in default namespace.

$ kubectl get pods
No resources found in default namespace.



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  ⚠️ Deleting the operator
&lt;/h4&gt;

&lt;p&gt;If you for some reason would like to delete the operator altogether, just run make delete from the root of the project..&lt;/p&gt;

&lt;p&gt;The idea behind the operator however, is that you let it remain in your cluster between test executions, only applying and deleting the actual K6 custom resources used to run the tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Things to consider
&lt;/h2&gt;

&lt;p&gt;While the operator makes running distributed load tests a lot easier, it still comes with a couple of drawbacks or gotchas that you need to be aware of and plan for. For instance, the lack of metric aggregation.&lt;/p&gt;

&lt;p&gt;We’ll go through in detail how to set up the monitoring and visualisation of these test runs in a future article, but for now, here’s a list of things you might want to consider:&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics will not be automatically aggregated
&lt;/h3&gt;

&lt;p&gt;Metrics generated by running distributed k6 tests using the operator won’t be aggregated, which means that each test runner will produce its own results and end-of-test summary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To be able to aggregate your metrics and analyse them together, you’ll either need to:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) Set up some kind of monitoring or visualisation software and configure your K6 custom resource to make your jobs output there.&lt;/p&gt;

&lt;p&gt;2) Use &lt;a href="https://github.com/elastic/logstash" rel="noopener noreferrer"&gt;logstash&lt;/a&gt;, &lt;a href="https://github.com/fluent/fluentd" rel="noopener noreferrer"&gt;fluentd&lt;/a&gt;, splunk, or similar to parse and aggregate the logs yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thresholds are not evaluated across jobs at runtime
&lt;/h3&gt;

&lt;p&gt;As the metrics are not aggregated at runtime, your thresholds won’t be evaluated using aggregation either. Currently, the best way to solve this is by setting up alarms for passed thresholds in your monitoring or visualisation software instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overpopulated nodes might create bottlenecks
&lt;/h3&gt;

&lt;p&gt;You want to make sure your k6 jobs have enough cpu and memory resources to actually perform your test. Using parallelism alone might not be sufficient. If you run into this issue, experiment with using the separate property.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experimental
&lt;/h3&gt;

&lt;p&gt;As mentioned in the beginning of the article, the operator &lt;em&gt;is&lt;/em&gt; experimental, and as such it might change a lot from commit to commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Total cost of ownership
&lt;/h3&gt;

&lt;p&gt;The k6 operator significantly simplifies the process of running distributed load tests in your own cluster. However, there still is a maintenance burden associated with self-hosting. If you'd rather skip that, as well as the other drawbacks listed above, and instead get straight to load testing, you might want to have a look at the &lt;a href="https://k6.io/cloud" rel="noopener noreferrer"&gt;k6 cloud offering&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/k6io/operator" rel="noopener noreferrer"&gt;The k6 operator project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  🙏🏼 Thank you for reading!
&lt;/h4&gt;

&lt;p&gt;If you enjoyed this article and would like to read others like it in the future, it would definitely make us happy campers if you hit the ❤️ or 🦄 buttons.&lt;/p&gt;

&lt;p&gt;To not miss out on any of our future content, make sure to press the follow button.&lt;/p&gt;

&lt;p&gt;Want to get in touch with us? Hit us up either in the comments below or &lt;a href="https://twitter.com/k6_io" rel="noopener noreferrer"&gt;on Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>cloud</category>
      <category>testing</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Performance testing gRPC services</title>
      <dc:creator>Simme</dc:creator>
      <pubDate>Thu, 12 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/k6/performance-testing-grpc-services-93c</link>
      <guid>https://forem.com/k6/performance-testing-grpc-services-93c</guid>
      <description>&lt;blockquote&gt;
&lt;h3&gt;
  
  
  🎉 New in v0.29.0
&lt;/h3&gt;

&lt;p&gt;v0.29.0 contained a lot of interesting features. Have a look at the &lt;a href="https://github.com/loadimpact/k6/releases/tag/v0.29.0"&gt;release notes&lt;/a&gt; for details!&lt;/p&gt;
&lt;/blockquote&gt;



&lt;blockquote&gt;
&lt;h3&gt;
  
  
  📖What you will learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What gRPC is.&lt;/li&gt;
&lt;li&gt;How gRPC differs from JSON-based REST.&lt;/li&gt;
&lt;li&gt;Creating and executing your first gRPC performance test using k6.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Outline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is gRPC&lt;/li&gt;
&lt;li&gt;API Types&lt;/li&gt;
&lt;li&gt;The proto definition&lt;/li&gt;
&lt;li&gt;Getting started&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;li&gt;See also&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is gRPC
&lt;/h2&gt;

&lt;p&gt;gRPC is a light-weight open-source RPC framework. It was originally developed by Google, with 1.0 being released in August 2016. Since then, it's gained a lot of attention as well as a wide adoption.&lt;/p&gt;

&lt;p&gt;In comparison to JSON, which is transmitted as human-readable text, gRPC is binary, making it both faster to transmit and more compact. In the benchmarks we've seen between gRPC and JSON-based REST, gRPC has proved to be a lot faster than its more traditional counterpart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/blog/beating-json-performance-with-protobuf/"&gt;A benchmark by Auth0&lt;/a&gt; reported up to 6 times higher performance, while other benchmarks, like&lt;a href="https://dev.to/plutov/benchmarking-grpc-and-rest-in-go-565"&gt;this one by Alex Pliutau&lt;/a&gt; or&lt;a href="https://medium.com/@EmperorRXF/evaluating-performance-of-rest-vs-grpc-1b8bdf0b22da"&gt;this one by Ruwan Fernando&lt;/a&gt;, suggests improvements of up to 10 times.&lt;/p&gt;

&lt;p&gt;For chatty, distributed systems, these improvements accumulate quickly, making the difference not only noticeable in benchmarks, but also by the end-user.&lt;/p&gt;

&lt;h2&gt;
  
  
  API types
&lt;/h2&gt;

&lt;p&gt;gRPC supports four different types of RPCs, unary, server streaming, client streaming, and bi-directional streaming. In reality, the messages are multiplexed using the same connection, but in the spirit of keeping things simple and approachable, this is not illustrated in the gRPC service model diagrams below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unary
&lt;/h3&gt;

&lt;p&gt;Unary calls work the same way as a regular function call: a single request is sent to the server which in turn replies with a single response.&lt;/p&gt;

&lt;p&gt;&lt;a href="///blog/static/c48fc5ca8336d9cd8b98618c4b9d86ec/7842b/grpc-unary.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uC2nSXnm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/c48fc5ca8336d9cd8b98618c4b9d86ec/7842b/grpc-unary.png" alt="unary call" title="unary call"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Streaming
&lt;/h3&gt;

&lt;p&gt;In server streaming mode, the client sends a single request to the server, which in turn replies with multiple responses.&lt;/p&gt;

&lt;p&gt;&lt;a href="///blog/static/fbc08c79e035da81aca99e4bc220f95e/7842b/grpc-server.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CMzS0hiF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/fbc08c79e035da81aca99e4bc220f95e/7842b/grpc-server.png" alt="server streaming" title="server streaming"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Streaming
&lt;/h3&gt;

&lt;p&gt;The client streaming mode is the opposite of the server streaming mode. The client sends multiple requests to the server, which in turn replies with a single response.&lt;/p&gt;

&lt;p&gt;&lt;a href="///blog/static/ebdd5a0dedb7bdae80e59ceba69c6b75/7842b/grpc-client.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IVqETxsj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/ebdd5a0dedb7bdae80e59ceba69c6b75/7842b/grpc-client.png" alt="client streaming" title="client streaming"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bi-directional streaming
&lt;/h3&gt;

&lt;p&gt;In bi-directional streaming mode, both the client and the server may send multiple messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="///blog/static/094a57810030c8df50d05a8c87a1dee1/7842b/grpc-bidirectional.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xI0n0guP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k6.io/blog/static/094a57810030c8df50d05a8c87a1dee1/7842b/grpc-bidirectional.png" alt="bi-directional streaming" title="bi-directional streaming"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;.proto&lt;/code&gt; definition
&lt;/h2&gt;

&lt;p&gt;The messages and services used for gRPC are described in .proto files, containing&lt;a href="https://en.wikipedia.org/wiki/Protocol_Buffers"&gt;Protocol buffers&lt;/a&gt;, or protobuf, definitions.&lt;/p&gt;

&lt;p&gt;The definition file is then used to generate code which can be used by both senders and receivers as a contract for communicating through these messages and services. As the binary format used by gRPC lacks any self-describing properties, this is the only way for senders and receivers to know how to interpret the messages.&lt;/p&gt;

&lt;p&gt;Throughout this article, we'll use the &lt;code&gt;hello.proto&lt;/code&gt; definition available for download on the&lt;a href="https://grpcbin.test.k6.io/"&gt;k6 grpcbin website&lt;/a&gt;. For details on how to build your own grpc proto definition, see &lt;a href="https://grpc.io/docs/what-is-grpc/core-concepts/"&gt;this excellent article from the official gRPC docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./definitions/hello.proto&lt;/span&gt;

&lt;span class="c1"&gt;// based on https://grpc.io/docs/guides/concepts.html&lt;/span&gt;

&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto2"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;HelloService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;SayHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HelloRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HelloResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;LotsOfReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HelloRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;HelloResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;LotsOfGreetings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;HelloRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HelloResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;BidiHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;HelloRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;HelloResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;HelloRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;HelloResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;With k6 v0.29.0, we're happy to introduce a native client for gRPC communication. In this early release, we've settled for providing a solid experience for unary calls. If any of the other modes would be particularly useful for you, we'd love to hear about your use case so we can move it up our backlog.&lt;/p&gt;

&lt;p&gt;The current API for working with gRPC in k6 using the native client is as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://k6.io/blog/javascript-api/k6-net-grpc/client/client-load-importpaths----protofiles"&gt;Client.load(importPaths, ...protoFiles)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Loads and parses the given protocol buffer definitions to be made available for RPC requests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://k6.io/blog/javascript-api/k6-net-grpc/client/client-connect-address-params"&gt;Client.connect(address [,params])&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Opens a connection to the given gRPC server.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://k6.io/blog/javascript-api/k6-net-grpc/client/client-invoke-url-request-params"&gt;Client.invoke(url, request [,params])&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Makes an unary RPC for the given service/method and returns a &lt;a href="https://k6.io/blog/javascript-api/k6-net-grpc/response"&gt;Response&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://k6.io/blog/javascript-api/k6-net-grpc/client/client-close"&gt;Client.close()&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Close the connection to the gRPC service.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Creating the test
&lt;/h3&gt;

&lt;p&gt;The gRPC module is a separate package, available from your test script as &lt;code&gt;k6/net/grpc&lt;/code&gt;. Before we can use it, we first have to create an instance of the client. Instantiating the client, as well as the &lt;code&gt;.load&lt;/code&gt; operation, is only available during test initialization, ie. directly in the global scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/net/grpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll load a &lt;code&gt;.proto&lt;/code&gt; definition applicable for the system under test. For the purpose of this article, we'll use &lt;a href="https://grpcbin.test.k6.io/"&gt;k6 grpcbin&lt;/a&gt;. Feel free to change this to whatever you please but keep in mind that you will also need an appropriate &lt;code&gt;.proto&lt;/code&gt; definition for the server you're testing. The &lt;code&gt;.load()&lt;/code&gt; function takes two arguments, the first one being an array of paths to search for proto files, and the second being the name of the file to load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/net/grpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;definitions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello.proto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, we'll go ahead and write our actual test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/net/grpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;definitions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello.proto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grpcbin.test.k6.io:9001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// plaintext: false&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello.HelloService/SayHello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's walk through this script to make sure we understand what's happening. First, we use the&lt;code&gt;.connect()&lt;/code&gt; function to connect to our system under test. By default, the client will set &lt;code&gt;plaintext&lt;/code&gt; to false, only allowing you to use encrypted connections. If you, for any reason, need to connect to a server that lacks SSL/TLS, just flip this setting to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We then continue by creating the object we want to send to the remote procedure we're invoking. In the case of &lt;code&gt;SayHello&lt;/code&gt;, it allows us to specify who the greeting should address using the &lt;code&gt;greeting&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Next, we invoke the remote procedure, using the syntax &lt;code&gt;&amp;lt;package&amp;gt;.&amp;lt;service&amp;gt;/&amp;lt;procedure&amp;gt;&lt;/code&gt;, as described in our proto file. This call is made synchronously, with a default timeout of 60000 ms (60 seconds). To change the timeout, add the key &lt;code&gt;timeout&lt;/code&gt; to the config object of &lt;code&gt;.connect()&lt;/code&gt; with the duration as the value, for instance &lt;code&gt;'2s'&lt;/code&gt; for 2 seconds.&lt;/p&gt;

&lt;p&gt;Once we've received a response from the server, we'll then make sure the procedure executed successfully. The grpc module includes constants for this comparison which are listed &lt;a href="https://k6.io/docs/javascript-api/k6-net-grpc/constants"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comparing the response status with &lt;code&gt;grpc.StatusOK&lt;/code&gt;, which is &lt;code&gt;200 OK&lt;/code&gt; just like for HTTP/1.1 communication, ensures the call was completed successfully.&lt;/p&gt;

&lt;p&gt;We'll then log the message in the response, close the client connection, and sleep for a second.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executing the test
&lt;/h3&gt;

&lt;p&gt;The test can be executed just like any other test, although you need to make sure you're on at least version &lt;code&gt;v0.29.0&lt;/code&gt; to have access to the gRPC module. To check this, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;k6 version
k6 v0.29.0 &lt;span class="o"&gt;((&lt;/span&gt;devel&lt;span class="o"&gt;)&lt;/span&gt;, go1.15.3, darwin/amd64&lt;span class="o"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything less than &lt;code&gt;v0.29.0&lt;/code&gt; here, will require you to first update k6. Instructions on how to do that can be found &lt;a href="https://k6.io/docs/getting-started/installation"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that's out of the way, let's run our test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ k6 run test.js

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

  execution: local
     script: /Users/simme/code/grpc/test.js
     output: -

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

INFO[0000] {"reply":"hello Bert"} source=console

running (00m01.4s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs 00m01.4s/10m0s 1/1 iters, 1 per VU

    ✓ status is OK

    checks...............: 100.00% ✓ 1 ✗ 0
    data_received........: 3.0 kB 2.1 kB/s
    data_sent............: 731 B 522 B/s
    grpc_req_duration....: avg=48.44ms min=48.44ms med=48.44ms max=48.44ms p(90)=48.44ms p(95)=48.44ms
    iteration_duration...: avg=1.37s min=1.37s med=1.37s max=1.37s p(90)=1.37s p(95)=1.37s
    iterations...........: 1 0.714536/s
    vus..................: 1 min=1 max=1
    vus_max..............: 1 min=1 max=1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the output, we can now tell that our script is working and that the server indeed responds with a greeting addressed to who, or what, we supplied in our request body. We can also see that our &lt;code&gt;check&lt;/code&gt; was successful, meaning the server responded with &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we've gone through some of the fundamentals of gRPC and how it works. We've also had a look at the gRPC client introduced in k6 v0.29.0. Last, but not least, we've created a working test script demonstrating this functionality.&lt;/p&gt;

&lt;p&gt;And that concludes this gRPC load testing tutorial. Thank you for reading!&lt;/p&gt;

&lt;h2&gt;
  
  
  See Also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://k6.io/docs/javascript-api/k6-net-grpc"&gt;k6 gRPC Module API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grpcbin.test.k6.io/"&gt;k6 gRPCBin: A simple request/response service for gRPC&lt;/a&gt;, similar to &lt;a href="https://httpbin.test.k6.io/"&gt;k6 httpbin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grpc.io/"&gt;The official website of the gRPC project&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/grpc/grpc/tree/master/examples"&gt;Official examples from the gRPC repo on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>performance</category>
      <category>testing</category>
      <category>grpc</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Autoscale Kubernetes Pods with Keda - Testing with k6</title>
      <dc:creator>Michael Wanyoike</dc:creator>
      <pubDate>Tue, 26 May 2020 18:22:23 +0000</pubDate>
      <link>https://forem.com/k6/how-to-autoscale-kubernetes-pods-with-keda-testing-with-k6-4nl9</link>
      <guid>https://forem.com/k6/how-to-autoscale-kubernetes-pods-with-keda-testing-with-k6-4nl9</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This article demonstrates how to Load Test an application deployed in a Kubernetes cluster, verify that the autoscaling is working, and identify potential performance bottlenecks.&lt;/p&gt;

&lt;p&gt;When you deploy an application to production on Kubernetes, you'll need to monitor traffic and scale up the application to meet heavy workloads during various times of the day. You'll also need to scale down your application to avoid incurring unnecessary costs. Doing this manually is impractical since heavy traffic can come at any time including late at night when your entire team is asleep. And even if you are awake, a spike might come and go before you had a chance to address it.&lt;/p&gt;

&lt;p&gt;The best approach is to automate scaling. This can be triggered by monitoring custom metrics such &lt;code&gt;cpu usage&lt;/code&gt;, &lt;code&gt;network bandwidth&lt;/code&gt; or &lt;code&gt;http requests per second&lt;/code&gt;. Scaling an application running on a Kubernetes platform can be done in the following ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal&lt;/strong&gt; : Adjust the number of replicas(pods)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vertical&lt;/strong&gt; : Adjust resource requests and limits imposed on a container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we'll focus on &lt;strong&gt;horizontal scaling based on a custom metric&lt;/strong&gt;. Which metric you should choose to trigger scaling is outside the scope of this article. We will use the &lt;code&gt;http request rate&lt;/code&gt; metric in this example, however other metric types can be used. We'll cover how to obtain these metrics and visualize them for analysis. We'll use the following to perform this task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://k6.io/open-source" rel="noopener noreferrer"&gt;k6 OSS&lt;/a&gt;: an open-source load testing tool. We'll use it to simulate heavy traffic(load) for our Kubernetes application. There's also &lt;a href="https://grafana.com/products/cloud/k6/" rel="noopener noreferrer"&gt;Grafana Cloud k6&lt;/a&gt; which you can use to scale load testing beyond your local computing and networking infrastructure limitations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;: an open-source monitoring platform. We'll use it to scrape metrics from our application and the Kubernetes API in real-time as while the load testing tool is running.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://grafana.com/grafana/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;: an open-source analytics platform. We'll use it to visualize real-time metrics being collected by Prometheus so that we can see the performance of our application across time. Below is a screenshot of how metrics are visualized on a Grafana dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foux0n6prkxndz534oaqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Foux0n6prkxndz534oaqj.png" alt="Grafana Dashboard to visualize Kubernetes autoscaling based on custom metrics"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The outline of this tutorial is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application Deployment&lt;/li&gt;
&lt;li&gt;Prometheus, Kube State Metrics and KEDA Deployment&lt;/li&gt;
&lt;li&gt;InfluxDB and Grafana Setup&lt;/li&gt;
&lt;li&gt;Configuring Horizontal Pod Autoscaling with Keda&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can follow along this entire tutorial on a single machine. Just note that results will be skewed since your CPU will handle both running the application and load testing it. For accurate results, these tasks need to be performed on separate machines. For learning and simplicity purposes, one machine will be sufficient.&lt;/p&gt;

&lt;p&gt;The source code of the application we'll be load testing is provided in our &lt;a href="https://github.com/k6io/example-kubernetes-autoscaling-nodejs-api" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. The load testing script is included there as well.&lt;/p&gt;

&lt;p&gt;Below is a complete illustration of the entire setup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwm1l934i2kna2osrbfuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwm1l934i2kna2osrbfuc.png" alt="Diagram of Kubernetes horizontal pod autoscaling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This article assumes you at least have some basic knowledge about running and configuring a Kubernetes cluster. We'll be using &lt;a href="https://kubernetes.io/docs/setup/learning-environment/minikube/" rel="noopener noreferrer"&gt;Minikube&lt;/a&gt; for this guide. However, feel free to use any other k8s implementation that can run on your computer.&lt;/p&gt;

&lt;p&gt;Before we proceed, you'll need to have the following installed on your machine. For windows users, you can use &lt;a href="https://chocolatey.org/" rel="noopener noreferrer"&gt;Chocolatey&lt;/a&gt; package manager to install most of these requirements. For macOS, use &lt;code&gt;brew&lt;/code&gt;. For Linux, use the instructions provided in the following links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://k6.io/docs/getting-started/installation" rel="noopener noreferrer"&gt;k6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GNU Make (pre-installed for Linux, install &lt;code&gt;xcode&lt;/code&gt; for macOS users)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/install-minikube/" rel="noopener noreferrer"&gt;Minikube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="app-deploy"&gt; Application Setup &amp;amp; Deployment&lt;/h2&gt;

&lt;p&gt;If you haven't, download the example project to your workspace now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# Download project&lt;/span&gt;
git clone git@github.com:k6io/example-kubernetes-autoscaling-nodejs-api.git
&lt;span class="nb"&gt;cd &lt;/span&gt;example-kubernetes-autoscaling-nodejs-api

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Run development server&lt;/span&gt;
npm run dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open the URL &lt;code&gt;http://localhost:4000/&lt;/code&gt; to confirm the application is running. If you click on the crocodiles API link or visit the url &lt;code&gt;http://localhost:4000/crocodiles&lt;/code&gt;, you should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmpow2gpmgewtuz5zdtku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmpow2gpmgewtuz5zdtku.png" alt="03-crocodile-api"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's quickly inspect the code. Open the file &lt;code&gt;db.json&lt;/code&gt; in your favorite code editor. This is where data is stored. You can add more records if you want. Next open &lt;code&gt;server.js&lt;/code&gt;. This is the complete project code where the server logic lies. You'll see it's a simple project that uses &lt;a href="https://www.npmjs.com/package/json-server" rel="noopener noreferrer"&gt;json-server&lt;/a&gt; package to provide CRUD API services.&lt;/p&gt;

&lt;p&gt;If you observe this section of code, you'll notice that an artificial delay has been implemented. Because the API application is so simple, we need to slow it down a bit just to help us simulate the results of a real-world application.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Add a delay to /crocodiles requests only&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/crocodiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxDelay&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;minDelay&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;minDelay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The next section of the code I want to show you is how to export the metrics from our application to Prometheus. Later on, we will query these metrics to monitor the status of the application.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prometheusExporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tailorbrands/node-exporter-prometheus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crocodile-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;collectDefaultMetrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ignoredRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/favicon.ico&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/__rules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promExporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prometheusExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promExporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promExporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you would like, you can define additional metrics for your application. The above code will output several default metrics. In your browser, open the URL &lt;code&gt;http://localhost:4000/metrics&lt;/code&gt; to view these metrics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwpwhc638tuha8xniyxn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwpwhc638tuha8xniyxn1.png" alt="04-crocodile-metrics-output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this application, we are using the npm package &lt;a href="https://www.npmjs.com/package/@tailorbrands/node-exporter-prometheus" rel="noopener noreferrer"&gt;@tailorbrands/node-exporter-prometheus&lt;/a&gt; to help us export metrics from our application in a format compatible with Prometheus scraping requirements with very little effort on our part. You can find other Prometheus client library for Node.js applications in the &lt;a href="https://www.npmjs.com/search?q=prometheus" rel="noopener noreferrer"&gt;npmjs registry&lt;/a&gt; repository. If you are working with a different programming language, you can find official and non-official &lt;strong&gt;Prometheus client libraries&lt;/strong&gt; on &lt;a href="https://prometheus.io/docs/instrumenting/clientlibs/" rel="noopener noreferrer"&gt;Prometheus website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The metrics displayed in the above screenshot will update when you interact with application. Simply refreshing the URL &lt;code&gt;http://localhost:4000/crocodiles&lt;/code&gt; in your browser or executing a &lt;code&gt;curl&lt;/code&gt; command on the site will cause the values of the metrics to update. For example, the metric &lt;code&gt;node_http_requests_total&lt;/code&gt; keeps track of the number of times a HTTP request is performed on the application. Do note the metrics page doesn't use AJAX. So you'll have to refresh from time to time to see the results.&lt;/p&gt;

&lt;p&gt;When you setup Prometheus, it will fetch the values of this metrics every 5 - 15 seconds and store it in a time-series database. This is what is referred to as scraping. With this information, Prometheus can plot for you a graph so that you can see how the metric values change over time.&lt;/p&gt;

&lt;p&gt;If you inspect the metrics provided by our application, you'll notice we have different &lt;a href="https://prometheus.io/docs/concepts/metric_types/" rel="noopener noreferrer"&gt;metric types&lt;/a&gt;. For this guide, we'll focus on the metric &lt;code&gt;node_http_requests_total&lt;/code&gt; which is of type &lt;strong&gt;counter&lt;/strong&gt;. A counter is a cumulative metric that always keeps going up. It can be reset to 0 if the application is restarted.&lt;/p&gt;

&lt;p&gt;Now, we run the k6 load testing tool to generate some traffic, and we will visualize how this counter metric changes over time. At the root of the application project, locate the script &lt;code&gt;performance-script.js&lt;/code&gt; which contains instructions on how to perform the load test.&lt;/p&gt;

&lt;p&gt;Below are 2 examples of the k6 load test configuration. The &lt;a href="https://k6.io/docs/using-k6/options" rel="noopener noreferrer"&gt;first option&lt;/a&gt; is a quick 3 minute load test you can use to quickly confirm metrics are being captured. The second option allows us to scale the number of virtual users over a duration of 12 minutes. This will give us enough data to analyze performance and behavior of our autoscaling configuration.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// First Load Testing Option : Quick Test with fixed no. of virtual users&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;http_req_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Second Load Testing Option : Long Test with varying no. of virtual users&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Before we can run the load testing script, we need to deploy Prometheus to scrape the application's metric. Our application needs to be deployed as well in order for Prometheus to discover our application. To deploy our project to our Kubernetes(minikube) node, simply execute the following commands:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# Build the application's docker image inside the minikube's environment&lt;/span&gt;
make image
&lt;span class="c"&gt;# Deploy the application&lt;/span&gt;
make apply


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you have trouble executing the above commands, just visit the file &lt;code&gt;Makefile&lt;/code&gt; and execute the commands under &lt;code&gt;image&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; sections. Below should be output of the last command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F30yluiblh7857ptgwog5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F30yluiblh7857ptgwog5.png" alt="05-deploy-app-service-output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take note of the IP address and the port. In the above case, we can access our application through a web browser using the following address: &lt;code&gt;http://10.98.55.109:4000&lt;/code&gt;. Unfortunately, the page will likely refuse to load since we haven't fully configured how our load balancer will expose our service. A quick work around is to create a tunnel by running the following command in a separate terminal:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

minikube tunnel


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once the tunnel is up and running, you should be able to access the application in your browser. Proceed to the next section and deploy Prometheus.&lt;/p&gt;

&lt;h2 id="prom-kube-deploy"&gt; Deploy Prometheus, Kube State Metrics and KEDA&lt;/h2&gt;

&lt;p&gt;To deploy Prometheus to our minikube node, follow this &lt;a href="https://devopscube.com/setup-prometheus-monitoring-on-kubernetes/" rel="noopener noreferrer"&gt;guide&lt;/a&gt;. You'll also need to deploy &lt;a href="https://github.com/kubernetes/kube-state-metrics" rel="noopener noreferrer"&gt;Kube State metrics&lt;/a&gt;. This is a service that accesses the Kubernetes API and provides Prometheus with metrics related to API objects such as deployments and pods. We need this service to track the number of running pods.&lt;/p&gt;

&lt;p&gt;Since we are deploying, let's also deploy &lt;a href="https://keda.sh/" rel="noopener noreferrer"&gt;KEDA&lt;/a&gt;, a Kubernetes Event-driven Autoscaling service. It works alongside &lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale" rel="noopener noreferrer"&gt;Horizontal Pod Autoscaler&lt;/a&gt; to scale pods up and down based on a threshold that we'll need to specify. Instructions for deploying using YAML files can be found on &lt;a href="https://keda.sh/docs/1.4/deploy/#yaml" rel="noopener noreferrer"&gt;this page&lt;/a&gt;. For convenience, these are the commands you need to execute to deploy KEDA:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git clone https://github.com/kedacore/keda &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;keda

kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/crds/keda.k8s.io_scaledobjects_crd.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/crds/keda.k8s.io_triggerauthentications_crd.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use the command &lt;code&gt;kubectl get po -A&lt;/code&gt; to ensure all the pods we have deployed are running. Use the command &lt;code&gt;kubectl get service -n monitoring&lt;/code&gt; to find the ip address and port where Prometheus dashboard can be accessed. Construct the URL and launch the Prometheus dashboard in your browser. First let's confirm that Prometheus has discovered metrics from our application. From the top menu, go to &lt;strong&gt;Status&lt;/strong&gt; &amp;gt; &lt;strong&gt;Targets&lt;/strong&gt; page and scroll down to the bottom and look for the label &lt;code&gt;crocodile-service&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs61m5tidvptlllsdsm9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs61m5tidvptlllsdsm9k.png" alt="06-prometheus-scraping-crocodile-service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see a similar status like the above, you are good to go on the next step. You should also confirm that the service &lt;code&gt;kube-state-metrics&lt;/code&gt; has been discovered as well. On the top menu, click the &lt;strong&gt;Graph&lt;/strong&gt; link to go to the graph page. This is where we enter &lt;a href="https://prometheus.io/docs/prometheus/latest/querying/basics/" rel="noopener noreferrer"&gt;query expressions&lt;/a&gt; to access the vast information that Prometheus is currently scraping.&lt;/p&gt;

&lt;p&gt;Before we execute a query, fire up the k6 load testing tool to get some data to work with. You'll need to execute the &lt;strong&gt;load test script&lt;/strong&gt; like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.98.55.109:4000/crocodiles k6 run performance-test.js &lt;span class="c"&gt;# replace ip address with yours&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On the &lt;strong&gt;Graph&lt;/strong&gt; page, enter this expression: &lt;code&gt;node_http_requests_total&lt;/code&gt;. It should autofill for you as you type. Click on the &lt;strong&gt;Graph&lt;/strong&gt; tab and you should see the following output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs61m5tidvptlllsdsm9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs61m5tidvptlllsdsm9k.png" alt="07-prometheus-http-requests-total-graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how the results are visualized after the test has completed. Notice how it flattens at the top. This is because the metric stopped incrementing when the test ended. If you click on the &lt;strong&gt;Table&lt;/strong&gt; tab, you should see two fields that look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnvvxokpgrim9vlp49n3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnvvxokpgrim9vlp49n3u.png" alt="07-prometheus-http-requests-total-table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;node_http_requests_total&lt;/code&gt; metric keeps track of the total number of requests per HTTP status code. If you were to run the test, the line graph will start shooting up from where it left of. This metric doesn't seem useful in it's current form.&lt;/p&gt;

&lt;p&gt;Fortunately, we can apply a function to make it useful. We can use the &lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#rate" rel="noopener noreferrer"&gt;&lt;code&gt;rate()&lt;/code&gt;&lt;/a&gt; function to calculate the number of requests per second over a specified duration. Update the expression as follows:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rate(node_http_requests_total[2m])&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This function will give us the number of requests per second within a 2 minute window. Basically, it calculates how fast are the increments increasing per second. When the increments stop, the &lt;code&gt;rate()&lt;/code&gt; function will give us &lt;strong&gt;0&lt;/strong&gt;. Below are the results of the load test with the &lt;code&gt;rate&lt;/code&gt; function applied in the expression:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frf2kmuxwof3hb55th9lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frf2kmuxwof3hb55th9lo.png" alt="08-prometheus-request-per-second"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, our loading test script peaked at approximately 65 requests per second. When the load test script completed, the graph line goes back to 0. This is now a useful metric we can use to determine if scaling our pods is needed.&lt;/p&gt;

&lt;p&gt;In the next section, we'll setup a more advance visualization dashboard that can display multiple metrics at once with automatic refresh rate.&lt;/p&gt;

&lt;h2 id="influx-grafana"&gt; Install InfluxDB and Grafana&lt;/h2&gt;

&lt;p&gt;In this section, we'll install InfluxDB which is an open-source time series database. InfluxDB is widely supported by many tools and applications. This provides new opportunities for collecting additional metric data from other sources such as &lt;a href="https://www.influxdata.com/time-series-platform/telegraf/" rel="noopener noreferrer"&gt;Telegraf&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; is an open-source analytic and monitoring platform that allows engineers to build dashboards for visually monitoring metrics from multiple sources in real-time. It supports the use of expressions to interpolate raw metric data into information that is easy to consume.&lt;/p&gt;

&lt;p&gt;Both InfluxDB and Grafana can be deployed on the Kubernetes node. However, its easier and faster installing them locally. Do not use the Docker option as it's not easy for containers to communicating with Kubernetes services. Below are the download links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.influxdata.com/get-influxdb/" rel="noopener noreferrer"&gt;Download InfluxDB&lt;/a&gt; : Get 1.8 OSS version&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grafana.com/get" rel="noopener noreferrer"&gt;Download Grafana&lt;/a&gt; : Get latest version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have installed both applications, make sure the services are running. For Ubuntu:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start influxdb grafana-server


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can interact with influxDB database server via the &lt;code&gt;influx&lt;/code&gt; command line interface. You can also interact with influxDB via &lt;code&gt;http://localhost:8086&lt;/code&gt; by passing query parameters.&lt;/p&gt;

&lt;p&gt;Grafana is accessed by visiting &lt;code&gt;http://localhost:3000&lt;/code&gt;. The default username and password should be &lt;code&gt;admin&lt;/code&gt; &lt;code&gt;admin&lt;/code&gt;. If this doesn't work, just edit the file &lt;code&gt;/etc/grafana/grafana.ini&lt;/code&gt; and ensure the following lines are enabled:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;

&lt;span class="nn"&gt;[security]&lt;/span&gt;
&lt;span class="c"&gt;# default admin user, created on startup
&lt;/span&gt;&lt;span class="py"&gt;admin_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;

&lt;span class="c"&gt;# default admin password, can be changed before first start of grafana,  or in profile settings
&lt;/span&gt;&lt;span class="py"&gt;admin_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You'll need to restart grafana server for the the change to take effect. Once you log in, you'll need to go to &lt;code&gt;Configuration&lt;/code&gt; &amp;gt; &lt;code&gt;Data Sources&lt;/code&gt; and add the following sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;InfluxDB K6 Database&lt;/li&gt;
&lt;li&gt;Prometheus data source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to use other sources. In the screenshot provided, I've additionally installed &lt;a href="https://www.influxdata.com/time-series-platform/telegraf/" rel="noopener noreferrer"&gt;Telegraf&lt;/a&gt; which is a service that collects real-time metrics from other database systems, IoT sensors and system performance metrics for CPU, memory, disk and network components. We won't use it for this article though.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft2b6qpv9h37llheodqlz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft2b6qpv9h37llheodqlz.png" alt="09-grafana-data-sources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below are the settings that I've used for InfluxDB data source. The rest of the fields not mentioned here are left blank or in their default setting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;InfluxDB-K6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;URL: &lt;code&gt;http://localhost:8086&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Access: &lt;code&gt;Server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database: &lt;code&gt;k6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;HTTP Method: &lt;code&gt;GET&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Min time interval: &lt;code&gt;5s&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click the &lt;code&gt;Save &amp;amp; Test&lt;/code&gt; button. If it says 'Data source is not working', it's because the database has not been created. When you run the following k6 command, the database will be created automatically:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# replace IP address&lt;/span&gt;
&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.98.55.109:4000/crocodiles k6 run &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;influxdb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8086/k6 performance-test.js


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Below are the settings that I've used for Prometheus data source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;InfluxDB-K6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;URL: &lt;code&gt;http://&amp;lt;prometheus ip address&amp;gt;:8080/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Access: &lt;code&gt;Server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Scrape interval: &lt;code&gt;5s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Query timeout: &lt;code&gt;30s&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;HTTP Method: &lt;code&gt;GET&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;code&gt;Save &amp;amp; Test&lt;/code&gt; and make sure you get the message "Data source is working". Otherwise, you won't be able to proceed to the next step.&lt;/p&gt;

&lt;p&gt;The next step is to create a new dashboard by importing the &lt;strong&gt;Crocodile Metrics Dashboard&lt;/strong&gt; from this &lt;a href="https://raw.githubusercontent.com/k6io/example-kubernetes-autoscaling-nodejs-api/master/grafana/crocodile-metrics-dashboard.json" rel="noopener noreferrer"&gt;link&lt;/a&gt;. Copy and paste the JSON code and hit save. This custom dashboard will allow you to visually track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP Request Rate (sourced from both k6 and application via Prometheus)&lt;/li&gt;
&lt;li&gt;Number of virtual users&lt;/li&gt;
&lt;li&gt;Number of active application pods and their status&lt;/li&gt;
&lt;li&gt;99th percentile response time (measured in milliseconds)&lt;/li&gt;
&lt;li&gt;Memory usage per pod (measured in megabytes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the panels are easy to configure. You can change widget type, adjust the query and display more values. You can also re-organize the layout and add new panels. Below is how the Crocodile Metrics Dashboard should look like when installed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz4llqkicu9bp1sbsiani.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz4llqkicu9bp1sbsiani.png" alt="10-grafana-crocodile-dashboard-setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next section, we'll configure KEDA to monitor and scale up our application.&lt;/p&gt;

&lt;h2 id="autoscaling"&gt;Configuring Horizontal Pod Autoscaling with Keda&lt;/h2&gt;

&lt;p&gt;Before we configure auto scaling, let's run a quick load test. Open &lt;code&gt;performance-test.js&lt;/code&gt; and ensure the following code is active:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run the k6 script using the following command ensuring InfluxDB is collecting K6 metrics.:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# replace IP address&lt;/span&gt;
&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.98.55.109:4000/crocodiles k6 run &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;influxdb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8086/k6 performance-test.js


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Grafana dashboard should start populating with data. As the number of http requests per second increases, the number of pods stays constant. You can wait for the load test to complete or you can cancel mid-way.&lt;/p&gt;

&lt;p&gt;Let's now configure KEDA to monitor and scale our application. Open the YAML config file &lt;code&gt;keda/keda-prometheus-scaledobject&lt;/code&gt; located in the project and analyze it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda.k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScaledObject&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus-scaledobject&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploymentName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crocodile-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploymentName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crocodile-api&lt;/span&gt;
  &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# Optional. Default: 30 seconds&lt;/span&gt;
  &lt;span class="na"&gt;cooldownPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="c1"&gt;# Optional. Default: 300 seconds&lt;/span&gt;
  &lt;span class="na"&gt;minReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# Optional. Default: 0&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# Optional. Default: 100&lt;/span&gt;
  &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
      &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Required&lt;/span&gt;
        &lt;span class="na"&gt;serverAddress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://10.103.240.12:8080&lt;/span&gt;
        &lt;span class="na"&gt;metricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;access_frequency&lt;/span&gt;
        &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;50'&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum(rate(node_http_requests_total[2m]))&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Take note of the query we provided:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sum(rate(node_http_requests_total[2m]))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;KEDA will run this query on Prometheus every 10 seconds. We've added the sum function to the expression in order to include data from all running pods. It will check the value against the threshold we provided, &lt;strong&gt;50&lt;/strong&gt;. If the value exceeds this amount, KEDA will increase the number of running pods up-to a maximum of 10. If the value is less, KEDA will scale our application pods back to 1. To deploy this configuration, execute the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; keda/keda-prometheus-scaledobject


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once again, run the k6 script test just like before and observe how the number of pods increases as the number of requests per second increase as well. Below is the final result after the test has completed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fow1mrzrwjczlyh85nfnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fow1mrzrwjczlyh85nfnx.png" alt="11-grafana-pods-scaling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take note of the &lt;strong&gt;Running Pods&lt;/strong&gt; chart. The number of application pods increased as the load increased. When the load throttled down, the number of pods decreased as well. And that's the beauty of automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;How to Autoscale Kubernetes Pods with Keda - Testing with k6&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to autoscale pods using KEDA and Prometheus&lt;/li&gt;
&lt;li&gt;How to analyse and visualize metrics using Grafana&lt;/li&gt;
&lt;li&gt;How to extract metrics from applications and services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the information you have at hand, you can benchmark any application and monitor the performance with accuracy. In addition to application metrics, you can also use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Network usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disk usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Temperature&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, I would like to point out some important facts we haven't covered here that you need to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For a real world application, you would have a separate pod containing the database. The data for this type of pod needs to synced when scaled out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will need to determine the request rate threshold that would make sense to scale&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more metrics you can monitor, the easier it is to identify bottlenecks affecting the performance of your application. For example, if your application has high CPU usage, optimizing the code can greatly boost the performance. If disk usage is high, using memory caching solutions can help a big deal.&lt;/p&gt;

&lt;p&gt;The point is, scaling the number of application pods shouldn't be the only solution when it comes to handling heavy traffic. Scaling vertically or increasing the number of nodes should be considered and could greatly improve performance during heavy loads. By using k6 load testing tool, and using Grafana to analyze results, you can discover where bottlenecks for your application are located.&lt;/p&gt;

</description>
      <category>k6</category>
      <category>kubernetes</category>
      <category>keda</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
