Нагрузочное тестирование на Gatling
Gatling — это framework для проведения нагрузочного тестирования. Он основан на трех технологиях: Scala, Akka и Netty.
В этой статье мы:
- Посмотрим, как установить и начать использовать Gatling.
- Разберем синтаксис скриптов Gatling на языке Scala.
- Напишем небольшой тест, где используем основные функции Gatling. Запустим тестовый скрипт при помощи sbt и сохраним отчет.
Почему Gatling
Большинство специалистов для нагрузки используют Jmeter — до тех пор, пока не понадобится нагружать сокеты.
Мы нашли плагин для Jmeter. Плагин показал плохую производительность: программа работала нестабильно уже при ста открытых коннектах. Gatling стал хорошей заменой: он содержит программный интерфейс нагрузки сокетов и выдерживает до 5000 открытых соединений без сбоев.
Когда мы познакомились с Gatling — его синтаксисом и возможностями — стали переводить все скрипты с Jmeter на Gatling.
Подготовка к работе с Gatling
Устанавливаем Scala SDK и SBT, чтобы создавать скрипты и запускать их в IDE — например, в IntelliJ IDEA с поддержкой SBT проектов.
Структура проекта:
Скрипт размещаем в /src/test/scala/. Чтобы запустить симуляцию из-под sbt, добавляем в plugins.sbt строчку:
addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.0")
В build.sbt добавляем:
enablePlugins(GatlingPlugin)
libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.2.2" % "test"
libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.2.2" % "test"
Idea выдаст ошибку на строку enablePlugins (GatlingPlugin), но эта проблема IDE.
Теперь мы готовы разработать скрипт нагрузки.
Синтаксис
Любой скрипт на Gatling состоит из двух частей: конфигурации и самого профиля.
Конфигурация:
Задаем файл с данными о пользователях, которые нагрузят систему:
val users = ssv(fileName).circular
ssv (semicolon separated values) — формат файла. Ему не обязательно совпадать с расширением файла. В документации можно посмотреть другие поддерживаемые форматы файлов.
fileName — строка с абсолютным именем файла (C:\data\users.csv)
circular — метод обхода значений в файле. У нас: когда доходим до последней строки с пользователем, возвращаемся в начало.
Дальше задаем конфиг http — он будет работать для всех запросов:
val httpConf = http
.baseURL("https://www.tinkoff.ru/ ")
.acceptHeader("*/*")
.acceptEncodingHeader("gzip, deflate, br")
.acceptLanguageHeader("ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3")
.userAgentHeader("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0")
.check(status is 200)
Здесь задаем нужные хедеры, базовый URL и другие настройки: например, указываем прокси или отключаем кеширование.
Создаем сценарий:
val basicLoad = scenario("BASIC_LOAD").feed(users).during(20 minutes) {
exec(BasicLoad.start)
}
setUp(
basicLoad.inject(rampUsers(1000) over (20 minutes))
.protocols(httpConf))
.maxDuration(21 minutes)
Конфигурация должна содержаться в классе, расширяющий класс Simulation
package load
import io.gatling.core.scenario.Simulation
class LoadScript extends Simulation{
// Здесь наш конфиг
}
Посмотрите на полный проект. Мы создаем сценарий, где используем наших пользователей и конфиг http. За 20 минут скрипт прогонит профиль BasicLoad.start. Если сервер повиснет, на 21-й минуте прогон принудительно завершится. Мы получим отчет по всем данным, которые успели попасть в лог.
Профиль нагрузки:
object BasicLoad {
val start =
exec(
http("HTTP Request auth")
.post("/rest/session-start")
.formParam("login", "${login}")
.formParam("password", "${password}")
)
.exec(
http("HTTP Request getSkills")
.get("/rest/skills")
.check(jsonPath("$.id").saveAs("idSkill"))
)
.exec(
http("HTTP Request getResults")
.get("/rest/results")
.check(jsonPath("$.id").saveAs("idResult"))
)
.repeat(15) {
exec(session => {
println("Some Log")
val tmp = getTen()
session.set("ten",tmp)
})
.exec(
http("HTTP Request completedtasksreport skill")
.get("/rest/v2/completedtasksreport/")
.queryParam("dateFrom", "${data}")
.queryParam("excludeNoAnswer", "false")
.queryParam("orderBy", "ResultDate")
.queryParam("orderDesc", "true")
.queryParam("skip", "0")
.queryParam("take",_.attributes.getOrElse("ten",None))
.queryParam("skillIds", "${idSkill}")
)
.exec(
http("HTTP Request completedtasksreport result")
.get("/rest/v2/completedtasksreport/")
.queryParam("dateFrom", "${data}")
.queryParam("excludeNoAnswer", "false")
.queryParam("orderBy", "ResultDate")
.queryParam("orderDesc", "true")
.queryParam("skip", "0")
.queryParam("take", _.attributes.getOrElse("idSkill",None))
.queryParam("resultId", "${idResult}")
)
.exec(
http("HTTP Request completedtasksreport skill and result")
.get("/rest/v2/completedtasksreport/")
.queryParam("dateFrom", "${data}")
.queryParam("excludeNoAnswer", "false")
.queryParam("orderBy", "ResultDate")
.queryParam("orderDesc", "true")
.queryParam("skip", "0")
.queryParam("take", _.attributes.getOrElse("idSkill",None))
.queryParam("skillIds", "${idSkill}")
.queryParam("resultId", "${idResult}")
)
}
}
exec — метод, по которому нагрузочный профиль выполняет единичное действие. Например, отправляет запрос, открывает сокет, отправляет сообщение по сокету или выполняет анонимную функцию.
http (samplerName: String).(get|post|put…) отправляет необходимый запрос http. В функции метода http указываем относительный путь. Базовый url мы уже указали при настройке конфига http. Далее указываем параметры запроса — queryParam | formParam.
check проверяет ответ. Можно проверить заголовок ответа. Мы также используем check, когда хотим проверить и сохранить тело ответа или его отдельные элементы.
Любые действия можно выполнить с помощью конструкции:
exec( session => {
// ваш код
})
Внутри этого блока мы ограничиваемся только возможностями Scala. Сессия, с которой мы работаем, уникальна для каждого юзера (потока). Поэтому можно задать для сессии параметры через set, чтобы они были доступны в других блоках exec. Получить доступ к заданным параметрам можно через вызов
"${idSkill}"
или
_.attributes.getOrElse("idSkill",None)
Запуск и отчет
Запускаем Gatling с помощью sbt.
> sbt
> gatling:testOnly load.LoadScript
Во время запуска в консоль будут поступать данные в формате:
2017-02-02 10:49:27 20s elapsed
---- BASIC_LOAD --------------------------------------------------------------------
[--------------------------------------------------------------------------] 0%
waiting: 0 / active: 10 / done:0
---- Requests ------------------------------------------------------------------
> Global (OK=5155 KO=0 )
> HTTP Request auth (OK=111 KO=0 )
> HTTP Request getSkills (OK=111 KO=0 )
> HTTP Request getResults (OK=111 KO=0 )
> HTTP Request completedtasksreport skill (OK=1610 KO=0 )
> HTTP Request completedtasksreport result (OK=1607 KO=0 )
> HTTP Request completedtasksreport skill and result (OK=1605 KO=0 )
Если какие-нибудь методы упадут, мы сразу увидим ошибку:
status.find.is(200), but actually found 500 1 (100,0%)
и запись в KO.
После прогона отчет попадет в папку /target/gatling/SCRIPT_NAME-TIMESTAMP.
В отчете мы видим все необходимые графики, включая персентили, количество запросов в секунду и распределение времени ответа. Также отчет содержит таблицу с полной информацией по методам:
Если нас интересует конкретный метод, отдельно смотрим статистику по нему:
Нагрузочное тестирование с чужой машины
Если запуск скрипта и анализ результатов проводит ваш коллега, подготовьте его машину:
- Скачайте архив. Распакуйте и скопируйте свой скрипт в папку /user-files/simulations/.
- Откройте папку /bin и запустите gatling.
. - Выберите свой скрипт в командной строке, нажмите нужную цифру.
После этого начнется нагрузка. Результаты попадут в папку /results. Чтобы посмотреть их, откройте index.html в любом браузере.
В архиве вы найдете утилиту recorder. С ее помощью можно сгенерировать скрипт двумя способами:
- на основе архива HAR — дамп-вкладки network в окне разработчика в браузере
- используя утилиту в качестве прокси между браузером и веб-сервером
Генерация скрипта с использованием рекордера не идеальна — в скрипте много «воды» и нет функций проверки ответов. Отчет трудно читать, методы в нем называются request_0, request_1 и т. д.
Что дальше
В статье мы поговорили только об основных функциях Gatling. Это гибкий фреймворк, его возможности намного шире.
В следующей статье:
- Рассмотрим нагрузку сокетов.
- Разберем рандомные ветвления.
- Зададим RPS.
- Сравним производительность Gatling с Jmeter.
Напишите в комментариях, что вы хотели бы обсудить подробнее.