Анализ сервисов HTTP с помощью wrk в Ubuntu 14.04

Данный мануал расскажет, как использовать открытый инструмент анализа wrk, который измеряет задержку HTTP-сервисов при высоких нагрузках.

Задержка – это временный интервал между моментом запроса и моментом получения ответа от сервиса. wrk можно использовать для симулирования задержки, которая происходит при посещении сайта с помощью браузера или любого другого метода, который отправляет HTTP-запросы.

Инструмент wrk полезен для тестирования любого веб-сайта или приложения, использующего HTTP, например:

  • Rails и других приложений Ruby
  • Express и других приложений JavaScript
  • PHP-приложений
  • Статических веб-сайтов, запущенных на веб-серверах
  • Сайтов и приложений на балансировщиках нагрузки типа Nginx
  • Уровня кэширования.

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

Инструмент с открытым исходным кодом wrk можно найти на GitHub.

Он очень стабилен и позволяет имитировать высокие нагрузки благодаря многопоточности. Главной особенностью wrk является его способность интегрировать сценарии Lua, что добавляет много возможностей:

  • Анализ запросов с файлами cookie.
  • Пользовательские отчеты
  • Анализ нескольких URL-адресов (чего не может популярный ab, инструмент тестирования Apache).

Требования

  • Приложение Express на Node.js.
  • Два сервера в одном регионе: один для wrk, который генерирует нагрузку, а второй – для приложения. Если попробовать поместить такую среду на один сервер, процессы будут соревноваться за ресурсы, и вы не получите точного результата.

Первый сервер должен иметь много ресурсов, чтобы обработать сбои системы, но в данном случае приложение настолько простое, что можно использовать обе машины одного размера.

  • Первый сервер будет называться wrk1, а второй сервер (для приложения) – app1.
  • Вам понадобится 2 Гб памяти.
  • На серверах установлена система Ubuntu 14.04.
  • Серверы настроены согласно этому руководству.
  • Включите частную сеть.

Меньшие серверы тоже подойдут, но в результатах теста следует ожидать большей задержки. В реальной среде тестирования сервер app1 должен быть того же размера, что вы собираетесь использовать в производстве.

1: Установка Docker

Примечание: Этот раздел нужно выполнить на обоих серверах.

Чтобы упростить работу, можно использовать Docker. Найдите приложение среди контейнеров. Это позволяет пропустить настройку среды Node.js, модулей npm и пакетов deb; все, что нужно сделать – загрузить и запустить соответствующий контейнер. Сэкономленное время можно потратить на изучение wrk.

Читайте также: Экосистема Docker: базовые компоненты

Чтобы установить Docker, обновите индекс пакетов:

sudo apt-get update

Установите инструменты wget и curl:

sudo apt-get install -y wget curl

А затем загрузите и установите Docker:

sudo wget -qO- https://get.docker.com/ | sh

Добавьте своего пользователя в группу docker, чтобы он мог выполнять команды Docker без sudo:

sudo gpasswd -a ${USER} docker
sudo service docker restart
newgrp docker

Примечание: Если вы используете другой дистрибутив Linux, Docker предлагает документацию по установке, где вы найдете полезную информацию.

Чтобы убедиться, что Docker установлен правильно, используйте следующую команду:

docker --version

Команда должна вернуть версию:

Docker version 1.7.1, build 786b29d

2: Подготовка тестового приложения

Примечание: Данный раздел нужно выполнить на сервере app1.

Для тестирования вы можете использовать образ приложения Docker в общедоступном реестре Docker. Он содержит простое приложение, написанное в Node.js, с небольшой производительностью, отлично подходящее для тестирования и отладки. Здесь вы можете проверить исходный код.

Конечно, лучше, если вы можете протестировать собственное приложение.

Прежде чем запустить приложение, нужно присвоить внутренний IP-адрес сервера переменной APP1_PRIVATE_IP:

export APP1_PRIVATE_IP=$(sudo ifconfig eth1 | egrep -o "inet addr:[^ ]*" | awk -F ":" '{print $2}')

Просмотрите внутренний IP-адрес:

echo $APP1_PRIVATE_IP
10.135.232.163

Примечание: Ваш внутренний IP-адрес будет отличаться.

Теперь запустите приложение:

docker run -d -p $APP1_PRIVATE_IP:3000:3000 --name=http-debugging-application czerasz/http-debugger

Приведенная выше команда сначала загрузит требуемый образ Docker, а затем запустит контейнер. Контейнер запускается в фоновом режиме. Опция -p $APP1_PRIVATE_IP:3000:3000 проксирует весь трафик в локальный контейнер и из него на порт 3000, а также от внутреннего IP-адреса хоста на порт 3000.

Запустите curl, чтобы убедиться, что приложение работает:

curl -i -XPOST http://$APP1_PRIVATE_IP:3000/test -d 'test=true'

Вы должны увидеть:

HTTP/1.1 200 OK
X-Powered-By: Express
X-Debug: true
Content-Type: text/html; charset=utf-8
Content-Length: 2
ETag: W/"2-79dcdd47"
Date: Wed, 13 May 2015 16:25:37 GMT
Connection: keep-alive
ok

Приложение очень простое и возвращает только сообщение ok. Поэтому каждый раз, когда инструмент wrk запрашивает это приложение, он получает только ok.

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

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

docker logs -f --tail=20 http-debugging-application
[2015-05-13 16:25:37] Request 1
POST/1.1 /test on :::3000
Headers:
- user-agent: curl/7.38.0
- host: 0.0.0.0:32769
- accept: */*
- content-length: 9
- content-type: application/x-www-form-urlencoded
No cookies
Body:
test=true

Эту команду можно не останавливать на протяжении всего тестирования.

Примечание: Чтобы остановить команду, нажмите CTRL-C.

3: Установка wrk

Примечание: Этот раздел нужно выполнить на сервере wrk1.

Теперь загрузите образ williamyeh/wrk из реестра Docker:

docker pull williamyeh/wrk

Эта команда загрузит образ Docker для wrk. Вам не придется самостоятельно собирать wrk или устанавливать дополнительные пакеты. Чтобы запустить wrk в контейнере, нужно только запустить контейнер на основе образа.

Загрузка займет всего несколько секунд, потому что образ очень маленький.

Примечание: Если вы хотите установить wrk на свой любимый дистрибутив Linux, посетите эту страницу вики и следуйте инструкциям.

Также нужно установить переменную APP1_PRIVATE_IP на этом сервере и указать внутренний IP-адрес сервера app1.

Экспортируйте переменную:

export APP1_PRIVATE_IP=10.135.232.163

Примечание: Не забудьте заменить условный IP-адрес IP-адресом своего сервера.

Переменная будет действительна только в течение данной сессии. В следующей сессии ее нужно повторно экспортировать.

4: Запуск теста wrk

Примечание: Данный раздел выполняется на сервере wrk1.

Просмотрите доступные опции wrk. Запуск контейнера wrk с флагом -version выведет краткий обзор его опций:

docker run --rm williamyeh/wrk --version
wrk 4.0.0 [epoll] Copyright (C) 2012 Will Glozer
Usage: wrk <options> <url>
-c, --connections <N>  Connections to keep open
-d, --duration    <T>  Duration of test
-t, --threads     <N>  Number of threads to use
-s, --script      <S>  Load Lua script file
-H, --header      <H>  Add header to request
--latency          Print latency statistics
--timeout     <T>  Socket/request timeout
-v, --version          Print version details
Numeric arguments may include a SI unit (1k, 1M, 1G)
Time arguments may include a time unit (2s, 2m, 2h)

Теперь составьте команду для запуска теста. Обратите внимание, что эта команда ничего не сделает, поскольку она запускается только внутри контейнера.

Вот простейшая команда wrk:

wrk -t2 -c5 -d5s -H 'Host: example.com' --timeout 2s http://$APP1_PRIVATE_IP:3000/

Она состоит из таких опций:

  • -t2: количество потоков (в данном случае 2).
  • -c5: количество соединений (в данном случае 6 – поскольку отсчет начинается с 0).
  • -d5s: запуск теста на 5 секунд.
  • -H ‘Host: example.com’: передает заголовок Host.
  • —timeout 2s: определяет тайм-аут (2 секунды).
  • http://$APP1_PRIVATE_IP:3000/: адрес и порт, который прослушивает целевое приложение (в данном случае $APP1_PRIVATE_IP:3000).

Иначе говоря, wrk создаст шесть пользователей, которые повторно запрашивают домашнюю страницу в течение пяти секунд.

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

Для тестирования нужно запустить эту команду:

docker run --rm williamyeh/wrk -t2 -c5 -d5s -H 'Host: example.com' --timeout 2s http://$APP1_PRIVATE_IP:3000/

Подождите пару секунд, чтобы тест завершился, и проверьте его результаты.

5: Результаты тестирования

Команда, приведенная выше, вернет такой вывод:

Running 5s test @ http://10.135.232.163:3000
2 threads and 5 connections
Thread Stats   Avg      Stdev     Max   +/- Stdev
Latency     3.82ms    2.64ms  26.68ms   85.81%
Req/Sec   550.90    202.40     0.98k    68.00%
5494 requests in 5.01s, 1.05MB read
Requests/sec:   1096.54
Transfer/sec:    215.24KB

Она провела пятисекундный тест для http://10.135.232.163:3000.

Строка 2 threads and 5 connections означает, что в тесте использовалось 2 потока и 5 соединений.

Thread Stats   Avg      Stdev     Max   +/- Stdev
Latency     3.82ms    2.64ms  26.68ms   85.81%
Req/Sec   550.90    202.40     0.98k    68.00%

Эта часть показывает, какие параметры имеет функция Гаусса.

Этот раздел отражает статистику о количестве запросов, переданных данных и пропускной способности:

5494 requests in 5.01s, 1.05MB read
Requests/sec:   1096.54
Transfer/sec:    215.24KB

Здесь мы видим, что в течение 5,01 секунд wrk может выполнить 5494 запросов и передать 1.05 МБ данных. путем нехитрых подсчетов получается результат 1096,54 запросов в секунду.

Как правило, чем больше клиентов вы устанавливаете, тем меньше запросов в секунду вы должны получить. Задержка также будет расти. Это связано с тем, что приложение будет находиться под более тяжелой нагрузкой.

Какой результат считается хорошим?

Ваша цель — максимально сократить количество запросов/сек и уменьшить задержку.

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

Теперь нужно понять: является ли 550.90 запросов/сек с задержкой в 3,82 мс хорошим результатом? К сожалению, простого ответа на этот вопрос нет. На это влияет много факторов:

  • Количество клиентов.
  • Серверные ресурсы.
  • Количество машин, обслуживающих приложение.
  • Тип сервиса (кэш, который обслуживает статические файлы или сервер, который обслуживает динамический контент).
  • Тип базы данных, размер кластера БД, тип подключения к БД.
  • Тип запроса и ответа (например, небольшой запрос AJAX или объемный API-вызов).
  • И многие другие.

6: Оптимизация задержки

Если вы не довольны производительностью приложения, вы можете:

  • Отладить сервис (проверьте свой код и посмотрите, что можно оптимизировать).
  • Проверить базу данных и убедиться, что в ней нет узких мест.
  • Масштабировать сервис по вертикали (добавьте ресурсы на свой компьютер).
  • Масштабировать сервис по горизонтали (для этого добавьте еще один экземпляр сервиса и настройте балансировщик нагрузки).
  • Добавить кэширование.

Читайте также: 5 способов оптимизировать настройку сервера производства

Обязательно протестируйте приложение снова после внесения всех изменений.

7: Использование сценариев Lua

Поскольку wrk имеет встроенный LuaJIT (компилятор Just-In-Time для Lua), его можно расширить с помощью сценариев Lua. Как упоминалось во введении, это добавляет много новых функций для wrk.

Использовать сценарии Lua с инструментом wrk очень легко. Просто добавьте путь к файлу после флага -s.

Поскольку wrk работает внутри Docker, сначала нужно поделиться этим файлом с контейнером. Для этого существует опция -v.

Чтобы использовать тестовый сценарий test.lua, нужно ввести такую команду:

docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c1 -t1 -d5s -s /scripts/test.lua http://$APP1_PRIVATE_IP:3000

Команду wrk и ее параметры мы разобрали ранее. Эта команда содержит только одно дополнение – путь к сценарию и пару команд, чтобы сообщить Docker, как найти его за пределами контейнера.

Флаг —rm автоматически удалит контейнер после его остановки.

Научиться писать сценарии Lua совсем не сложно. Здесь мы рассмотрим простой пример, на основе которого вы самостоятельно сможете писать более сложные сценарии.

Работа wrk состоит из таких этапов:

  • Разрешение IP-адреса.
  • Установка потоков.
  • Стрессовое тестирование.
  • Завершение работы.

При использовании нескольких потоков второй и третий этап дублируются.

Кроме того, третий этап может состоять из трех фаз: инициации, запроса и ответа.

Согласно документации в сценариях Lua можно использовать следующие методы:

  • setup(поток): выполняется, когда все потоки инициализированы, но еще не запущены. Используется для передачи данных в потоки.
  • init(аргумент): вызывается при инициализации каждого потока. Эта функция получает дополнительные аргументы командной строки для сценария, которые отделяются от аргументов wrk с помощью —. Например:

wrk -c3 -d1s -t2 -s /scripts/debug.lua http://$APP1_PRIVATE_IP:3000 -- debug true

  • request(): возвращает объект HTTP для каждого запроса. Эта функция может изменить метод, заголовки, путь и тело. Используйте вспомогательную функцию wrk.format для формирования объекта запроса. Например:

return wrk.format(method, path, headers, body)

  • response(статус, заголовок, тело): вызывается, когда возвращается ответ.
  • done(сводка, задержка, запросы): выполняется, когда все запросы обработаны и вычисляется статистика. Внутри этой функции доступны следующие свойства:
Свойство Описание
summary.duration продолжительность прогона в микросекундах
summary.requests общее количество выполненных запросов
summary.bytes общее количество полученных байтов
summary.errors.connect ошибки подключения
summary.errors.read ошибки чтения сокета
summary.errors.write ошибки записи сокета
summary.errors.status общие коды состояния HTTP> 399
summary.errors.timeout общий тайм-аут запроса
latency.min минимальная задержка во время теста
latency.max максимальная задержка во время теста
latency.mean средняя задержка во время теста
latency.stdev стандартное отклонение
latency:percentile(99.0) Значение 99-го процентиля
latency[i] необработанные данные запроса i

Каждый поток имеет свой собственный контекст Lua и в нем индивидуальные локальные переменные.

Теперь нужно рассмотреть несколько практических примеров. Много полезных сценариев для тестирования можно найти в каталоге scripts проекта wrk.

Пример: POST запрос

Начнем с простейшего примера – симуляции запроса POST.

Запросы POST обычно используются для отправки данных на сервер. С его помощью можно проанализировать:

  • HTML-обработчики форм: используйте адрес, который находится в атрибуте action HTML-формы:

<form action="/login.php">
...
</form>

  • Конечные точки API: если у вас есть API, используйте конечную точку, в которой вы создаете свою запись:

POST /articles

Создайте файл scripts/post.lua на сервере wrk1.

cd ~
mkdir scripts
nano scripts/post.lua

Добавьте в файл следующие строки:

wrk.method = "POST"
wrk.body   = "login=8host&password=test"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

Этот сценарий очень прост и даже не использует вышеупомянутые методы. Он просто изменяет глобальные свойства объекта wrk.

Метод запроса изменен на POST, также появились дополнительные параметры входа и заголовок Content-Type для MIME-типа HTML-формы.

Теперь запустите тест с помощью следующей команды (на сервере wrk1):

docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c1 -t1 -d5s -s /scripts/post.lua http://$APP1_PRIVATE_IP:3000
Running 5s test @ http://10.135.232.163:3000
1 threads and 1 connections
Thread Stats   Avg      Stdev     Max   +/- Stdev
Latency     1.04ms  718.38us  12.28ms   90.99%
Req/Sec     1.02k   271.31     1.52k    66.00%
5058 requests in 5.00s, 0.97MB read
Requests/sec:   1011.50
Transfer/sec:    198.55KB

Обратите внимание, что здесь сравнивается только одно соединение. Это соответствует ситуации, когда только один пользователь постоянно входит в систему и вводит имя пользователя и пароль. При этом не требуются какие-либо файлы CSS, изображения или JavaScript.

Для более реалистичного сценария необходимо увеличить количество клиентов и потоков, одновременно отслеживая параметр задержки, чтобы узнать, как быстро приложение может проверять учетные данные пользователя.

Пример: несколько путей

Еще одной распространенной проблемой является одновременное тестирование нескольких путей приложения.

Создайте файл paths.txt в каталоге data и добавьте все пути, которые вы хотите использовать во время теста.

cd ~
mkdir data
nano data/paths.txt

Вот простейший пример data/paths.txt:

/feed.xml
/contact/
/about/
/blog/
/2015/04/21/nginx-maintenance-mode/
/2015/01/06/vagrant-workflows/
/2014/12/10/top-vagrant-plugins/

Затем загрузите простой сценарий и сохраните его как scripts/multiple-url-paths.lua:

-- Load URL paths from the file
function load_url_paths_from_file(file)
lines = {}
-- Check if the file exists
-- Resource: http://stackoverflow.com/a/4991602/325852
local f=io.open(file,"r")
if f~=nil then
io.close(f)
else
-- Return the empty array
return lines
end
-- If the file exists loop through all its lines
-- and add them into the lines array
for line in io.lines(file) do
if not (line == '') then
lines[#lines + 1] = line
end
end
return lines
end
-- Load URL paths from file
paths = load_url_paths_from_file("/data/paths.txt")
print("multiplepaths: Found " .. #paths .. " paths")
-- Initialize the paths array iterator
counter = 0
request = function()
-- Get the next paths array element
url_path = paths[counter]
counter = counter + 1
-- If the counter is longer than the paths array length then reset it
if counter > #paths then
counter = 0
end
-- Return the request object with the current URL path
return wrk.format(nil, url_path)
end

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

Сценарий multiple-url-paths.lua открывает файл /data/paths.txt, и если этот файл содержит пути, они сохраняются во внутреннем массиве путей. С каждым запросом берется следующий путь.

Чтобы запустить тест, используйте следующую команду на сервере wrk1:

docker run --rm \
-v `pwd`/scripts:/scripts \
-v `pwd`/data:/data \
williamyeh/wrk -c1 -t1 -d5s -s /scripts/multiple-url-paths.lua http://$APP1_PRIVATE_IP:3000

Команда вернет:

multiplepaths: Found 7 paths
multiplepaths: Found 7 paths
Running 5s test @ http://10.135.232.163:3000
1 threads and 1 connections
Thread Stats   Avg      Stdev     Max   +/- Stdev
Latency     0.92ms  466.59us   4.85ms   86.25%
Req/Sec     1.10k   204.08     1.45k    62.00%
5458 requests in 5.00s, 1.05MB read
Requests/sec:   1091.11
Transfer/sec:    214.17KB

Расширенные запросы JSON и YAML

Другие инструменты анализа могут также выполнять эти типы тестов. Кроме того, wrk также имеет возможность обрабатывать расширенные HTTP-запросы с использованием форматирования JSON или YAML.

Например, вы можете загрузить JSON или YAML-файл, который подробно описывает каждый запрос.

Пример запроса JSON можно найти в этом блоге.

Вы можете протестировать любой HTTP-запрос с помощью wrk и Lua.

Tags: , , ,