TailSampler — паралельная отправка GET-запросов в Apache.JMeter
1. Назначение плагина «HTTP Request Tail»
Плагин упрощает загрузку встроенных ресурсов, позволяет параллельно выполнять указанные GET-запросы. Делая тест максимально близким к работе браузера по составу загружаемых ресурсов и по способу загрузки этих ресурсов.
TailSampler выручает если нужно:
- выполнить группу GET-запросов паралельно;
- выполнить 1000 GET-запросов, не создавая 1000 компонентов HTTP Request;
- протестировать сайт, активно использующий AJAX, Adobe Flash, Adobe AIR, SilverLigth, …
2. Инструкция по применению
HTTP Request Tail преобразует список ссылок в HTML-документ, загрузка встроенных ресурсов которого создаст GET-запрос по каждой из указанных ссылок.
[embedded content]
3. Работа стандартных html-парсеров JMeter
Стандартный способ получения HTML-документа по протоколу HTTP в JMeter — использование HTTP Request sampler. В HTTP Request есть простой способ запросить встроенные ресурсы страницы — галочка [v] Retrieve Embedded Resources. Стандартным парсером, формирующим ссылки на ресурсы страницы является LagartoBasedHtmlParser. Парсер можно поменять в настройке htmlparser.classname файла jmeter.properties.
Исследование парсеров Apache.JMeter для пяти популярных сайтов приведено в статье «Выбираем html-парсер для Apache.JMeter»:
- https://habrahabr.ru/post/308254/
При использовании парсеров загружаются почти все нужные ресурсы. Но для разных сайтов полнота загрузки разная. Так если веб-сайт реализован на Microsoft Silverlight, то эффективность работы парсера Apache.JMeter будет около 0%. Тогда как, используя, TailSampler, можно будет подать нагрузку, аналогичную работе браузера простым способом.
3.1. Пример работы со сложным сайтом atlas.mos.ru
Рисунок 2. Сайт atlas.mos.ru в браузере — 85 запросов к основному домену |
Рисунок 3. Сайт atlas.mos.ru в JMeter — 2 запроса к основному домену |
Рисунок 4. Трассировку выполнения запросов можно увидеть в webpagetest.org |
Рисунок 5. Браузер выполняет 85 запросов к основному домену, а JMeter только 2 — эффективность 2,35% , см. логи в google docs |
При открытии сайта atlas.mos.ru, активно использующего AJAX, видна разница:
-
2
GET-запроса, если работает LagartoBasedHtmlParser, HTTP Request, JMeter 3.0; -
85
GET-запросов, если работает браузер Chrome.
Таким образом, при использовании HTTP Request с настройкой [v] Retrieve Embedded Resources для адреса atlas.mos.ru
83
GET-запрос не будет отправлен.Маловато будет
«Падал прошлогодний снег»
Чтобы сэмулировать отправку
83
GET-запроса, нужно будет добавить 83
компонента HTTP Request в скрипт JMeter: - скрипт JMeter станет громоздким.
Добавленные 83
HTTP Request будут обрабатываться последовательно друг за другом — браузер же отправляет запросы на встроенные ресурсы параллельно:
- суммарная длительность загрузки ресурсов страницы будет больше, чем в браузере, последовательная загрузка выполняется дольше параллельной на незагруженном сервере;
- количество одновременно открытых соединений с сервером будет меньше, чем при работе браузера, количество соединений с сервером косвенно влияет на производительность.
Рисунок 6. Отчёт по загрузке сайта pflb.ru в Firefox — http://www.webpagetest.org/result/160319_RQ_Q3W/1/details/ — видны группы параллельной загрузки по 6
запросов
Таким образом, используя только HTTP Request не удастся полностью повторить загрузку встроенных ресурсов так, чтобы точно замерить время загрузки html-страницы со всеми подзапросами.
Рисунок 7. HTTP Request не позволяет увидеть картину целиком, реализовать хвост подзапросов поможет HTTP Request Tail
4. История создания
Плагин HTTP Request Tail для JMeter создан в качестве альтернативы Tile Server — сервису на python, описание смотри ниже.
Первое знакомство с сервисом Tile Server произошло, когда коллеги Женя Бороденков и Максим Конышев рассказывали про нагрузочное тестирование веб-проекта, загружающего большие изображения кусочками, тайлами. Тогда мы решали задачу нагрузочного тестирования одной из версий этого веб-проекта, использующей SilverLight на клиенте и потоковое получение содержимого от сервера по протоколу SOAP/MSBin1. Послать из JMeter запросы по протоколу SOAP/MSBin1 и обработать ответы на них мы сначала не знали как, обсуждали варианты.
Рассказ был примерно таким:
— Если бы тут был Вова, он бы сказал использовать промежуточный сервис для формирования нужных запросов.— Промежуточный сервис, это слишком сложно (отвечал им). Давайте напишем плагин для JMeter. Плагин — просто и надёжно.
— Вот когда было предыдущее тестирование, Андрей Пищулин написал сервис Tile Server на python, этим сервисом до сих пор пользуемся, сервис для работы с тайлами:
Давай делать также.
- JMeter отправляет серверу Tile Server список ссылок методом POST через HTTP Request;
- Tile Server реализует веб-сервер, принимает список ссылок, формирует из ссылок html-документ со списком iframe-ов, указывающих на ссылки и возвращает html-документ JMeter;
- JMeter парсит html-документ и выполняет нужные GET-запросы, как подзапросы.
— Хорошо, давай сделаем промежуточный сервис для реализации работы с SOAP/MSBin1.
Забегая вперёд скажу, сделали мы также, как когда-то. Сделали прокси-сервис на .NET, который формировал запросы по SOAP/MSBin1 — JMeter посылал команды этому сервису по SOAP/XML, а сервис посылал запросы к нагружаемому узлу уже по SOAP/MSBin1 и возвращал ответы к JMeter.
И при высокой нагрузке прокси-сервис стал узким местом, не смог он генерировать запросы и обрабатывать ответы так, чтобы нагружаемые серверы приуныли и прилегли. Нагрузку тестируемые серверы получили, но если они отвечали прокси-сервису за 10
секунд, прокси-сервис отвечал JMeter-у за 110
секунд. По статистике из логов JMeter выходило, что нагружаемый сервис приуныл, и да, нагрузка подавалась хорошая, но нагружаемый сервис отвечал бодро, бодрее, чем свидетельствовали логи JMeter. Оперативное добавление подробного логирования в прокси-сервис исправило ситуацию, но когда прокси-сервис зависал, то и логирование на нём запаздывало — надо было масштабировать промежуточный сервис или переписывать его полностью, один экземпляр не тянул на роль «Царь-пушки».
Рисунок 8. Царь-пушка
Прокси-сервис стал точкой отказа. Тогда вернулись к идее плагина для JMeter, и сделали из JSR223, библиотек JNA и прокси-клиента на .NET пулемёт для работы по протоколу SOAP/MSBin1, вышло здорово. Плагину уже не приходится обрабатывать несколько входящих потоков. Накладных расходов на оперативную память, конечно, больше, но работает это быстрее.
Тогда же возникла идея написать sampler JMeter на java, на замену Tile Server, вдруг и он является точкой отказа при нагрузке. Даже название будущего sampler-а появилось — «HTTP Request Tail» или «Tail Sampler». Из-за плохого знания английского языка услышал слово «Tile», как «Tail», немного не понял, причём тут «тайлы» и слово, которое переводится как «хвост». Глухие телефоны, хвост так хвост, образ русалки дополнил картину. Задумка закрепилась, идея ясна, название есть. Оставалось самое малое — сделать. Тут помогла Саша Перевозчикова Sanchez92 — эксперт по разработке плагинов.
Претензий к Tile Server не имею, коллеги говорят — это быстрый и надёжный инструмент. Плагин создавался из любопытства и интереса к новому для меня инструменту JMeter, и Сашу надо было чем-то занять, а то скучала девица.
Все имена в этой истории невымышленные, явно или косвенно плагин «HTTP Request Tail» сделали:
- Саша Перевозчикова;
- Андрей Пищулин;
- Вова Лаврентьев;
- Максим Конышев;
- Женя Бороденков;
- и я там был, чего-то пил.
5. Описание
5.1. Настройки по умолчанию
Рисунок 9. Настройки по умолчанию
По умолчанию используются настройки:
- [v] Retrieve All Embedded Resources — по умолчанию галочка поставлена, её можно снять, но тогда не будут выполняться подзапросы, и HTTP Request Tail станет бесполезным;
- [v] Use concurent pool — по умолчанию галочка поставлена, на большом количестве встроенных ресурсов многопоточная загрузка увеличивает скорость закачки;
- Use concerent pool Size:
4
— по умолчанию используется значение 4, это значение используется JMeter в качестве базового:- HttpClient4 при настройке Use concerent pool Size:
4
будет посылать до 4 запросов одновременно, каждый поток будет использовать по 1 постоянному соединению на каждый домен:- запустится группа потоков, размер группы определяется настройкой Use concerent pool Size;
- при настройке [v] Use keepalive каждый поток для каждого уникального домена будет создавать одно постоянное соединение (persistent-connection);
- Браузер Mozilla Firefox 44.0 по умолчанию посылает до
6
одновременных запросов на каждый домен (см.about:config
):-
256
— network.http.max-connections — максимальное число соединений; -
6
— network.http.max-persistent-connections-per-server — максимальное число постоянных соединений с сервером (keepalive); -
32
— network.http.max-persistent-connections-per-proxy — максимальное число постоянных соединений с прокси-сервером (keepalive);
-
- Если ориентироваться на настройки Mozilla Firefox 44.0, и то, что ссылки на встроенные ресурсы в проектах нагрузочного тестирования обычно принадлежат одному домену, то в Use concerent pool Size можно ставить значение
6
, вместо стандартного значения4
.
- HttpClient4 при настройке Use concerent pool Size:
Неиспользуемые настройки — настройки для POST-запросов, значения никак не используются ни главным запросом ни подзапросами:
- [ ] Use multipart/form-data for POST;
- [ ] Browser-compatible headers.
Главный запрос генерируется, а не отправляется, на него настройки для POST-запросов не действуют. Подзапросы используют метод GET, для них также не действуют настройки для POST-запросов.
Остальные настройки действуют на подзапросы.
HTTP Request Tail является наследником HTTP Request, описание настроек можно посмотреть в документации на HTTP Request:
- http://jmeter.apache.org/usermanual/component_reference.html#HTTP_Request — документация на HTTP Request.
5.2. Настроенный HTTP Request Tail
Рисунок 10. Настроенный HTTP Request Tail
Ссылки на встроенные ресурсы указываются в текстовом поле Embedded resources. Можно указывать относительные и абсолютные ссылки.5.2.1. Абсолютные ссылки
Описание формата абсолютных ссылок смотри в RFC: https://tools.ietf.org/html/rfc3986.
Абсолютные ссылки начинаются с протокола:
-
file://
-
http://
-
https://
Другие протоколы не обработаются HTTP Request Tail.
Примеры абсолютных ссылок:
-
file://C:\Data\htmlDocument.html#Part1
-
http://www.pflb.ru/
-
https://yandex.ru/?q=Testing&client=Mozilla
Относительные ссылки дополняются значениями полей:
- Path — каталог для тех ссылок, что являются относительными относительно страницы, а не относительно хоста, тут может быть указан и протокол и хост и порт.
- Web Server — хост и порт:
- Server Name or IP;
- Port Number;
- Protocol [http] — протокол, если в Path не указан протокол значение учитывается, допустимы значения
file
,http
,https
.
Пример относительных ссылок:
-
image1.png
-
/images/image1.png
-
/ResourceGenerator.aspx?id=0121
-
subFolder/style.css
-
subFolder/1/2/3/test.php
Для параметризации GET-запросов можно использовать переменные и функции JMeter. Пример:
-
${variable_URL}
-
/search.php?q=${variable_Query_String}
-
/search.php?q=${variable_Query_String}&client=Mozilla
-
/search.php?q=${__urlencode(${variable_Query_String})}&client=Mozilla
При формировании html-страницы из ссылок используется html-экранирование. Поэтому при написании ссылок можно использовать:
- кириллические домены;
- специальные символы, такие как
<
,>
,&
и другие; - любые unicode-символы.
Структура html-страницы не нарушится, ссылки обработаются корректно и в полном объёме.
Полный список html-сущностей для html4 смотри тут:
- http://www.w3.org/TR/html4/sgml/entities
Если какая-то сущность из спецификации не экранируется, то оформите замечание к плагину.
Не нужно предварительно экранировать специальные символы. Так если есть необходимость указать URL вида:
-
/search.php?q=Testing&client=Mozilla
— правильно.
То так и надо писать — просто
&
, заранее экранировать на &
не надо: -
/search.php?q=Testing&client=Mozilla
— неправильно.
Замечено, что если в адресе есть unicode-символ, например, ®, Ω, π, ≈:
-
/search.php?q=Microsoft®
И в настройке Implementation стоит значение
Java
, то в подзапросе unicode-символ будет заменен на квадратик: -
/search.php?q=Microsoft□
При запуске JMeter из Windows с помощью bat-файла кодировкой для java назначается
windows-1251
, предполагаю это причина замены unicode-символа на квадратик. Чтобы задать кодировку нужно указать в bat-файле аргумент для java: -Dfile.encoding=UTF-8
. При использовании HttpClient4
и HttpClient3.1
такой нежелательной трансформации не происходит.5.3. Генерируемый ответ
Рисунок 11. Ответ на основной запрос — генерируемый ответ
Ответ на основной запрос генерируется. Запроса нет, есть только тело ответа.
Тело ответа представляет собой html-документ, текст с кодировкой UTF-8, где для каждой ссылки на встроенный ресурс сгенерирован тег iframe
.
Пример документа:
Embedded resources
Рисунок 12. Статистика выполнения генерируемого запроса5.3.1. Планы на изменение
При написании статьи про парсеры JMeter в комментариях появился разработчик JMeter Philippe M. philmdot и попросил запостить дефект на счёт рекурсивной обработки:
- https://habrahabr.ru/post/308254/#comment_9767524
Планирую запостить дефект, и даже исправить его. Сделав так, чтобы для ответов на запросы из тегов
img
не выполнялся рекурсивный поиск ссылок на ресурсы. Чтобы в JMeter парсинг работал также, как это делает браузер. Вот если iframe
, то надо выполнять парсинг, а если просто img
, то выполняется единичный запрос.И тогда же надо будет изменить плагин TailSampler так, чтобы при генерации использовались теги img
вместо тегов iframe
.
Сегодня Philippe написал, что хотел бы увидеть этот код в ядре JMeter, что это будет хороший способ работы с AJAX. Попробую успеть в этом году, будет интересный опыт.
5.4. Известные эффекты
5.4.1. +1 запрос в логеКорневой запрос хоть и не отправляется, но попадает в лог:
- слабо, но завышает значение показателя Hits per second — хиты в секунду;
- коcвенно, влияет на общую статистику по логу JMeter;
- чтобы снизить влияние этого одиночного запроса на статистику — количество реальных подзапросов должно быть большим, десятки ссылок в поле Embedded resources.
Если стоит настройка Follow Redirect, то для каждого встроенного ресурса с кодом ответа
302
(Found): - будет выполняться перенаправление на страницу, указанную в значении Location заголовка ответа;
- перенаправления отображаются в JMeter как подзапросы — визуально выглядит, как каскад запросов.
Редкий, но возможный эффект — загрузка встроенных ресурсов для встроенного ресурса:
- с типом содержимого (смотри настройки htmlParser.Types и wmlParser.Types в jmeter.properties):
- text/html;
- application/xhtml+xml;
- application/xml;
- text/xml;
- text/vnd.wap.wml;
- выполняется:
- загрузка встроенных ресурсов для текущего встроенного ресурса — возможно, подзапросов будет больше, чем ссылок в поле Embedded resources.
В планах ограничить глубину рекурсии до настраиваемого из интерфейса плагина параметра в 3 уровня по умолчанию. Браузер при загрузке картинки (тег img) не выполняет для неё загрузку встроенных ресурсов. Тут же, все ресурсы обёрнуты в тег iframe, и Apache.JMeter в настоящий момент не различает, для какого тега осуществлялся запрос — всегда парсит ответ на предмет подзапросов, если Content Type ответа подходящий.
Рисунок 13. Виден первый запрос test_server.ru, который не выполнялся на самом деле (эффект »+1 запрос»). Виден каскад перенаправлений (эффект «Визуальный обман для ответа 302») и подзапросов для подзапросов (эффект «Рекурсия»).
5.5. Временные характеристики
Если снять галочку [ ] Retrieve All Embedded Resources или не указать ни одной ссылки в Embedded resources, то в логах будет написано, что запрос отправился мгновенно, и ответ на него пришел мгновенно.
Описание временных характеристик:
- Load Time — суммарная длительность загрузки встроенных ресурсов;
- Connect time всегда
0
; - Latency всегда
0
.
6. Проект на Github
- https://github.com/pflb/Jmeter.Plugin.TailSampler — ссылка на проект;
- https://github.com/pflb/Jmeter.Plugin.TailSampler/issues — оформить замечание к проекту.
7. Структура проекта
Исходный код в каталоге:
/src/ru/pflb/jmeter
- protocol/http/config/gui:
- TailUrlConfigGui.java — элемент управления с большим полем ввода для ссылок на встроенные ресурсы;
- samplers:
- wrapper — обёртки, чтобы использовать указанный на форме Implementation:
- WrapperHTTPFileImpl.java — обёртка, чтобы использовать обработчик протокола
file://
для подзапросов; - WrapperHTTPHC3Impl.java — обёртка, чтобы использовать
HttpClient3.1
из настройки Implementation для подзапросов; - WrapperHTTPHC4Impl.java — обёртка, чтобы использовать
HttpClient4
из настройки Implementation для подзапросов; - WrapperHTTPJavaImpl.java — обёртка, чтобы использовать
Java
из настройки Implementation для подзапросов; - WrapperHTTPSamplerFactory.java — фабрика, для создания обёрток, возвращает обработчик по значениям протокола и настройке Implementation;
- WrapperHTTPFileImpl.java — обёртка, чтобы использовать обработчик протокола
- EscapeUtils.java — реализация html-экранирования, позволяет работать с русскими доменами, юникодом и специальными символами в ссылках;
- ITailHTTPImpl.java — базовый интерфейс для всех обработчиков;
- TailHTTPHC4Impl.java — модифицированный HttpClient4, который использует указанное тело ответа не отправляя запрос;
- TailHTTPSamplerProxy.java — прокси-класс в котором реализована вся логика работы TailSampler:
- для первого запроса берёт список ссылок из поля Embedded resources и передаёт в TailHTTPHC4Impl — эмуляция получения страницы со списком iframe-ов в содержимом;
- для запросов на встроенные ресурсы создаётся и вызывается стандартный обработчик, указанный в настройке Implementation — реальная отправка запросов;
- TailHttpSamplerGui.java — визуальное представление TailSampler.
- wrapper — обёртки, чтобы использовать указанный на форме Implementation:
Другие каталоги вспомогательные, служат для удобства отладки проекта.
7.1. Форма
Форма TailUrlConfigGui является модификацией главной формы HTTP Request из Apache.JMeter 2.13, откуда удалены поля для редактирования тела запроса и задания списка параметров, но добавлено одно большое поле для ввода списка ссылок.
А внешний вид TailHttpSamplerGui также является копией HTTP Request, где теперь применяется новый главный элемент управления TailUrlConfigGui.
Тут не обошлось без копипасты. Пошел на этот шаг, чтобы HTTP Request Tail почти не отличался от HTTP Request.
Но в JMeter 3.0 внешний вид HTTP Request был изменён, элементы управления переставлены местами. Теперь HTTP Request Tail отличается HTTP Request для JMeter 3.0. Но это на работу не влияет.
7.2. Обёртки
Хотелось по максимуму использовать существующий код, но копипастой заниматься не хотелось. Поэтому был применён антипатерн «Паблик Морозов», благодаря которому методы sample, notifyFirstSampleAfterLoopRestart и threadFinished стали публичными.
7.3. Утилита для экранирования
Код класса, выполняющего экранирование взят с сайта ibm.com: www.ibm.com/developerworks/ru/library/se-prevent/index.html
Таблица html-сущностей расширена за счёт RFC с описанием HTML4.
8. Установка
- Скачать плагин ru.pflb.jmeter.samplers.TailSampler.jar:
- Все версии: github.com/pflb/Jmeter.Plugin.TailSampler/releases
- Скопировать плагин в каталог lib/ext для JMeter.
- Перезапустить JMeter.
Рисунок 14. Теперь плагин HTTP Request Tail доступен в JMeter
Пример каталога:
D:\TOOLS\apache-jmeter-2.13\lib\ext\ D:\TOOLS\apache-jmeter-2.13\lib\ext\ru.pflb.jmeter.samplers.TailSampler.jar D:\TOOLS\apache-jmeter-3.0\lib\ext\ D:\TOOLS\apache-jmeter-3.0\lib\ext\ru.pflb.jmeter.samplers.TailSampler.jar
9. Системные требования
9.1. Для работы с Apache.JMeter 2.13
- Java™ SE Runtime Environment версии 6 (build 1.6.0_45-b06) или выше:
- http://www.oracle.com/technetwork/java/javase/downloads/419409 — минимально необходимая версия (проект надо будет пересобрать, в репозитории проект собранный для JDK 1.7);
- http://www.java.com/ru/download/manual.jsp — последняя версия Oracle JDK;
- OpenJDK версии не ниже, чем 1.6 также подходит, например, OpenJDK 1.7.0_09-icedtea.
- JMeter 2.13:
- https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-2.13.zip — Apache.JMeter 2.13.
Apache.JMeter 2.13 собран с использованием Java 6. И если собрать плагин TailSampler с использованием, например, OpenJDK 1.7.0_09-icedtea, и запустить Apache.JMeter 2.13 + собранный плагин на компьютере, где есть только Java 6, то Apache.JMeter 2.13 запустится, а плагин нет. В результате их связка работать не будет. Вопрос сборки проектов и библиотек для различных версий Java и их совместимости заслуживает отдельной инструкции.
9.2. Для работы с Apache.JMeter 3.0
- Java™ SE Runtime Environment версии 7 (build 1.7.0_80-b15 (Oracle Corporation)) или выше:
- http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7–521261.html#jre-7u80-oth-JPR — минимально необходимая версия;
- http://www.java.com/ru/download/manual.jsp — последняя версия Oracle JRE;
- OpenJDK версии не ниже, чем 1.7 также подходит.
- JMeter 3.0:
- http://jmeter.apache.org/download_jmeter.cgi — Apache.JMeter 3.0.
Apache.JMeter 3.0 собран с использованием Java 7. Плагин TailSampler нужно собирать с использованием Java 7 или той версии Java, которая будет использоваться для запуска JMeter и плагина.