<?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: Paul Royer</title>
    <description>The latest articles on Forem by Paul Royer (@paulroyer).</description>
    <link>https://forem.com/paulroyer</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%2Fuser%2Fprofile_image%2F1289243%2Ff405690d-1f75-4702-af7b-002ce8f8e3d7.png</url>
      <title>Forem: Paul Royer</title>
      <link>https://forem.com/paulroyer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/paulroyer"/>
    <language>en</language>
    <item>
      <title>API Load Testing: Enhance Your Skills with Locust</title>
      <dc:creator>Paul Royer</dc:creator>
      <pubDate>Wed, 28 Feb 2024 13:30:00 +0000</pubDate>
      <link>https://forem.com/theodo/api-load-testing-enhance-your-skills-with-locust-2367</link>
      <guid>https://forem.com/theodo/api-load-testing-enhance-your-skills-with-locust-2367</guid>
      <description>&lt;p&gt;Load testing is a serious job and doing it poorly can have disastrous consequences for big companies' production servers. But &lt;strong&gt;load testing is not only for experts&lt;/strong&gt; in massive companies! You may want to quickly check that your server performances are appropriate, using only your own computer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://locust.io/" rel="noopener noreferrer"&gt;Locust&lt;/a&gt; is a perfect tool to use on such occasion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A great user interface, allowing you to &lt;strong&gt;launch tests and monitor results in real-time&lt;/strong&gt;, directly from your web browser.&lt;/li&gt;
&lt;li&gt;Writting scripts in Python, perhaps the most popular language according to, for example, the &lt;a href="https://spectrum.ieee.org/the-top-programming-languages-2023" rel="noopener noreferrer"&gt;top programming language IEEE study&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Installable in seconds, free and open source&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start load testing with Locust!
&lt;/h2&gt;

&lt;p&gt;First, you need an API to load test. Let's say you have an amusement park application. It's running on &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; and exposing the route &lt;code&gt;GET /bookings&lt;/code&gt; with two query parameters: &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To begin with Locust, you basically need a dozen seconds. With pip already installed, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip3 &lt;span class="nb"&gt;install &lt;/span&gt;locust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you need to write a locust file, describing how users will interact with your API. Let's create a basic one, under the name &lt;code&gt;locustfile.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_booking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;
        &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-06-08&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bookings?age=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;date=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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 User class represents the expected behaviour of a user. For each user created, Locust will execute on a loop all the tasks defined. In this simple case, we have only one task and the requested URL will be always the same as age and date variables are hardcoded. We just set a pause of 1 to 2 seconds between each user loop.&lt;/p&gt;

&lt;p&gt;Set-up is done, let's swarm into our API using Locust! In your terminal, &lt;strong&gt;in the folder where your locustfile is located,&lt;/strong&gt; simply run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;On your browser, head to &lt;a href="http://0.0.0.0:8089/" rel="noopener noreferrer"&gt;http://0.0.0.0:8089/&lt;/a&gt; and fill up the three parameters before launching the test:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nfgsi1dpcata05e4eja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nfgsi1dpcata05e4eja.png" alt="Locust interface filled with a number of users of 20, a spawn rate of 1 and host set to http://localhost:8080" width="398" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press "Start swarming" and tadaa! ✨ We can now observe the response time of our API fluctuate in real-time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91ibhfebbqlkngf65ftu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91ibhfebbqlkngf65ftu.png" alt="A time serie graph representing the 50th and 95th percentiles of response time" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the number of users grows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farnrqmks9zby8ru3v166.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farnrqmks9zby8ru3v166.png" alt="A time serie graph, showing the number of active users ramping up until 20 before stabilizing" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the test is done, you can download a report in HTML format. Allowing you to quickly share the results visually with your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefiting from Locust plugins to deliver more value
&lt;/h2&gt;

&lt;p&gt;Here, we load-tested our API in a basic way (one endpoint, always the same request parameters). We can do a lot more, and most of the time we must!&lt;/p&gt;

&lt;p&gt;Let's say your API is enjoying some success. To handle the increasing load, you &lt;a href="https://blog.theodo.com/2023/09/how-to-cache-in-java-application/" rel="noopener noreferrer"&gt;implement some cache on your endpoint&lt;/a&gt;, allowing fast response to common requests. It's nice, but &lt;strong&gt;if you keep having only one request when load testing, cache will handle all your requests.&lt;/strong&gt; So your server will be able to accept way more requests than in a real situation, where all requests could be different and cache would miss some of them.&lt;/p&gt;

&lt;p&gt;To solve this issue, we should &lt;strong&gt;vary the parameters of the requests we send&lt;/strong&gt;, that is &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt;. Here is one of the many possibilities, using the &lt;a href="https://github.com/SvenskaSpel/locust-plugins/blob/master/examples/csvreader_ex.py" rel="noopener noreferrer"&gt;CSV reader plugin for Locust&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;First, we need to create a CSV file listing all the test cases we want to use, for example in a file named &lt;code&gt;booking-param.csv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12, 2024-06-08
20, 2024-06-08
12, 2024-06-09
20, 2024-06-09
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use a little script to build this CSV by making the &lt;a href="https://en.wikipedia.org/wiki/Cartesian_product#A_deck_of_cards" rel="noopener noreferrer"&gt;cartesian product&lt;/a&gt; of all your parameters' possible values.&lt;/p&gt;

&lt;p&gt;Then, we modify the locust file to import our parameters from the CSV file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust_plugins.csvreader&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CSVReader&lt;/span&gt; &lt;span class="c1"&gt;# add import to the CSV reader plugin
&lt;/span&gt;
&lt;span class="n"&gt;booking_param_reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CSVReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;booking-param.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# read the CSV you created
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_booking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;booking_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking_param_reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booking_params&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="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booking_params&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bookings?age=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;date=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bookings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relaunch your script and each task will be executed with a different line of the CSV file you created, making each request different (if you send more requests than there are lines in your CSV file - it will quickly happen - further requests will be sent starting over from the start of the file).&lt;/p&gt;

&lt;p&gt;Note that here we add a &lt;code&gt;name&lt;/code&gt; argument to our &lt;code&gt;get&lt;/code&gt; method: &lt;strong&gt;Locust will group all these requests in one line in its report,&lt;/strong&gt; which would prove very convenient for analysis!&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay tuned: Locust is evolving
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://k6.io/blog/comparing-best-open-source-load-testing-tools/" rel="noopener noreferrer"&gt;2020 exhaustive comparison of load testing software&lt;/a&gt; (done by a k6 engineer 😉), Locust was pinpointed for its low performance and the clumsiness of running it locally on several CPU cores. Since then &lt;strong&gt;Locust can run a process for each of your cores with a single command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locust --processes -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although Locust was created in 2011, it keeps gaining functionality, making it worth giving it a (re)-try!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>performance</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
