Использование Gatling. Введение

от автора

Привет! Мы — команда тестирования производительности в Тинькофф, и мы любим инструмент 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.

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

  1. project — каталог содержит информацию о зависимостях, плагинах и версии sbt проекта;

  2. src — основной каталог. Содержит скрипты и конфигурации;
    2.1. resources — содержит конфигурацию Gatling, настройки симуляции и логирования;
    2.2. cases — каталог для запросов симуляции;
    2.3. scenarios — каталог для сценариев симуляции;
    2.4. test.scala — конфигурация протокола, который используется для тестирования системы;
    2.5. Debug.scala, MaxPerformance.scala, Stability.scala — готовые симуляции для отладки, теста на поиск максимальной производительности и теста стабильности соответственно.

  3. 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, который состоит из различных сценариев и инжекторов (способов моделирования нагрузки) для этих сценариев. Симуляцию можно разделить на следующие части:

  1. Конфигурация протокола (в примере — HTTP).

  2. Определение запросов.

  3. Определение сценария.

  4. Вызов основного метода симуляции 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 есть три типа предустановленных способов подачи нагрузки:

  1. Debug. Используется для отладки и проверки работоспособности логики самих скриптов.

class Debug extends Simulation {    setUp(     // Сценарий запустится на одном пользователе.      // Фактически мы получим одну итерацию сценария CommonScenario()     CommonScenario().inject(atOnceUsers(1)),   ).protocols(     httpProtocol,   ).maxDuration(testDuration)  }
  1. 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) }
  1. 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:

Разберем, что происходит в этом коде:

  1. Мы пишем http-запрос с некоторым параметром, значение которого мы хотим параметризовать. Для этого мы используем специальную конструкцию #{value}.

  2. Создаем файловый фидер.

  3. Файл содержит заголовок и значения. Заголовок должен называться так же, как и параметр.

  4. Добавляем фидер в сценарий. Теперь вместо #{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.

Полезные ссылки

  1. Основные концепции в Gatling.

  2. Подробнее о сессии в Gatling.

  3. Подробнее о составлении http запросов в Gatling.

  4. Подробнее о фидерах в Gatling.


ссылка на оригинал статьи https://habr.com/ru/company/tinkoff/blog/655341/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *