Дебаг маршрутов Camel в Red Hat Dev Studio или как отпескоструить пескоструй

Один мой хороший приятель как-то рассказал мне историю как он подрабатывал промышленным альпинизмом. Ему поступил заказ на чистку труб на каком-то заводе. Он со своими соратниками приехал на объект, им выдали пескоструй, показали трубы. И первое, что он очистил - это сам пескоструй, так как вид грязного девайса противоречил его эстетическим взглядам. Эта история пришла мне на ум когда возникла задача дебага самого процесса дебага. Об этом и хотел бы поведать в блоге.

Часть 1 Погружение в механизм установки брекпоинтов

Для данного блога я использовал следующие инструменты:

  •  Старенькую Red Hat JBoss Developer Studio 11.3.0.GA
  • Старенький Eclipse Oxygen.2 Release (4.7.2)
  • Red Hat JBoss Fuse 7.7.0.GA (на Apache Karaf)

Перед запуском студии потребуется добавить следующее в devstudio.ini:

-Xdebug
-Xnoagent
-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

и только потом запустить. Добавьте в студию тестовый проект с маршрутом Apache Camel.

Далее, импортируйте в Eclipse исходный код из репозитория https://github.com/jbosstools/jbosstools-fuse. В основном нам понадобится проект editor/plugins/org.fusesource.ide.launcher. Его и надо взять для настройки удалённого дебага студии:

Поставьте брекпоинт, например, в классе org.fusesource.ide.launcher.debug.util.CamelDebugUtils в методе createAndRegisterEndpointBreakpoint. Когда дебаг подцепился к студии можно попробовать открыть в графическом редакторе проект Apache Camel и установить простую точку останова на любом элементе маршрута. Если всё сконфигурировано верно, то выполнение остановится в обозначенной точке - можно начать исследование.

В ходе пошагового дебага выясняется, что в классе org.eclipse.debug.internal.core.BreakpointManager создаётся понятие маркера для последующего сохранение этой информации на диск. Кроме этого, происходит изменение оперативной информации по синхронному событию в дебаггере по умолчанию - org.fusesource.ide.launcher.debug.model.CamelDebugTarget метод breakpointAdded. Маркеры не сохраняются синхронно на диск, а происходит это только при штатном закрытии Eclipse. Файл находится примерно по такому пути:

JBOSSTOOLS_WORKSPACE/.metadata/.plugins/org.eclipse.core.resources/.projects/org.fusesource.ide.launcher/.markers

Часть 2 Тем временем в рантайме

Сразу оговорюсь - текущий блог писался с использованием достаточно старой версии Camel - 2.21.0. В Camel 3+ пошёл максимальный отказ от Java Reflection и, возможно, механизмы будут другие. Причина использования старой версии - JBossTools ещё не адаптированы для работы с Camel 3.

Итак, начнём - сперва давайте запустим Red Hat Fuse в режиме debug следующей командой:

$./fuse debug

Предполагается, что проект с нашим тестовым маршрутом уже импортирован в Red Hat Developer Studio. Я использовал простой тестовый маршрут из примеров самого Camel:

<?xml version="1.0" encoding="UTF-8"?>

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

    <camelContext id="simple-cbr-context"

        xmlns="http://camel.apache.org/schema/blueprint" xmlns:order="http://fabric8.com/examples/order/v7">

        <route id="route1">

            <from id="_from1" uri="file:work/cbr/input"/>

            <log id="_log1" message="Receiving order ${file:name}"/>

            <choice id="_choice1">

                <when id="_when1">

                    <xpath>/order:order/order:customer/order:country = 'UK'</xpath>

                    <log id="_log2" message="Sending order ${file:name} to the UK"/>

                    <to id="_to1" uri="file:work/cbr/output/uk"/>

                </when>

                <when id="_when2">

                    <xpath>/order:order/order:customer/order:country = 'US'</xpath>

                    <log id="_log3" message="Sending order ${file:name} to the US"/>

                    <to id="_to2" uri="file:work/cbr/output/us"/>

                </when>

                <otherwise id="_otherwise1">

                    <log id="_log4" message="Sending order ${file:name} to another country"/>

                    <to id="_to3" uri="file:work/cbr/output/others"/>

                </otherwise>

            </choice>

            <log id="log5" message="Done processing ${file:name}"/>

        </route>

    </camelContext>

</blueprint>

Следующим шагом мы подключаемся удалённым дебагером к запущенному Red Hat Fuse:

На первой вкладке выбираем наш файл с маршрутом:

Далее определяем параметры JMX:

И параметры JDWP:

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

Проверка наличия брекпоинтов происходит через аспекты - AOP. На процессор - класс, который отвечает за непосредственное выполнение компонента в Camel "навешивается" advice, который выполняется до или после компонента - https://github.com/apache/camel/blob/camel-2.21.x/camel-core/src/main/java/org/apache/camel/processor/CamelInternalProcessor.java#L591.  Для коммуникаций между Camel и сторонними инструментами служит класс https://github.com/apache/camel/blob/camel-2.21.x/camel-core/src/main/java/org/apache/camel/processor/interceptor/BacklogDebugger.java - в этом классе происходит работа с брекпоинтами - через него идёт их установка и удаление, проверка наличия брекпоинта для узла в процессе выполнения. JBossTools общается с классом BacklogDebugger через JMX - для этого есть отдельный фасад - https://github.com/jbosstools/jbosstools-fuse/blob/jbosstools-4.16.0.Final/editor/plugins/org.fusesource.ide.launcher/src/org/fusesource/ide/launcher/debug/model/CamelDebugFacade.java. 

При обнаружении брекпоинта экземпляр класса BacklogDebugger ставит на паузу в 300 секунд дальнейшее выполнение Camel и происходит передача управления в JBossTools путём добавления значения в мапу suspendedBreakpoints. Со своей стороны в JBossTools запущена периодическая проверка содержания этой мапы через JMX https://github.com/jbosstools/jbosstools-fuse/blob/jbosstools-4.16.0.Final/editor/plugins/org.fusesource.ide.launcher/src/org/fusesource/ide/launcher/debug/model/EventDispatchJob.java#L45  и, при обнаружении нового значения, процесс перехватывается и передаётся в https://github.com/jbosstools/jbosstools-fuse/blob/jbosstools-4.16.0.Final/editor/plugins/org.fusesource.ide.launcher/src/org/fusesource/ide/launcher/debug/model/CamelThread.java#L101, где приостанавливается выполнение потока и управление передаётся UI. Передача управления в UI происходит через события.

16.10.2020