
Привет! Мы — команда тестирования производительности в Тинькофф, и мы любим инструмент Gatling. В цикле статей мы расскажем об использовании Gatling и дополнительных инструментов, упрощающих разработку скриптов.
Возможно, вы уже читали наши статьи про Gatling: первую и вторую. Они успели устареть, поэтому мы решили вернуться с обновленной информацией.
Почему стоит прочитать эту статью
Вы узнаете, как подготовить шаблонный Gatling-проект из нашего шаблона gatling-template.g8, и познакомитесь с базовыми понятиями в Gatling. А также изучите базовые возможности подключаемой библиотеки gatling-picatinny.
Кроме того, статья будет полезна новичкам в нагрузочном тестировании (НТ), которые хотят запустить его на своем проекте, но не знают, с чего начать. Мы познакомим вас с Gatling и расскажем об инфраструктуре и инструментах команды НТ Тинькофф, которые вы сможете использовать.
Дисклеймер
На момент написания статьи используемые плагины поддерживают версию Gatling не выше 3.7.5. Для получения работающего скрипта в будущем достаточно будет обновить версии плагинов в соответствии с их документацией.
Что такое шаблон gatling-g8 и как его использовать
Шаблон gatling-g8 — это удобный инструмент, который позволяет быстро создавать SBT-проект, в нашем случае — для Gatling-проекта. Он включает в себя готовую структуру каталогов и файлов для написания скриптов, а также подключенную gatling-picatinny. Это библиотека с полезными функциями, расширяющими Gatling DSL и повышающими производительность. Шаблон g8 мы создаем с использованием инструмента giter8. Разберемся, с чего начать работу с gatling-g8.
Создание шаблона проекта. Убедитесь, что в вашей системе установлена sbt версии не ниже 1.6.2. Она понадобится для дальнейшей работы. Затем откройте консоль и выполните команду ([version] — указать актуальную версию):
sbt new TinkoffCreditSystems/gatling-template.g8 -t [version]
После выполнения команды у вас запросят несколько параметров. Укажите только имя вашего сервиса:

Остальные параметры оставляйте как есть. Теперь у вас есть готовый scala-проект для Gatling.
Обзор структуры проекта. Рассмотрим, из чего именно состоит проект. Пойдем по каталогам сверху вниз:

-
project — каталог содержит информацию о зависимостях, плагинах и версии sbt проекта;
-
src — основной каталог. Содержит скрипты и конфигурации;
2.1. resources — содержит конфигурацию Gatling, настройки симуляции и логирования;
2.2. cases — каталог для запросов симуляции;
2.3. scenarios — каталог для сценариев симуляции;
2.4. test.scala — конфигурация протокола, который используется для тестирования системы;
2.5. Debug.scala, MaxPerformance.scala, Stability.scala — готовые симуляции для отладки, теста на поиск максимальной производительности и теста стабильности соответственно. -
build.sbt — конфигурация sbt.
Как видно из структуры, для создания скриптов необходимо написать запросы в каталоге cases. Далее — сформировать из них сценарии в каталоге scenarios и подключить эти сценарии в симуляции Debug.scala, MaxPerformance.scala или Stability.scala. О них расскажем ниже. А затем — добавить в настройки симуляции нужные переменные. О написании скриптов мы подробнее расскажем в следующих статьях.
Запуск проекта. Чтобы запустить проект, используйте Gatling runner. Он позволяет запускать скрипты не через sbt-plugin, а напрямую из IntelliJ IDEA. В некоторых случаях это позволяет улучшить опыт отладки с помощью стандартных возможностей IDE (например, breakpoints). Откройте объект GatlingRunner по пути src/test/scala/ru/tinkoff/load/[projectName]/GatlingRunner.scala и измените класс симуляции, который хотите запустить:
object GatlingRunner { def main(args: Array[String]): Unit = { /* Здесь вы указываете класс, который хотите запустить. По умолчанию стоит Debug */ val simulationClass = classOf[Debug].getName val props = new GatlingPropertiesBuilder props.simulationClass(simulationClass) Gatling.fromMap(props.build) } }
Затем нажмите на Run GatlingRunner, если работаете в IDE InteliJ IDEA:

Если выбрали другую IDE, воспользуйтесь SBT для запуска в командной строке:
sbt "Gatling/testOnly ru.tinkoff.load.projectName.simulationName"
Теперь, когда мы рассказали про использование шаблона, предлагаем разобраться с основными понятиями.
Какие базовые понятия в Gatling важно знать
Симуляция. В общем виде симуляция в Gatling — это класс Scala, который состоит из различных сценариев и инжекторов (способов моделирования нагрузки) для этих сценариев. Симуляцию можно разделить на следующие части:
-
Конфигурация протокола (в примере — HTTP).
-
Определение запросов.
-
Определение сценария.
-
Вызов основного метода симуляции setUp.
В примере ниже мы показываем общий вид симуляции без использования нашего шаблона gatling-template.g8.
import io.gatling.core.Predef. import io.gatling.core.structure.ScenarioBuilder import io.gatling.http.Predef. import io.gatling.http.protocol.HttpProtocolBuilder import io.gatling.http.request.builder.HttpRequestBuilder import scala.concurrent.duration.DurationInt // Создаем класс симуляции, который наследуется от класса Simulation class BasicSimulation extends Simulation { // Определяем http протокол val httpProtocol: HttpProtocolBuilder = http // Базовый URL относительно которого будут строиться все запросы .baseUrl("http://computer-database.gatling.io") // Добавляем необходимые хедеры .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8") .acceptLanguageHeader("en-US,en;q=0.5") .acceptEncodingHeader("gzip, deflate") .userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0") // Создаем запрос val request: HttpRequestBuilder = http("request_1") .get("/myRequest") // Создаем сценарий, в который добавлен наш запрос val scn: ScenarioBuilder = scenario("BasicSimulation") .exec(request) // Основной метод setUp, в который добавляем сценарии setUp( // Моделируем нагрузку, используя специальные inject-методы scn.inject( // Ничего не делаем 4 секунды nothingFor(4.seconds), // Поднимает сразу заданное количество пользователей atOnceUsers(10), // Поднимает заданное количество пользователей, // равномерно распределенных в течении заданного периода rampUsers(10).during(5.seconds), // В течение заданного времени будет поддерживать определенное // кол-во пользователей в секунду constantUsersPerSec(20).during(15.seconds), // Тоже самое, что и выше, но пользователи будут вводиться через // рандомизированные интервалы constantUsersPerSec(20).during(15.seconds).randomized, // Поднимает пользователей от начального до целевого значения // равномерно в течении заданного периода rampUsersPerSec(10).to(20).during(10.minutes), // Тоже самое, что и выше, но пользователи будут вводиться через // рандомизированные интервалы rampUsersPerSec(10).to(20).during(10.minutes).randomized, ) // Подключаем http протокол ).protocols(httpProtocol) }
Типы симуляций. В нашем шаблоне g8 есть три типа предустановленных способов подачи нагрузки:
-
Debug. Используется для отладки и проверки работоспособности логики самих скриптов.
class Debug extends Simulation { setUp( // Сценарий запустится на одном пользователе. // Фактически мы получим одну итерацию сценария CommonScenario() CommonScenario().inject(atOnceUsers(1)), ).protocols( httpProtocol, ).maxDuration(testDuration) }
-
MaxPerformance. Используется для запуска тестов со ступенчато-подаваемой нагрузкой. С его помощью мы определяем точки максимальной производительности. Ниже — график и пример его реализации в Gatling.

На графике можно увидеть ступенчато-подаваемую нагрузку. Выход на очередную ступень нагрузки происходит в течение ramp duration. Сама ступень длится в течении stage duration, интенсивность на ступень рассчитывается по формуле intensity / stage numbers, где stage numbers — это количество ступеней в тесте.
class MaxPerformance extends Simulation with Annotations { setUp( CommonScenario().inject( // Интенсивность на ступень incrementUsersPerSec((intensity / stagesNumber).toInt) // Количество ступеней .times(stagesNumber) // Длительность полки .eachLevelLasting(stageDuration) // Длительность разгона .separatedByRampsLasting(rampDuration) // Начало нагрузки с 0 rps .startingFrom(0), ), ).protocols(httpProtocol) // Общая длительность теста .maxDuration(testDuration) }
-
Stability. Используется для запуска тестов со стабильно подаваемой нагрузкой с предварительным постепенным разгоном. Такой тест может быть полезен для подтверждения стабильной работы приложения и проведения регрессионных тестов. Ниже — пример теста со стабильно подаваемой нагрузкой и код в Gatling.

На графике можно увидеть стабильно подаваемую нагрузку. Выход на стабильную нагрузку происходит в течение ramp duration, стабильная нагрузка длится в течение stage duration с интенсивностью intensity.
class Stability extends Simulation with Annotations { setUp( CommonScenario().inject( // Длительность разгона rampUsersPerSec(0) to intensity.toInt during rampDuration, // Длительность полки constantUsersPerSec(intensity.toInt) during stageDuration, ), ).protocols(httpProtocol) // Длительность теста = разгон + полка .maxDuration(testDuration) }
Feeder. Фидеры в Gatling представляют из себя механизм внедрения тестовых данных в сценарии. Тестовые данные могут поступать из различных источников, например самый распространенный вариант — это чтение данных из файла. Также можно читать данные из БД и писать собственные фидеры, которые генерируют тестовые данные по некоторой функции.
Gatling предлагает несколько стратегий для встроенных фидеров:
-
queue — очередь, используется по умолчанию, читает записи последовательно;
-
random — случайным образом выбирает запись из последовательности;
-
shuffle — перемешивает записи, а затем ведет себя как queue;
-
circular — читает записи последовательно, но при достижении конца последовательности начинает заново.
При использовании queue или shuffle убедитесь, что ваш набор данных содержит достаточно записей. Если у фидера заканчивается запись, Gatling принудительно закончит тест.
Также фидеры делятся на:
-
файловые — читают данные из заранее подготовленных файлов;
-
JDBC — читает данные из базы данных;
-
Sitemap — читает данные из специального формата XML-файлов;
-
Redis — читает данные из Redis.
-
Custom — читает данные из произвольной функции, поставляется с gatling-picatinny.
Пример использования файлового фидера:
... val someRequest: HttpRequestBuilder = http("some request") .get("/") .queryParam("someParam", "#{value}") // 1 val csvFeeder: BatchableFeederBuilder[String] = csv("value.csv").random // 2 /* Пример того, как выглядит файл value.csv // 3 value 1 2 ... n */ val scn: ScenarioBuilder = scenario("BasicSimulation") .feed(csvFeeder) // 4 .exec(someRequest) ...
Пример файла value.csv:

Разберем, что происходит в этом коде:
-
Мы пишем http-запрос с некоторым параметром, значение которого мы хотим параметризовать. Для этого мы используем специальную конструкцию #{value}.
-
Создаем файловый фидер.
-
Файл содержит заголовок и значения. Заголовок должен называться так же, как и параметр.
-
Добавляем фидер в сценарий. Теперь вместо #{value} там окажется случайное значение из файла. Убедиться, что значения из фидера попали в запросы, можно запустив отладочный сценарий (Debug.scala).
Так мы можем «на лету» создавать разные запросы.
Сессия. Это состояние виртуального пользователя. В сессии хранятся данные только для этого пользователя. Передача информации между сессиями по умолчанию невозможна, но можно использовать workaround — например, redis. По сути, это пара «ключ-значение», где ключ — это атрибут сессии, или параметр, а значение — это содержимое этого атрибута. В сессии хранятся как различные переменные, например текущее значение фидера, так и название сценария, id виртуального пользователя и другие параметры.
Вы можете читать данные из сессии и добавлять их туда. Вот так можно положить значение в сессию:
class CommonScenario { val scn: ScenarioBuilder = scenario("Common Scenario") // Положили в сессию параметр 'key' с значением 'value' .exec(session => session.set("key", "value")) .exec { session => /* Обратите внимание, если вы хотите положить несколько значений в сессию, то такой вариант не сработает и в сессии будет только параметр 'm' */ session.set("k", "v") session.set("m", "d") } .exec { session => // Правильное использование session.set("k", "v").set("m", "d") } .exec(GetMainPage.getMainPage) }
А так — получить из сессии значение:
class CommonScenario { val scn: ScenarioBuilder = scenario("Common Scenario") .exec { session => // Взяли значение из сессии, обязательно нужно указать as[type] val someValue = session("someValue").as[Int] // Изменили значение val newValue = someValue + 1 // Положили новое значение обратно в сессию session.set("key", newValue) } .exec(GetMainPage.getMainPage) }
Еще можно вывести сессию в консоль. Например, с целью отладки:
class CommonScenario { val scn: ScenarioBuilder = scenario("Common Scenario") .exec { session => // Вывели в консоль всю сессию println(session) session } .exec { session => // Вывели в консоль только необходимый атрибут println(session("addComputer").as[String]) /* Можно так же поставить breakpoint в этом месте для достижения такого же результата */ session } }
Вывод в консоли выглядит так:
Session(CommonScenario,1,HashMap(gatling.http.cache.baseUrl -> http://computer-database.gatling.io, randomComputerName -> JLSZBCAOWBZNI, company -> 40, gatling.http.ssl.sslContexts -> io.gatling.http.util.SslContexts@5993df64, gatling.http.referer -> http://computer-database.gatling.io/computers, gatling.http.cookies -> CookieJar(Map()), introduced -> 2022-01-26, gatling.http.cache.dns -> io.gatling.http.resolver.ShufflingNameResolver@5d64e0f7, discontinued -> 2022-01-27),OK,List(), io.gatling.core.protocol.ProtocolComponentsRegistry$$Lambda$698/0x00000008007a0840@ae111d9, io.netty.channel.nio.NioEventLoop@13047d7d) JLSZBCAOWBZNI
Gatling Picatinny. Это библиотека с множеством полезных функций. Она расширяет Gatling DSL и повышает производительность при написании скриптов. Мы уже писали об этой библиотеке в тексте об арсенале Gatling. Полное описание всех функций с примерами вы можете найти в README. А о новых функциях библиотеки мы расскажем ниже.
Какие фидеры появились в Picatinny
HashiCorp Vault. Фидер, способный извлекать секретные данные из HC Vault. Авторизация проходит через approle, при этом используется v1 API. Фидер работает с kv Secret Engine. Он не перебирает ключи, а возвращает полную карту с ключами, найденными при каждом вызове. Параметры фидера:
-
vaultUrl — URL хранилища, например https://vault.ru;
-
secretPath — путь к секретным данным в вашем хранилище. Например, testing/data;
-
roleId — approle логин;
-
secretId — approle пароль;
-
keys — список ключей, которые вы хотите получить из хранилища.
private val vaultUrl: String = System.getenv("vaultUrl") private val secretPath: String = System.getenv("secretPath") private val roleId: String = System.getenv("roleId") private val secretId: String = System.getenv("secretId") private val keys: List[String] = List("k1", "k2", "k3") val vaultFeeder: Feeder[String] = VaultFeeder(vaultUrl, secretPath, roleId, secretId, keys)
Utils. Создает токен JWT с использованием шаблона json и сохраняет его в сессии Gatling. После этого вы можете использовать его для подписи запросов. Пример объемный, его лучше посмотреть в README.
Random method. Содержит множество различных методов для генерации рандомных значений.
Assertion. Загружает конфигурации assertion из файлов YAML. Пример файла nfr.yml:
nfr: - key: '99 перцентиль времени выполнения' value: GET /: '500' MyGroup / MyRequest: '900' request_1: '700' all: '1000' - key: 'Процент ошибок' value: all: '5' - key: 'Максимальное время выполнения' value: GET /: '1000' all: '2000'
Пример использования:
import ru.tinkoff.gatling.assertions.AssertionsBuilder.assertionFromYaml class test extends Simulation { setUp( scn.inject( atOnceUsers(10) ).protocols(httpProtocol) ).maxDuration(10) // Подключили конфигурации assertion из файла .assertions(assertionFromYaml("src/test/resources/nfr.yml")) }
Transactions. Позволяет оборачивать запросы для замера времени, но учитывает паузы в сценарии. Используется на проектах с JDBC и проектах с транзакционной нагрузкой.
import io.gatling.core.Predef._ import io.gatling.core.structure.ScenarioBuilder // Добавляем импорт import ru.tinkoff.gatling.transactions.Predef._ object DebugScenario { val scn: ScenarioBuilder = scenario("Debug") .exec(Actions.createEntity()) // Начало транзакции .startTransaction("transaction1") .doWhile(_ ("i").as[Int] < 10)( feed(feeder) .exec(Actions.insertTest()) .pause(2) .exec(Actions.selectTest) ) // Конец транзакции .endTransaction("transaction1") .exec(Actions.batchTest) .exec(Actions.selectAfterBatch) } // Наследуемся от SimulationWithTransactions class DebugTest extends SimulationWithTransactions { setUp( DebugScenario.scn.inject(atOnceUsers(1)) ).protocols(dataBase) }
Заключение
В этой статье мы познакомились с нашим шаблоном для создания проектов, рассмотрели базовые возможности Gatling и изучили новые функции нашей библиотеки Picatinny. Мы не упомянули результаты тестирования в Gatling, но о них можно узнать из статьи об анализе результатов нагрузочного тестирования.
В следующих статьях мы расскажем, как Gatling и другие инструменты помогают создавать скрипты для различных протоколов, таких как HTTP, gRPC, JDBC, AMQP и Kafka.
Полезные ссылки
-
Подробнее о сессии в Gatling.
-
Подробнее о составлении http запросов в Gatling.
-
Подробнее о фидерах в Gatling.
ссылка на оригинал статьи https://habr.com/ru/company/tinkoff/blog/655341/
Добавить комментарий