Locust — быстрый гайд

Кратко

Locust — простой масштабируемый инструмент тестирования производительности сайтов. Вы описываете поведение пользователей обычным Python-кодом, что делает Locust расширяемым и удобным.

Идея заключается в том, что большое количество эмулированных пользователей («саранча», отсюда название) отправляет запросы на тестируемый сайт. Поведение каждого пользователя описывается вами, процесс отслеживается в веб-интерфейсе в реальном времени. Это позволяет провести “боевое” тестирование и выявить узкие места в коде, “прежде чем пускать реальных пользователей”.

Тестовый сервер

Чтобы запустить Locust, нужен «веб-сервер», производительность которого мы хотим проверить. В наш контейнер «locust» мы уже включили простой веб-сервер, который вы можете использовать в этом гайде. Чтобы запустить этот сервер, выполните в контейнере следующую команду:

$ simple-server

Должно появиться сообщение о том, что сервер запущен на порту 3000.

К сожалению, по умолчанию сервер работает в фоновом режиме, поэтому вам придется сначала завершить его, если вы хотите выполнить команду (например, ls) в контейнере, и в этот момент сервер уже будет недоступен.

У этой ситуации есть два возможных решения:

  1. Запустить сервер в фоновом режиме с помощью следующей команды:

$ simple-server > /dev/null &

Команда > /dev/null глушит вывод и & выполняет команду в дочернем процессе, отделенном от текущей оболочки, так что вам не придется ждать завершения процесса. Теперь вы сможете выполнить команду.

  1. Запустить другую оболочку (либо с помощью tmux, либо командой docker exec -it locust /bin/bash) и выполните следующую команду в новой оболочке. 

Используя один из двух вышеперечисленных методов, убедитесь, что ваш сервер работает в контейнере и у вас по-прежнему есть интерактивная оболочка, через которую вы можете выполнять команды внутри контейнера. 

Простой тест

Теперь, когда наш тестовый сервер запущен, давайте запустим на нем наш первый тест производительности. Мы создали простой «файл Locust», locustfile.py (ссылка). Загрузите этот файл и запустите сервер Locust Web UI на основе загруженного файла:

$ locust -f ./locustfile.py --host=http://localhost:3000

Вышеприведенная команда указывает “locust file” (-f), который определяет «поведение пользователя», например, сколько запросов выдается через какие промежутки времени и т. д. Также указывается адрес веб-сайта, который мы тестируем (—host), в нашем примере это порт 3000 на http://localhost

После выполнения приведенной выше команды вы, скорее всего, увидите следующий результат, который означает, что Locust Web UI запущен:

[2021-04-21 20:30:06,201] 46d3e53a99d9/INFO/locust.main: Starting web interface at http://0.0.0.0:8089 (accepting connections from all network interfaces)
[2021-04-21 20:30:06,210] 46d3e53a99d9/INFO/locust.main: Starting Locust 1.4.3

Теперь откройте браузер на хост-машине и перейдите по адресу http://localhost:8089. Вы увидите следующий пользовательский интерфейс Locust с предложением «Start new Locust swarm” (Запустить новый рой):

Locust start

Введите количество пользователей, которое хотите эмулировать, например 200, и укажите скорость эмуляции (“порождения”, или “спауна” в терминологии Locust), например 100. Здесь «количество пользователей для эмуляции» означает, сколько одновременных пользователей Locust должен симулировать. “Скорость порождения» означает скорость, с которой эти пользователи создаются вначале, пока не будет создано указанное количество одновременных пользователей. 

(Из-за ограничений производительности процессора вашей машины реальная скорость может быть меньше указанного вами числа).

Теперь нажмите кнопку «Start» и посмотрите, что произойдет. В пользовательском интерфейсе будет отображаться статистика в реальном времени о том, как сервер отвечает на запросы, отправленные Locust:

Locust статистика

Термины

Более подробное объяснение того, что означает информация на этой странице:

  • Host — Адрес сайта, на котором мы тестируем. (http://localhost:3000 в нашем случае)
  • Status — Статус показывает «SPAWNING» в начале, когда “порождаются” новые пользователи, но как только заданное количество пользователей будет порождено, статус изменится на «RUNNING» и покажет количество одновременных пользователей, 200 в нашем примере.
  • RPS — Количество запросов в секунду. Показывает, сколько HTTP-запросов в данный момент отправляется на сервер. 
  • Failures — Процент запросов, которые не выполняются из-за ошибки соединения, таймаута, не найденной страницы, плохого запроса или по другой подобной причине. Абсолютное число и причина отказов записываются на вкладке «Failures». 
  • Stop — Остановить текущий тест. 
  • Reset Stats —  Если вы нажмете эту кнопку, вся статистика будет обнулена. 
  • Median, 90 percentile, Average, Min, Max — Эти столбцы показывают время ответа сервера, интервал между отправкой запроса и получением ответа от сервера. 
  • Average size — Средний размер ответа от сервера 
  • Current RPS, Current Failures/s — Эти столбцы показывают, сколько запросов отправляется на сервер в данный момент, и сколько отказов он получает.

Запуская Locust с разным количеством пользователей и просматривая статистику, вы можете увидеть, насколько хорошо работает сервер при различных условиях нагрузки. Измените количество пользователей, например до 500, и повторно запустите задачи, чтобы оценить, как изменилась производительность. Поиграйте с интерфейсом.

Составление Locust-файла

Теперь, когда вы знакомы с возможностями Locust, давайте узнаем, как написать locust-файл, описывающий набор задач, которые необходимо выполнить для тестирования производительности. Прежде чем продолжить, взгляните на наш пример файла locust, locustfile.py (здесь ссылка), чтобы получить представление как он выглядит:

import sys, time, random
from locust import HttpUser, task, between

class MyUser(HttpUser):
    wait_time = between(1, 2.5)

    @task
    def list(self):
        self.client.get('/api/posts?username=user2')
        self.client.get('/editor/post?action=list&username=user2')

    @task(2)
    def preview(self):
        # generate a random postid between 1 and 100
        postid = random.randint(1, 100)
        self.client.get("/blog/user2/" + str(postid), name="/blog/user2/")

    def on_start(self):
        """on_start is called when a Locust start before any task is scheduled"""
        res = self.client.post("/login", data={"username":"user2", "password": "blogserver"})
        if res.status_code != 200:
            print("Failed to authenticate the user2 user on the server")
            sys.exit();

Примечание: К счастью, для пользования Locust не нужно знать многих тонкостей Python, вполне достаточно полистать учебник

Разбираем код

import sys, time, random
from locust import HttpUser, task, between

Файл locust — это обычный модуль Python, он может импортировать код из других файлов или пакетов.

class MyUser(HttpUser):

Здесь мы определяем класс для пользователей, которых мы будем эмулировать. Он наследуется от HttpUser, что дает каждому пользователю атрибут client, который является экземпляром HttpSession, который может быть использован для выполнения HTTP-запросов к целевой системе. Когда тест начнется, locust создаст экземпляр этого класса для каждого пользователя, которого он моделирует, и каждый из этих пользователей начнет работать в своем собственном «зеленом» потоке gevent.

wait_time = between(1, 2.5)

Наш класс определяет время ожидания (wait_time), которое заставит эмулируемых пользователей ждать от 1 до 2,5 секунд после выполнения каждой задачи (см. ниже). Более подробную информацию можно найти в атрибуте wait_time.

@task
def list(self):
    self.client.get('/api/posts?username=user2')
    self.client.get('/editor/post?action=list&username=user2')

Методы, декорированные @task, являются ядром вашего locust-файла. Для каждого запущенного пользователя Locust создает greenlet (микропоток), который будет вызывать эти методы.

@task
def list(self):
    ...

@task(2)
def preview(self):
    ...

Мы объявили две задачи, декорировав два метода с помощью @task, одному из которых был присвоен больший “вес” (2). Когда наш MyUser запустится, он выберет одну из объявленных задач — в данном случае list или preview — и выполнит ее. Задачи выбираются случайным образом, но вы можете придать им разный вес. В приведенной выше конфигурации Locust в два раза чаще выбирает preview, чем list. После завершения выполнения задачи пользователь будет “спать” в течение времени ожидания (в данном случае от 1 до 2,5 секунд). По истечении времени ожидания он выберет новую задачу и повторит все сначала. 

Обратите внимание, что выбираться будут только методы, декорированные @task, поэтому вы можете определять свои кастомные внутренние методы-хелперы как угодно.

self.client.get('/api/posts?username=user2')

Атрибут self.client позволяет выполнять HTTP-запросы, которые будут регистрироваться Locust. Информацию о том, как выполнять другие виды запросов, проверять ответ и т. д., смотрите в разделе документации Использование HTTP-клиента.

@task(3)
def preview(self):
    # generate a random postid between 1 and 100
    postid = random.randint(1, 100)
    self.client.get("/blog/user2/" + str(postid), name="/blog/user2/")

В задаче preview мы используем 100 различных URL, выбирая случайный postid между 1 и 100. Чтобы не получить 100 отдельных записей в статистике — поскольку статистика группируется по URL — мы используем параметр name, чтобы сгруппировать все эти запросы под записью с именем «/blog/user2/».

def on_start(self):
    """on_start is called when a Locust start before any task is scheduled"""
    res = self.client.post("/login", data={"username":"user2", "password": "blogserver"})
    if res.status_code != 200:
        print("Failed to authenticate the user2 user on the server")
        sys.exit();

Кроме того, мы объявили метод on_start. Метод с таким именем будет вызываться для каждого эмулируемого пользователя при запуске. Подробнее см. методы on_start и on_stop.

Запуск без веб-интерфейса 

Вы можете запустить locust без веб-интерфейса, используя флаг —headless вместе с -u и -r:

$ locust -f ./locustfile.py --host=http://localhost:3000 --headless -u 100 -r 100

Параметр —headless в команде указывает на то, что мы отключим веб-интерфейс и запустим его сразу в консоли. -u задает количество Users для порождения, а -r — скорость порождения (количество пользователей, запускаемых в секунду). Когда вы выполните приведенную выше команду, Locust начнет тест и будет выводить статистику каждые 2 секунды, пока вы не остановите его нажатием «Ctrl+C».

[2021-04-22 09:34:24,116] 46d3e53a99d9/INFO/locust.main: No run time limit set, use CTRL+C to interrupt.
[2021-04-22 09:34:24,116] 46d3e53a99d9/INFO/locust.main: Starting Locust 1.4.3
[2021-04-22 09:34:24,117] 46d3e53a99d9/INFO/locust.runners: Spawning 100 users at the rate 100 users/s (0 users already running)...

[2021-04-22 09:34:25,211] 46d3e53a99d9/INFO/locust.runners: All users spawned: MyUser: 100 (100 total running)
 Name                                       # reqs   # fails | Avg Min Max Median | req/s failures/s
-----------------------------------------------------------------------------------------------------
 GET /api/posts?username=user2                  85  0(0.00%) |   2   1  13      2 |  0.00    0.00
 GET /blog/user2/                               64  0(0.00%) |   2   1  13      2 |  0.00    0.00
 GET /editor/post?action=list&username=user2    85  0(0.00%) |   2   1  10      2 |  0.00    0.00
 POST /login                                   100  0(0.00%) |   8   1 104      2 |  0.00    0.00
-----------------------------------------------------------------------------------------------------
 Aggregated                                    334  0(0.00%) |   4   1 104      2 |  0.00    0.00
...

KeyboardInterrupt
2021-04-22T16:35:01Z
[2021-04-22 09:35:01,697] 46d3e53a99d9/INFO/locust.main: Running teardowns...
[2021-04-22 09:35:01,698] 46d3e53a99d9/INFO/locust.main: Shutting down (exit code 0), bye.
[2021-04-22 09:35:01,698] 46d3e53a99d9/INFO/locust.main: Cleaning up runner...
[2021-04-22 09:35:01,699] 46d3e53a99d9/INFO/locust.runners: Stopping 100 users
[2021-04-22 09:35:01,713] 46d3e53a99d9/INFO/locust.runners: 100 Users have been stopped, 0 still running
 Name                                       # reqs   # fails | Avg Min Max Median |  req/s failures/s
------------------------------------------------------------------------------------------------------
 GET /api/posts?username=user2                1669  0(0.00%) |   2   1  24      2 |  44.43    0.00
 GET /blog/user2/                              854  0(0.00%) |   2   1  13      2 |  22.73    0.00
 GET /editor/post?action=list&username=user2  1669  0(0.00%) |   2   1  27      2 |  44.43    0.00
 POST /login                                   100  0(0.00%) |   8   1 104      2 |   2.66    0.00
------------------------------------------------------------------------------------------------------
 Aggregated                                   4292  0(0.00%) |   2   1 104      2 | 114.26    0.00

Response time percentiles (approximated)
 Type  Name                                    50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs
-----|----------------------------------------|---|---|---|---|---|---|---|---|-----|------|----|------|
 GET   /api/posts?username=user2                 2   2   3   3   4   5   6   8    21     25   25   1669
 GET   /blog/user2/                              2   2   3   3   3   4   5   7    13     13   13    854
 GET   /editor/post?action=list&username=user2   2   2   2   3   3   4   5   7    21     27   27   1669
 POST  /login                                    2   3   3   4  18  64  98 100   100    100  100    100
-----|----------------------------------------|---|---|---|---|---|---|---|---|-----|------|----|------|
 None  Aggregated                                2   2   3   3   3   4   6   9    64    100  100   4292 

Давайте посмотрим на последнюю таблицу, которая описывает распределение времени ответа. В первой строке таблицы мы имеем 1669 запросов, отправленных по адресу /api/posts?username=user2 методом GET и получивших ответы, среди которых 50 % запросов отвечают за 2 мс, 90 % — за 4 мс, а 99 % — в пределах 8 мс. Допустим, если время отклика должно быть менее 10 мс, наш сайт обслуживает 99% пользователей в пределах этого лимита, в то время как 100 пользователей посещают сайт каждую секунду. 

В ваших результатах количество запросов для GET /api/posts?username=user2 отличается от количества запросов для GET /editor/post?action=list&username=user2. Это может произойти потому, что Locust считает только те запросы, на которые был получен ответ, иначе у этих запросов не было бы статистики времени ответа. Когда время истечет, Locust просто прекратит подсчет, выведет сводку и завершится, даже если некоторые из отправленных им запросов не были отвечены. А причина, по которой POST /login содержит ровно 100 запросов, заключается в том, что для каждого пользователя выполняется одна аутентификация.

Ограничение по времени, сброс статистики и “Только сводка”

Когда вы хотите запустить Locust с помощью скрипта, полезно остановить его, установив ограничение по времени, а не нажимая «Ctrl+C». Это можно сделать с помощью опции -t, как показано ниже:

$ locust -f ./locustfile.py --host=http://localhost:3000 --headless -u 100 -r 100 -t 30s

Приведенная выше команда остановит выполнение Locust через 30 секунд. В общем случае вы можете использовать формат времени XhYmZs (X часов, Y минут и Z секунд). 

Также полезно знать опцию —reset-stats, которая указывает Locust сбросить данные, когда STATUS меняется с «SPAWNING» на «RUNNING». Эта опция иногда полезна, потому что для того, чтобы как можно быстрее перейти на стадию «RUNNING», обычно нужно быстро увеличить количество пользователей на стадии «SPAWNING». Это часто приводит к неестественно высокой нагрузке на сервер и, возможно, к гораздо более высокому времени отклика и количеству ошибок, чем обычно. Сброс статистики после завершения начального этапа наращивания юзеров может предотвратить “загрязнение” текущей статистики. 

Иногда нам не нужны все подробности при выполнении теста, и сводной таблицы, выведенной в конце, достаточно, чтобы понимать ситуацию. Мы можем запустить Locust без веб-интерфейса и добавить only summary в файл summary.txt, выполнив следующую команду:

$ locust -f ./locustfile.py --host=http://localhost:3000 --headless --reset-stats -u 100 -r 100 -t 30s --only-summary |& tee summary.txt

Параметр —only-summary указывает Locust подавлять вывод статистики во время теста и печатать только резюме. |& — это UNIX-пайп для перенаправления STDERR и STDOUT процесса Locust на STDIN UNIX-команды tee. Команда tee выведет все сообщения в консоль, а также сохранит их в файле, указанном в качестве параметра. 

Прежде чем покинуть этот гайд, остановите наш простой сервер следующей командой, если вы запустили его в фоновом режиме:

$ pkill simple-server && pkill node

Этого гайда должно быть достаточно для знакомства с Locust. Но если хотите узнать больше, ознакомьтесь с официальной документацией.

University of California


Locust локуст туториал гайд руководство

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас