Средства языка D предоставляют несколько механизмов ограничения вывода, таких как предикаты и thread-local переменные, но в определенных случаях таких ограничений бывает недостаточно.
Например, если в системном вызове возникает случайная ошибка, причем число безошибочных выполнений этого вызова значительно выше, для определения условий возникновения ошибки может понадобиться анализ стека вызовов функций между входом и выходом из системного вызова.
Провайдер fbt (function boundary tracing) создает датчики для момента входа во все функции ядра и выхода из них - следовательно, можно определить, какую последовательность вызовов функций ядра имеет определенный системный вызов, например ioctl, выполняющий различные управляющие действия над обычными устройствами и псевдоустройствами:
syscall::ioctl:entry
{
self->follow = 1;
}
fbt:::
/self->follow/
{
}
syscall::ioctl:return
/self->follow/
{
self->follow = 0;
}
Для ограничения вывода используется thread-local переменная self->follow
– выводятся только вызовы функций, происходящие в одном и том же с системным вызовом потоке команд.
Результат работы скрипта:
dtrace: script 'ioctl.d' matched 63230 probes
CPU ID FUNCTION:NAME
0 26940 ioctl:entry
0 26575 getf:entry
0 23036 set_active_fd:entry
0 23037 set_active_fd:return
0 26576 getf:return
0 20264 get_udatamodel:entry
0 20265 get_udatamodel:return
<…>
Чтобы определить, регулярны ли причины возникновения ошибки (по времени или количеству вызовов), не используется операция exit (0) и вывод получается еще более длинным, чем тогда, когда причину можно выявить сразу, и не всегда для поиска нужного стека вызовов достаточно программ grep и less.
Для решения подобных задач в DTrace используется буферизация данных при помощи переменных или спекулятивная трассировка (в этом случае получить нужный вывод без последующей обработки проще), когда выделяется специальный буфер - спекулятивный - куда будет заноситься вся информация, затем данные из этого буфера удаляются или переносятся в главный буфер, откуда попадают на вывод.
Функции спекулятивной трассировки
speculation
Функция speculation() (у которой нет аргументов) выделяет спекулятивный буфер и возвращает его идентификатор, который будет использоваться в вызовах функции speculate(). Если при вызове speculation() не окажется свободного буфера, функция вернет нулевой идентификатор и DTrace выведет сообщение об ошибке:
#dtrace: 8 failed speculations (no speculative buffer space available)
Нулевой идентификатор является недопустимым, но может быть передан в качестве параметра функциям speculate(), commit() или discard(). Количество спекулятивных буферов по умолчанию равно единице, но может быть и увеличено. Для этого нужно увеличить значение параметра nspec – например, при помощи прагмы или из консоли:
#pragma D option nspec=2
dtrace -x nspec=2 -s script_name.d
speculate
Все действия после передачи идентификатора, возвращенного вызовом speculation() в качестве аргумента функции speculate(), в компоненте, содержащей вызов последней функции, будут трассироваться спекулятивно. Если вызову speculate() будет предшествовать запись данных, появится ошибка времени компиляции. Поэтому недопустимо использование обоих видов трассировки одновременно и при спекулятивной трассировке не могут использоваться деструктивные и агрегирующие действия, а также exit (возникает та же ошибка). Существует ограничение и на количество вызовов speculate() - в каждой компоненте не может присутствовать более одного вызова этой функции.
commit
Эта функция копирует данные из спекулятивного буфера в главный. Если в спекулятивном буфере данных больше, чем места в главном буфере, то данные не копируются - возникает ошибка. Компонента, в которой присутствует вызов этой функции, не может содержать действия записи данных. Чтобы обеспечить слияние нескольких спекулятивных буферов, в одной компоненте может быть несколько вызовов commit().
В системах с несколькими процессорами для каждого из них выделяется отдельный буфер для хранения данных, полученных при трассировке (как спекулятивной, так и нет). Сразу копируются только данные из буфера процессора, на котором была вызвана функция commit(), а информация из буферов других – только по прошествии определенного времени, обратно пропорционального частоте, с которой происходит очистка буферов - cleanrate (по умолчанию cleanrate=101).
Этот параметр можно изменить с помощью прагмы или из командной строки:
dtrace -x cleanrate=404hz -s script_name.d
#pragma D option cleanrate=404hz
Изменение параметров cleanrate и nspec увеличивает количество ресурсов для DTrace.
До перевода данных в главный буфер из спекулятивного последний будет недоступен для использования функциями commit(), discard() и speculate().
discard
Commit() выполняет очистку спекулятивных буферов и копирование данных, а discard() предназначена для очистки этого буфера, если информация из него не представляет интереса и может быть уничтожена.
Если все спекулятивные буферы заняты и discard() не успеет очистить их до вызова функции speculation(), то DTrace выдаст сообщение об ошибке:
#dtrace: 1 failed speculation (no speculative buffer available)
Теперь можно переписать скрипт с использованием спекулятивной трассировки:
syscall::ioctl:entry
{
self->spec = speculation();
printf( "ioctl (%s)\n ", copyinstr(arg0) );
}
fbt:::
/self->spec/
{
}
syscall::ioctl:return
/self->spec && errno == 0/
{
discard( self->spec );
self->spec= 0;
}
syscall::ioctl:return
/self->spec && errno != 0/
{
commit( self->spec );
self->spec = 0;
}
После его выполнения на терминал будут выводиться сообщения только при наличии ошибок, например такое:
#dtrace: error on enabled probe ID 1 (ID 65911: syscall::ioctl:entry):
invalid address (0x102) in action #2 at DIF offset 28
В приведенном примере идентификатор спекулятивного буфера присваивается thread-local переменной self->spec, используемой затем в качестве аргумента в предикатах.