Нагрузочное тестирование на Gatling

Статья публикуется от имени Перфильева Алексея, akaaxel

image altGatling — это framework для проведения нагрузочного тестирования. Он основан на трех технологиях: Scala, Akka и Netty.
В этой статье мы:

  1. Посмотрим, как установить и начать использовать Gatling.
  2. Разберем синтаксис скриптов Gatling на языке Scala.
  3. Напишем небольшой тест, где используем основные функции Gatling. Запустим тестовый скрипт при помощи sbt и сохраним отчет.

Почему Gatling


Большинство специалистов для нагрузки используют Jmeter — до тех пор, пока не понадобится нагружать сокеты.

Мы нашли плагин для Jmeter. Плагин показал плохую производительность: программа работала нестабильно уже при ста открытых коннектах. Gatling стал хорошей заменой: он содержит программный интерфейс нагрузки сокетов и выдерживает до 5000 открытых соединений без сбоев.

Когда мы познакомились с Gatling — его синтаксисом и возможностями — стали переводить все скрипты с Jmeter на Gatling.

Подготовка к работе с Gatling


Устанавливаем Scala SDK и SBT, чтобы создавать скрипты и запускать их в IDE — например, в IntelliJ IDEA с поддержкой SBT проектов.

Структура проекта:

0d1fdcdcc8b84be0943659a71b2d5b39.png

Скрипт размещаем в /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.

В отчете мы видим все необходимые графики, включая персентили, количество запросов в секунду и распределение времени ответа. Также отчет содержит таблицу с полной информацией по методам:

2d112fe57e8c421bb8ca98e0561ad685.png

Если нас интересует конкретный метод, отдельно смотрим статистику по нему:

10f2bca257354c3f83e1e12e89ab74ad.png

Нагрузочное тестирование с чужой машины


Если запуск скрипта и анализ результатов проводит ваш коллега, подготовьте его машину:
  1. Скачайте архив. Распакуйте и скопируйте свой скрипт в папку /user-files/simulations/.
  2. Откройте папку /bin и запустите gatling..
  3. Выберите свой скрипт в командной строке, нажмите нужную цифру.

После этого начнется нагрузка. Результаты попадут в папку /results. Чтобы посмотреть их, откройте index.html в любом браузере.

В архиве вы найдете утилиту recorder. С ее помощью можно сгенерировать скрипт двумя способами:

  • на основе архива HAR — дамп-вкладки network в окне разработчика в браузере
  • используя утилиту в качестве прокси между браузером и веб-сервером

Генерация скрипта с использованием рекордера не идеальна — в скрипте много «воды» и нет функций проверки ответов. Отчет трудно читать, методы в нем называются request_0, request_1 и т. д.

Что дальше


В статье мы поговорили только об основных функциях Gatling. Это гибкий фреймворк, его возможности намного шире.

В следующей статье:

  1. Рассмотрим нагрузку сокетов.
  2. Разберем рандомные ветвления.
  3. Зададим RPS.
  4. Сравним производительность Gatling с Jmeter.

Напишите в комментариях, что вы хотели бы обсудить подробнее.

Комментарии (0)

© Habrahabr.ru