Для элементарных замеров latency в торговых приложениях на стадии разработки и QA часто используется перехватчик пакетов tcpdump, а пакеты анализируются с помощью приложения Wireshark.
tcpdump не применяют на PROD, потому что он сам оказывает влияение на производительность системы. Тонкие замеры latency на PROD производятся другими средствами, о чем я расскажу в свое время.
В данной статье я хочу продемонстрировать технику замера latency на примере двух моих приложений SimpleFIXClient и SimpleFIXExecutor. Надеюсь, пошаговая инструкция будет достаточно понятной и не слишком запутает вас.
Описание тестовой конфигурации
Напомню, приложение SimpleFIXExecutor эмулирует торговый движок биржи. Вы подключаетесь к нему по протоколу FIX, отправляете ему NOS, а SimpleFIXExecutor сначала отправляет ACK, а потом FILL, словно ордер исполнился на реальной бирже. SimpleFIXExecutor не делает никакого реального сведения ордеров. Вы можете посылать ему любой мусор, и он счастлив будет ответить вам ACK (да, сэр!) и FILL (ваш приказ исполнен, сэр!). SimpleFIXExecutor исполняет свое главное назначение: если у вас есть FIX-приложение, SimpleFIXExecutor станет для него задушевным собеседником, всегда готовым ответить!
В данном примере мы попытаемся получить ответ на вопрос: «Как быстро после получения NOS от FIX-клиента наш SimpleFIXExecutor отправляет ACK, а затем отправляет FILL?» Разница в timestamp между пакетом содержащим ACK и пкатом с NOS даст нам «внутреннюю» latency — время, которое уходит у SimpleFIXExecutor на обработку запроса. А разница между ACK и FILL — время на «заполнение» ордера.
tcpdump является утилитой для Linux, поэтому SimpleFIXExecutor будет запущен в виртуальной Linux-машине в VirtualBox, созданной с помощью Vagrant. А SimpleFIXClient будет запущен на той же машине Windows 10. Само собой разумеется, что все эти слои виртуализации дают свой вклад в задержки, но мы не ожидаем получить здесь никаких феноменальных показателей latency, цель статьи — продемонстрировать саму технику ее замера.
Создание виртуальной машины Linux
Для вашего удобства я создал конфигурацию для Vagrant и поместил ее в отдельном репозитарии. Проект содержит всего два файла:
/vagrant │ README.md │ Vagrantfile
Для запуска вирутальной машины потребуется добавить файлы:
- Java: jdk-19_linux-x64_bin.rpm
- Maven: apache-maven-3.8.7-bin.tar.gz
- Gradle: gradle-7.6-bin.zip
- Spring Boot: spring-boot-cli-2.7.7-bin.tar.gz
/vagrant │ apache-maven-3.8.7-bin.tar.gz │ gradle-7.6-bin.zip │ jdk-19_linux-x64_bin.rpm │ README.md │ spring-boot-cli-2.7.7-bin.tar.gz │ Vagrantfile
Gradle собственно говоря не нужен, но я его ставлю обычно для всех проектов.
Vagrant создаст виртуальную машину, добавит нужные пакеты, установит Java, Maven, Spring Boot и откроет порт 9878, через который наш FIX-клиент будет общаться с FIX-сервером, работающим внутри виртуальной машины.
Vagrant по умолчанию монтирует папку проекта в папку /vagrant. Так что все файлы, помещенные в проект в Windows, будут видны в виртуальной машине в папке /vagrant.
В папке /vagrant создайте под-папку «project». Здесь мы будем хранить все файлы нашего эксперимента. В пакет «project» создайте подпапку «target», сюда мы будем складывать наши jar-файлы FIX-клиента и FIX-сервера. Дерево папок внутри виртуальной машины Linux выглядит так:
/vagrant │ apache-maven-3.8.7-bin.tar.gz │ gradle-7.6-bin.zip │ jdk-19_linux-x64_bin.rpm │ README.md │ spring-boot-cli-2.7.7-bin.tar.gz │ Vagrantfile └───project | └───target
Надеюсь, пока все понятно?
Собираем и запускаем SimpleFIXExecutor
SimpleFIXExecutor можно взять из моего репозитария. Проект SimpleFIXExecutor собирается с помощью «mvn clean install». В результате сборки в папке target проекта появляется jar-файл simplefixexecutor-0.0.1-SNAPSHOT.jar. Его следует скопировать в папку /vagrant/project/target/. Из проекта также скопируйте скрипт startServer.sh в папку /vagrant/project/.
Теперь дерево выглядит так:
/vagrant │ apache-maven-3.8.7-bin.tar.gz │ gradle-7.6-bin.zip │ jdk-19_linux-x64_bin.rpm │ README.md │ spring-boot-cli-2.7.7-bin.tar.gz │ Vagrantfile | └───project | startServer.sh | └───target simplefixexecutor-0.0.1-SNAPSHOT.jar
В виртуальной машине из командной строки запустите скрипт ./startServer.sh. На экране пойдет вывод логов приложения. В конце концов SimpleFIXExecutor запустит несколько потоков в ожидании подключения клиентов по протоколу FIX. Клиентов, «разговаривающих» по протоколу FIX 4.2 SimpleFIXExecutor будет ожидать на порту 9878.
Собираем и запускаем SimpleFIXClient
SimpleFIXClient можно взять из моего репозитария. Проект SimpleFIXClient собирается с помощью «mvn clean install». В результате сборки в папке target проекта появляется jar-файл simplefixclient-2.0.jar. Его следует скопировать в папку /vagrant/project/target/. Из проекта также скопируйте скрипт startClient.bat в папку /vagrant/project/.
Для SimpleFIXClient нам понадобится еще несколько файлов:
- definitions.dsl и simplefixclient.cfg положите в папку project
- скопируйте целиком папки config и scenarios в папку project
Теперь дерево выглядит так:
/vagrant │ apache-maven-3.8.7-bin.tar.gz │ gradle-7.6-bin.zip │ jdk-19_linux-x64_bin.rpm │ README.md │ spring-boot-cli-2.7.7-bin.tar.gz │ Vagrantfile │ └───project │ definitions.dsl │ simplefixclient.cfg │ startClient.bat │ startServer.sh │ ├─── config │ application.properties │ ├───scenarios │ Scenario1.groovy │ └───target simplefixclient-2.0.jar simplefixexecutor-0.0.1-SNAPSHOT.jar
В Windows из командной строки запустите скрипт startClient.bat. Если все пройдет хорошо, SimpleFIXClient запустится, подключится к порту 9878, передаст два ордера по протоколу FIX с интервалом 2 секунды, получит ACK, а затем FILL по каждому из них, и завершит исполнение. Так SimpleFIXClient исполнит «сценарий», который записан в файле Scenario1.groovy в папке scenarios.
Если у вас что-то не запустилось или не получилось, пишите в комментариях, разберемся.
Запускаем SimpleFIXClient в длинном цикле
2 ордера это слишком мало, чтобы что-то успеть замерить. Давайте отправим SimpleFIXClient в длинный цикл отправки скажем 10.000 ордеров, чтобы он в фоне тихонечко работал, а мы снимали нужные нам мерки по мере надобности.
Откройте файл Scenario1.groovy. Найдите в файле сценария строку цикла:
for(x in 1..2) {
И поставьте 10000 вместо 2.
for(x in 1..10000) {
Теперь на отправку 10.000 ордеров с интервалом 2 секунды у SimpleFIXClient уйдет 20.000 секунд. А это сколько в минутах? 333,333 минуты. А сколько это в часах? 5,5 часов. Этого нам вполне хватит наиграться. Запускайте снова startClient.bat и пусть он тихонько работает в фоне. А мы приступим к самому вкусному.
Запускаем tcpdump и пишем пакеты в файл
Замечательная статья на Хабре Давайте изучим tcpdump с Джулией Эванс поможет вам освоить самые азы работы с tcpdump.
В отдельном окне терминала нашей виртуальной машины запустим следующую команду:
sudo tcpdump -n -i any port 9878 -w /vagrant/packets.pcap
В папке /vagrant появится файл packets.pcap. По мере наполнения буфера памяти tcpdump будет сбрасывать данные в файл. После завершения работы с помощью Ctrl-C все данные будут записаны в файл, которые мы будем анализировать с помощью Wireshark.
Смотрим дамп пакетов в Wireshark и вычисляем latency
Wireshark — бесплатная утилита для анализа сетевых пакетов. ее можно загрузить с сайта. Запускаем Wireshark и открываем файл packets.pcap с его помощью.

Wireshark показывает нам: Ethernet фрейм, в нем — IP-пакет, в нем TCP-пакет, а в нем — payload — «полезную нагрузку». Так как Wireshark по умолчанию не знает, что там в TCP мы передаем, он показывает наше сообщение в пакете как набор байтов. Давайте включим FIX-протокол в Wireshark с помощью пункта Analyze -> Enabled Protocols …, найдем там FIX и включим его:

На экране пока ничего не изменилось. Надо сказать теперь программе Wireshark, что пакеты, приходящие на порт 9878 и уходящие через этот порт, надо декодировать как FIX-сообщения. Выбираем любой пакет, кликаем правой кнопкой мышки на нем, выбираем пункт Decode As … и в колонке Current выбираем FIX.

Вот теперь на экран приятно смотреть:

Например, пакет 236. Видно, что это было FIX-сообщение NewOrderSingle. В ответ ему был отправлен ExecutionReport (ACK), а еще позднее — еще один ExecutionReport (FILL). Через 2 секунды к нам опять поступил NewOrderSingle. И эта комбинация повторяется каждые 2 секунды (90.ххх, 92.ххх, 94.ххх, 96.ххх…), что и ожидалось.
Теперь не составит труда посчитать latency. Пакет с NOS поступил в 94,168.601 (это столько секунд прошло с начала сбора пакетов), а ответный ACK прошел через tcpdump в 94,170.271. Получаем, 94,170.217 — 94,168.601 = 0,001.616, то есть на ответ понадобилось 1 миллисекунда и 616 микросекунд. Между ACK и FILL прошло: 94,170.743 — 94,170.217 = 0,000.526, т.е. 526 микросекунд.
Задача решена, ответ получен.
Что мы видим и почему?
Как видим показатели latency довольно удручающие. Почему они такие:
- QuickFIX/J — не самая быстрая реализация FIX-протокола
- мой домашний PC — не самая быстрая машина (процессор, память, сетевой интерфейс, куча процессо в фоне)
- Java-приложение внутри виртуальной машины работает тоже медленнее из-за накладок на виртуализацию
- Java-приложение написано с помощью Spring Boot, т.е. без всяких оптимизаций кода для достижения низкого latency
- Java-приложение не достаточно «разогрето»
- tcpdump сам добавляет нагрузку на виртуальную машину
Выводы
- tcpdump требует прав администратора на установку и работу. На QA скорей всего вам его установит сетевой администратор.
- tcpdump не рекомендуется ставить на PROD, так как он вносит дополнительную нагрузку на ядро системы и ее сетевые интерфейсы.
- при анализе трафика, если tcpdump работает круглосуточно, его работу следует всегда ограничивать по порту и по IP-адресу, с которым вы обмениваетесь пакетами. В противном случае файл с дампами пакетов займет очень скоро все дисковое пространство и ваша система рухнет.
- файл с записью пакетов лучше всего ротировать и хранить историю, скажем, за пять предыдущих дней, а все остальные удалять по мере «старения». Это следует делать также во избежание исчерпания места на диске.
Круто, спасибо! Плачевно, что Wireshark поставить в проекте e’ impossibile 🙂 на раб машину: остается только домашние условия
Ждем новых сторий из мира алго/dma/
НравитсяНравится 1 человек
согласен. Wireshark не приветствуется админами. У нас в банке он ставился на Windows desktop только с разрешения начальства и объяснением для чего это нужно, и с клятвой, что не будет использоваться для вредных действий.
НравитсяНравится 1 человек
На самом деле на помощь может прийти python (например scapy, pyshark), правда придется «писать свой велосипед» по парсингу pcap file.
НравитсяНравится 1 человек