Однажды у нас был случай, когда скрипт на php непостижимым образом выдавал лишнюю пустую строку в генерируемой странице. А страница импортировалась в поток RSS у разных хороших людей. То есть должна была, но не импортировалась, потому что лишняя пустая строка все портила. Тогда дело решилось внимательным изучением всех включаемых файлов .php и обнаружением лишней строки. Однако бывают и более сложные случаи, когда при отладке web-приложений хочется понять, как на самом деле это приложение работает. Сейчас очень многие веб-сайты, даже довольно простые, строятся на основе системы управления контентом (CMS,
Можем ли мы в отладке веб-приложений применить DTrace для того, чтобы отследить все передаваемые от веб-обозревателя к базе данных (и обратно) параметры на всем пути их следования через скриптыобработчики? Если мы используем Solaris – то да. И более того, легко.
Вспомним, что технология DTrace в настоящее время воплощена в Solaris (начиная с Solaris 10) и портирована в Mac OS X Leopard, FreeBSD 6.2 (частично) и DTrace могут быть еще недоступны для скачивания. Однако если вы хотите инструментировать любое приложение с открытым кодом, используя DTrace, никто не вправе помешать вам. Описание того, как это сделать с помощью провайдера (statically defined tracing), можно найти на странице http://www.solarisinternals.com/wiki/index.php/DTrace_Topics_USDT.
Приводимые в статье примеры основаны на наборе DTrace Toolkit, который состоит из 386 скриптов (количество верно для версии DTrace Toolkit 0.99) на языке D и содержит скрипты почти на все случаи жизни, начиная с простых примеров и продолжая трассировкой java-приложений, о которой можно написать отдельную статью. Скачать DTrace Toolkit можно по адресу http://www.opensolaris.org/os/community/dtrace/dtracetoolkit/
Каждый датчик, по срабатыванию которого мы можем выполнять требуемые нам действия в D-скрипте, относится к определенному провайдеру. Системным провайдерам (типа syscall, io и пр.) соответствует одноименный модель ядра, а за провайдеры, созданные сторонними разработчиками, отвечает модуль ядра . Это вам, скорее всего, уже известно из предыдущей статьи про DTrace в декабрьском номере "Системного администратора" (а может быть, вы это узнали еще раньше?) Теперь мы изучим возможности, которые нам предоставляет провайдер javascript, а в следующем разделе разберемся с провайдерами php и postgresql.
В свежих сборках Firefox (забирать по адресу http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/contrib/latest-trunk/) в код вставлены датчики DTrace. Из D-скриптов к ним надо обращаться через провайдер javascript. Для экспериментов мы выбрали firefox-3.0a9pre.en-US.solaris11-i386.tar.bz2. Кстати, имейте в виду, что более старые версии firefox могли включать этот же провайдер под другим именем – mozilla.
Что именно позволяет посмотреть провайдер javascript?
# dtrace -l -n 'javascript*:::'|more ID PROVIDER MODULE FUNCTION NAME 72803 javascript2258 libmozjs.so jsdtrace_execute_done execute-done 72804 javascript2258 libmozjs.so js_Execute execute-done 72805 javascript2258 libmozjs.so jsdtrace_execute_start execute-start 72806 javascript2258 libmozjs.so js_Execute execute-start ... (вывод команды сокращен)
Как видно из вывода dtrace, имя провайдера появляется сопряженным с идентификатором процесса firefox-bin, а датчики вводятся для ряда основных функций. Также есть датчики function-entry, funtion-info, function-return, function-rval, object-create, object-create-start, object-createdone и object-create-finalize. Набор датчиков в будущем может быть расширен, но в той версии mozilla, которая оказалась в нашем распоряжении, других датчиков не было.
Какой толк можно получить из срабатывания этих датчиков? Во-первых, можно традиционно трассировать выполнение функции, написанной на javascript, и мерять время выполнения разных ее компонент. Во-вторых, уже сейчас доступна экспериментальная функциональность провайдера javascript по трассировке функций с выводом их аргументов.
Детальную информацию об аргументах датчиков, предназначенных для этого, можно почерпнуть на странице http://news.speeple.com/blogs.sun. com/2007/10/23/dtrace-mozilla-
javascript*::: function-args Args: (char *filename, char *classname, char *funcname, int argc, void *argv, void *argv0,void *argv1, void *argv2, void *argv3, void *argv4) javascript* :::function-rval Args: (char *filename, char *classname, char *funcname, int lineno, void *rval, void *rval0)
Датчик function-args служит для показа аргументов вызванной javascript-функции в скрипте, а function-rval – для показа возвращаемого функцией значения.
Кстати, если под рукой нет документации, то некоторое представление о том, какие аргументы есть у датчика, и следовательно, какие параметры arg0..argN вам требуется выводить командой printf в скрипте, можно получить, используя ключи -l -v команды dtrace (только давать их надо перед ключом -n, если вы его применяете, это важно!)
dtrace -l -v -n 'javascript*:::function-args' 72808 javascript2258 libmozjs.so js_Interpret function-args Probe Description Attributes Identifier Names: Private Data Semantics: Private Dependency Class: Unknown Argument Attributes Identifier Names: Private Data Semantics: Private Dependency Class: Unknown Argument Types args[0]: char * args[1]: char * args[2]: char * args[3]: int args[4]: void * args[5]: void * args[6]: void * args[7]: void * args[8]: void * args[9]: void *
Как видно, датчик function-args может иметь до 9 аргументов, смысл которых пояснен выше, а при определенном воображении о значении аргументов можно догадаться по их типу. Так или иначе, команду dtrace -l -v стоит иметь в виду.
Попробуем выяснить с помощью датчика function-args, какое значение из формы в файле .html передается в написанную нами функцию на javascript. Наш экспериментальный файл и код javascript представляют собой проcтую форму для ввода единственного поля – даты и проверку на формат даты соответственно:
<html>
<head>
<title>Input form</title>
<script language="JavaScript" type="text/javascript">
function testDate(date) {
alert(date);
return(true);
}
function testBox1(form) {
/* Parsing of date and time */
IDate = form.date.value;
if (testDate(IDate)) return (true);
return (false);
}
function runSubmit (form, button) {
if (!testBox1(form)) return;
document.inputform.submit(); // un-comment to actually submit form
return;
}
</script>
</head>
<body>
<form name="inputform" action="/cgi-bin/main.pl">
<input name="date" type="text" size="8"></td>
<input type="button" name="act" value="Submit"
onClick="runSubmit(this.form,
this)">
</form>
</body>
</html>
Функция testDate в этом примере фактически не проверяет дату, а лишь выдает ее значение в окне сообщения, однако вместо такой заглушки можно написать честную проверку. Для демонстрации работы с dtrace она нам не понадобится. В скрипт /cgi-bin/main.pl передаются введенные данные из поля. Листинг main.pl мы не приводим, так как сейчас он не имеет для нас значения – мы изучаем работу функции проверки данных на стороне клиента, а скрипт main.pl работает на сервере.
Запускаем firefox-bin с включенными в него датчиками, открываем файл с вышеприведенным кодом и переходим к эксперименту:
# dtrace -n 'javascript*:::function-args /copyinstr(arg2) ==
"testDate"/ {printf("%s %s %d %s",copyinstr(arg0),copyinstr(arg2),arg
3,copyinstr(arg5));}'
dtrace: description 'javascript*:::function-args ' matched 4 probes
CPU ID FUNCTION:NAME
1 18348 jsdtrace_function_args:function-args file:///export/home/
filip/jstest.html testDate 1 00.00.00
Как видно из вывода нашего скрипта, в качестве даты мы вводили значение 00.00.00. Будем надеяться, что настоящая функция проверки даты такого безобразия не пропустит!
Мы уже знаем, что с помощью DTrace можно выяснить, какие конкретно функции вызываются, в каком порядке, какие аргументы передаются и сколько времени потрачено на их выполнение. Все это могло вдохновить системных администраторов и разработчиков на языках С и С++, а интересы программистов на Java оставались в стороне. В этой статье мы рассмотрим, как можно использовать DTrace для отладки приложений на Java.
Для предоставления такой возможности Sun Microsystems встроила в код JVM (начиная с JDK 6.0) датчики двух провайдеров – hotspot и hotspot_jni. В JDK 5.0 был провайдер . Для тех, кто привык им пользоваться, есть хорошая новость – датчики провайдера hotspot носят такие же имена, как и датчики провайдера .
Провайдер hotspot имеет ряд датчиков, с помощью которых можно отслеживать запуск и работу сборщика мусора в JVM (
Провайдер hotspot_jni требуется для отслеживания событий, связанных с обращениями через
Уже было упомянуто, что провайдеры hotspot и hotspot_jni основаны на провайдере и на их датчики можно ссылаться только с упоминанием идентификатора процесса самой машины Java (JVM). Вот пример скрипта, который показывает частоту вызова GC:
hotspot$target:::gc-begin
{
printf("GC called at %Y\n", walltimestamp);
}
Если запустить выполнение программы на java, скажем, демонстрационный пример /usr/jdk/instances/jdk1.6.0/demo/
# java -jar /usr/jdk/instances/jdk1.6.0/demo/jfc/Java2D/Java2Demo.jar
[1] 1147
# dtrace -qn 'hotspot$target:::gc-begin
{
printf("GC called at %Y\n", walltimestamp);
}' -p 1147
GC called at 2008 Feb 7 01:50:15
GC called at 2008 Feb 7 01:50:16
GC called at 2008 Feb 7 01:50:17
GC called at 2008 Feb 7 01:50:18
GC called at 2008 Feb 7 01:50:19
GC called at 2008 Feb 7 01:50:20
GC called at 2008 Feb 7 01:50:21
GC called at 2008 Feb 7 01:50:25
А что, если нам надо выяснить, какие методы вызываются в приложении Java? Возьмем вот такое приложение:
# cat Greeting.java
public class Greeting {
public void greet() {
System.out.println("Hello DTrace!");
}
}
# cat TestGreeting.java
public class TestGreeting {
public static void main(String[] args) {
Greeting hello = new Greeting();
while (true) {
hello.greet();
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
Скомпилируем его:
javac TestGreeting.java
Теперь надо запустить получившееся приложение (мирно лежащее в TestGreeting.class ). Однако прежде надо разобраться, как включать датчики в Java SE 6. По умолчанию в виртуальной машине Java доступен лишь небольшой набор датчиков – тех, которые практически не влияют на производительность. Эти датчики позволяют отследить не все события: сборку мусора, компиляцию методов, создание нового потока команд и загрузку класса.
Вызов метода, например, в этот список не входит, потому что соответствующий датчик оказывает значимое влияние на производительность и оттого по умолчанию недоступен. К таким датчикам, кроме датчиков вызова методов, также относятся датчики создания объектов и датчики событий, связанных с мониторами Java. Чтобы сделать такой датчик доступным и "видимым" для dtrace, надо запускать java с ключами, разрешающими использование этих датчиков, или (если JVM уже запущена) использовать команду jinfo для управления JVM.
Доступны следующие ключи при запуске java:
-XX:+DTraceAllocProbes -XX:+DTraceMethodProbes -XX:+DTraceMonitorProbes -XX:+ExtendedDTraceProbes
Первые три ключа разрешают использование датчиков создания объектов, вызова методов и датчиков событий, связанных с мониторами Java соответственно, а четвертый разрешает использование всех датчиков в JVM. Фактическое влияние включенных датчиков на производительность может быть различным в зависимости от частоты вызова кода, содержащего встроенный датчик.
Запускаем наше приложение:
# java -XX:+ExtendedDTraceProbes TestGreeting [1] 1448 Hello DTrace! Hello DTrace! Hello DTrace! Hello DTrace!
Возьмем скрипт hs.d, вот такой:
# cat hs.d
hotspot$target:::method-entry
{
printf("%s.\%s %s\n",copyinstr(arg1,arg2),copyinstr(arg3,arg4),
copyinstr(arg5,arg6));
}
tick-5ms
{
exit(0);
}
Конструкция hotspot$target означает, что вместо $target будет подставлен аргумент, переданный с ключом p при запуске dtrace (это будет идентификатор процесса java).
Из описания датчиков провайдера hotspot (http://java.sun.com/ javase/6/docs/technotes/guides/vm/dtrace.html) известно, что аргументы датчика method-entry следующие:
args[0] |
идентификатор того потока команд (Java thread ID), который вызывает метод |
args[1] |
имя класса метода, указатель на строку в кодировке UTF-8 |
args[2] |
длина имени класса метода (в байтах) |
args[3] |
имя метода, указатель на строку в кодировке UTF-8 |
args[4] |
длина имени метода (в байтах) |
args[5] |
сигнатура метода, указатель на строку |
args[6] |
длина сигнатуры метода (в байтах) |
Сигнатура метода – это совокупность имени функции, типа возвращаемого значения и списка аргументов с указанием порядка их следования и типов. Подробнее об обозначениях, применяемых в сигнатурах, можно прочесть, например, в разделе Type Signatures в спецификации
Наш скрипт должен при каждом срабатывании датчика methodentry, т.е. при каждом вызове любого метода в нашем приложении на Java, выводить имя класса, имя метода и подпись. Обратите внимание на использование функции copyinstr для вывода строк.
Для автоматического завершения скрипта через 5 миллисекунд добавлена конструкция tick-5ms.
Запустим скрипт в соседнем окне терминала:
# dtrace -qs hs.d -p 1448 Greeting.\greet ()V java/io/PrintStream.\println (Ljava/lang/String;)V java/io/PrintStream.\print (Ljava/lang/String;)V java/io/PrintStream.\write (Ljava/lang/String;)V java/io/PrintStream.\ensureOpen ()V java/io/Writer.\write (Ljava/lang/String;)V java/io/BufferedWriter.\write (Ljava/lang/String;II)V java/io/BufferedWriter.\ensureOpen ()V java/io/BufferedWriter.\min (II)I java/lang/String.\getChars (II[CI)V java/lang/System.\arraycopy (Ljava/lang/Object;ILjava/lang/Object;II)V java/io/BufferedWriter.\flushBuffer ()V java/io/BufferedWriter.\ensureOpen ()V java/io/OutputStreamWriter.\write ([CII)V sun/nio/cs/StreamEncoder.\write ([CII)V sun/nio/cs/StreamEncoder.\ensureOpen ()V sun/nio/cs/StreamEncoder.\implWrite ([CII)V java/nio/CharBuffer.\wrap ([CII)Ljava/nio/CharBuffer; java/nio/HeapCharBuffer.\<init> ([CII)V java/nio/CharBuffer.\<init> (IIII[CI)V java/nio/Buffer.\<init> (IIII)V java/lang/Object.\<init> ()V
Функциональность DTrace становится все популярнее, и ее добавляют не только в Java, но и в другие приложения. Далее мы рассмотрим изучение работы скриптов на php и СУБД, с которой они общаются, с помощью dtrace.
Как и в случае с JVM, нам потребуется php с встроенными в код датчиками. Для этого потребуется скачать и установить модуль dtrace.so для PHP. В Solaris Express Developer Edition 1/08 модуль уже есть – /usr/. Когда модуль уже готов к работе, надо вписать в
extension=dtrace.so
Проверяем, есть ли поддержка dtrace в PHP:
# /usr/php5/5.2.4/bin/php -i | grep -i dtrace /etc/php5/5.2.4/conf.d/dtrace.ini, dtrace dtrace support => enabled
Теперь предположим, что у нас есть база данных, которая состоит из единственной таблицы, где хранится наша записная книжка. Чтобы облегчить задачу, предположим, что в ней записаны только имена, фамилии и телефоны наших знакомых:
CREATE TABLE notebook ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), lastname VARCHAR(50), phone VARCHAR(15) );
Теперь напишем скрипт на php, который будет извлекать из этой таблицы сведения:
<?php
$dbname="personal";
$dbhost="localhost";
$dblogin="filip";
$dbpass="9bdx57";
$link = mysql_pconnect($dbhost, $dblogin, $dbpass);
mysql_select_db ($dbname,$link) or die ('Connection Error.');
$query = "SELECT name , lastname , phone FROM notebook";
$result = mysql_query($query) or die ('Execution error. ' .
mysql_error());
$message = '';
while ($row = mysql_fetch_array($result)) {
$message = $message . sprintf("%s %% %s %% %s
\n",$row[0],$row[1],$row[2]);
}
echo $message;
mysql_free_result($result);
?>
Пропустим здесь этап заполнения базы данных сведениями и предположим, что данные в таблице DTrace в php? Провайдер php предоставляет два датчика, срабатывающих при вызове функции и окончании ее работы: function-entry и function-return.
Аргументы этих датчиков:
arg0 – имя вызванной функции (указатель на строку внутри PHP);arg1 – имя файла скрипта (указатель на строку внутри PHP);arg2 – номер строки, откуда вызвана функция.Возьмем простой скрипт для трассировки работы нашего приложения на php:
#!/usr/sbin/dtrace -Zqs
php*:::function-entry
{
printf("called %s() in %s at line %d\n",copyinstr(arg0),
copyinstr(arg1),arg2)
}
Ключ Z позволяет запустить скрипт, даже если дескриптор ( php*:::function-entry ) не соответствует ни одному датчику (а это в момент запуска скрипта будет именно так, потому что мы еще на запустили скрипт и модуль dtrace.so не загружен). В одном окне запустим наше приложение на php:
php simpledb.php Philip % Rock % +74951110808 Ivan % Markov % +74951110808 а в другом – скрипт dtrace для отслеживания его работы: ./php-trace.d called mysql_pconnect() in /export/home/filip/simpledb.php at line 7 called mysql_select_db() in /export/home/filip/simpledb.php at line 8 called mysql_query() in /export/home/filip/simpledb.php at line 11 called mysql_fetch_array() in /export/home/filip/simpledb.php at line 15 called sprintf() in /export/home/filip/simpledb.php at line 16 called mysql_fetch_array() in /export/home/filip/simpledb.php at line 15 called sprintf() in /export/home/filip/simpledb.php at line 16 called mysql_fetch_array() in /export/home/filip/simpledb.php at line 15 called mysql_free_result() in /export/home/filip/simpledb.php at line 20
Теперь мы знаем больше о том, как вызываются функции в нашем скрипте на php.
А еще можно выяснить, какие команды наше приложение передает серверу баз данных и сколько времени он на это тратит.
Мы рассмотрим возможности по динамическому наблюдению за сервером СУБД, которые предоставляет вживленный в MySQL и Postgresql код. По имеющимся сейчас сведениям от разработчиков MySQL провайдер mysql планируется включить в код СУБД в версии 6.0.4. Однако уже в версии 6.0 можно обнаружить предварительный вариант кода этого провайдера, и если вы хотите его попробовать уже сейчас, это можно сделать, указав при установке из исходных текстов ключ --enable-dtrace при вызове ./configure.
Более детально можно познакомиться с инструкциями по использованию DTrace в MySQL 6.0 на форуме пользователей DTrace по адресу http://www.opensolaris.org/jive/forum.jspa?forumID=7.
Здесь мы рассмотрим более хитрую методику отлова команд, переданных серверу MySQL, а затем перейдем к работе с postgresql, который уже давно поставляется с встроенными датчиками DTrace.
Дело в том, что с помощью провайдера pid мы можем получить информацию о вызове любой функции в любом приложении. Кстати, если нас больше интересует производительность ввода-вывода СУБД, то лучше обратиться к провайдеру io, но это уже предмет другой статьи.
В MySQL обработка запросов выполняется функцией mysql_parse
mysql_parse(THD *thd, char *inBuf, uint length)
Название ее второго аргумента звучит многообещающе. Попробуем следующий скрипт для того, чтобы получить
cat mysql-catch.d
pid$target::*mysql_parse*:entry
{
printf("%Y %s\n", walltimestamp, copyinstr(arg1));
}
Запустим его в одном окне
# dtrace -qs mysql-catch.d -p 1019
А в другом запустим уже знакомый скрипт на php для получения содержимого нашей записной книжки:
# php simpledb.php Philip % Rock % +74951110808 Ivan % Markov % +74951110808
В первом окне мы получим ответ скрипта:
2008 Feb 7 22:58:35 SELECT name , lastname , phone FROM notebook
Готово!
Только придется помнить, что если на сервере запущено несколько экземпляров mysqld, надо позаботиться об указании правильного идентификатора процесса: это обязательно при работе с датчиками провайдера pid. Представленный скрипт справляется с тем, чтобы сообщить нам запрос, отправленный серверу. Может случиться так, что сервер отвергнет запрос – из-за отсутствия права доступа к данным у запрашивающего или из-за некорректности запроса.
Для анализа значения, которое возвращает функция, можно воспользоваться датчиком
pid$target::*mysql_execute_command*:return .
В arg1 у датчиков провайдера pid находится код возврата. Успешное завершение операции сервером MySQL даст код 0, и ненулевое значение — в противном случае.
Для контроля за выполнением операций достаточно добавить в скрипт mysql-catch.d еще один компонент:
pid$target::*mysql_execute_command*:return
{
printf("%Y %d \n", walltimestamp, arg1);
}
Теперь разберемся с Postgresql.
Начиная с версии postgresql 8.2 в него встроен код провайдера DTrace — posgresql.
Стало быть, для анализа запросов к этой СУБД можно использовать встроенные датчики, не исследуя исходный код сервера. Вот перечень этих датчиков:
probe transaction__start(int); probe transaction__commit(int); probe transaction__abort(int); probe lwlock__acquire(int, int); probe lwlock__release(int); probe lwlock__startwait(int, int); probe lwlock__endwait(int, int); probe lwlock__condacquire(int, int); probe lwlock__condacquire__fail(int, int); probe lock__startwait(int, int); probe lock__endwait(int, int);
Воспользуемся датчиками transaction-start и transaction-end для получения распределения времени транзакций:
#!/usr/sbin/dtrace -Zqs
postgresql*:::transaction-start
{
self->ts=timestamp;
@cnt[pid]=count();
}
postgresql*:::transaction-commit
{
@avg[pid]=avg(timestamp - self->ts);
}
tick-5sec
{
normalize(@avg, 1000000);
printf("%15s %30s %30s\n","PID","Total queries","Avegrage time (ms)");
printf("\t=================================================\n\n");
printa("%15d %@30d %@30d\n",@cnt,@avg);
printf("\t=================================================\n\n");
clear(@cnt);
clear(@avg);
}
Запуск этого скрипта даст нам реальную картину затрат времени на транзакции. Возможно, в вашем случае распределение будет иным – все зависит от фактической загрузки сервера:
# ./postgres_avg_query_time.d PID Total queries Avegrage time (ms) ========================================================== 23814 46 57 23817 58 34 23816 59 32 23815 59 33 23818 75 26 ==========================================================
А теперь обратимся к уже хорошо знакомому нам провайдеру pid и напишем скрипт для отслеживания запросов к СУБД (обратите внимание: имя функции в postgresql иное, нежели в mysql ):
#!/usr/sbin/dtrace -Zwqs
BEGIN
{
freopen("sql.trace");
}
pid$1::pg_parse_query:entry
{
printf("%s\n",copyinstr(arg0));
}
Обратите внимание на вызов freopen("sql.trace") – так мы перенаправим вывод скрипта с экрана в файл с указанным именем.
В этой лекции мы познакомились с тем, как использовать DTrace для анализа различных приложений, в том числе приложений на javascript, Java, PHP и SQL. Фактически, это дает нам полный набор инструментов для отладки как клиентской части приложений Web 2.0, так и серверной их части.
Мы старались показать, что даже если какое-то приложение не имеет встроенных датчиков DTrace, вы всегда можете либо добавить их в исходный код (как уже сделали разработчики PostgreSQL, например), либо использовать известные вам функции для анализа передаваемых ими аргументов и возвращаемых значений – с помощью провайдера pid.
Еще более подробную информацию о DTrace можно почерпнуть в руководстве "Dynamic Tracing Guide", где приводится подробное описание этих провайдеров. Этот документ вкупе с "DTrace User Guide" содержит полную документацию по DTrace и находится в свободном доступе на сайте http://docs.sun.com/. Также можно посоветовать страницу сообщества разработчиков DTrace на cайте opensolaris.org: http://opensolaris.org/ os/community/dtrace.
Однажды у нас был случай, когда скрипт на php непостижимым образом выдавал лишнюю пустую строку в генерируемой странице. А страница импортировалась в поток RSS у разных хороших людей. То есть должна была, но не импортировалась, потому что лишняя пустая строка все портила. Тогда дело решилось внимательным изучением всех включаемых файлов .php и обнаружением лишней строки. Однако бывают и более сложные случаи, когда при отладке web-приложений хочется понять, как на самом деле это приложение работает. Сейчас очень многие веб-сайты, даже довольно простые, строятся на основе системы управления контентом (CMS,
Можем ли мы в отладке веб-приложений применить DTrace для того, чтобы отследить все передаваемые от веб-обозревателя к базе данных (и обратно) параметры на всем пути их следования через скриптыобработчики? Если мы используем Solaris – то да. И более того, легко.
Вспомним, что технология DTrace в настоящее время воплощена в Solaris (начиная с Solaris 10) и портирована в Mac OS X Leopard, FreeBSD 6.2 (частично) и DTrace могут быть еще недоступны для скачивания. Однако если вы хотите инструментировать любое приложение с открытым кодом, используя DTrace, никто не вправе помешать вам. Описание того, как это сделать с помощью провайдера (statically defined tracing), можно найти на странице http://www.solarisinternals.com/wiki/index.php/DTrace_Topics_USDT.
Приводимые в статье примеры основаны на наборе DTrace Toolkit, который состоит из 386 скриптов (количество верно для версии DTrace Toolkit 0.99) на языке D и содержит скрипты почти на все случаи жизни, начиная с простых примеров и продолжая трассировкой java-приложений, о которой можно написать отдельную статью. Скачать DTrace Toolkit можно по адресу http://www.opensolaris.org/os/community/dtrace/dtracetoolkit/
Каждый датчик, по срабатыванию которого мы можем выполнять требуемые нам действия в D-скрипте, относится к определенному провайдеру. Системным провайдерам (типа syscall, io и пр.) соответствует одноименный модель ядра, а за провайдеры, созданные сторонними разработчиками, отвечает модуль ядра . Это вам, скорее всего, уже известно из предыдущей статьи про DTrace в декабрьском номере "Системного администратора" (а может быть, вы это узнали еще раньше?) Теперь мы изучим возможности, которые нам предоставляет провайдер javascript, а в следующем разделе разберемся с провайдерами php и postgresql.
В свежих сборках Firefox (забирать по адресу http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/contrib/latest-trunk/) в код вставлены датчики DTrace. Из D-скриптов к ним надо обращаться через провайдер javascript. Для экспериментов мы выбрали firefox-3.0a9pre.en-US.solaris11-i386.tar.bz2. Кстати, имейте в виду, что более старые версии firefox могли включать этот же провайдер под другим именем – mozilla.
Что именно позволяет посмотреть провайдер javascript?
# dtrace -l -n 'javascript*:::'|more ID PROVIDER MODULE FUNCTION NAME 72803 javascript2258 libmozjs.so jsdtrace_execute_done execute-done 72804 javascript2258 libmozjs.so js_Execute execute-done 72805 javascript2258 libmozjs.so jsdtrace_execute_start execute-start 72806 javascript2258 libmozjs.so js_Execute execute-start ... (вывод команды сокращен)
Как видно из вывода dtrace, имя провайдера появляется сопряженным с идентификатором процесса firefox-bin, а датчики вводятся для ряда основных функций. Также есть датчики function-entry, funtion-info, function-return, function-rval, object-create, object-create-start, object-createdone и object-create-finalize. Набор датчиков в будущем может быть расширен, но в той версии mozilla, которая оказалась в нашем распоряжении, других датчиков не было.
Какой толк можно получить из срабатывания этих датчиков? Во-первых, можно традиционно трассировать выполнение функции, написанной на javascript, и мерять время выполнения разных ее компонент. Во-вторых, уже сейчас доступна экспериментальная функциональность провайдера javascript по трассировке функций с выводом их аргументов.
Детальную информацию об аргументах датчиков, предназначенных для этого, можно почерпнуть на странице http://news.speeple.com/blogs.sun. com/2007/10/23/dtrace-mozilla-
javascript*::: function-args Args: (char *filename, char *classname, char *funcname, int argc, void *argv, void *argv0,void *argv1, void *argv2, void *argv3, void *argv4) javascript* :::function-rval Args: (char *filename, char *classname, char *funcname, int lineno, void *rval, void *rval0)
Датчик function-args служит для показа аргументов вызванной javascript-функции в скрипте, а function-rval – для показа возвращаемого функцией значения.
Кстати, если под рукой нет документации, то некоторое представление о том, какие аргументы есть у датчика, и следовательно, какие параметры arg0..argN вам требуется выводить командой printf в скрипте, можно получить, используя ключи -l -v команды dtrace (только давать их надо перед ключом -n, если вы его применяете, это важно!)
dtrace -l -v -n 'javascript*:::function-args' 72808 javascript2258 libmozjs.so js_Interpret function-args Probe Description Attributes Identifier Names: Private Data Semantics: Private Dependency Class: Unknown Argument Attributes Identifier Names: Private Data Semantics: Private Dependency Class: Unknown Argument Types args[0]: char * args[1]: char * args[2]: char * args[3]: int args[4]: void * args[5]: void * args[6]: void * args[7]: void * args[8]: void * args[9]: void *
Как видно, датчик function-args может иметь до 9 аргументов, смысл которых пояснен выше, а при определенном воображении о значении аргументов можно догадаться по их типу. Так или иначе, команду dtrace -l -v стоит иметь в виду.
Попробуем выяснить с помощью датчика function-args, какое значение из формы в файле .html передается в написанную нами функцию на javascript. Наш экспериментальный файл и код javascript представляют собой проcтую форму для ввода единственного поля – даты и проверку на формат даты соответственно:
<html>
<head>
<title>Input form</title>
<script language="JavaScript" type="text/javascript">
function testDate(date) {
alert(date);
return(true);
}
function testBox1(form) {
/* Parsing of date and time */
IDate = form.date.value;
if (testDate(IDate)) return (true);
return (false);
}
function runSubmit (form, button) {
if (!testBox1(form)) return;
document.inputform.submit(); // un-comment to actually submit form
return;
}
</script>
</head>
<body>
<form name="inputform" action="/cgi-bin/main.pl">
<input name="date" type="text" size="8"></td>
<input type="button" name="act" value="Submit"
onClick="runSubmit(this.form,
this)">
</form>
</body>
</html>
Функция testDate в этом примере фактически не проверяет дату, а лишь выдает ее значение в окне сообщения, однако вместо такой заглушки можно написать честную проверку. Для демонстрации работы с dtrace она нам не понадобится. В скрипт /cgi-bin/main.pl передаются введенные данные из поля. Листинг main.pl мы не приводим, так как сейчас он не имеет для нас значения – мы изучаем работу функции проверки данных на стороне клиента, а скрипт main.pl работает на сервере.
Запускаем firefox-bin с включенными в него датчиками, открываем файл с вышеприведенным кодом и переходим к эксперименту:
# dtrace -n 'javascript*:::function-args /copyinstr(arg2) ==
"testDate"/ {printf("%s %s %d %s",copyinstr(arg0),copyinstr(arg2),arg
3,copyinstr(arg5));}'
dtrace: description 'javascript*:::function-args ' matched 4 probes
CPU ID FUNCTION:NAME
1 18348 jsdtrace_function_args:function-args file:///export/home/
filip/jstest.html testDate 1 00.00.00
Как видно из вывода нашего скрипта, в качестве даты мы вводили значение 00.00.00. Будем надеяться, что настоящая функция проверки даты такого безобразия не пропустит!
Мы уже знаем, что с помощью DTrace можно выяснить, какие конкретно функции вызываются, в каком порядке, какие аргументы передаются и сколько времени потрачено на их выполнение. Все это могло вдохновить системных администраторов и разработчиков на языках С и С++, а интересы программистов на Java оставались в стороне. В этой статье мы рассмотрим, как можно использовать DTrace для отладки приложений на Java.
Для предоставления такой возможности Sun Microsystems встроила в код JVM (начиная с JDK 6.0) датчики двух провайдеров – hotspot и hotspot_jni. В JDK 5.0 был провайдер . Для тех, кто привык им пользоваться, есть хорошая новость – датчики провайдера hotspot носят такие же имена, как и датчики провайдера .
Провайдер hotspot имеет ряд датчиков, с помощью которых можно отслеживать запуск и работу сборщика мусора в JVM (
Провайдер hotspot_jni требуется для отслеживания событий, связанных с обращениями через
Уже было упомянуто, что провайдеры hotspot и hotspot_jni основаны на провайдере и на их датчики можно ссылаться только с упоминанием идентификатора процесса самой машины Java (JVM). Вот пример скрипта, который показывает частоту вызова GC:
hotspot$target:::gc-begin
{
printf("GC called at %Y\n", walltimestamp);
}
Если запустить выполнение программы на java, скажем, демонстрационный пример /usr/jdk/instances/jdk1.6.0/demo/
# java -jar /usr/jdk/instances/jdk1.6.0/demo/jfc/Java2D/Java2Demo.jar
[1] 1147
# dtrace -qn 'hotspot$target:::gc-begin
{
printf("GC called at %Y\n", walltimestamp);
}' -p 1147
GC called at 2008 Feb 7 01:50:15
GC called at 2008 Feb 7 01:50:16
GC called at 2008 Feb 7 01:50:17
GC called at 2008 Feb 7 01:50:18
GC called at 2008 Feb 7 01:50:19
GC called at 2008 Feb 7 01:50:20
GC called at 2008 Feb 7 01:50:21
GC called at 2008 Feb 7 01:50:25
А что, если нам надо выяснить, какие методы вызываются в приложении Java? Возьмем вот такое приложение:
# cat Greeting.java
public class Greeting {
public void greet() {
System.out.println("Hello DTrace!");
}
}
# cat TestGreeting.java
public class TestGreeting {
public static void main(String[] args) {
Greeting hello = new Greeting();
while (true) {
hello.greet();
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
Скомпилируем его:
javac TestGreeting.java
Теперь надо запустить получившееся приложение (мирно лежащее в TestGreeting.class ). Однако прежде надо разобраться, как включать датчики в Java SE 6. По умолчанию в виртуальной машине Java доступен лишь небольшой набор датчиков – тех, которые практически не влияют на производительность. Эти датчики позволяют отследить не все события: сборку мусора, компиляцию методов, создание нового потока команд и загрузку класса.
Вызов метода, например, в этот список не входит, потому что соответствующий датчик оказывает значимое влияние на производительность и оттого по умолчанию недоступен. К таким датчикам, кроме датчиков вызова методов, также относятся датчики создания объектов и датчики событий, связанных с мониторами Java. Чтобы сделать такой датчик доступным и "видимым" для dtrace, надо запускать java с ключами, разрешающими использование этих датчиков, или (если JVM уже запущена) использовать команду jinfo для управления JVM.
Доступны следующие ключи при запуске java:
-XX:+DTraceAllocProbes -XX:+DTraceMethodProbes -XX:+DTraceMonitorProbes -XX:+ExtendedDTraceProbes
Первые три ключа разрешают использование датчиков создания объектов, вызова методов и датчиков событий, связанных с мониторами Java соответственно, а четвертый разрешает использование всех датчиков в JVM. Фактическое влияние включенных датчиков на производительность может быть различным в зависимости от частоты вызова кода, содержащего встроенный датчик.
Запускаем наше приложение:
# java -XX:+ExtendedDTraceProbes TestGreeting [1] 1448 Hello DTrace! Hello DTrace! Hello DTrace! Hello DTrace!
Возьмем скрипт hs.d, вот такой:
# cat hs.d
hotspot$target:::method-entry
{
printf("%s.\%s %s\n",copyinstr(arg1,arg2),copyinstr(arg3,arg4),
copyinstr(arg5,arg6));
}
tick-5ms
{
exit(0);
}
Конструкция hotspot$target означает, что вместо $target будет подставлен аргумент, переданный с ключом p при запуске dtrace (это будет идентификатор процесса java).
Из описания датчиков провайдера hotspot (http://java.sun.com/ javase/6/docs/technotes/guides/vm/dtrace.html) известно, что аргументы датчика method-entry следующие:
args[0] |
идентификатор того потока команд (Java thread ID), который вызывает метод |
args[1] |
имя класса метода, указатель на строку в кодировке UTF-8 |
args[2] |
длина имени класса метода (в байтах) |
args[3] |
имя метода, указатель на строку в кодировке UTF-8 |
args[4] |
длина имени метода (в байтах) |
args[5] |
сигнатура метода, указатель на строку |
args[6] |
длина сигнатуры метода (в байтах) |
Сигнатура метода – это совокупность имени функции, типа возвращаемого значения и списка аргументов с указанием порядка их следования и типов. Подробнее об обозначениях, применяемых в сигнатурах, можно прочесть, например, в разделе Type Signatures в спецификации
Наш скрипт должен при каждом срабатывании датчика methodentry, т.е. при каждом вызове любого метода в нашем приложении на Java, выводить имя класса, имя метода и подпись. Обратите внимание на использование функции copyinstr для вывода строк.
Для автоматического завершения скрипта через 5 миллисекунд добавлена конструкция tick-5ms.
Запустим скрипт в соседнем окне терминала:
# dtrace -qs hs.d -p 1448 Greeting.\greet ()V java/io/PrintStream.\println (Ljava/lang/String;)V java/io/PrintStream.\print (Ljava/lang/String;)V java/io/PrintStream.\write (Ljava/lang/String;)V java/io/PrintStream.\ensureOpen ()V java/io/Writer.\write (Ljava/lang/String;)V java/io/BufferedWriter.\write (Ljava/lang/String;II)V java/io/BufferedWriter.\ensureOpen ()V java/io/BufferedWriter.\min (II)I java/lang/String.\getChars (II[CI)V java/lang/System.\arraycopy (Ljava/lang/Object;ILjava/lang/Object;II)V java/io/BufferedWriter.\flushBuffer ()V java/io/BufferedWriter.\ensureOpen ()V java/io/OutputStreamWriter.\write ([CII)V sun/nio/cs/StreamEncoder.\write ([CII)V sun/nio/cs/StreamEncoder.\ensureOpen ()V sun/nio/cs/StreamEncoder.\implWrite ([CII)V java/nio/CharBuffer.\wrap ([CII)Ljava/nio/CharBuffer; java/nio/HeapCharBuffer.\<init> ([CII)V java/nio/CharBuffer.\<init> (IIII[CI)V java/nio/Buffer.\<init> (IIII)V java/lang/Object.\<init> ()V
Функциональность DTrace становится все популярнее, и ее добавляют не только в Java, но и в другие приложения. Далее мы рассмотрим изучение работы скриптов на php и СУБД, с которой они общаются, с помощью dtrace.
Как и в случае с JVM, нам потребуется php с встроенными в код датчиками. Для этого потребуется скачать и установить модуль dtrace.so для PHP. В Solaris Express Developer Edition 1/08 модуль уже есть – /usr/. Когда модуль уже готов к работе, надо вписать в
extension=dtrace.so
Проверяем, есть ли поддержка dtrace в PHP:
# /usr/php5/5.2.4/bin/php -i | grep -i dtrace /etc/php5/5.2.4/conf.d/dtrace.ini, dtrace dtrace support => enabled
Теперь предположим, что у нас есть база данных, которая состоит из единственной таблицы, где хранится наша записная книжка. Чтобы облегчить задачу, предположим, что в ней записаны только имена, фамилии и телефоны наших знакомых:
CREATE TABLE notebook ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), lastname VARCHAR(50), phone VARCHAR(15) );
Теперь напишем скрипт на php, который будет извлекать из этой таблицы сведения:
<?php
$dbname="personal";
$dbhost="localhost";
$dblogin="filip";
$dbpass="9bdx57";
$link = mysql_pconnect($dbhost, $dblogin, $dbpass);
mysql_select_db ($dbname,$link) or die ('Connection Error.');
$query = "SELECT name , lastname , phone FROM notebook";
$result = mysql_query($query) or die ('Execution error. ' .
mysql_error());
$message = '';
while ($row = mysql_fetch_array($result)) {
$message = $message . sprintf("%s %% %s %% %s
\n",$row[0],$row[1],$row[2]);
}
echo $message;
mysql_free_result($result);
?>
Пропустим здесь этап заполнения базы данных сведениями и предположим, что данные в таблице DTrace в php? Провайдер php предоставляет два датчика, срабатывающих при вызове функции и окончании ее работы: function-entry и function-return.
Аргументы этих датчиков:
arg0 – имя вызванной функции (указатель на строку внутри PHP);arg1 – имя файла скрипта (указатель на строку внутри PHP);arg2 – номер строки, откуда вызвана функция.Возьмем простой скрипт для трассировки работы нашего приложения на php:
#!/usr/sbin/dtrace -Zqs
php*:::function-entry
{
printf("called %s() in %s at line %d\n",copyinstr(arg0),
copyinstr(arg1),arg2)
}
Ключ Z позволяет запустить скрипт, даже если дескриптор ( php*:::function-entry ) не соответствует ни одному датчику (а это в момент запуска скрипта будет именно так, потому что мы еще на запустили скрипт и модуль dtrace.so не загружен). В одном окне запустим наше приложение на php:
php simpledb.php Philip % Rock % +74951110808 Ivan % Markov % +74951110808 а в другом – скрипт dtrace для отслеживания его работы: ./php-trace.d called mysql_pconnect() in /export/home/filip/simpledb.php at line 7 called mysql_select_db() in /export/home/filip/simpledb.php at line 8 called mysql_query() in /export/home/filip/simpledb.php at line 11 called mysql_fetch_array() in /export/home/filip/simpledb.php at line 15 called sprintf() in /export/home/filip/simpledb.php at line 16 called mysql_fetch_array() in /export/home/filip/simpledb.php at line 15 called sprintf() in /export/home/filip/simpledb.php at line 16 called mysql_fetch_array() in /export/home/filip/simpledb.php at line 15 called mysql_free_result() in /export/home/filip/simpledb.php at line 20
Теперь мы знаем больше о том, как вызываются функции в нашем скрипте на php.
А еще можно выяснить, какие команды наше приложение передает серверу баз данных и сколько времени он на это тратит.
Мы рассмотрим возможности по динамическому наблюдению за сервером СУБД, которые предоставляет вживленный в MySQL и Postgresql код. По имеющимся сейчас сведениям от разработчиков MySQL провайдер mysql планируется включить в код СУБД в версии 6.0.4. Однако уже в версии 6.0 можно обнаружить предварительный вариант кода этого провайдера, и если вы хотите его попробовать уже сейчас, это можно сделать, указав при установке из исходных текстов ключ --enable-dtrace при вызове ./configure.
Более детально можно познакомиться с инструкциями по использованию DTrace в MySQL 6.0 на форуме пользователей DTrace по адресу http://www.opensolaris.org/jive/forum.jspa?forumID=7.
Здесь мы рассмотрим более хитрую методику отлова команд, переданных серверу MySQL, а затем перейдем к работе с postgresql, который уже давно поставляется с встроенными датчиками DTrace.
Дело в том, что с помощью провайдера pid мы можем получить информацию о вызове любой функции в любом приложении. Кстати, если нас больше интересует производительность ввода-вывода СУБД, то лучше обратиться к провайдеру io, но это уже предмет другой статьи.
В MySQL обработка запросов выполняется функцией mysql_parse
mysql_parse(THD *thd, char *inBuf, uint length)
Название ее второго аргумента звучит многообещающе. Попробуем следующий скрипт для того, чтобы получить
cat mysql-catch.d
pid$target::*mysql_parse*:entry
{
printf("%Y %s\n", walltimestamp, copyinstr(arg1));
}
Запустим его в одном окне
# dtrace -qs mysql-catch.d -p 1019
А в другом запустим уже знакомый скрипт на php для получения содержимого нашей записной книжки:
# php simpledb.php Philip % Rock % +74951110808 Ivan % Markov % +74951110808
В первом окне мы получим ответ скрипта:
2008 Feb 7 22:58:35 SELECT name , lastname , phone FROM notebook
Готово!
Только придется помнить, что если на сервере запущено несколько экземпляров mysqld, надо позаботиться об указании правильного идентификатора процесса: это обязательно при работе с датчиками провайдера pid. Представленный скрипт справляется с тем, чтобы сообщить нам запрос, отправленный серверу. Может случиться так, что сервер отвергнет запрос – из-за отсутствия права доступа к данным у запрашивающего или из-за некорректности запроса.
Для анализа значения, которое возвращает функция, можно воспользоваться датчиком
pid$target::*mysql_execute_command*:return .
В arg1 у датчиков провайдера pid находится код возврата. Успешное завершение операции сервером MySQL даст код 0, и ненулевое значение — в противном случае.
Для контроля за выполнением операций достаточно добавить в скрипт mysql-catch.d еще один компонент:
pid$target::*mysql_execute_command*:return
{
printf("%Y %d \n", walltimestamp, arg1);
}
Теперь разберемся с Postgresql.
Начиная с версии postgresql 8.2 в него встроен код провайдера DTrace — posgresql.
Стало быть, для анализа запросов к этой СУБД можно использовать встроенные датчики, не исследуя исходный код сервера. Вот перечень этих датчиков:
probe transaction__start(int); probe transaction__commit(int); probe transaction__abort(int); probe lwlock__acquire(int, int); probe lwlock__release(int); probe lwlock__startwait(int, int); probe lwlock__endwait(int, int); probe lwlock__condacquire(int, int); probe lwlock__condacquire__fail(int, int); probe lock__startwait(int, int); probe lock__endwait(int, int);
Воспользуемся датчиками transaction-start и transaction-end для получения распределения времени транзакций:
#!/usr/sbin/dtrace -Zqs
postgresql*:::transaction-start
{
self->ts=timestamp;
@cnt[pid]=count();
}
postgresql*:::transaction-commit
{
@avg[pid]=avg(timestamp - self->ts);
}
tick-5sec
{
normalize(@avg, 1000000);
printf("%15s %30s %30s\n","PID","Total queries","Avegrage time (ms)");
printf("\t=================================================\n\n");
printa("%15d %@30d %@30d\n",@cnt,@avg);
printf("\t=================================================\n\n");
clear(@cnt);
clear(@avg);
}
Запуск этого скрипта даст нам реальную картину затрат времени на транзакции. Возможно, в вашем случае распределение будет иным – все зависит от фактической загрузки сервера:
# ./postgres_avg_query_time.d PID Total queries Avegrage time (ms) ========================================================== 23814 46 57 23817 58 34 23816 59 32 23815 59 33 23818 75 26 ==========================================================
А теперь обратимся к уже хорошо знакомому нам провайдеру pid и напишем скрипт для отслеживания запросов к СУБД (обратите внимание: имя функции в postgresql иное, нежели в mysql ):
#!/usr/sbin/dtrace -Zwqs
BEGIN
{
freopen("sql.trace");
}
pid$1::pg_parse_query:entry
{
printf("%s\n",copyinstr(arg0));
}
Обратите внимание на вызов freopen("sql.trace") – так мы перенаправим вывод скрипта с экрана в файл с указанным именем.
В этой лекции мы познакомились с тем, как использовать DTrace для анализа различных приложений, в том числе приложений на javascript, Java, PHP и SQL. Фактически, это дает нам полный набор инструментов для отладки как клиентской части приложений Web 2.0, так и серверной их части.
Мы старались показать, что даже если какое-то приложение не имеет встроенных датчиков DTrace, вы всегда можете либо добавить их в исходный код (как уже сделали разработчики PostgreSQL, например), либо использовать известные вам функции для анализа передаваемых ими аргументов и возвращаемых значений – с помощью провайдера pid.
Еще более подробную информацию о DTrace можно почерпнуть в руководстве "Dynamic Tracing Guide", где приводится подробное описание этих провайдеров. Этот документ вкупе с "DTrace User Guide" содержит полную документацию по DTrace и находится в свободном доступе на сайте http://docs.sun.com/. Также можно посоветовать страницу сообщества разработчиков DTrace на cайте opensolaris.org: http://opensolaris.org/ os/community/dtrace.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.