wrk - HTTP benchmarking tool

В данной статье разъясняются нюансы работы с wrk

Главное: wrk не делает указанное кол-во запросов, он создает указанную нагрузку в течении указанного количества времени! 

Для начала руководство:

-d, --duration: продолжительность испытания (время в течении которого wrk будет работать): 2s, 2m, 2h

-t, --threads: общее количество используемых потоков (один поток может делать несколько подключений)

-c, --connections: количеством одновременно выполняемых запросов (количество HTTP-соединений, которые нужно поддерживать открытыми в каждом потоке)

-s, --script: сценарий LuaJIT, см. раздел СЦЕНАРИИ

-H, --header: HTTP-заголовок для добавления в запрос, например "Агент пользователя: wrk"

--latency: печать подробной статистики задержки

--timeout: обрывать connection, если сервер не ответил за указанное тут количество секунд (на сервере будет 499)

Зачем указывать -t, если уже есть -c

  • -c (connections) — сколько одновременных HTTP-соединений будет установлено.
  • -t (threads) — сколько параллельных рабочих потоков будет обслуживать эти соединения.

Хорошее правило: число потоков -t должно быть равно количеству CPU-ядер на машине, где запускается wrk.

  • если слишком мало потоков — не загружаешь CPU полностью.
  • если слишком много — потоки начинают конкурировать и мешать друг другу (переключение контекста, overhead).

Например на обычном 4-ядерном процессоре разумно использовать -t4

Что произойдёт, если указать -t1 и -c100

Один поток будет обрабатывать все 100 соединений сам.

Это даст нагрузку, но недореализует потенциал CPU — поток просто не успевает обслуживать все соединения вовремя.

Как следствие — выше latency, меньше req/sec.

Практика

Get запрос - пример в котором мы делаем 2 одновременных запроса в течении 10 секунд:

docker run --rm williamyeh/wrk -t1 -c2 -d10s --timeout 5s https://yapro.ru/page?test=wrk1

Post запрос - пример в котором мы сделаем 3 конкурентных POST-запроса, для этого создаем файл post.lua

wrk.method = "POST"
wrk.body   = "{'hello':'world'}"
wrk.headers["Content-Type"] = "application/json"

local counter = 1

function response()
   if counter == 3 then
      wrk.thread:stop()
   end
   counter = counter + 1
end

Запускаем тест:

docker run --rm --net=host -v `pwd`:/data williamyeh/wrk -s post.lua -t1 -c1 -d2s http://my/api/page

Почему не было запросов

docker run --rm --net=host -v `pwd`:/data williamyeh/wrk -s post.lua -t1 -c3 -d1s http://my/api/page
Running 1s test @ http://my/api/page
  1 threads and 3 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.00us    0.00us   0.00us    -nan%
    Req/Sec     0.00      0.00     0.00      -nan%
  0 requests in 1.00s, 0.00B read
Requests/sec:      0.00
Transfer/sec:       0.00B

На самом деле запросы были, но:

  • 3 connections означает, что было выполнено 3 подключения за 1 секунду (см. -d1s)
  • 0 requests означает было выполнено 0 успешных запросов за 1 секунду (см. -d1s), успешные это с http status 200
  • внимание - на сервере образовалось 3 запроса 499, потому что wrk не стал дожидаться ответа и отключился через 1 сек:
nginx_1     | 172.18.0.1 - - [24/Aug/2021:10:22:20 +0000] "POST /api/page HTTP/1.1" 499 0 "-" "-"
nginx_1     | 172.18.0.1 - - [24/Aug/2021:10:22:20 +0000] "POST /api/page HTTP/1.1" 499 0 "-" "-"
nginx_1     | 172.18.0.1 - - [24/Aug/2021:10:22:20 +0000] "POST /api/page HTTP/1.1" 499 0 "-" "-"

php-fpm_1   | 172.18.0.4 -  24/Aug/2021:10:22:19 +0000 "POST /index.php" 200
php-fpm_1   | 172.18.0.4 -  24/Aug/2021:10:22:19 +0000 "POST /index.php" 200
php-fpm_1   | 172.18.0.4 -  24/Aug/2021:10:22:32 +0000 "POST /index.php" 200

Важно: таким образом, если веб-сервер не успевает обработать запросы за указанное время --timeout то мы неизбежно получим 499, а еще такие 499 мы всегда получаем в конце работы wrk ведь он просто обрывает соединения согласно (см. -d1s) .

Столбцы строки Latency

Avg - среднее время ответа сервера на один запрос (сумму времени всех запросов делят на количество запросов) 

Stdev (Standard deviation) - среднеквадратичное отклонение (см. хороший пример):

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

Max - максимальное время ответа

+/- Stdev - доля запросов, которые отклоняются от среднего в пределах одного стандартного отклонения.

Столбцы строки Req/Sec

Avg - за сколько в среднем обрабатывается запрос (это точно не среднее количество запросов в секунду! уверен!)

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

Max - не разобрался пока (это точно не максимальное количество запросов в секунду! уверен!)

+/- Stdev - процент времени, когда число запросов в секунду находилось в пределах одного стандартного отклонения.

Transfer/sec - пропускная способность, например: 396.8 КБайт в секунду.

Вывод

  1. Latency Avg: если приближается к 1+ секунде — пользователи уже ощущают "тормоза".
  2. Requests/sec: если перестает расти при увеличении соединений — сервер достиг предела.
  3. Ошибки (в stderr): timeouts, connection reset, read errors — признак перегрузки.
  4. Transfer/sec: может падать, если сервер перестает отдавать ответы полностью.

Скрипты

Приятно, что wrk поддерживает луа-скрипты, много примеров можно найти на странице гитхаба.

В помощь

Очень удобно отслеживать параллельность запросов с помощью log_format в nginx.

А еще кто-то научился писать полные ответы от сервера.

p.s. всякие мои тесты, не обращайте внимание:

ПараметрыКол-во запросов, которые делает wrk, если php-fpm обрабатывает запрос мгновенно

Кол-во запросов, которые делает wrk, если php-fpm обрабатывает запрос за 1 секунд.

Повезло / не повезло - означает, что могло быть иначе, причина в том, отключится ли wrk от nginx в течении секунды или нет.

Кол-во запросов, которые делает wrk, если php-fpm обрабатывает запрос за 8 секунд
-t1 -c1 -d2s31 успешный + 1 успешный (повезло)1 неуспешный
-t1 -c2 -d1s42 успешных (повезло)2 неуспешных
-t1 -c2 -d2s42 успешных + 2 успешных2 неуспешных
-t1 -c2 -d10s42 успешных + 2 успешных2 успешных + 2 неуспешных
-t1 -c3 -d1s53 неуспешных (не повезло)3 неуспешных
-t1 -c3 -d10s52 успешных + 2 успешных + 1 успешный3 успешных + 2 неуспешных
-t1 -c9 -d1s11

запустил 3 раза и получил по разному:

- одновременно: 3 успешных и 6 неуспешных
- одновременно: 1 успешных и 8 неуспешных
- одновременно: 5 успешных и 4 неуспешных

9 неуспешных
-t1 -c9 -d10s119 успешных (повезло) + 2 неуспешных9 (2 усп + 7 неусп) + 2 неуспешных
-t1 -c9 -d100s1111 успешных11 успешных

18.11.2011 14:48