Опубликован: 04.07.2008 | Уровень: специалист | Доступ: платный | ВУЗ: Европейский Университет в Санкт-Петербурге
Лекция 15:

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

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

Модест Матвеевич, это не диван! Это транслятор универсальных превращений!

Аркадий и Борис Стругацкие "Понедельник начинается в субботу".

В Solaris можно досконально изучить все, что происходит в системе: узнать, сколько времени занимает выполнение конкретного системного вызова или группы вызовов, какое приложение и какие параметры передает библиотечной функции, какие вызовы делает приложение или ядро – и все это благодаря встроенной в ядро функциональности динамической трассировки DTrace.

DTrace – это не просто приложение и не только команда. Это целая технология, которая позволяет получить полную информацию о том, что происходит с любым приложением в операционной системе – будь то сервер баз данных или скрипт на php. Как бы "умно" не выглядели существующие ныне программы, все действия приложения, по большому счёту, сводятся к трём операциям: выполнение команд процессора, чтение из памяти и запись в память. При наблюдении за этими операциями при помощи DTrace становится понятно, как "чувствует себя" приложение в операционной системе.

DTrace – это рентген для работающей системы. Ведь как рентген может "заглянуть" внутрь живого организма, так и DTrace позволяет увидеть процессы, происходящие внутри операционной системы и пользовательских приложений. С помощью этой технологии можно получить исчерпывающую информацию о внутреннем состоянии приложения, причём сам процесс исследования "пациента" абсолютно безопасен, так как он не оказывает никакого влияния на "жизнедеятельность" объекта исследования.

Правда, это не совсем полная аналогия, поскольку принципы работы DTrace и рентгеновского аппарата отличаются. DTrace получает информацию от многочисленных датчиков, которые находятся в ядре, библиотеках и приложениях и срабатывают только в том случае, когда кто-то включил их в явном виде. Выключенные датчики не оказывают никакого влияния на производительность, и система в целом работает так, как будто их вообще нет. Весь секрет в том, что DTrace умеет динамически модифицировать объект исследования.

Позволим себе провести еще одну параллель между DTrace и рентгеном. Для успешного рентгеновского исследования необходимо знание физиологии, анатомии и устройства рентгеновского оборудования. Так и для эффективного использования DTrace необходимо понимание принципов работы операционной системы и устройства инструмента, который позволяет заглянуть внутрь работающей системы.

В этой лекции будут даны базовые понятия, связанные с DTrace, приведён обзор архитектуры DTrace, её составляющих элементов и методологии модификации системы. Отдельно кратко описан язык программирования D, который предоставляет универсальный доступ ко всем возможностям DTrace. А в следующей лекции мы рассмотрим ряд практических примеров и советов по использованию DTrace.

Обзор архитектуры DTrace

Основные архитектурные компоненты DTrace – это потребители, корневой модуль DTrace, датчики и провайдеры. Процессы становятся потребителями (consumers) DTrace, инициируя общение с корневым модулем DTrace. Корневой модуль находится в ядре операционной системы и обеспечивает доступ к средствам модификации кода, буферизацию и обработку событий от датчиков ( probes ). У потребителя две основных задачи: передать спецификации трассировки модулю DTrace1Далее по тексту термины корневой модуль и модуль DTrace суть синонимы. и обрабатывать данные, полученные в процессе трассировки. Потребители обращаются к корневому модулю через интерфейс, предоставляемый библиотекой libdtrace(3LIB). Данные между потребителем и ядром передаются посредством вызовов ioctl(2) для псевдо-устройства dtrace, реализованного драйвером dtrace(7d).

Общая архитектура DTrac

Рис. 15.1. Общая архитектура DTrac

Потребителем DTrace может быть любая программа, и в состав Solaris 10 включены утилиты lockstat(1M), plockstat(1M) и intrstat(1M), являющиеся оными. Однако каноническим потребителем является утилита dtrace(1M), которая дает универсальный доступ ко всем средствам DTrace, так как является драйвером2В компиляторостроении под "драйвером" понимается инструмент, который позволяет управлять процессом трансляции и вызывает все остальные компоненты, как то собственно компилятор, оптимизатор, кодогенератор и.т.д. Например, команда сс – это драйвер компилятора С. для компилятора языка программирования D (сам компилятор находится в библиотеке libdtrace(3LIB) ). Также можно использовать dtrace(1M) в качестве самостоятельной утилиты трассировки.

Провайдеры и датчики

Сам модуль DTrace не оснащает код инструментальными средствами, делегируя эту задачу провайдерам, которые в свою очередь также являются модулями ядра. Когда модуль DTrace даёт соответствующую команду, провайдеры определяют точки в системе, куда они потенциально могут вставить инструментальные средства. Для каждой найденной точки провайдер делает обратный вызов модуля DTrace, чтобы создать датчик ( probe ). То есть, по сути, провайдер – это модуль ядра, который создаёт множество датчиков, сгруппированых по тому или иному принципу (например, функции ядра, системные вызовы или функции какогото пользовательского приложения). При помощи команды modinfo(1M) можно посмотреть на некоторые модули ядра некоторых стандартных провайдеров в Solaris:

bash-3.00# modinfo | grep Tracing
4 f93dc000 16438 155 1 dtrace (Dynamic Tracing)
207 f930631c d98 156 1 profile (Profile Interrupt Tracing)
208 f9200ab8 554 157 1 systrace (System Call Tracing)
209 feb56c04 c5c 158 1 fbt (Function Boundary Tracing)
211 f93894f8 1304 159 1 sdt (Statically Defined Tracing)
212 f927b000 3fcc 167 1 fasttrap (Fasttrap Tracing)
225 f91358bc 68c 241 1 lx_systrace (Linux Brand System Call
Tracing)

В данном примере мы видим модули ядра самого DTrace и провайдеры, которые присутствуют при установке Solaris 10 в базовой конфигурации. О некоторых из них мы поговорим подробнее, сейчас же давате обратим внимание на модуль lx_systrace, который реализует провайдер lxsyscall для типизированных зон Linux (BrandZ)3При разговоре о типизированных зонах также иногда используется термин брэндовые зоны, но это скорее профессиональный жаргон. и содержит множество датчиков для трассировки системных вызовов Linux. По состоянию на январь 2008 года это был единственно возможный способ воспользоваться DTrace для трассировки Linux-приложений.

Для того, чтобы создать датчик, провайдер специфицирует имена модуля и функции для точки инструментальной модификации кода и собственно семантическое имя датчика; причем под модулем в данном контексте надо понимать программную принадлежность датчика. Для датчиков ядра это будет название модуля ядра (например, zfs ), для приложений это будет или библиотека ( libjvm.so, libc.so ), или даже некоторая пользовательская подсистема ( Xorg ). Функция – это имя функции, в которой находится датчик, как, например, ufs_read() в ядре или же printf() из libc для пользовательского приложения; а семантическое имя по сути – осмысленное название датчика, как, например, on-cpu и off-cpu для датчиков планировщика или же start для датчиков ввода-вывода.

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

<провайдер, модуль, функция, имя>

Создание датчиков ещё не модифицирует систему, это лишь определение возможных точек модификации. После создания датчика DTrace только возвращает идентификатор датчика провайдеру. В какой же момент происходит модификация? Созданные провайдером датчики становятся видимыми для потребителей, которые могут включить группу датчиков, задав комбинацию элементов из вышеупомянутой четвёрки. После включения DTrace создаёт и привязывает к датчику блок управления (ECB – enabling control block), где определены действия (actions) и предикат (predicate), то есть что и при каком условии будет выполнено в случае срабатывания датчика. Если во время создания данного ECB других связанных с текущим датчиком ECB нет, DTrace обращается к провайдеру с указанием включить данный датчик. Если ECB у датчика есть, то новый блок становится в хвост цепочки ECB блоков для этого датчика. В этот момент провайдер динамически модифицирует систему таким образом, чтобы при срабатывании датчика управление переходило к модулю DTrace, причём первым аргументом в обращении к нему следует идентификатор датчика.

Когда датчик срабатывает и управление передаётся DTrace, на текущем процессоре запрещаются прерывания и DTrace отрабатывает действия, определённые в каждом ECB-блоке из цепочки ECB-блоков сработавшего датчика. Затем прерывания разрешаются вновь и управление возвращается провайдеру.

Пример модификации кода провайдером

Это была сухая теория, теперь давайте посмотрим, как же реально модифицируется код на платформе x86 в Solaris. Для этого нам понадобится Solaris 10 или Solaris Express Developer Edition, доступ с правами администратора, штатный отладчик модулей ядра mdb и два терминальных окошка. В одном из терминалов запускаем mdb и дизассемблируем функцию ufs_lookup(), ограничив вывод тремя первыми командами, чтобы не загромождать вывод:

jedi# mdb -k
Loading modules: [ unix genunix specfs dtrace uppc pcplusmp scsi_
vhci ufs ip hook neti sctp arp usba uhci fctl nca lofs zfs random
audiosup sppp ptm md cpc crypto fcip fcp logindmux nsctl sdbc sv
ii rdc ipc ]
> ufs_lookup::dis -n 3
ufs_lookup: pushl %ebp
ufs_lookup+1: movl %esp,%ebp
ufs_lookup+3: subl $0x10,%esp

Выходим из mdb при помощи Ctrl-d, а затем включим датчики на модуле ufs, запустив dtrace в другом терминальном окне:

jedi# dtrace -m 'ufs { trace(execname);}'
dtrace: description 'ufs ' matched 836 probes
CPU ID FUNCTION:NAME
6 20874 ufs_getpage:entry dtrace
6 21348 ufs_lockfs_begin_getpage:entry dtrace
6 20930 ufs_lockfs_is_under_rawlockfs:entry dtrace
6 20931 ufs_lockfs_is_under_rawlockfs:return dtrac
6 21349 ufs_lockfs_begin_getpage:return dtrace
6 21512 bmap_has_holes:entry dtrace
...

Вывод показывает, какие исполняемые файлы в системе вызывают функции модуля ufs. В нашем случае это команда dtrace, которую мы же сами и запустили. Оставим dtrace работать дальше, а сами снова вернёмся к первому окошку и снова запустим mdb:

jedi# mdb -k
Loading modules: [ unix genunix specfs dtrace uppc pcplusmp
scsi_vhci ufs ip hook neti sctp arp usba uhci fctl nca lofs zfs
random audiosup sppp ptm md cpc crypto fcip fcp logindmux nsctl
sdbc sv ii rdc ipc ]
> ufs_lookup::dis -n 3
ufs_lookup: lock movl %esp,%ebp
ufs_lookup+3: subl $0x10,%esp
ufs_lookup+6: andl $0xfffffff0,%esp

Как нетрудно увидеть, в начале первой инструкции появился префикс lock4Если вы попробуете воспроизвести эти действия, то возможно, что вместо #lock увидите нечто другое. Полученный после модификации ассемблерный код для x86, amd64 и, разумеется, для SPARC будет различаться. Подробнее об этом позже.. Это не совсем корректный код с точки зрения ассемблера x86, потому что по спецификации #lock нельзя использовать с комбинации с инструкцией movl. Поэтому эта комбинация генерирует программное прерывание ( illegal opcode ), управление перехватывается ловушкой ( trap ), где в итоге передаётся DTrace. Теперь снова вернёмся к обзору архитектуры DTrace.

Предикаты и действия. Буферы и DIF

У каждого ЕCB блока может быть ассоциированый с ним предикат. Если таковой имеется, но его условие не выполняется, то DTrace по цепочке переходит к следующему ECB блоку. Помимо предиката у каждого блока ECB есть список действий, и, если условие предиката удовлетворено, то обработка ECB блока продолжится последовательным исполнений всех действий данного блока. Если действие предполагает запись каких-то данных, то они будут сохранены в специальном буфере, который выделяется для каждого CPU и однозначно привязывается к потребителю, создавшему ECB блок. Это делается потому, что для обеспечения безопасности использования действия не могут содержать явную запись в память ядра, изменение регистров, равно как и выполнение каких-то других явных операций, изменяющих состояние системы. Они могут делать это косвенно, но строго определённым способом, например остановить текущий процесс или сгенерировать точку остановки в ядре. Такие действия можно делать только пользователям с определёнными полномочиями и явно разрешив деструктивные действия.

ECB, предикаты и действия

Рис. 15.2. ECB, предикаты и действия

Еще одна линия защиты системы от нанесения непреднамеренного вреда – это виртуальная машина DTrace с собственным набором машиннонезависимых команд RISC, который называется DIF (D Intermediate Format) и является целевым языком компиляции для libdtrace. D-скрипты транслируются в DIF и эмулируются в ядре при срабатывании датчика, подобно тому, как виртуальная машина Java (JVM) интерпретирует байткод Java. Использование эмуляции в момент исполнения гарантирует, что возможные ошибки, способные дестабилизировать систему, будут выловлены и обработаны безопасным способом. При помощи ключа -S команды dtrace можно посмотреть на генерируемые DIF-объекты (DIFO):

jedi# cat difo.d && dtrace -S -s difo.d
syscall::ioctl:entry
{
self->follow = 1;
}
DIFO 0x6f8cc0 refcnt=1 returns D type (integer) (size 4)
OFF OPCODE INSTRUCTION
00: 25000001 setx DT_INTEGER[0], %r1 ! 0x1
01: 2d050001 stts %r1, DT_VAR(1280) ! DT_VAR(1280) = "follow"
02: 23000001 ret %r1
NAME ID KND SCP FLAG TYPE
follow 500 scl tls w D type (integer) (size 4)
dtrace: script 'difo.d' matched 1 probe
^C

На этом мы закончим обзор архитектуры и перейдем к краткому описанию языка программирования D.

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

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

Анатолий Натрусов
Анатолий Натрусов
Россия
Виктор Саркисов
Виктор Саркисов
Россия, Нижний Новгород, НГТУ, 2001