Динамическая трассировка
QHB предоставляет средства для поддержки динамической трассировки сервера баз данных. Это позволяет вызывать внешнюю утилиту в определенных точках кода и тем самым отслеживать его выполнение.
Несколько зондов или точек трассировки уже встроено в исходный код. Предполагается, что эти зонды будут использоваться разработчиками и администраторами базы данных. По умолчанию зонды не скомпилированы в QHB; пользователю необходимо явно указать скрипту конфигурации, чтобы тот обеспечил наличие этих зондов.
В настоящее время поддерживается утилита DTrace, которая на момент составления этой документации доступна для Solaris, macOS, FreeBSD, NetBSD и Linux. Проект SystemTap для Linux представляет собой эквивалент DTrace и также может быть использован. Теоретически возможна поддержка и других утилит динамической трассировки, для чего нужно изменить определения для макроса.
Компиляция для динамической трассировки
По умолчанию зонды недоступны, поэтому необходимо явно указать скрипту конфигурации, чтобы тот обеспечил их наличие в QHB. Чтобы добавить поддержку DTrace, укажите --enable-dtrace в файле конфигурации.
Встроенные зонды
В исходном коде имеется несколько встроенных зондов, представленных в Таблице 46; в Таблице 47 перечислены типы данных, используемые в этих зондах. Конечно, для улучшения наблюдаемости QHB можно добавить дополнительные зонды.
Таблица 46. Встроенные зонды DTrace
| Имя | Параметры | Описание |
|---|---|---|
| transaction-start | (LocalTransactionId) | Зонд, который срабатывает в начале новой транзакции. arg0 является идентификатором этой транзакции. |
| transaction-commit | (LocalTransactionId) | Зонд, который срабатывает при успешном завершении транзакции. arg0 является идентификатором этой транзакции. |
| transaction-abort | (LocalTransactionId) | Зонд, который срабатывает при завершении транзакции с ошибкой. arg0 является идентификатором этой транзакции. |
| query-start | (const char *) | Зонд, который срабатывает в начале обработки запроса. arg0 является строкой запроса. |
| query-done | (const char *) | Зонд, который срабатывает по завершении обработки запроса. arg0 является строкой запроса. |
| query-parse-start | (const char *) | Зонд, который срабатывает в начале анализа запроса. arg0 является строкой запроса. |
| query-parse-done | (const char *) | Зонд, который срабатывает по завершении анализа запроса. В arg0 передается строка запроса. |
| query-rewrite-start | (const char *) | Зонд, который срабатывает в начале перезаписи запроса. arg0 является строкой запроса. |
| query-rewrite-done | (const char *) | Зонд, который срабатывает по завершении перезаписи запроса. arg0 является строкой запроса. |
| query-plan-start | () | Зонд, который срабатывает в начале планирования запроса. |
| query-plan-done | () | Зонд, который срабатывает по завершении планирования запроса. |
| query-execute-start | () | Зонд, который срабатывает в начале выполнения запроса. |
| query-execute-done | () | Зонд, который срабатывает по завершении выполнения запроса. |
| statement-status | (const char *) | Зонд, который срабатывает каждый раз, когда серверный процесс изменяет свой pg_stat_activity.status. arg0 является новой строкой состояния. |
| checkpoint-start | (int) | Зонд, который срабатывает в начале контрольной точки. arg0 содержит битовые флаги, используемые для дифференциации разных типов контрольных точек, например shutdown, immediate или force. |
| checkpoint-done | (int, int, int, int, int) | Зонд, который срабатывает по завершении контрольной точки. (Зонды, перечисленные далее, срабатывают последовательно во время обработки контрольной точки.) arg0 является количеством записанных буферов. arg1 — общим количеством буферов. arg2, arg3 и arg4 содержат количество добавленных, удаленных и переработанных файлов WAL соответственно. |
| clog-checkpoint-start | (bool) | Зонд, который срабатывает в начале этапа записи контрольной точки в CLOG. arg0 равен true для обычной контрольной точки и false для контрольной точки типа shutdown. |
| clog-checkpoint-done | (bool) | Зонд, который срабатывает по завершении этапа записи контрольной точки в CLOG. arg0 имеет то же значение, что и для clog-checkpoint-start. |
| subtrans-checkpoint-start | (bool) | Зонд, который срабатывает в начале этапа записи контрольной точки в SUBTRANS. arg0 равен true для обычной контрольной точки и false для контрольной точки типа shutdown. |
| subtrans-checkpoint-done | (bool) | Зонд, который срабатывает по завершении этапа записи контрольной точки в SUBTRANS. arg0 имеет то же значение, что и для subtrans-checkpoint-start. |
| multixact-checkpoint-start | (bool) | Зонд, который срабатывает в начале этапа записи контрольной точки в MultiXact. arg0 равен true для обычной контрольной точки и false для контрольной точки типа shutdown. |
| multixact-checkpoint-done | (bool) | Зонд, который срабатывает по завершении этапа записи контрольной точки в MultiXact. arg0 имеет то же значение, что и для multixact-checkpoint-start. |
| buffer-checkpoint-start | (int) | Зонд, который срабатывает в начале записи буферов контрольной точки. arg0 содержит битовые флаги, используемые для дифференциации разных типов контрольных точек, например shutdown, immediate или force. |
| buffer-sync-start | (int, int) | Зонд, который срабатывает в начале записи грязных буферов во время контрольной точки (после определения, какие буферы должны быть записаны). arg0 является общим количеством буферов. arg1 — количеством буферов, которые в данный момент являются грязными и должны быть записаны. |
| buffer-sync-written | (int) | Зонд, который срабатывает после записи каждого буфера во время контрольной точки. arg0 является идентификатором этого буфера. |
| buffer-sync-done | (int, int, int) | Зонд, который срабатывает после записи всех грязных буферов. arg0 является общим количеством буферов. arg1 — количеством буферов, фактически записанных процессом контрольной точки. arg2 — количеством буферов, которое должно было быть записано (arg1 в buffer-sync-start); любое различие говорит о том, что во время контрольной точки другие процессы сбрасывали буферы на диск. |
| buffer-checkpoint-sync-start | () | Зонд, который срабатывает после записи грязных буферов в ядро и до начала выдачи запросов fsync. |
| buffer-checkpoint-done | () | Зонд, который срабатывает по завершении синхронизации буферов с диском. |
| twophase-checkpoint-start | () | Зонд, который срабатывает в начале двухфазного этапа контрольной точки. |
| twophase-checkpoint-done | () | Зонд, который срабатывает по завершении двухфазного этапа контрольной точки. |
| buffer-extend-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int) | Зонд, который срабатывает при запуске расширения отношения. arg0 содержит расширяемую ветвь. arg1, arg2 и arg3 содержат OID табличного пространства, базы данных и отношения, идентифицирующие это отношение. arg4 является идентификатором обслуживающего процесса, создавшего временное отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. arg5 является количеством блоков, на которое желает расшириться вызывающее отношение. |
| buffer-extend-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int, BlockNumber) | Зонд, который срабатывает по завершении расширения отношения complete. arg0 содержит расширяемую ветвь. arg1, arg2 и arg3 содержат OID табличного пространства, базы данных и отношения, идентифицирующие это отношение. arg4 является идентификатором обслуживающего процесса, создавшего временное отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. arg5 является количеством блоков, на которое расширилось это отношение, оно может быть меньше количества в buffer-extend-start, из-за ограничений ресурса. arg6 содержит BlockNumber первого нового блока. |
| buffer-read-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Зонд, который срабатывает в начале чтения из буфера. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат OID табличного пространства, базы данных и отношения, идентифицирующие это отношение. arg5 является идентификатором обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. |
| buffer-read-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) | Зонд, который срабатывает по завершении чтения из буфера. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат OID табличного пространства, базы данных и отношения, идентифицирующие это отношение. arg5 является идентификатором обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. arg6 равен true, если буфер был обнаружен в пуле, и false, если нет. |
| buffer-flush-start | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Зонд, который срабатывает перед выдачей любого запроса на запись в разделяемый буфер. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат OID табличного пространства, базы данных и отношения, идентифицирующие это отношение. |
| buffer-flush-done | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Зонд, который срабатывает по завершении запроса на запись. (Обратите внимание, что это отражает только время передачи данных ядру; обычно они еще не записаны на диск.) Аргументы те же, что и для buffer-flush-start. |
| wal-buffer-write-dirty-start | () | Зонд, который срабатывает, когда серверный процесс начинает запись в грязный буфер WAL, потому что в буферах WAL не осталось места. (Если это происходит часто, значит, значение wal_buffers слишком мало.) |
| wal-buffer-write-dirty-done | () | Зонд, который срабатывает по завершении записи в грязный буфер WAL. |
| wal-insert | (unsigned char, unsigned char) | Зонд, который срабатывает при добавлении записи в WAL. В arg0 передается идентификатор менеджера ресурсов (rmid) для записи. arg1 содержит информационные флаги. |
| wal-switch | () | Зонд, который срабатывает при запросе на переключение сегмента WAL. |
| smgr-md-read-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Зонд, который срабатывает в начале чтения блока из отношения. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. |
| smgr-md-read-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) | Зонд, который срабатывает по завершении чтения блока. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. В arg6 передается количество фактически прочитанных байтов, тогда как в arg7 — количество запрошенных байтов (если эти значения различаются, это говорит о наличии проблем). |
| smgr-md-write-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Зонд, который срабатывает в начале записи блока в отношение. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. |
| smgr-md-write-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) | Зонд, который срабатывает по завершении записи блока. arg0 и arg1 содержат номера ветви и блока этой страницы. arg2, arg3 и arg4 содержат идентификаторы табличного пространства, базы данных и отношения, которые определяют это отношение. В arg5 передается идентификатор обслуживающего процесса, создавшего временной отношение для локального буфера, или InvalidBackendId (-1) для разделяемого буфера. В arg6 передается количество фактически записанных байтов, тогда как в arg7 — количество запрошенных байтов (если эти значения различаются, это говорит о наличии проблем). |
| sort-start | (int, bool, int, int, bool, int) | Зонд, который срабатывает в начале операции сортировки. В arg0 передается сортировка кучи, индекса или элемента данных. arg1 равен true для анализа уникальных данных. В arg2 передается количество ключевых столбцов. arg3 — количество килобайт доступной рабочей памяти. arg4 равен true, если требуется произвольный доступ к результату сортировки. В arg5 значение 0 указывает на последовательный рабочий процесс, 1 — на параллельный, а 2 — на ведущий процесс в параллельной группе. |
| sort-done | (bool, long) | Зонд, который срабатывает по завершении сортировки. arg0 равен true для внешней сортировки и false для внутренней. В arg1 передается количество дисковый блоков, использованных для внешней сортировки, или килобайтов памяти, использованных для внутренней сортировки. |
| lwlock-acquire | (char *, LWLockMode) | Зонд, который срабатывает при получении блокировки LWLock. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная. |
| lwlock-release | (char *) | Зонд, который срабатывает при освобождении блокировки LWLock (но обратите внимание, что никакие освобожденные ждущие процессы еще не пробуждены). В arg0 передается транш LWLock. |
| lwlock-wait-start | (char *, LWLockMode) | Зонд, который срабатывает, когда блокировка LWLock не доступна моментально и серверный процесс начинает ждать ее освобождения. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная. |
| lwlock-wait-done | (char *, LWLockMode) | Зонд, который срабатывает при освобождении серверного процесса от ожидания LWLock (но фактически он блокировку еще не получил). В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная. |
| lwlock-condacquire | (char *, LWLockMode) | Зонд, который срабатывает при успешном получении LWLock, когда запросивший ее процесс указал режим без ожидания. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная. |
| lwlock-condacquire-fail | (char *, LWLockMode) | Зонд, который срабатывает, если LWLock не была успешно получена, когда запросивший ее процесс указал режим без ожидания. В arg0 передается транш LWLock. В arg1 — запрошенный режим блокировки: эксклюзивная или совместная. |
| lock-wait-start | (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) | Зонд, который срабатывает, когда запрос на тяжелую блокировку (блокировку lmgr) переходит в состояние ожидания, потому что эта блокировка недоступна. В аргументах с arg0 по arg3 передаются поля тегов, идентифицирующие блокируемый объект. arg4 указывает тип блокируемого объекта. arg5 указывает тип запрошенной блокировки. |
| lock-wait-done | (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) | Зонд, который срабатывает, когда запрос на тяжелую блокировку (lmgr lock) завершает ожидание (т. е. получил эту блокировку). Аргументы те же, что и для lock-wait-start. |
| deadlock-found | () | Зонд, который срабатывает при обнаружении взаимной блокировки детектором взаимоблокировок. |
Таблица 47. Определенные типы, используемые в параметрах зондов
| Тип | Определение |
|---|---|
| LocalTransactionId | unsigned int |
| LWLockMode | int |
| LOCKMODE | int |
| BlockNumber | unsigned int |
| Oid | unsigned int |
| ForkNumber | int |
| bool | unsigned int |
Использование зондов
В приведенном ниже примере показан скрипт DTrace для анализа числа транзакций в системе, как альтернатива созданию снимка pg_stat_database до и после теста производительности:
#!/usr/sbin/dtrace -qs
qhb$1:::transaction-start
{
@start["Start"] = count();
self->ts = timestamp;
}
qhb$1:::transaction-abort
{
@abort["Abort"] = count();
}
qhb$1:::transaction-commit
/self->ts/
{
@commit["Commit"] = count();
@time["Total time (ns)"] = sum(timestamp - self->ts);
self->ts=0;
}
После выполнения D-скрипт в этом примере выдает результат вида:
# ./txn_count.d `pgrep -n qhb` or ./txn_count.d <PID>
^C
Start 71
Commit 70
Total time (ns) 2312105013
Примечание
В SystemTap используется отличная от DTrace запись для скриптов трассировки, хотя лежащие в их основе точки трассировки совместимы. Однако стоит отметить, что на момент написания этого раздела скрипты SystemTap должны обращаться к зондам, обрамляя их имена двойными подчеркиваниями, а не дефисами. Ожидается, что в будущих релизах SystemTap это будет исправлено.
Следует помнить, что скрипты DTrace должны быть аккуратно написаны и отлажены, иначе собранная трассировочная информация может оказаться бессмысленной. В большинстве случаев причиной обнаруженных проблем является инструментарий, а не лежащая в основе система. При обсуждении информации, полученной с использованием динамической трассировки, обязательно прилагайте скрипт, с помощью которого они были получены, чтобы его тоже можно было проверить и обсудить.
Определение новых зондов
Новые зонды можно определить в любом месте кода, где пожелает разработчик, однако это потребует перекомпиляции. Ниже приведены шаги, которые необходимо предпринять для добавления новых зондов:
-
Определиться с именами проб и данными, которые должны стать доступными благодаря этим зондам
-
Добавить описания зондов в /usr/local/qhb/backend/utils/probes.d
-
Включить pg_trace.h, если его еще нет в модуле (или модулях), содержащих точки зондов, и добавить зондовый макрос TRACE_QHB в желаемые места в исходном коде
-
Перекомпилировать и убедиться, что новые зонды доступны
Пример:
Ниже приведен пример того, как можно добавить зонд для трассировки всех новых транзакций по их идентификатору.
-
Решаем, что зонд будет называться transaction-start и принимать параметр типа LocalTransactionId
-
Добавляем определение зонда в /usr/local/qhb/backend/utils/probes.d:
probe transaction__start(LocalTransactionId);Обратите внимание на использование двойного подчеркивания в имени зонда. В скрипте DTrace, использующем этот зонд, двойное подчеркивание следует заменить на дефис, поэтому в документации для пользователей именем этого зонда будет transaction-start.
-
Во время компиляции transaction__start преобразуется в макрос с именем TRACE_QHB_TRANSACTION_START (заметьте, что здесь подчеркивание одинарное), который доступен в результате включения pg_trace.h. Добавляет вызов этого макроса в подходящее место в исходном коде. В данном случае это будет выглядеть примерно так:
TRACE_QHB_TRANSACTION_START(vxid.localTransactionId); -
После перекомпиляции и запуска нового двоичного файла выполняем следующую команду DTrace для проверки, что только что добавленный зонд доступен. Вывод будет выглядеть примерно так:
# dtrace -ln transaction-start ID PROVIDER MODULE FUNCTION NAME 18705 qhb49878 qhb StartTransactionCommand transaction-start 18755 qhb49877 qhb StartTransactionCommand transaction-start 18805 qhb49876 qhb StartTransactionCommand transaction-start 18855 qhb49875 qhb StartTransactionCommand transaction-start 18986 qhb49873 qhb StartTransactionCommand transaction-start
При добавлении макросов трассировки в код на языке C/RUST следует позаботиться о нескольких вещах:
-
Следует позаботиться о том, чтобы типы данных, указанные в параметрах зонда, совпадали с типами данных переменных, используемых в макросе. Иначе вы получите ошибки при компиляции.
-
На большинстве платформ, если QHB собрана с указанием --enable-dtrace, аргументы макроса трассировки будут вычисляться каждый раз, когда макрос получает управление, даже если трассировка не производится. Обычно об этом не нужно беспокоиться, если вы просто возвращаете значения нескольких локальных переменных. Но избегайте помещения в эти аргументы затратных вызовов функций. Если это все-таки необходимо, попробуйте защитить макрос проверкой, чтобы понять, действительно ли включена трассировка:
if (TRACE_QHB_TRANSACTION_START_ENABLED()) TRACE_QHB_TRANSACTION_START(some_function(...));У каждого макроса трассировки есть соответствующий макрос ENABLED.