Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Руководство для начинающих пользователей SystemTap. Принцип работы SystemTap

Оригинал: SystemTap Beginners Guide
Авторы: Don Domingo, William Cohen
Дата публикации: 20 июля 2009 г.
Перевод: А.Панин
Дата перевода: 30 сентября 2014 г.

Глава 3. Принцип работы SystemTap

3.4. Ассоциативные массивы

SystemTap также предоставляет возможность использования ассоциативных массивов. В то время, как обычная переменная представляет одно отдельное значение, ассоциативные массивы могут представлять набор значений. Просто рассматривайте ассоциативный массив как коллекцию уникальных ключей; при этом каждый ключ из этого массива имеет ассоциированное с ним значение.

Ввиду того, что ассоциативные массивы обычно используются одновременно из множества зондов (что мы продемонстрируем позднее), они должны объявляться как глобальные переменные (с использованием спецификатора области действия переменной global) в сценарии SystemTap. Синтаксис доступа к элементу ассоциативного массива аналогичен синтаксису языка awk и является следующим:
имя_массива[значение_индекса]

В данном случае в качестве имени имя_массива может использоваться любое произвольное имя переменной массива. Значение индекса значение_индекса используется для ссылки на определенный уникальный ключ в массиве. Для иллюстрации изложенной информации, давайте попытаемся создать массив с именем foo, в котором будет содержаться информация о возрастах трех людей с именами tom, dick и harry (которые будут являться уникальными ключами). Для связывания последних с возрастами (ассоциированными значениями) в 23 года, 24 года и 25 лет соответственно, мы будем использовать следующие операторы по отношению к переменной массива:

Пример 3.13. Базовые операторы, используемые при работе с массивами
foo["tom"] = 23
foo["dick"] = 24
foo["harry"] = 25
Вы можете указать до девяти значений индексов при использовании аналогичного оператора, причем каждый из индексов должен быть отделен с помощью символа запятой (,). Это полезно в том случае, если вы хотите создать ключ, сформированный на основе фрагментов различных данных. В приведенной ниже строке из сценария disktop.stp для создания ключа используется 5 элементов: идентификатор процесса, имя исполняемого файла, идентификатор пользователя, идентификатор родительского процесса, а также строка "W". В ней происходит ассоциация значения переменной devname с данным ключом.
device[pid(),execname(),uid(),ppid(),"W"] = devname
Важная информация
Ассоциативные массивы должны объявляться как глобальные переменные (с использованием спецификатора области действия переменной global) вне зависимости от того, используется ли конкретный массив в рамках одного или нескольких зондов.

3.5. Операции с массивами в SystemTap

В данном разделе описаны некоторые из наиболее часто осуществляемых операций с массивами в SystemTap.

3.5.1. Присваивание ассоциированного значения

Используйте оператор = при необходимости установки ассоциированного значения для уникального ключа аналогичным образом:
имя_массива[значение_индекса] = значение

В Примере 3.13 "Базовые операторы, используемые при работе с массивами" показан очень простой способ явной установки ассоциированного значения для уникального ключа. Также вы можете использовать функцию-обработчик как в качестве ключа значение_индекса, так и в качестве значения значение. Например, вы можете использовать массивы для установки метки времени в качестве ассоциированного значения для имении процесса (которое вы можете использовать в качестве уникального ключа) аналогичным образом:

Пример 3.14. Ассоциация меток времени с именами процессов
foo[tid()] = gettimeofday_s()

В любом случае при исполнении кода из Примера 3.14 "Ассоциация меток времени с именами процессов" SystemTap будет возвращать соответствующее значение из функции tid() (являющееся идентификатором программного потока, который впоследствии будет использован в качестве уникального ключа). В то же время, SystemTap также будет использовать функцию gettimeofday_s() для установки соответствующей метки времени в качестве ассоциированного значения для уникального ключа, возвращенного функцией tid(). Таким образом будет создаваться массив из пар ключей, представленных идентификаторами потоков, и ассоциированных с ними значений, представленных метками времени.

При работе с этим же примером в случае возврата функцией tid() значения, которое уже объявлено в рамках массива foo, использование оператора присваивания приведет к отбрасыванию оригинального ассоциированного значения и его замене на текущее значение метки времени, возвращаемое функцией gettimeofday_s().

3.5.2. Чтение значений из массивов

Также вы можете читать значения из массива точно таким же образом, как вы могли бы читать значение переменной. Для этого вы можете использовать объявление имя_массива[значение_индекса] в качестве элемента математического выражения. Например:

Пример 3.15. Использование значений из массивов для простых вычислений
delta = gettimeofday_s() - foo[tid()]

При создании данного примера предполагалось, что массив foo был сформирован с помощью операторов присваивания, использованных ранее в Примере 3.14 "Ассоциация меток времени с именами процессов" (из Раздела 3.5.1, "Присваивание ассоциированного значения"). В данном случае значение из массива будет точкой отсчета, используемой для вычисления значения delta.

Конструкция из Примера 3.15, "Использование значений из массивов для простых вычислений" производит расчет значения переменной delta путем вычитания ассоциированного с возвращаемым функцией tid() значением ключа значения метки времени из возвращаемого функцией gettimeofday_s() текущего значения метки времени. Конструкция выполняет описанное действие путем чтения ассоциированного с возвращаемым функцией tid() значением ключа значения метки времени из массива. Данная конкретная конструкция полезна для вычисления времени, прошедшего между двумя событиями, такими, как начало и завершение операции чтения данных.

Примечание
В том случае, если в ходе чтения значения, ассоциированного с переданным значением индекса значение_индекса, из массива не удается найти соответствующего уникального ключа, по умолчанию возвращается значение 0 (для операций с числами, таких, как операция, описанная в Примере 3.15, "Использование значений из массивов для простых вычислений") или нулевое/пустое строковое значение (для операций со строками).

3.5.3. Увеличение ассоциированных значений

Следует использовать оператор ++ для увеличения значения, ассоциированного с уникальным ключом массива также, как показано ниже:
имя_массива[значение_индекса] ++

И снова вы также можете использовать функцию-обработчик для получения ключа значение_индекса. Например, в том случае, если бы вы захотели подсчитать количество операций чтения из виртуальной файловой системы, осуществленных определенным процессом (задействовав для этого событие vfs.read), вы могли бы использовать следующий зонд:

Пример 3.16. vfsreads.stp
probe vfs.read
{
  reads[execname()] ++
}

В Примере 3.16, "vfsreads.stp" в первый раз, когда зонд возвращает имя процесса gnome-terminal (это происходит тогда, когда приложение gnome-terminal выполняет чтение из виртуальной файловой системы), это имя процесса используется в качестве уникального ключа gnome-terminal с ассоциированным целочисленным значением 1. В следующий раз, когда зонд вернет имя процесса gnome-terminal, SystemTap произведет увеличение ассоциированного с ключом gnome-terminal значения на 1. SystemTap будет выполнять эту операцию для всех имен процессов по мере их возврата зондом.

3.5.4. Обработка множества элементов массива

После того, как вы собрали достаточный объем информации в рамках массива, вам понадобится механизм для получения и обработки всех элементов этого массива для того, чтобы массив выполнял полезные функции. Рассмотрите Пример 3.16, "vfsreads.stp": сценарий собирает информацию о том, как много операций чтения из виртуальной файловой системы осуществляет каждый из процессов, но он не предполагает какой-либо обработки этой информации. Очевидное решение, которое позволит сделать сценарий из Примера 3.16, "vfsreads.stp" полезным, заключается в выводе пар ключ-значение из массива reads, но как его реализовать?

Лучший способ обработки всех пар ключ-значение из массива (итерационной обработки) заключается в использовании оператора foreach. Рассмотрите следующий пример:

Пример 3.17. cumulative-vfsreads.stp
global reads
probe vfs.read
{
  reads[execname()] ++
}
probe timer.s(3)
{
  foreach (count in reads)
    printf("%s : %d \n", count, reads[count])
}

В рамках второго варианта зонда из Примера 3.17, "cumulative-vfsreads.stp" вместе с оператором foreach используется переменная count, предназначенная для ссылки на каждый уникальный ключ из массива reads в рамках процесса итерационной обработки. С помощью выражения reads[count] в рамках того же зонда осуществляется чтение значения, ассоциированного с каждым из уникальных ключей массива.

Помимо уже известных нам подробностей работы первого зонда сценария, приведенного в Примере 3.17, "cumulative-vfsreads.stp", следует отметить, что данный сценарий выводит статистику осуществления операций чтения из виртуальной файловой системы через каждые 3 секунды, отражая в ней имена процессов, которые осуществляли чтение из виртуальной файловой системы, вместе с соответствующим количеством операций чтения из виртуальной файловой системы.

Теперь следует вспомнить о том, что оператор foreach в Примере 3.17, "cumulative-vfsreads" в рамках итерационного процесса выводит все имена процессов из массива без какого-либо их упорядочивания. Вы можете сообщить сценарию о том, что необходимо осуществлять итерации в определенном порядке, используя символ + (для сортировки по возрастанию) или - (для сортировки по убыванию). В дополнение вы также можете ограничить количество итераций, которые должен осуществить сценарий, воспользовавшись параметром limit количество, причем вместо строки "количество" должно быть указано соответствующее количество итераций.

Например, рассмотрите следующий альтернативный вариант реализации зонда:
probe timer.s(3)
{
  foreach (count in reads- limit 10)
    printf("%s : %d \n", count, reads[count])
}

В данном случае оператор foreach сообщает сценарию о необходимости обработки элементов массива reads с сортировкой по убыванию (для сортировки используются ассоциированные значения). Параметр limit 10 позволяет оператору foreach осуществлять только первые десять итераций (а именно, выводить первые 10 значений начиная с наибольшего значения).

3.5.5. Очистка массивов и удаление элементов массивов

Иногда вам может понадобиться удалить ассоциированные с элементами массива значения или очистить весь массив для повторного использования в рамках другого зонда. Сценарий из Примера 3.17, "cumulative-vfsreads.stp", приведенного в Разделе 3.5.4, "Обработка множества элементов массива", позволяет вам отслеживать постоянно растущее с течением времени количество операций чтения из виртуальной файловой системы для каждого из процессов, но он не выводит информацию о количестве операций чтения из виртуальной файловой системы, которые осуществляет каждый из процессов в течение 3-х секундного периода.

Для реализации описанной функции вам придется удалять значения, накапливаемые в рамках массива. Вы можете реализовать описанное поведение, воспользовавшись оператором delete для удаления элементов из массива или очистки всего массива. Рассмотрите следующий пример:

Пример 3.18. noncumulative-vfsreads.stp
global reads
probe vfs.read
{
  reads[execname()] ++
}
probe timer.s(3)
{
  foreach (count in reads)
    printf("%s : %d \n", count, reads[count])
  delete reads
}

В Примере 3.18, "noncumulative-vfsreads.stp" в рамках второго зонда осуществляется вывод информации о количестве операций чтения из виртуальной файловой системы, которые были осуществлены каждым процессом исключительно в течение 3-х секундного периода. В строке delete reads производится очистка массива reads в рамках зонда.

Примечание
Вы можете выполнять множество операций с массивом в рамках одного и того же зонда. Используя примеры из Раздела 3.5.4, "Обработка множества элементов массива" и Раздела 3.5.5. "Очистка массивов и удаление элементов массивов", вы можете отследить количество операций чтения, осуществленных каждым из процессов в течение 3-х секундного периода и подсчитать совокупное количество операций чтения из виртуальной файловой системы, осуществленных этими же процессами. Рассмотрите следующий пример:
global reads, totalreads

probe vfs.read
{
  reads[execname()] ++
  totalreads[execname()] ++
}

probe timer.s(3)
{
  printf("=======\n")
  foreach (count in reads-)
    printf("%s : %d \n", count, reads[count])
  delete reads
}

probe end
{
  printf("ОБЩЕЕ КОЛИЧЕСТВО\n")
  foreach (total in totalreads-)
    printf("%s : %d \n", total, totalreads[total])
}
			

В данном примере с помощью массивов reads и totalreads осуществляется отслеживание одних и тех же событий и сбор одной и той же информации, вывод которой осуществляется аналогичным образом. Единственное отличие в данном случае заключается в том, что массив reads очищается после каждого 3-х секундного периода, в то время, как массив totalreads продолжает накапливать информацию.

3.5.6. Использование массивов совместно с условными переходами

Также вы можете использовать ассоциативные массивы совместно с операторами условных переходов if. Это полезно в том случае, если вы хотите исполнять подфункцию только в том случае, если для значения из массива выполняется определенное условие. Рассмотрите следующий пример:

Пример 3.19. vfsreads-print-if-1kb.stp
global reads
probe vfs.read
{
  reads[execname()] ++
}

probe timer.s(3)
{
  printf("=======\n")
  foreach (count in reads-)
    if (reads[count] >= 1024)
      printf("%s : %dКБ \n", count, reads[count]/1024)
    else
      printf("%s : %dБ \n", count, reads[count])
}

После каждых трех секунд сценарий из Примера 3.19, "vfsreads-print-if-1kb.stp" будет выводить список всех процессов вместе с количеством осуществленных каждым из этих процессов операций побайтового чтения из виртуальной файловой системы. В том случае, если ассоциированное с именем процесса значение равно или больше 1024, с помощью оператора условного перехода if выполняется преобразование и вывод значения в КБ.

Проверка наличия ключа в массиве

Вы также можете проверить, находится ли определенный уникальный ключ в массиве. Более того, проверка наличия ключа в массиве может осуществляться в рамках условных переходов if следующим образом:
if([значение_индекса] in имя_массива) оператор

Для иллюстрации изложенной выше информации рассмотрим следующий пример:

Пример 3.20. vfsreads-stop-on-stapio2.stp
global reads

probe vfs.read
{
  reads[execname()] ++
}

probe timer.s(3)
{
  printf("=======\n")
  foreach (count in reads+)
    printf("%s : %d \n", count, reads[count])
  if(["stapio"] in reads) {
    printf("операция чтения осуществлена утилитой stapio, работа сценария завершается\n")
    exit()
  }
}

Строка if(["stapio"] in reads) сообщает сценарию о необходимости вывода строки "операция чтения осуществлена утилитой stapio, работа сценария завершается" после добавления уникального ключа stapio в массив reads.

3.5.7. Расчет статистических показателей

Статистические показатели используются при сборе статистики о результирующих числовых значениях в тех случаях, когда важно накапливать новые данные быстро и в больших объемах (в этих случаях сохраняются исключительно вычисленные для потока данных статистические показатели). Статистические показатели могут накапливаться как в глобальных переменных, так и в элементах массива.

Для добавления значения в переменную для расчета статистического показателя, следует использовать оператор <<< совместно с этим значением.

Пример 3.21. stat-aggregates.stp
global reads
probe vfs.read
{
  reads[execname()] <<< $count
}

В Примере 3.21, "stat-aggregates.stp" оператор <<< $count сохраняет в массиве reads значение целевой переменной $count в качестве ассоциированного значения для соответствующего уникального ключа, возвращаемого функцией execname(). Помните о том, что эти данные сохраняются; они не добавляются к ассоциированным с каждым уникальным ключом значениям, а также не используются для замены текущих ассоциированных значений. Можно считать, что каждый уникальный ключ (возвращаемый функцией execname()) имеет множество ассоциированных значений, накапливающихся по мере вызовов обработчика зонда.

Примечание
В контексте Примера 3.21, "stat-aggregates.stp" целевая переменная $count содержит объем данных, которые были прочитаны из виртуальной файловой системы процессом с именем, возвращаемым функцией execname().

Для извлечения данных, собранных с помощью переменных для расчета статистических показателей, следует использовать формат @функция_извлечения(переменная/массив[значение_индекса]). В качестве функции функция_извлечения может быть использована одна из следующих функций для извлечения числовых данных:

count

Возвращает количество значений, сохраненных в переменной/элементе массива, идентифицируемом с помощью значения индекса. В случае работы со сценарием из Примера 3.21, "stat-aggregates.stp" выражение @count(reads[execname()]) вернет количество значений, сохраненных для каждого из уникальных ключей массива reads.

sum

Возвращает сумму всех значений, сохраненных в переменной/элементе массива, идентифицируемом с помощью значения индекса. Как и раньше, в случае работы со сценарием из Примера 3.21, "stat-aggregates.stp" выражение @sum(reads[execname()]) вернет сумму всех значений, сохраненных для каждого из уникальных ключей массива reads.

min

Возвращает наименьшее из всех значений, сохраненных в переменной/элементе массива, идентифицируемом с помощью значения индекса.

max

Возвращает наибольшее из всех значений, сохраненных в переменной/элементе массива, идентифицируемом с помощью значения индекса.

avg

Возвращает среднее значение, вычисленное на основе всех значений, сохраненных в переменной/элементе массива, идентифицируемом с помощью значения индекса.

При использовании статистических показателей вы также можете создавать конструкции для массивов, содержащие по нескольку значений индексов (максимальное количество равно 5). Это полезно для захвата дополнительной информации о контексте в процессе исследования. Например:

Пример 3.22. Множество индексов массива
global reads
probe vfs.read
{
  reads[execname(),pid()] <<< 1
}
probe timer.s(3)
{
  foreach([var1,var2] in reads)
    printf("%s (%d) : %d \n", var1, var2, @count(reads[var1,var2]))
}

В Примере 3.22, "Множество индексов массива" в рамках первого зонда происходит отслеживание количества операций чтения из виртуальной системы, осуществляемых каждым из процессов. Отличие данного примера от ранее рассматриваемых примеров заключается в том, что при использовании массива устанавливается ассоциация количества операций чтения как с именем процесса, так и с соответствующим идентификатором процесса.

Реализация второго зонда из Примера 3.22, "Множество индексов массива" демонстрирует методику обработки и вывода информации, собранной в рамках массива reads. Обратите внимание на то, что совместно с оператором foreach используется то же количество переменных (а именно, var1 и var2), которое было использовано при работе с массивом reads в обработчике первого зонда.

3.6. Тапсеты

Тапсеты (tapsets) являются сценариями из библиотеки предварительно разработанных зондов и функций, которые предназначены для использования в рамках сценариев SystemTap. В момент, когда пользователь инициирует исполнение сценария SystemTap, система SystemTap проверяет наличие описаний событий зондов и их обработчиков в библиотеке тапсетов; после этого SystemTap загружает соответствующие реализации зондов и функций перед преобразованием сценария в код на языке C (обратитесь к Разделу 3.1, "Арихитектура" для получения дополнительной информации о том, какие операции осуществляются во время существования сессии SystemTap).

Как и файлы сценариев SystemTap, файлы тапсетов используют расширение .stp. Стандартная библиотека тапсетов по умолчанию расположена в директории /usr/share/systemtap/tapset/. Однако, в отличие от сценариев SystemTap, тапсеты не предназначены для непосредственного исполнения; вместо этого они составляют основу библиотеки, объявления из которой могут использовать другие сценарии.

Библиотека тапсетов является слоем абстракции, спроектированным с целью упрощения процесса описания событий и функций пользователями. Тапсеты предоставляют полезные псевдонимы для функций, которые пользователи могут использовать в качестве событий; запомнить правильный псевдоним, который должен быть использован в определенных условиях, по большому счету, гораздо проще, чем запоминать имена специфичных функций ядра ОС, которые могут отличаться в разных версиях ядер.

Некоторые обработчики и функции из Раздела 3.2.1. "События", а также функции из списка Функции SystemTap объявлены в рамках тапсетов. Например, функция thread_indent() объявлена в рамках тапсета indent.stp.


Следующий раздел : Исследования в пространстве пользователя.