Европейский Университет в Санкт-Петербурге
Опубликован: 04.07.2008 | Доступ: свободный | Студентов: 1076 / 303 | Оценка: 4.30 / 3.78 | Длительность: 18:28:00
Лекция 15:

Наблюдение, профилирование и трассировка работы приложений и системы. Концепция DTrace

< Лекция 14 || Лекция 15: 12345 || Лекция 16 >

Спекулятивная трассировка

Я хочу лишний раз подчеркнуть, что оба рассмотренных метода: обработка вывода dtrace другими утилитами (например, graphviz ) и буферизация данных при помощи переменных, – не являются ущербными. И в некоторых случаях могут оказаться необходимым подспорьем. Некоторая ирония в рассказе о них вызвана тем, что, в DTrace предусмотрена возможность "припрятать" трассировочные данные до поры, до времени, чтобы позже принять решение: стоит ли отдать собранные данные в буфер трассировки или же просто выбросить. Собственно, второй из ранее рассмотренных методов, как раз и является неким доморощенным вариантом такого буфера. Однако, используя спекулятивную трассировку, получить интересующий вывод без последующей обработки и с минимальными затратами будет значительно проще.

Вкратце принципы спекулятивной трассировки можно описать так: выделяется специальный буфер, именуемый спекулятивным, куда будет заноситься вся информация, которая может пригодиться в дальнейшем, а может и нет; затем данные из этого буфера либо выкидываются совсем, либо переносятся в главный буфер, откуда и попадают на вывод. Интерфейс спекулятивных функций DTrace выглядит следующим образом (см. табл. 15.1).

Таблица 15.1. Интерфейсные функции спекулятивной трассировки
Имя функции Аргументы Описание
speculation Нет Возвращает идентификатор нового спекулятивного буфера
speculate Идентификатор Указывает, что последующий вывод компоненты будет перенаправлен в спекулятивный буфер с указанным идентификатором
commit Идентификатор Заносит данные из спекулятивного буфера, связанного с указанным идентификатором, в главный буфер
discard Идентификатор Сбрасывает данные, прежде занесенные в спекулятивный буфер, который связан с указанным идентификатором
speculation

Теперь опишу процесс более подробно. Функция speculation() выделяет спекулятивный буфер и возвращает его идентификатор, который должен использоваться в последующих вызовах функции speculate(). Спекулятивные буферы являются конечным ресурсом, и если при очередном вызове speculation() не окажется доступного буфера, то функция вернёт нулевой идентификатор, DTrace увеличит счёт ошибок и известит пользователя об этом факте примерно таким сообщением:

#dtrace: 2 failed speculations (no speculative buffer space available)

Нулевой идентификатор, хоть и является всегда недопустимым, может быть передан в качестве параметра функциям speculate(), commit() или discard(). Количество спекулятивных буферов по умолчанию равно единице, но может быть увеличено. Для этого надо увеличить значение параметра nspec, установив его значение в требуемое количество буферов. Это можно сделать прямо в скрипте при помощи прагмы:

#pragma D option nspec=3
speculate

Для использования спекулятивного буфера идентификатор, возвращенный вызовом функции speculation(), обязан быть передан в качестве аргумента функции speculate() до того момента, как в компоненте будут выполняться какие-либо действия, связанные с записью данных. Все последующие действия подобного характера в компоненте, содержащей вызов speculate(), будут трассироваться спекулятивно. Если же вызову speculate() в коде компоненты будут предшествовать какие-либо действия записи данных, то компилятор с языка D выдаст ошибку времени компиляции. Таким образом, компоненты могут быть предназначены либо для спекулятивной, либо для не-спекулятивной трассировки, но не для обоих видов трассировки одновременно.

Из вышеизложенного факта очевидным образом следует, что деструктивные действия не могут использоваться при спекулятивной трассировке. Ведь они существуют для того, чтобы предоставить возможность модификации системы, что суть непосредственное изменение данных, которые влияют на состояние системы. Чуть менее очевиден тот факт, что по тем же причинам это относится к агрегирующим действиям и действию exit. Любая попытка использования любого из этих трёх типов действий в компоненте, содержащей вызов функции speculate(), опять же приведёт к ошибке времени компиляции. Завершая описание speculate(), стоит еще отметить, что в каждой компоненте не может присутствовать более одного вызова этой функции (что, как вы думаете, произойдёт, если в некоторой компоненте обнаружится несколько вызовов speculate()?).

commit

Разобрались, как происходит сбор данных; теперь о том, что с ними можно сделать в дальнейшем. Собственно, варианта всего два: либо эти данные попадут в вывод, либо будут отброшены. Для первого варианта используется функция commit(), которая копирует данные из спекулятивного буфера в главный. Если случилось так, что в спекулятивном буфере данных больше, чем места в главном буфере, то никакие данные не копируются, а соответствующий счётчик ошибок увеличивается на единицу. (В первой части статьи рассказывалось о том, что на системах с несколькими CPU для каждого из них выделяется отдельный буфер для хранения данных, полученных при трассировке, – то же самое относится и к спекулятивным буферам.) В случае, когда при спекулятивной трассировке были использованы буферы, принадлежащие нескольким CPU, незамедлительно копируются только спекулятивные данные из буфера того CPU, на котором была вызвана функция commit(), а информация из буферов других CPU – только по прошествии некоторого времени. Таким образом, данные из буфера одного из CPU и остальных будут разнесены по времени при переносе в главный, однако это время гарантированно не больше, чем значение параметра настройки DTrace, который называется cleanrate (по умолчанию cleanrate=101 ). При необходимости его можно изменить, увеличив частоту с которой происходит очистка буферов CPU. Как и в случае с nspec, это можно сделать или при помощи прагмы, или из командной строки:

ostap#dtrace -x cleanrate=303 -s speculation.d

Стоит рассказать про один подводный камень, на который можно натолкнуться при использовании спекулятивной трассировки на системе с несколькими CPU. До тех пор, пока на каждом CPU не будут переписаны данные в главный буфер из спекулятивного, последний будет недоступен для использования функциями commit(), discard() и speculate(). В этом случае вызовы этих функций тихо "завалятся". Последнее, что стоит сказать про commit() – это то, что компонента в которой присутствует вызов этой функции не может содержать действия, записывающие данные. Для того, чтобы обеспечить слияние нескольких спекулятивных буферов в одной компоненте, может быть несколько вызовов commit().

discard

Осталась последняя функция – discard(), которая предназначена для чистки спекулятивного буфера, если информация, находящаяся в данном буфере, не представляет интереса. Поскольку можно сказать, что функционально commit() – это чистка спекулятивных буферов плюс что-то еще (копирование данных), то всё, что относилось к "первому слагаемому" commit(), также справедливо и для discard(). И если, к примеру, все спекулятивные буферы заняты и discard() не успеет отработать до очередного вызова функции speculation(), то DTrace выдаст сообщение об ошибке наподобие такого:

#dtrace: 1 failed speculation (available buffer(s) still busy)

Изменяя значение параметра cleanrate, можно избавиться от этого сообщения или же сделать так, чтобы оно появлялось реже. Стоит заметить, что изменение параметров cleanrate и nspec увеличивает количество ресурсов для DTrace как приложения, увеличивая нагрузку на систему. Теперь можно вернуться к ранее приведенному скрипту и переписать его с использованием спекулятивной трассировки.

#!/usr/bin/dtrace -s
#pragma D option flowindent
syscall::open:entry
/ execname == "ls" /
{
self->spec = speculation();
printf( "open (%s)\n ", copyinstr(arg0) );
}
fbt:::
/self->spec/
{
speculate(self->spec);
}
syscall::open:return
/self->spec/
{
speculate( self->spec );
trace( errno );
}
syscall::open:return
/self->spec && errno == 0/
{
discard( self->spec );
self->spec= 0;
}
syscall::open:return
/self->spec && errno != 0/
{
commit( self->spec );
self->spec = 0;
}

После подробного рассказа вряд ли стоит подробно комментировать изменения. Ограничусь только одним замечанием. В этом примере идентификатор спекулятивного буфера присваивается thread-local переменной, которая впоследствии фигурирует в качестве аргумента в предикатах. Это один из распространённых шаблонов при написании скриптов на D.

< Лекция 14 || Лекция 15: 12345 || Лекция 16 >
Александр Тагильцев
Александр Тагильцев

Где проводится профессиональная переподготовка "Системное администрирование Windows"? Что-то я не совсем понял как проводится обучение.