Всем привет, меня зовут Сергей, я занимаюсь тестированием производительности. Недавно поднялся вопрос в выборе инструмента для воспроизведения довольно интенсивной нагрузки, в основном по HTTP. Инструментов для тестирования производительности сейчас представлено довольно много, в том числе многие из них являются Open Source — проектами и доступны бесплатно. Стало интересно, какой же инструмент справится с подобной задачей лучше, сможет воспроизвести большую нагрузку затратив меньше ресурсов.
Решил поставить несколько популярных инструментов в одинаковые условия и проверить результат. Если интересно что из этого получилось, добро пожаловать под кат.
Дисклеймер
Данное тестирование не претендует на объективность, и показывает результаты только в одних конкретных условиях с конкретным подходом и только при использовании HTTP. Возможно что при изменении подхода или в результате какого-либо тюнинга результаты будут совершенно другие.
Итак, рассмотрим претендентов
Gatling (https://gatling.io/)
![](https://habrastorage.org/getpro/habr/upload_files/fab/87a/33a/fab87a33abe8be32563c4ea7e17c53a2.png)
Open Source инструмент для тестирования производительности, написан на Scala, поэтому работает на JVM и требует установленную Java, для разработки скриптов можно использовать Scala, Java или Kotlin, но основным языком все-таки считается Scala, большинство встречающихся примеров написано именно на нем. Из коробки умеет работать с HTTP и WebSocket, но для популярных протоколов существуют не официальные плагины. Не имеет интерфейса, все скрипты пишутся при помощи кода или рекордера. Умеет строить довольно красивые краткие отчеты. Gatling не очень популярен в России, но его популярность стремительно растет в последнее время.
![Пример отчета формируемый в Gatling Пример отчета формируемый в Gatling](https://habrastorage.org/getpro/habr/upload_files/66e/53f/c88/66e53fc884aac3d6678f5031dbf33e62.png)
Apache JMeter (https://jmeter.apache.org/)
![](https://habrastorage.org/getpro/habr/upload_files/9d2/ecf/eb9/9d2ecfeb912ec53f2bf05584a2e75f5c.png)
Наверно самый известный инструмент для нагрузочного тестирования. Написан на Java, для работы требуется JVM, поэтому может выполняться в любой среде где есть Java. Имеет довольно понятный интерфейс. Большую часть логики запросов можно сделать без программирования, добавляя и конфигурируя нужные блоки. Из коробки поддерживает довольно много протоколов. А так же для JMeter существует огромное количество плагинов реализующих более специфические протоколы которых нет по-умолчанию. Так же вокруг JMeter очень большое сообщество специалистов, в том числе русскоязычных.
K6 (https://k6.io/)
![](https://habrastorage.org/getpro/habr/upload_files/f8d/4bd/ab8/f8d4bdab8a3c05daf91541a3b7b0456b.png)
Подающий надежды новичок в области тестирования производительности. Распространяется бесплатно, имеет коммерческую версию, предоставляемую как сервис. Разработан на Go, скрипты пишутся на JS, из коробки умеет работать только с HTTP, но уже имеется набор плагинов для популярных протоколов. Не имеет интерфейса, все делается при помощи кода и параметров запуска.
Locust (https://locust.io/)
![](https://habrastorage.org/getpro/habr/upload_files/1a0/57b/d3e/1a057bd3e1c822d6cbbf16cb105365d0.jpeg)
Open source фреймворк для тестирования производительности разработанный на Python, скрипты так же пишутся на Python. Очень прост в освоении, особенно для знакомых с Python. Имеет веб-интерфейс для запуска, конфигурирования параметров теста и просмотра результатов.
MF LoadRunner (https://www.microfocus.com/)
![](https://habrastorage.org/getpro/habr/upload_files/299/4b8/f9a/2994b8f9abea3f85b3e6e604ddf86b13.png)
Единственный проприетарный инструмент в нашем тесте. Очень популярный в банковской сфере, поддерживает огромное количество протоколов, имеет удобные приложения с интерфейсами для разработки, запуска тестов и анализа результатов тестирования. В зависимости от протокола доступны разные языки написания скриптов.
Для тестирования был написан примитивный веб-сервер на языке Go, который обрабатывает единственный GET-запрос и возвращает статический ответ. Кому интересно, код ниже.
Код
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":8089", nil)
}
Максимальная производительность сервиса достигнутая на хост-машине находится примерно на уровне 150 000 запросов в секунду.
Все инструменты, кроме LoadRunner, запускались на виртуальной машине QEMU с ОС Ubuntu Server 20.04, версия ядра 5.4.0-92-generic, конфигурация виртуальной машины 4vCPU, 4GB RAM, само тестируемое приложение размещено на хост-машине, где и развернута виртуалка. Конфигурация хост-машины: AMD Ryzen 5 5500U, 16GB RAM. Kubuntu 21.10 версия ядра 5.13.0-27-generic.
Для увеличения количества подключений, на обоих линуксах были выполнены следующие настройки:
Увеличено максимальное число открытых файлов в /etc/security/limits.conf
* hard nofile 97816
* soft nofile 97816
и количество подключений
sysctl net.ipv4.ip_local_port_range="15000 61000"
sysctl net.core.somaxconn=1024
sysctl net.ipv4.tcp_tw_reuse=1
sysctl net.core.netdev_max_backlog=2000
sysctl net.ipv4.tcp_max_syn_backlog=2048
LoadRunner запускался на аналогичной конфигурации, но в качестве ОС уже была Windows Server 2019.
Мониторинг построен на связке Telegraf, Influx, Grafana. Telegraf установлен на обоих машинах, Influx и Grafana установлены на хост-машине.
Все инструменты, кроме LoadRunner, были настроены на отправку данных в Influx, понимаю что это создает лишнюю нагрузку, но зачем нужно тестирование без мониторинга.
Результаты тестирования
Если подробности не интересны, лучше сразу перейти к таблице в конце статьи.
Gatling
Для тестирования использовалась версия 3.7.3 (последняя на момент написания статьи)
Скрипт довольно примитивный, каждый виртуальный пользователь делает запрос, каждые 2 мс., количество пользователей возрастает от 1 до 300 за 10 минут.
Код скрипта
import io.gatling.core.scenario._
import io.gatling.http.Predef._
import io.gatling.core.Predef._
import scala.concurrent.duration.DurationInt
class GatlingTest extends Simulation{
val httpConf = http.baseUrl("http://192.168.122.1:8088")
val rq = http("SampleRq").get("/")
val scn = scenario("SampleScenario").forever(
pace(2.millis).
exec(rq))
setUp(
scn.inject(rampConcurrentUsers(1).to(300).during(600)).protocols(httpConf)
).maxDuration(10.minutes)
}
Стабильный рост нагрузки происходил примерно до 22 500 запросов в секунду, что можно считать максимальной производительностью для Gatling в данных условиях.
Важно заметить что, чем больше виртуальных пользователей, тем больше создается сетевых коннектов, которые потребляют большую часть ресурсов, при быстром выполнении запросов и минимальном количестве пользователей создается максимальная производительность.
![Количество запросов в секунду Количество запросов в секунду](https://habrastorage.org/getpro/habr/upload_files/206/fe7/6fa/206fe76fadc67191196d3f6a937b0cfc.png)
Как можно заметить, рост количества запросов продолжался и дальше, и можно заметить пики на уровне 40 000 запросов в секунду, однако производительность на этом уровне была уже очень нестабильной, поэтому за максимум считаю 22 500 запросов в секунду.
![Утилизация CPU на виртуальной машине Утилизация CPU на виртуальной машине](https://habrastorage.org/getpro/habr/upload_files/7b3/65e/1f4/7b365e1f4a28eff6f464e21c9904af96.png)
Перед началом нестабильной работы утилизация процессора виртуалки находилась на уровне около 65%, думаю такие показания связаны со склонностью виртуалок занижать потребление процессора при измерении изнутри.
![Времена отклика Времена отклика](https://habrastorage.org/getpro/habr/upload_files/2ea/c16/da7/2eac16da74f5d0668662e24913af4f17.png)
Время отклика начало расти, как только уровень нагрузки стал нестабильным.
![Количество виртуальных пользователей Количество виртуальных пользователей](https://habrastorage.org/getpro/habr/upload_files/03f/9da/121/03f9da12194ec98ef69181363521751d.png)
Количество виртуальных пользователей увеличивалось равномерно
![Количество соединенеий tcp Количество соединенеий tcp](https://habrastorage.org/getpro/habr/upload_files/81c/30b/98b/81c30b98b77aa354e2caf42ea385e5a1.png)
Количество соединений росло пропорционально нагрузке
JMeter
Для тестирования использовалась версия 5.4.3 (последняя на момент написания статьи) Подход к формированию скрипта здесь аналогичный, для пауз использовался Constant Throughput Timer с целевой производительностью 30 000 запросов в минуту на один поток. Количество пользователей 300, запускаются за 10 минут.
Скрипт
![Конфигурация запуска пользователей Конфигурация запуска пользователей](https://habrastorage.org/getpro/habr/upload_files/cf9/23d/6b2/cf923d6b2857d5a77d9c1fc9711f8883.png)
Стабильный рост нагрузки продолжался примерно до 28 000 запросов в секунду, Jmeter создает минимальное количество подключений, что положительно сказывается на производительности.
![Количество запросов в секунду Количество запросов в секунду](https://habrastorage.org/getpro/habr/upload_files/3dc/c5b/75e/3dcc5b75e201d0343bdb224d14daa512.png)
Количество запросов возрастало довольно стабильно, пока не закончились ресурсы CPU.
![Утилизация CPU виртуальной машины Утилизация CPU виртуальной машины](https://habrastorage.org/getpro/habr/upload_files/e40/a32/681/e40a326813a8fc33a218b4f737e070b5.png)
По достижению максимальной утилизации процессора, перестала расти и производительность.
![Использование оперативной памяти Использование оперативной памяти](https://habrastorage.org/getpro/habr/upload_files/f5c/bd6/daa/f5cbd6daa935a63f46e55ef66d5bfc3d.png)
Оперативная память утилизировалась довольно умеренно.
![Количество подключений tcp Количество подключений tcp](https://habrastorage.org/getpro/habr/upload_files/e5f/af8/39e/e5faf839eb4c7dbd0403bd90830827d3.png)
Количество подключений возрастало пропорционально нагрузке.
![Время отклика Время отклика](https://habrastorage.org/getpro/habr/upload_files/d3e/c1a/2fa/d3ec1a2fa837710f7a47cde70ce9d113.png)
Время отклика начало расти, как только достигли максимальной производительности.
K6
Для тестирования использовалась версия k6 v0.36.0. Использовался примерно такой же подход к формированию скрипта пауза между итерациями 10 мс. Запуск 200 виртуальных пользователей за 10 минут.
Код скрипта
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
stages: [
{ duration: '600s', target: 200 },
{ duration: '20s', target: 0 },
],
};
export default function () {
http.get('http://192.168.122.1:8089');
sleep(0.01);
}
Стабильный рост нагрузки продолжался примерно до 4 500 запросов в секунду.
![Количество запросов в секунду Количество запросов в секунду](https://habrastorage.org/getpro/habr/upload_files/8db/a6d/0f4/8dba6d0f444792cf2fa52167cbeb1a2d.png)
В пике производительность возрастала до 8000 запросов в секунду, но на таком уровне нагрузка была очень нестабильной.
![Время отклика Время отклика](https://habrastorage.org/getpro/habr/upload_files/534/813/2c0/5348132c09d1f3422c9cefd11834efce.png)
Время отклика возрастало на протяжении всего теста.
![Количество виртуальных пользователей Количество виртуальных пользователей](https://habrastorage.org/getpro/habr/upload_files/768/38c/bf6/76838cbf68be249350af10b09f95324e.png)
Количество виртуальных пользователей возрастало равномерно.
![Утилизация CPU вритуальной машины Утилизация CPU вритуальной машины](https://habrastorage.org/getpro/habr/upload_files/960/c63/bc9/960c63bc95382620d4f3ee52af15401a.png)
Утилизация CPU в начале нестабильной нагрузки была на уровне около 65%.
![Использование оперативной памяти Использование оперативной памяти](https://habrastorage.org/getpro/habr/upload_files/890/e5a/80e/890e5a80e6e1536df4a5c21586ca50ef.png)
Оперативная память утилизировалась существенно, наблюдался значительный рост утилизации в начале нестабильной работы.
![](https://habrastorage.org/getpro/habr/upload_files/09a/d48/55f/09ad4855ff1125c737fb55084c2828eb.png)
Количество коннектов росло пропорционально нагрузке
Locust
Для тестирования использовалась версия 2.5.1. В скрипте прописаны паузы в 200 мс. Между запросами, количество пользователей 200, запускаются по 1 в секунду. Для отправки результатов в Influx использовалась библиотека InfluxDBListener.
Код скрипта
from locust import between, constant, events, tag, task, HttpUser
from locust_influxdb_listener import InfluxDBListener, InfluxDBSettings
@events.init.add_listener
def on_locust_init(environment, **_kwargs):
"""
Hook event that enables starting an influxdb connection
"""
influxDBSettings = InfluxDBSettings(
influx_host = '192.168.122.1',
influx_port = '8086',
user = 'admin',
pwd = 'admin',
database = 'monitoring'
)
InfluxDBListener(env=environment, influxDbSettings=influxDBSettings)
class HelloWorldUser(HttpUser):
@task
def hello_world(self):
self.client.get("/")
wait_time = constant(0.2)
Стабильный рост производительности наблюдался до 835 запросов в секунду, после происходит существенны спад и производительность становится нестабильной.
![Количество запросов в секунду Количество запросов в секунду](https://habrastorage.org/getpro/habr/upload_files/b31/5ae/8c1/b315ae8c1a5549cc3c7d8be1ac9ded83.png)
При этом утилизируется только одно ядро виртуальной машины.
![Вывод команды top Вывод команды top](https://habrastorage.org/getpro/habr/upload_files/f6e/aee/426/f6eaee426edc1880e02ff46ce26e88f9.png)
Так как использовалось только одно ядро, утилизация CPU была в пределах 25%.
![Утилизация CPU виртуальной машины Утилизация CPU виртуальной машины](https://habrastorage.org/getpro/habr/upload_files/1a3/69e/71b/1a369e71b44edd4c0b4d61546b318dee.png)
После начала нестабильной работы время отклика начало расти.
![Время отклика Время отклика](https://habrastorage.org/getpro/habr/upload_files/0e6/d4d/b51/0e6d4db5136bdf811a42e5e3961db4c6.png)
Оперативная память утилизировалась несущественно.
![Использование оперативной памяти Использование оперативной памяти](https://habrastorage.org/getpro/habr/upload_files/e06/319/daa/e06319daa83bd6160aa49edf5dc7d3a4.png)
MF LoadRunner
Для тестов использовалась верся 2021 build 371, тип скрипта WebHTTP, к сожалению простыми путями указать интервал запуска итераций меньше секунды в LoadRunner не представляется возможным, никакой паузы не указывалось. Запускались 50 пользователей (ограничение бесплатной версии) по одному в минуту.
Код скрипта
Action()
{
web_rest("GET: http://192.168.122.1",
"URL=http://192.168.122.1:8089",
"Method=GET",
"Snapshot=t449913.inf",
LAST);
return 0;
}
В RunTimeSettings обязательно нужно снять галочку Sumulate a new user on each iteration, иначе на каждый раз создается новое подключение и подключения быстро заканчиваются.
Стабильный рост производительности продолжался примерно до 7800 запросов в секунду, пока не закончились ресурсы CPU.
![Количество запросов в секунду Количество запросов в секунду](https://habrastorage.org/getpro/habr/upload_files/b35/b90/07b/b35b9007be2da0507e9663845a69268d.png)
Утилизация CPU росла пропорционально нагрузке.
![Утилизация CPU виртуальной машины Утилизация CPU виртуальной машины](https://habrastorage.org/getpro/habr/upload_files/e05/69d/002/e0569d00295008d8b26c16e546665169.png)
Оперативная память утилизировалась незначительно, небольшой рост наблюдается при достижении максимальной производительности.
![Использование оперативной памяти Использование оперативной памяти](https://habrastorage.org/getpro/habr/upload_files/c63/dc8/5e9/c63dc85e9cd175a62c4240a416927285.png)
Время отклика начало немного расти после достижения максимальной производительности.
![Время отклика. Время отклика.](https://habrastorage.org/getpro/habr/upload_files/594/efd/6d9/594efd6d94e038af0f7676445251911c.png)
Итоговая таблица
№ | Название инструмента | Полученный RPS |
1 | Gatling | 22 500 |
2 | JMeter | 28 000 |
3 | K6 | 4 500 |
4 | Locust | 835 |
5 | LoadRunner | 7 800 |
Выскажу свое впечатление по каждому инструменту по результатам тестов.
Gatling — мощный инструмент, показал высокую производительность, для новичка в НТ будет сложноват из-за подхода только через код и необходимости осваивать Scala, но специалисту с опытом определенно может пригодиться, особенно если придется тестировать веб-сокеты.
JMeter — победитель в текущем конкурсе. Хороший инструмент как для новичка, потому что легок в освоении, так и для специалиста, имеет огромный функционал из коробки, а так же возможности для расширения.
K6 — возможно еще сыроват и поэтому показал не очень хорошие результаты. Может требует какого то тюнинга, но из коробки результаты получились не очень хорошие. Если кто то заметил что я сделал с ним не так, напишите пожалуйста в комментариях.
Locust — производительность этого инструмента оказалась, мягко говоря, «слабовата». Зато он имеет очень приятный веб-интерфейс с неплохим функционалом и позволяет писать на Python. Думаю для Python-разработчиков, которым по-быстрому нужно протестировать не очень высоко нагруженный проект идеальный вариант.
LoadRunner — производительность этого ветерана банковской сферы оказалась примерно по-середине, но и нужно учитывать, что запускался он под Windows. Несомненный плюс его результатов в том что нагрузка была предсказуемой, даже после исчерпания ресурсов CPU производительность не начала скакать, а находилась примерно на одном уровне.
Как видно, каждый инструмент хорош для своих целей, поэтому не стоит воспринимать этот тест как рейтинг инструментов тестирования производительности. Возможно инструмент показавший себя плохо в данном тесте, будет лидером в других условиях.