Отладка Java приложения, которое нельзя остановить. Ловим экзотику выполнения самыми доступными средствами — BTrace подход

в 6:35, , рубрики: BTrace, java, Агент, дебаг, джава, отладка

Отладка Java приложения, которое нельзя остановить. Ловим экзотику выполнения самыми доступными средствами — BTrace подход
Java приложения — значит в современном Java мире возможность встретить такое процентов на 90%, а то и больше (рассматриваем самые распространённые окружения, HotSpot based JVM версии от 1.6)
которое нельзя остановить — приложение работает, и перезапускать его по тем или иным причинам категорически нельзя
экзотика — нечто такое этакое, что не каждый день в голову взбредёт поймать (определённая последовательность вызова методов, диковинные комбинации значений параметров, ...)
доступными средствами — бесплатно, работоспособно, эффективно, легко, просто и т.д и т.п. В данной статье рассмотрен замечательный инструмент BTrace kenai.com/projects/btrace

И само собой в код Java приложения заранее ничего специально не добавлено касательно средств дебага…

Данная статья по сути является продолжением поста «Отладка Java приложения, когда оно совсем не ждёт — добро пожаловать в InTrace подход» habrahabr.ru/post/219661, в котором было показано как вклиниться в уже запущенное приложение и собрать достаточно подробный трейс выполнения. Что есть весьма полезное мастерство, но в реальной жизни, иногда, бывают случаи, когда проскакивает непонятное поведение с вероятностью 1 на 1 000, а то и хуже, и попробуй пади это найди в тоннах трейсов.
Поэтому берём для примера простенькую программу (файл excitement/Coin.java) и будем собирать «экзотику» на лету.

package excitement;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Random;

public class Coin {
  public void head() {
  }

  public void tail() {
  }

  public static void main(String... args) throws Exception {
    System.out.println("Нажмите любой Enter для продолжения...");
    new BufferedReader(new InputStreamReader(System.in)).readLine();

    Random rand = new Random();

    for (int count = 0; count < 1000; ++count) {
      if (rand.nextInt(2) > 0) {
        new Coin().head();
      } else {
        new Coin().tail();
      }
    }
    System.out.println("Вот и всё!");
  }

}

Скомпилируем

javac excitement/Coin.java

И запустим

javac excitement.Coin

Проще ведь некуда, правда? )

За экзотику я возьму волнующий вопрос: «Сколько же раз подряд максимально выпадут орёл и решка, ну а также вообще сколько раз они просто выпадут?» Такой себе тест rand.nextInt(2). Каковы прогнозы? Ставки принимаются…

Получить ответ поможет весьма известный и, к ко всему прочему, просто великолепный инструмент BTrace kenai.com/projects/btrace, неоднократно упоминаемый на хабре в коментах, но к сожалению ни разу доселе не описанный в постах.

Для его запуска стоит рассмотреть пару способов:
1) любителям командной строки — консольная утилита скачиваемая с kenai.com/projects/btrace/downloads/directory/releases/release-1.2.4 (последняя доступная версия)
и запускаемая как

btrace <PID> TracingScript.java

где
PID — это идентификатор процесса (получаемый, к примеру, через jps)
TracingScript.java — трассирующий скрипт, с коим более плотное знакомство будет чуть далее

2) любителям окошек предлагается использовать плагин в VisualVM visualvm.java.net/download.html. Для чего заходим в Tools->Plugins->Available Plugins кликаем BTrace Workbench и давим «Install», внимательно читаем лицензию (хотя кто их читает), ладно, так и быть, без малейших колебаний соглашаемся в этом и последующих окнах на всё при всё. И теперь, после успешной установки, в контекстном меню интересующего процесса, в VisualVM появился новый пункт «Trace Application...»

Отладка Java приложения, которое нельзя остановить. Ловим экзотику выполнения самыми доступными средствами — BTrace подход

BTrace делает свою работу полагаясь на алгоритм описанный в очень Java подобном скрипте (также можно пользовать D-scriptы). Очень подобном — поскольку это как бы и самая что ни наесть Java, но всё же из-за того, что BTrace не изменяет выполнение трассируемой программы (я имею в виду всёже старается не модифицировать её поведение, только получать информацию о выполнении максимально следуя формату «read-only»), приходиться забыть про множество вещей джавы (начиная с создания новых объектов и заканчивая ещё много чем, см. kenai.com/projects/btrace/pages/UserGuide BTrace Restrictions) и использовать средства предоставляемые непосредственно BTrace.

А теперь скрипт (файл TracingScript.java)

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace // скриптом выступает джава класс
public class TracingScript {
@Property // возможно смотреть значения "на лету" через MBean JMX (jconsole, VisualVM, ...)
private static long tailCount;
@Property(name="Total head count is") // другое имя для JMX
private static long headCount;
@Property
private static long maxHeadSequence = 1;
@Property
private static long maxTailSequence = 1;
@Property
private static long sequence = 1;
@Property
private static long prevId = -1;

@OnMethod(clazz = "excitement.Coin",  // вклиниваемся в метод в пакете  excitement класса Coin
         method = "head", // c именем head
         location = @Location(Kind.RETURN)) // при возврате из него
 public static void onHead() {
    ++headCount;
    sequence = prevId == 0 ? sequence + 1 : 1;
    if (sequence > maxHeadSequence) maxHeadSequence = sequence;
    prevId = 0;
 }

@OnMethod(clazz = "excitement.Coin", 
         method = "tail", 
         location = @Location(Kind.RETURN))
 public static void onTail() {
    ++tailCount;
    sequence = prevId == 1 ? sequence + 1 : 1;
    if (sequence > maxTailSequence) maxTailSequence = sequence;
    prevId = 1;
 }

@OnExit // вызывается при завершении программы
public static void onexit(int code) {
        println(strcat("total heads:", str(headCount))); // из-за ограничения на создание объектов  наблюдаются свои примочки по работе со строками
        println(strcat("total tails:", str(tailCount)));
        println(strcat("max tail sequence:", str(maxTailSequence)));
        println(strcat("max head sequence:", str(maxHeadSequence)));
    }
}

В конце концов, запускаем этот скрипт, жмём «Enter» в ожидающей бросания монет программе и получаем (у кого как, а у меня вышло так):

total heads:531
total tails:469
max tail sequence:9
max head sequence:8

В целом орёл и решка случилось выпадали по 8 и 9 раз подряд (хотя у меня за несколько запусков бывало и 10-11 раз). Желающим предлагается самостоятельно проверить насколько полученное совпадает с результатами формул теории вероятностей (дабы не заехать тут в сложную тему касательно способов генерации таких простых случайных чисел).

Подводя итоги:
BTrace изрядно мощный инструмент, позволяющий на лету трассировать весьма и весьма диковенные особенности выполнения. В данной статье затронута лишь вершина айсберга его шикарных возможностей (при желании хоть бери да пиши книгу), материал приводится с целью преподать самые азы и как можно более заинтересовать. Кого зацепило, смотрите более подробно тут kenai.com/projects/btrace/pages/UserGuide, прежде всего, обратите внимание на количество аннотаций и длинный список очень-очень жизненно полезных примеров. Но всё же не забывайте — всё происходит на свой страх и риск, ибо применяемая BTracе для достижения цели (вклинивания) трансформация Java классов, всегда может сыграть злую шутку.

И напоследок, про монеты (физика да и только) — зачастую монета не идеально сбалансирована (обычно орёл чуть тяжелее решки), так что подбросить монету и получить 50/50 в реальной жизни не удастся. Будьте бдительны, берите сторону монеты непосредственно умом.
Да прибудет с вами удача )

Благодарю за внимание!

Автор: indality

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js