Java и DTrace: новые возможности диагностики Java приложений
Мы начинаем новую серию постов, посвященных DTrace и Java HotSpot Virtual Machine (VM). В начале мы кратко расскажем что же такое DTrace, расскажем что было сделано в Java VM для поддержки DTrace и затем перейдем к наиболее интересной части—примерам, демонстрирующим анализ и диагностику Java приложений с помощью DTrace.
Что такое DTrace?
DTrace (Dynamic Tracing Framework) является частью операционной системы Solaris 10 и представляет собой мощный инструмент для диагностики как самой ОС так и пользовательских приложений в режиме реального времени. С помощью DTrace вы можете отслеживать узкие места производительности приложений, проводить настройку, анализ и диагностику системы. И все это DTrace делает динамически, не требуя остановки работы анализируемого приложения, в отличие от таких инструментов как truss, pstack, prstack и т.п.
Ниже мы приведем забавный пример такой динамической модификации—подмену информации, выводимой командой uname.
Как работает DTrace?
DTrace оперирует такими понятиями, как проба (probe) и провайдер (provider). Проба—это точка (или точка перехвата), в которой снимаются показатели системы. Пробы в свою очередь вводятся в систему специальными модулями ядра, именуемыми провайдерами. В ядро системы встроено больше десятка провайдеров (syscall, io, sched, ...) и более 30.000 проб. Каждый из провайдеров позволяет отлаживать определенную область операционной системы. Как следствие, посредством различных проб вы можете отслеживать любые процессы в Solaris 10, от пользовательских функций до системных вызовов и исполнения конкретных инструкций.
Важно отметить, что пока DTrace не начнет действовать, пробы находятся в пассивном состоянии, не сказываясь на производительности системы. Гарантируется также, что DTrace не нарушает функционирование систем, на которых он исполняется, что делает его еще более привлекательным для использования в уже работающих приложениях.
Как же использовать DTrace? Например, как посмотреть, откуда и сколько раз вызывается системный вызов write?
Инструкции для DTrace пишутся в виде скриптов на языке D. Этот язык очень простой и в то же время достаточно мощный. Он очень похож на C, но не пугайтесь, никаких указателей в D нет :)
Взаимодействие с DTrace происходит так:
Пользователь пишет запросы и скрипты на языке D (будем называть их D-скриптами);
D-скрипты компилируются и отправляются на исполнение DTrace;
DTrace активирует динамически загружаемые провайдеры, которые в свою очередь создают пробы;
для каждой активированной пробы срабатывает действие, определенное в D-скрипте. Например, действем может быть вывод на печать аргументов системного вызова write или read
Вот пример D-скрипта, с помощью которого вы можете узнать какими процессами или командами вызывается системный вызов write. Этому вызову соответствует проба syscall::write:entry.
% cat write.d #!/usr/sbin/dtrace -s
syscall::write:entry
{
printf("pid: %d, execname: %s", pid, execname);
} %
Делаем файл write.d исполняемым (chmod +x write.d) и запускаем:
% ./write.d
CPU ID cc FUNCTION:NAME
0 530 write:entry pid: 4880, execname: sshd
0 530 write:entry pid: 5023, execname: mozilla-bin
0 530 write:entry pid: 4999, execname: mozilla
0 530 write:entry pid: 5024, execname: expr
0 530 write:entry pid: 5026, execname: cat
0 530 write:entry pid: 5028, execname: uname
0 530 write:entry pid: 5029, execname: pwd
0 530 write:entry pid: 5030, execname: domainname
0 530 write:entry pid: 5025, execname: which
0 530 write:entry pid: 5032, execname: whoami
... %
Много интересных примеров вы можете найти в блоге Глеба Рейса и в официальном руководстве по DTrace, а также в богатой коллекции примеров под названием DTrace ToolKit.
А вот пример D-скрипта, который модифицирует вывод команды uname:
% cat uname.d #!/usr/sbin/dtrace -s
#pragma D option quiet
#pragma D option destructive
syscall::uname:entry
{
/* запоминаем значение переменной функции в локальной переменной потока self->addr */
self->addr = arg0;
}
syscall::uname:return
{
copyoutstr("Windows 3000", self->addr, 257);
copyoutstr("mars-earth-gw.org", self->addr+257, 257);
copyoutstr("911.911.911", self->addr+(257*2), 257);
copyoutstr("zetasparc v42", self->addr+(257*3), 257);
copyoutstr("impossible-improbability-driver", self->addr+(257*4), 257);
/* обнуляем переменную, чтобы DTrace мог освободить память, выделенную под нее */ self->addr = 0; } %
Если вы запустите этот скрипт перед uname -a, вы обнаружите, что являетесь счастливым обладателем не Solaris, а ОС Windows 3000:
% ./uname.d &
% uname -a
Windows 3000 mars-earth.gw.org 911.911.911 zetasparc v42 impossible-improbability-driver
% mail -s 'UNBELIEVABLE!' everybody@sun.com
...but it worked!
^D
Поддержка DTrace в Java HotSpot VM
Все сказанное выше не может не радовать простого программиста, но использовать DTrace для отладки и диагностики Java приложения на уровне системных вызовов все же не так удобно. Гораздо удобнее было бы, например, иметь возможность отслеживать создание объектов определенных классов, создание потоков, захват и освобождение мониторов на уровне языка Java.
И новая версия Java HotSpot VM предоставляет такую возможность.
Как это сделано? Помимо встроенных провайдеров и проб DTrace позволяет разработчикам приложений создавать для приложения собственные провайдеры и пробы. Тем самым вы можете добавлять любые пробы в ваше приложение, да еще и в любом количестве! Такие провайдеры доступны с помощью провайдера pid.
В Java HotSpot Virtual Machine (VM), которая входит в состав Java SE 6.0 (Mustang), реализованы два провайдера: hotspot и hotspot_jni. Теперь вы можете узнать почти все (ну, или почти все) о том, что и в какой момент происходит в Java VM.
Провайдер hotspot
Этот провайдер предоставляет следующий список проб:
| VM Lifecycle Probes: пробы, связанные с жизненным циклом VM | |
|---|---|
vm-init-begin |
момент начала инициализации VM |
vm-init-end |
момент окончания инициализации VM |
vm-shutdown |
момент завершения VM |
| Thread Lifecycle Probes: пробы, связанные с жизненным циклом Java потоков | |
thread-start |
момент запуска потока Java |
thread-stop |
момент остановки потока Java |
| Classloading Probes: пробы, связанные с дейтельностью загрузчика классов | |
class-loaded |
момент завершения загрузки класса |
class-unloaded |
момент сигнализирующий о завершении выгрузки класса |
| GC Probes: пробы, связанные с работой сборщика мусора | |
gc-begin |
момент начала работы сборщика мусора |
gc-end |
момент завершения работы сборщика мусора |
mem-pool-gc-begin |
момент начала сборки мусора или очистки данного коллектора |
mem-pool-gc-end |
момент окончания сборки мусора или очистки данного коллектора |
| Method Compilation Probes: пробы, связанные с компиляцией Java методов JIT компилятором | |
method-compile-begin |
момент начала компиляции метода |
method-compile-end |
момент окончания компиляции метода |
compiled-method-load |
момент начала загрузки скомпилированного метода в VM |
compiled-method-unload |
момент окончания загрузки скомпилированного метода в VM |
| Monitor Probes: пробы, связанные с работой с Java мониторами | |
monitor-contended-enter |
момент начала захвата монитора, уже захваченного другим потоком |
monitor-contended-exit |
момент освобождения потоком монитора, который в данный момент пытается захватить другой поток |
monitor-wait |
момент, когда поток входит в состояние ожидания в результате вызова Object.wait() |
monitor-waited |
момент завершения ожидания Object.wait() в силу истечения времени ожидания (timeout) или получения нотификации от другого потока с помощью Object.notify() или Object.notifyAll() |
monitor-notify |
момент вызова Object.notify() |
monitor-notifyAll |
момент вызова Object.notifyAll() |
| Application Tracking probes: пробы, связанные с работой приложения | |
method-entry |
момент входа в Java метод |
method-exit |
момент выхода из Java метода |
object-alloc |
момент создания Java объекта |
Более подробно о каждой группе проб, об аргументах, предаваемых в пробы мы расскажем в следующих постах. Пока же, приведем простой пример наблюдения за работой сборщика мусора.
Напишем D-скрипт gc1.d, в котором будем отслеживать пробы hotspot:::gc-begin и hotspot:::gc-end.
% cat gc1.d #!/usr/sbin/dtrace -s
#pragma D option quiet
hotspot$target:::gc-begin
{
printf("GC started at %Y\n", walltimestamp);
}
hotspot$target:::gc-end
{
printf("GC finished at %Y\n", walltimestamp);
} %
Поскольку hotspot провайдер определяется в рамках процесса, то полное имя провайдера на самом деле является hotspotPID, где PID - номер конкретного процесса. В данном примере используется макро переменная $target, которая указывает на pid Java процесса, который передается в командной строке DTrace-скрипту.
Теперь запусттим приложение Java, например, jconsole (начиная с версии JDK 6.0 jconsole входит в стандартный дистрибутив Java SE Development Kit).
% jconsole & %
Сделаем файл gc1.d исполняемым и запустим, передав в качестве аргумента идентификатор процесса (говоря по-русски, PID) jconsole:
% ./gc1.d -p 33322 GC started at 2006 Apr 26 20:44:56
GC finished at 2006 Apr 26 20:44:56
GC started at 2006 Apr 26 20:44:56
GC finished at 2006 Apr 26 20:44:56
GC started at 2006 Apr 26 20:44:59
GC finished at 2006 Apr 26 20:45:00
...
%
Интересно, почему так часто запускается GC и что происходит между его вызовами? Чтобы узнать это, можно усложнить скрипт, например, добавив пробы, информирующие о деятельности отдельных коллекторов или о создании новых Java объектов. Но об этом в следующих постах.
Провайдер hotspot_jni
Данный провайдер предоставляет DTrace набор пробов для входа и выхода для каждого JNI метода. То есть, для каждого JNI метода имеется пара проб: hotspot_jni$pid:::JNIMETHODNAME-entry и hotspot_jni:::JNIMETHODNAME-return. Поскольку методов в JNI более двухсот, то для иллюстрации приведем только несколько проб:
hotspot_jni:::AttachCurrentThread-entry hotspot_jni:::AttachCurrentThread-return hotspot_jni:::CallVoidMethod-entry hotspot_jni:::CallVoidMethod-return hotspot_jni:::GetStringLength-entry hotspot_jni:::GetStringLength-return ...
Аргументы, переданные в соответствующие методы JNI (за исключением Invocation API) являются одновременно и аргументами проб.
Продемонстрируем работу с hotspot_jni пробами на примере скрипта, считающего статистику вызовов методов JNI и по окончнию работы скрипта выводящий данные на терминал.
Сначала пишем D-скрипт hotspot_jni_1.d, и, как обычно, делаем его исполняемым.
#!/usr/sbin/dtrace -s #pragma D option quiet hotspot_jni$target:::*-entry { JNI_CALLS ++; @jni_calls[probename] = count(); } :::END { printa("%10@d %s\n", @jni_calls); printf("\n"); printf("Total number of JNI calls: %d\n", JNI_CALLS); }
Опять запускаем jconsole, а затем—hotspot_jni_1.d, передавая в качестве аргумента номер процесса jconsole:
% jconsole & % ./hotspot_jni_1.d -p 69
2 CallObjectMethod-entry
2 GetStringCritical-entry
2 NewObject-entry
2 NewObjectArray-entry
2 ReleaseStringCritical-entry
2 SetIntField-entry
2 SetObjectArrayElement-entry
2 SetObjectField-entry
4 NewStringUTF-entry
26 GetObjectClass-entry
28 CallStaticVoidMethod-entry
74 GetByteArrayRegion-entry
74 SetByteArrayRegion-entry
148 GetObjectField-entry
260 GetStringUTFLength-entry
260 GetStringUTFRegion-entry
262 GetStringLength-entry
416 IsAssignableFrom-entry
500 GetSuperclass-entry
840 IsInstanceOf-entry
... %
Результат потрясающий, а главное—все так просто! :)
Полезные ссылки
Solaris 10
http://www.sun.com/software/solaris/
http://www.sun.com/bigadmin/products/sol10.html
http://www.opensolaris.org/os/DTrace
Общество любителей DTrace
Официальное руководство по DTrace
Коллекция примеров и D-скриптов
Немного историиБлоги
Глеб Рейс
Bryan Cantrill—ведущий разработчика DTrace
Adam Leventhal—один из авторов DTrace
А также много других блогов
Е.П. опубликовал vmrobot ( май 02 2006, 06:42:21 AM MSD ) Permalink Комментарии [1]


опубликовал somebody Июнь 04, 2007 at 08:35 PM MSD #