Кстати я написал небольшое пособие по DTrace и SystemTap: Инструменты динамической трассировки DTrace и SystemTap
Итак, с пробами мы поигрались, теперь попробуем перейти к реальным скриптам на SystemTap. Тех, кто хочет досконально изучить этот язык, отсылаю к Language Reference, я же попробую остановиться на некоторых его аспектах. Сразу замечу, что все сказанное тут касается диалекта SystemTap - при использовании Embedded C (возможности встраивать код на C в скрипты SystemTap) - логика работы соответствует C-шной.
Переменные SystemTap
Локальные переменные, которые, как следует из названия, видны только в рамках одной пробы. Предварительное объявление им в общем-то не требуется (хотя SystemTap и переводит код в C, он сам разбирается с типизацией). Присвоить переменную можно довольно очевидным путем: myint = 1;
Обращаться к ней также надо без каких-либо спецификаторов:
printf("myint = %d\n", myint);
Особый тип локальных переменных - переменные контекста пробы. Как правило для пробы, привязываемой к вызову той или иной функции ядра это аргументы этой функции. Что характерно, имена этих переменных совпадают с именами аргументов, что можно считать большим шагом вперед, по сравнению с DTrace и его args[n] :-) Пример:
probe module("usb_storage").function("usb_stor_control_thread") {
bio = @cast($__us, "us_data")->srb->request->bio;
}
Собственно __us - и есть тот самый аргумент. Здесь же можно видеть также оператор @cast, преобразующий тип указателей. Иногда cast'у следует "подсказывать", из какого хидера брать описание типа, например:
@cast(tv, "timeval", "<sys/time.h>")
.
Куда интересней и полезней в нашей работе глобальные переменные. Синтаксически они отличаются от локальных одной незначительной деталью - указанием директивы global <имя переменной>; в начале stp-скрипта. Так как SystemTap это прежде всего одно из средств получения статистики из уровня ядра, то наиболее необходимые типы переменных - это ассоциативные массивы и аггрегации.
Ассоциативные массивы и аггрегации
Объявлять ассоциативные массивы необязательно, ключи определяются по первому присваиванию и указываются через запятую, например:
global csw;
probe scheduler.cpu_on {
csw[cpu(),execname()]++;
}
probe end {
foreach([cpu,exec+] in csw) {
printf("%d\t%s\t%d\n", cpu, exec, csw[cpu, exec]);
}
}
При первом обращении к одному из значений, это значение инициализируется нулем. В случае, если мы где-то напутаем с ключами, то выпадет ошибка inconsistent arity. В этом же примере можно также видеть оператор foreach, обходящий ассоциативный массив. Указание + или - после имени ключа означает сортировку этого ключа. Количество записей в ассоциативных массивах ограничено переменной MAXMAPENTRIES, также количество записей можно установить при объявлении массива:
Чтобы не засорять память, следует удалять записи с помощью операции delete.
Более сложный пример ассоциативных массивов - аггрегации. Как и в DTrace они служат цели сохранения статистики, однако тип аггрегации определяется не при присваивании, а при выводе агррегации на экран. Чтобы добавить запись в аггрегацию, используется оператор <<<. Например напишем простенький профилировщик системных вызовов:
global call_ts;
global call_tm;
probe syscall.*
{
//Добавим в ассоциативный массив время вызова функции
call_ts[cpu(), probefunc()] = gettimeofday_ns();
}
probe syscall.*.return
{
if(call_ts[cpu(), probefunc()]) { //Если вызов определен
//Аггрегируем кумулятивное время выполнения системного вызова
call_tm[probefunc()] <<< gettimeofday_ns() - call_ts[cpu(), probefunc()];
//Удаляем устаревшую запись
delete call_ts[cpu(), probefunc()];
}
}
probe end {
printf("%32s\t%s\t%s\t%s\n", "SYSCALL", "AVG", "COUNT", "TOTAL");
foreach(funcname+ in call_tm) {
printf("%32s\t%d\t%d\t%d\n", funcname,
@avg(call_tm[funcname]), //Вытаскиваем статистику аггрегации
@count(call_tm[funcname]),
@sum(call_tm[funcname]));
}
}
Здесь, как видно из примера мы сначала создаем массив call_ts, чтобы сохранить таймстамп вызова функции, а при возврате (пробы .return), считаем дельту и сохраняем ее уже в аггрегацию call_tm. Из аггрегации уже можно вытаскивать и среднее значение и количество таких вызовов.
Операторы и функции SystemTap
Я не буду подробно останавливаться на таких вещах, как арифметические выражения и т.д. - язык скриптов SystemTap C-подобен. Отмечу лишь ряд особенностей SystemTap. Во-первых это функции контекста:
-
cpu() определяет номер процессора, на котором сработала пробами
-
tid() и pid() определяют текущий поток и процесс, также execname() возвратит строку, содержающую имя исполнимого образа
-
target() возвращает pid процесса, к которому привязан stap с помощью опций -c/-x
-
probefunc() и probemod() определяют саму пробу.
Вообще говоря подробно все функции и пробы SystemTap описаны в SystemTap Tapset Reference Manual http://sourceware.org/systemtap/tapsets/
Для замеров производительности следует использовать либо функции gettimeofday_*(), возвращающие текущее системное время или get_cycles(), возвращающая количество циклов процессора (подозреваю, что это обертка над инструкцией RDTSC). Для работы со строками предназначены функции strlen(), strcpy().
И немного об операциях SystemTap. В отличие от DTrace, SystemTap не поддерживает предикаты проб, однако как я уже говорил там есть замечательный сишный оператор if. Для того, чтобы пропустить пробу, необходимо использовать оператор next, например
probe syscall.read {
if(execname() != "httpd") next;
/*...*/
}
Также SystemTap поддерживает циклы в стиле C: операторы while, for, break и continue. Ну и нельзя упомянуть о такой замечательной вещи, как try/catch, позволяющей перехватывать некоторые ошибки SystemTap, что например полезно против "пустых" аггрегаций:
try {
printf("avg=%d\n", @avg(someaggr));
} catch (msg) {
printf("no statistics\n");
}
Вместо заключения
Безусловно SystemTap - мощное средство, и рассказывать о нем можно многое. Пробуйте и у вас получится :-) На последок - полезные ссылки.
* sourceware.org/systemtap/langref/ - SystemTap Language Reference
* sourceware.org/systemtap/tapsets/ - SystemTap Tapset Reference Manual
* sourceware.org/systemtap/wiki/ScriptsTools - Примеры скриптов
* sourceware.org/systemtap/wiki/TipExhaustedResourceErrors - Типичные ошибки, связанные с недостатком ресурсов для трассировки
* lxr.linux.no/ - Удобное средство для работы с деревом исходников Linux