The easiest way to test a web application under load using JMeter and Grafana

Hi, my name is Ramil, I am a programmer in the server solutions development department YouMoneyIn this article, I will tell you about my experiment with load testing using JMeter, Grafana and Prometheus, and show how I tested three scenarios: with two, 10 and 100 requests per second.

What is JMeter

JMeter (Apache JMeter) is a web application performance testing tool. JMeter allows you to check how your application will perform under different loads and also helps you create test scenarios that simulate user actions on a website or web application.

What you can do with JMeter:

  • Create different test scenarios to check the functionality and performance of applications via HTTP, HTTPS, FTP, JDBC and many other protocols.

  • Generate load on servers to assess whether they can handle a large number of requests.

  • Monitor and analyze test results with out-of-the-box graphs and reports.

JMeter is an open source project written in Java. The application consists of a core that allows you to run test scenarios and generate a report on them, and an interface for visually configuring scenarios. When you first start JMeter, it is recommended not to use the graphical interface to run large test scenarios that require system resources.

First launch of the program through the console

First launch of the program through the console

JMeter GUI

JMeter GUI

Test plan

To demonstrate the capabilities of the monitoring system together with JMeter, we will conduct a couple of tests for the endpoint of lesson creation POST /dep_1/work_plan/data pet project, recording indicators.

Let's test three scenarios:

1. Two requests per second for 10 seconds.

2. 100 requests per second for 10 seconds.

3. 10 requests sent simultaneously.

Next, I will show the process and results of testing. But before moving on to creating a test scenario, you need to set up a monitoring system – there are many instructions on the Internet. If you want a separate article about this, write in the comments, I will share my developments.

Final setup:

Creating a test scenario

The first thing to add is the Thread Group settings for the scenario, these will determine the plan for requests to the service.

Adding a Thread Group

Adding a Thread Group

I will list the main settings of Thread Group. They are used to describe the test scenario.

1. Number of Threads (users) — number of users. Defines the number of virtual users (threads) that will simultaneously send requests to the server. In other words, the parameter indicates the total number of threads that will be used in the test.

2. Ramp-Up Period (in seconds) — ramp-up period (in seconds). Sets the time for which JMeter will run all threads specified in Number of Threads one by one. Simply put, this is the time during which the specified number of requests must be executed. For example, if 10 threads are set with a ramp-up period five secondsthen each new thread will be launched once every 0.5 seconds.

3. Loop Count — number of iterations. Determines how many times each thread will repeat the test plan execution. If set to -1, the threads will repeat the test execution indefinitely.

Let's specify the parameters for Test 1 in Thread Group:

· Number of Threads 20;

· Ramp-Up Period 10;

· Loop Count 1.

That is, 20 users one after another with an interval of 10/20=0.5 seconds will send one request. The same test scenario can be obtained differently: if one user within 10 seconds must send 20 requests.

· Number of Threads 1;

· Ramp-Up Period 10;

· Loop Count 20.

Let's set up the query parameters. To do this, select in the right menu Add->Sampler->HTTP Requestwe will indicate the address and body of the request.

Adding call parameters

Adding call parameters

After that, we specify an unexpired JWT token in the Authorization header, adding it through the right sidebar of the HTTP Header Manager. Here we also add the Content-type request header indicating that we plan to send JSON in the request body.

Adding HTTP Header Manager

Adding HTTP Header Manager

HTTP Header Manager Menu

HTTP Header Manager Menu

It remains to configure the method of presenting test results. To do this, add the Listener entities — View Results Tree and Summary Report. The system is ready for the first test.

Adding Listeners

Adding Listeners

Test 1. Goal: Send two requests per second for 10 seconds

We launch the test from the application interface by clicking on the green RUN button in the top panel, after the script is completed we observe the results. Grafana shows that the web application under test has sent information about 20 challenges between 23:37:00 and 23:39:00.

Grafana: Custom metrics for call volume

Grafana: Custom metrics for call volume

Consumption of the CPU resource allocated to the process increased from 2.5% in idle mode to 5.5%The growth is justified because the application was idle.

Grafana: CPU_USAGE Metric

Grafana: CPU_USAGE Metric

In JMeter, in the View Result Tree tab, you can see the results of each request. In the Response headers tabs, we see that the status of the requests is 200, i.e. requests are successful.

JMeter: View Result Tree

JMeter: View Result Tree

Test 2. Goal: Send 100 requests per second for 10 seconds

Let's make changes to the test config for Test 2. First, let's change the parameters in Thread Group:

· Number of Threads – 1000;

Ramp-Up Period – 10;

· Loop Count — 1.

Secondly, in Grafana we will set up more frequent data import from Prometheus to see the change in the user metric more accurately.

Grafana: Query Options. Editing the Interval parameter

Grafana: Query Options. Editing the Interval parameter

Grafana: Custom chart before Query Options changes

Grafana: Custom chart before Query Options changes

Grafana: Custom Graph After Query Options Changes

Grafana: Custom Graph After Query Options Changes

Thirdly, since we expect responses with errors due to the high request rate (RPS, Requests per Second), we will use JMeter to start marking requests with a response code of 200 as successful, and others as unsuccessful. To do this, we will add the Response Assertion entity to the test configuration via the menu Add->Assertions ->Response Assertionthen Let's ask it to search for the string “200” in any response field. This will allow you to quickly see unsuccessful requests.

JMeter: Response Assertion

JMeter: Response Assertion

JMeter: Response Assertion (2)

JMeter: Response Assertion (2)

Since the load is large, such a test should be launched from the console. It can be assumed that it is better to launch large tests not from the graphical interface, because otherwise the resources of one operating system process may not be enough. To demonstrate the difference, let's try to launch the test not from the console, but from the graphical interface. In the first case, the test finishes not in 10 seconds, as planned, but in 30.

To execute the test from the console, first save the script in JMeter. To do this, click on the diskette icon, open the saved file with the LoadTest1000.jmx test script as a text document and find that this test script has xml markup. I will provide a part of it, opening it as an .xml document.

Please note that it is not difficult to match xml tags with test script settings, so sometimes it is easier to edit the script by editing LoadTest1000.jmx as a text file, without using the graphical interface:

<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group">
        <intProp name="ThreadGroup.num_threads">1000</intProp>
        <intProp name="ThreadGroup.ramp_time">10</intProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
          <stringProp name="LoopController.loops">1</stringProp>
          <boolProp name="LoopController.continue_forever">false</boolProp>
        </elementProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="AddLesson">
          <stringProp name="HTTPSampler.domain">localhost</stringProp>
          <stringProp name="HTTPSampler.port">8082</stringProp>
          <stringProp name="HTTPSampler.path">/dep_1/work_plan/data</stringProp>

Let's run LoadTest1000.jmx. To run the test on Windows OS, execute jmeter.bat from the JMeter directory in the console, passing it the LoadTest1000.jmx file as a parameter and specifying “.\result-1000\” as the directory for storing results:

“jmeter.bat -n -t LoadTest1.jmx -l log-10.jtl -e -o .\result-1000\ “.

Test run results in console

Test run results in console

The test execution time was about 10 Secondsas planned.

Test results 2. CPU USAGE

Test results 2. CPU USAGE

Let's look at the results in Grafana:

  • The CPU usage has increased significantly because there are many incoming requests to process.

  • The maximum request processing time has increased fourfold.

  • The user metric registered 1000 new requests with a successful response.

Test Results 2. Response Time

Test Results 2. Response Time

Test Results 2. User Metrics

Test Results 2. User Metrics

Let's look at the JMeter report on the testing results:

  • A test report in html format appeared in the result-1000 directory.

  • It is indicated that 1000 requests were processed correctly., and received a status of 200 in the response, which matches the information from the Grafana custom metric.

  • I don't see any errors in the web application log either, so the statistics are correct.

  • The report also provides a graph with query execution times:

50th percentile – 23 ms

90th percentile – 36 ms

95th percentile – 43 ms

99th percentile – 84 ms

Test Results 2. JMeter General Report

Test Results 2. JMeter General Report

Test Results 2. JMeter Report. Response Time

Test Results 2. JMeter Report. Response Time

According to this data, 99% of users' requests are processed in less than 85ms, which meets the requirements for the speed of modern systems. But keep in mind that the client (JMeter) and server (web applications) are on the same computer, so you should not rely on this response time: if the client and server are on different machines, the time will increase.

For the 99th percentile in the JMeter Report, we see an increase in response time with response time. more than four times due to the heavy load. This is explained by the fact that a Spring application based on a Tomcat server has a certain number of threads (pool threads) that can process requests. A thread is released only when the server has processed a request and returned an http response – the rest of the time the thread is either working or in the WAIT state. In the process of processing each request, we save records to the DB, and this is a blocking operation, that is, when saving a lesson in the DB, the thread goes into the WAIT state and is idle until the DB returns the result. While some of the threads are waiting for a response from the DB, some of the requests are waiting for a free thread to process, which increases the time it takes to complete requests.

To get rid of this drawback, you can use a reactive approach when designing the system. This will avoid blocking threads. In the context of this scenario, the thread of the reactive application, having sent a request to the DB, instead of going into the WAIT state, goes to accept another request or get a response from the DB and generate a response to the user. Another free thread will process the DB response and generate an http response.

Distributing the CPU load when processing a request using a thread pool.

Distributing the CPU load when processing a request using a thread pool.

Test 3. Goal: send 10 requests simultaneously

Let's send 10 requests to add lessons at once to check how the web application generates a unique identifier when storing information about a lesson. Let's change the parameters in Thread Group:

· Number of Threads – 10;

· Ramp-Up Period — 0;

· Loop Count — 1.

That is, 10 users will simultaneously send one request. Let's run the scenario.

Test Results 3. JMeter

Test Results 3. JMeter

According to the scenario in the screenshot, the requests were sent with a maximum time difference of 0.006 seconds. All responses received the status 200, and unique sequential identifiers were generated for new lessons. It can be concluded that in this scenario, the sequence for generating DB record identifiers worked without any complaints. The results of Test 3.CPU Usage prove that the CPU load readings are normal – 16%.

Test Results 3. Custom Graph

Test Results 3. Custom Graph

Test Results 3. CPU Usage

Test Results 3. CPU Usage

Conclusion

We have found that the combination of JMeter, Prometheus, and Grafana tools allows for effective load testing of web applications. JMeter allows you to generate load on the application, while Prometheus and Grafana help track the application's resource consumption, the success of requests, and other important characteristics. To measure the load correctly, you need to install the service (server) being tested on a separate machine in an environment close to the real one.

Real-time metrics collection allows you to quickly identify bottlenecks and issues in your application under load. This means that developers and administrators can take timely action and improve application performance.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *