null

Анализируем ядро Linux с помощью SystemTap. Часть 2

Кстати я написал небольшое пособие по 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, также количество записей можно установить при объявлении массива:

	global array[SIZE];

 


Чтобы не засорять память, следует удалять записи с помощью операции 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

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs