|
APC личная страница |
· JMeter vs MSRPC · |
|
JMeter vs MSRPCОбсудить статьюВступлениеПочему LoadRunner все же плохС момента предыдущих экспериментов по нагрузочному тестированию RPC-сервера прошел примерно год. Весь этот год наша компания провела под флагом повышения производительности и стабильности ПО. Приятно отметить, что мои нагрузочные тесты процедуры логина c помощью LoadRunner сыграли в этих работах небольшую, но полезную роль. Интереснее всего было то, что банк заказал нагрузочное тестирование некоей сторонней компании (о сторонних компаниях либо хорошо, либо не указывать название). Компания эта опытна в нагрузочном тестировании, имеет солидных клиентов и использует для реализации проектов LoadRunner. Я опущу множество язвительных замечаний по общему ощущению от работы этой компании, сосредоточусь на нагрузочных тестах:
Вообще говоря, очень грустный опыт. Код этих тестов компания передавала мне, я всячески старался вникнуть в решение, но игнорировать видимые невооруженным глазом колоссальные проблемы развития и поддержки решения просто глупо. При ближайшей же модификации кода в сервере нагрузочные тесты поломались, и вопрос их починки закопан где-то в глубине сишного кода… Самое интересное, что код тестов передан, а вот инструмент тестирования, запускающий этот код, банку пришлось бы покупать у HP! И вот тут становится совсем уж весело, так как порядок стоимости закупки выходит на десятки тысяч долларов (и очень может быть, что выходит за сотню). Нужно отдать должное в целом неплохому инструменту LoadRunner, но я назову финансовый аспект главным его минусом: при такой стоимости готового продукта более приемлемой может оказаться разработка собственного инструмента и работа с продуктами Open Source. Почему JMeter все же хорошВ прошлый заход на нагрузочное тестирование я писал, что безуспешно искал инструменты тестирования TCP-трафика помимо LR. Тогда я натыкался на JMeter, но не нашел толкового описания, как c его помощью тестировать бинарный TCP-трафик. Возможно, хороший туториал изменил бы расклад, но такового я не встретил. Однако, благодаря тому, что за год я достаточно много провозился с нагрузочным тестированием веб-приложения на JMeter (в этой области он точно поворотливей LR), я увидел, как можно использовать JMeter для тестов RPC. Плюс к этому, после получения опыта написания плагинов для JMeter я понял, что при необходимости сам смогу дописать нужные мне функции в программу. При этом ключевые плюсы, которые были видны сразу, это:
В общем, для оптимизма есть серьезные основания. Прежде чем перейти к самой истории теста RPC-сервера с помощью JMeter, хочу для читателей предыдущей статьи сделать ряд замечаний об изменениях в организации тестирования. Замечания о тестируемом ПО и окруженииЗа прошедший год самые существенные изменения произошли как раз в процедуре логина. Теперь второй поток непрерывной связи с сервером отсутствует, и не сервер опрашивает клиентов на предмет живости, а клиенты оповещают сервер запросами keep-alive, что они не отвалились. А еще я узнал, что очень важно проверять коды возврата от методов RPC-сервера, иначе можно сделать жутко некорректный тест и не заметить этого. В нагрузочной модели четко выделились два сценария, дающие 60% транзакционной нагрузке в банке: это взнос и выдача наличных. Также возросло количество данных (около 80ГБ) и количество пользователей, работающих с сервером, выросло до 600(!). Нужно помнить, что палочка-выручалочка это Wireshark, без него мне не отладить тесты бинарного RPC-трафика. Я не буду даже пытаться использовать запись в JMeter, так как убедился, что надежней работать с помощью Wireshark. И, наконец, спецификация RPC также должна быть всегда под рукой. RPC Bind и первый RPC Call с помощью JMeterИтак, запускаем JMeter, добавляем в тест-план Thread Group, TCP Sampler и View Results Tree. TCP Sampler настраиваем на работу с бинарным трафиком – указываем TCPClient-класс BinaryTCPClientImpl, адрес и порт сервера, а также таймаут в 5000 миллисекунд, чтобы сократить цикл отладки. Теперь самое главное – копируем из Wireshark байты пакета RPC Bind командой Copy – Bytes (Hex Stream) и вставляем их в поле Text to send. BinaryTCPClientImpl понимает как раз этот формат представления байт, и, в отличие от варианта работы с LoadRunner, мне не нужно мучиться с переформатированием последовательности. Наш bind-пакет таков: 05000b03100000004800000001000000d016d016 Не забываем запустить Wireshark для контроля достоверности трафика, запускаем наш тест и идем смотреть в View Results Tree результаты. Первое наблюдение – сэмпл завершил работу по таймауту, слава Богу, мы поставили его достаточно коротким. Второе наблюдение – Wireshark показывает ответ RPC Bind_ack, это значит, что трафик был эмулирован успешно. Response data показывает нам Hex-байты всего пакета ответа. Не будем отвлекаться на мелкие препятствия, проверим, что там у нас с эмуляцией непосредственно RPC-вызовов. Благо, первый RPC вызов прост как пробка – он спрашивает у сервера, жив ли тот. Обязательно отмечаем первому TCP Sampler галочку Re-use connection, чтобы сокет не закрывался после Bind и последующие запросы работали по тому же сокету. Эту галочку нужно будет проставлять всем запросам. Переименовываем первый TCP Sampler в RPC Bind и добавляем второй TCP Sampler, сразу с именем Is Server Alive. Настраиваем опции второго сэмплера так же как у первого. Снова копируем все байты эталонного пакета и вставляем их в поле Text to send:
Wireshark перезапущен, очищаем результаты JMeter (Ctrl+E), запускаем тест, смотрим результаты. Запрос RPCCall сработал успешно, мы видим это в Wireshark и View Results Tree. Успешность обнадеживает, вот только есть несколько «но»... Замечания по работе с TCP SamplerПри появлении второго TCP Sampler приходит мысль, что хорошо бы уже начать использовать TCP Sampler Config, чтобы выделить общие настройки. «Но» в отношении TCP Sampler Config состоит в том, что срабатывают все опции кроме TCPClient classname, и приходится имя класса прописывать (и по необходимости менять) в каждом сэмплере. Долг пользователя открытого и бесплатного ПО побуждает отправить разработчикам багрепорт о найденной проблеме, что я и сделал, записав баг 48709. Вторая бяка расположена в TCP Sampler: JMeter проглотит результат запроса, если класс TCPClient породит exception, а это есть штатный способ сообщить о сбое в работе тонких материй бинарного TCP-трафика. Проблему можно будет увидеть только изучая файл jmeter.log, View Results Tree просто промолчит. На эту тему я записал баг 48747, и сразу дал к нему патч, исправляющий ситуацию. Очень приятно, что разработчики быстро приняли патч и включили исправление в готовящуюся к выпуску версию JMeter. Как уже было замечено выше, самое первое и главное «но» в использовании TCP Sampler – это его неумение определять окончание бинарного ответа, в результате чего он всегда дожидается таймаута. Имеющийся Length-Prefixed класс LengthPrefixedBinaryTCPClientImpl улучшает работу с отправляемыми данными, но в RPC необходимо вовремя остановить их прием. Счастье состоит в том, что JMeter имеет открытые исходные коды и ряд удачных архитектурных решений, позволяющих писать к нему плагины и всячески расширять имеющуюся функциональность. Я воспользуюсь возможностью указать любой TCP Client класс, и напишу свой собственный. Собственный TCPClient для RPCМне легко переходить к написанию кода, дополняющего JMeter, так как к этому моменту я уже написал плагин Samples vs Active Threads. AbstractTCPClient – достаточно простой класс, реализуя его методы read и write, можно заложить в JMeter требующуюся логику работы с трафиком. Я не буду утомлять читателя подробностями программирования на Java, опишу в вкратце функциональность первого прототипа DCERPCSampler:
NetBeans и юнит-тесты помогли мне отладить этот класс. Теперь можно указать этот класс двум сэмплерам (сожалея о баге в TCP Sampler Config) и изменить тексты запросов в понятный DCERPCSampler вид. Замечу, что указывать собственный класс TCP Client нужно полным идентификатором класса, в моем случае kg.apc.jmeter.dcerpc.DCERPCSampler. Формат запроса bind теперь выглядят так:
Запрос IsServerAlive без заголовка стал полностью соответствовать своей простой пробковой сути:
Забегая вперед, хочу сказать, что со временем функциональность этого класса будет дополняться новыми и новыми удобствами, сохраняя, тем не менее, совместимость с первым простым форматом. Кстати, многие параметры запроса RPC Bind пока сознательно прошиты в коде и не вынесены в параметры, время покажет, нужно ли выделать их. Кто-то из читателей заметил мне, что вместо написания класса TCPClient я мог бы использовать Java Request или BeanShell Sampler. Действительно, мог бы, но сознательно не стал, поскольку мне пришлось бы заботиться о передаче между запросами открытого сокета и обеспечивать прочую инфраструктуру сетевого сэмплера, которая уже готова к работе в TCP Sampler. С другой стороны, я мог бы реализовать вообще полноценный RPC Sampler, чтобы с заданием параметров запросов все было совсем красиво, но для меня затраты на разработку уже готовой в TCP Sampler функциональности пока не оправдываются потребностями в красивом GUI. Параметризация RPC-трафика в JMeterКак и в случае с LoadRunner, наша цель – корректный нагрузочный тест процедуры логина на десятках (а лучше сотнях) параллельных пользователей. Такой тест требует соответствующей параметризации запросов: нужно генерировать UUID клиента и использовать уникальное имя пользователя, так как тестируемый сервер отслеживает эти параметры. Очередной запрос в последовательности логина это Register User, несущий целый ряд текстовых параметров, что видно по отображению пакета в WireShark. Сырые данные заглушки выглядят так:
Где тут UUID, где тут логин можно подсмотреть в Wireshark, который по возможности визуализирует данные заглушки. Я добавляю еще один TCP Sampler и начинаю обнажать закономерности. Визуализация текстаЗдравый смысл подсказывает, что работать с HEX-последовательностью – не самый эффективный путь, по возможности нужно визуализировать получаемую и отправляемую информацию, уходя от уровня TCP-трафика на уровень человекопонятных строк и чисел. Класс DCERPCSampler пришлось сделать чуть умнее, снабдив его двумя ключевыми умениями:
Запрос логина (без параметров CallID и OpNum) стал выглядеть так (я не забываю проверять его корректность запуском теста JMeter под присмотром Wireshark):
Внимательный глаз сразу видит паттерн: префикс длины, нулевой int, снова префикс длины, строка текста с нулевым байтом в конце. Это явно штатный ход маршалинга, и он обнажает проблему эмуляции трафика: если данные будут переменной длины, то нужно для них обеспечивать корректные префиксы длины. Пробы показали, что если не соблюсти это правило, сервер будет отвечать пакетом RPC Fault, и о корректном тесте без корректных префиксов не может быть речи. Дополнительные параметры маршалингаЯ снова дорабатываю DCERPCSampler, добавляя опцию «режим маршалинга» к блокам текста. Мой первый режим маршалинга – «D», двойной префикс длины и нулевой байт в конце. Отлично! Запрос обретает все более гуманные черты:
Оставшиеся HEX-байты так и остаются непонятыми, но корректности теста это не вредит. Пробы показывают, что теперь можно менять логин на любой и RPC-пакет будет прочитан сервером корректно. Генерация UUID, набор логинов и авто-инкремент CallIDДо сих пор я мог запускать тест только в однопоточном режиме, так как UUID клиента у меня был один и сервер отвечал ошибкой «Дубликат UUID» на попытку запустить многопоточный тест. Для генерации UUID в JMeter я воспользовался примерно таким же путем как в LoadRunner – добавил в Test Plan элемент RandomVariable и указал ей формат FFFFFFFF-FFFF-FFFF-FFFF-000000000000 (остальные параметры очевидны). Для набора логинов я использовал CSV Data Set Config, подготовив для него файлик с парой сотен тестовых логинов. Еще одна вещь, о которой пора позаботиться, это CallID. Спецификация RPC требует, чтобы в рамках соединения этот параметр был уникальным и возрастающим у каждого запроса. Попытка использовать элемент JMeter Config -> Counter показала, что этот счетчик считает итерации теста, а не обращения к переменной. Зато у JMeter есть функция intSum, дающая необходимую логику инкремента. В корне Test Plan я завел переменную callID=0, а во всех TCP-запросах поставил использование функции intSum. Итак, Text to send в запросе Register User теперь выглядит так:
Теперь мне по глазам бьет цифра 9, так как совсем неочевидно, что 9 это OpNum запроса Register User. Номера методов OpNumРешение для проблемы визуализации OpNum я искал значительное время. Прежде всего, я убедился, что в JMeter поживиться на эту тему нечем, кроме BeanShell, который ввиду своей универсальности может все, но уж очень неудобен в отладке. Я понял, что придется писать какой-то собственный Config-плагин к JMeter, реализующий то, что мне нужно. Информация о методах RPC хранится в исходниках тестируемого сервера в файлике IDL, не знаю, по какому стандарту он написан. Первоначально я ломанулся в область написания плагина, который будет парсить этот файлик и как-то потом передавать информацию сэмплерам. Для этого я погрузился в проект ANTLR и дня три потратил на попытки заставить его сделать то что мне нужно. Я не жалею этих трех дней, знакомство с ANTLR было приятным, но выхлоп для решения моей проблемы был нулевой. Правильным ответом было написать универсальный плагин Variables from CSV File, который просто берет из файла строки и превращает их в переменные JMeter. Далее написал на PHP элементарный парсер IDL и получил csv-файл с содержимым «имя метода = номер OpNum». Этот файл скармливается плагину и я получаю в свое распоряжение переменные JMeter вида ${OpNum_RegisterUser}. Теперь я могу смело программировать нагрузочный тест без опаски, что при очередном изменении IDL все номера методов поедут вкривь и вкось. Работа с Multi-PDU запросами и ответамиУспешно пройдя формирование следующего запроса Auth User, я столкнулся с очередной проблемой корректной эмуляции RPC: Multi-PDU ответы от сервера. В ответ на запрос GetProgVersions я получаю не один RPC-пакет, а последовательно два пакета, каждый со своим заголовком, имеющим соответствующие флаги FirstPacket и LastPacket. А следующий запрос к серверу GetUserRights возвращает даже четыре пакета, а еще следующий запрос GetXMLOptions вообще шесть пакетов возвращает. Кстати говоря, по спецификации, понятие Multi-PDU распространяется и на запросы и на ответы, данные заглушки разбиваются на порции по лимитам MaxXmitFrag и MaxRecvFrag (передаются в запросе Bind), в заголовке пакетов проставляются соответствующие флаги. Я снова дорабатываю свой класс, добавляя в него поддержку Multi-PDU ответов (поддержку запросов я добавлю значительно позже, в рамках процедуры логина она мне не понадобилась). Юнит-тесты в NetBeans снова служат мне хорошую службу. Надо заметить, что тема обработки Multi-PDU оказалась очень интересной для меня c точки зрения программирования на Java. ФиналНу вот, когда инфраструктура разработки нагрузочного теста готова, можно переходить к созданию сложного тест-плана и на этом рассказ подходит к завершению. Финальные действия это: добавление сэмплеров для оставшихся вызовов логина и логаута, использование Assertion, чтобы убеждаться в корректности ответов сервера и настройка Thread Group на работу сотен пользователей. Запуск тестов показал устойчивую работу JMeter и плотную загрузку сервера приложений и баз данных. Через пару месяцев работы я получил тест-план, покрывающий два самых частых сценария и молотящий транзакции. За это время тестовый класс оброс дополнительными форматами визуализации входящего/исходящего трафика и работой с Multi-PDU запросами. Страшно представить, во что бы мне обошлось то же самое при использовании LoadRunner. Стоимость двух месяцев моей работы пока что менее $10 000, а качество результата намного выше по сравнению с неназванной компанией, так что экономическая эффективность применения JMeter также налицо. Мои итоговые замечания к JMeter:
Netscape unfriendly HTML (дата последней модификации этого файла: 15.03.2010 г.) © Сделал сам R G B |
||