Динамическая трассировка
Эта страница переведена при помощи нейросети GigaChat.
PostgreSQL предоставляет средства для поддержки динамического отслеживания сервера баз данных. Это позволяет внешней утилите вызываться в определенных точках кода и тем самым отслеживать выполнение.
В исходный код уже вставлено несколько зондов или точек трассировки. Эти зонды предназначены для использования разработчиками баз данных и администраторами. По умолчанию зонды не компилируются в PostgreSQL, пользователь должен явно указать сценарию настройки сделать зонды доступными.
В настоящее время поддерживается утилита DTrace, которая, на момент написания этой статьи, доступна в Solaris, macOS, FreeBSD, NetBSD и Oracle Linux. Проект SystemTap для Linux предоставляет эквивалент DTrace и также может быть использован. Теоретически возможно поддерживать другие динамические утилиты трассировки путем изменения определений макросов в src/include/utils/probes.h
.
Компиляция для динамической трассировки
По умолчанию зонды недоступны, поэтому нужно будет явно указать сценарию настройки сделать зонды доступными в PostgreSQL. Чтобы включить поддержку DTrace, укажите --enable-dtrace
к конфигурации. См. раздел «Процедура установки» для получения дополнительной информации.
Встроенные точки трассировки
В исходном коде предоставляется ряд стандартных зондов, как показано в таблице ниже. Таблица «Предопределенные типы, используемые в параметрах зондирования» показывает типы, используемые в зондах. Конечно, можно добавить больше зондов для улучшения наблюдаемости PostgreSQL.
Встроенные точки трассировки 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 содержатся битовые флаги, используемые для различения различных типов контрольных точек, таких как завершение работы, немедленная или принудительная. |
checkpoint-done | (int, int, int, int, int) | Зонд, срабатывающий после завершения контрольной точки. (Следующие перечисленные зонды срабатывают последовательно во время обработки контрольной точки.) Аргумент arg0 --- это количество записанных буферов. Arg1 --- общее количество буферов. Arg2, arg3 и arg4 содержат соответственно количество файлов журнала предзаписи (WAL), добавленных, удаленных и переработанных. |
clog-checkpoint-start | (bool) | Зонд, срабатывающий при старте части контрольной точки CLOG. Arg0 равен true для обычной контрольной точки и false для контрольной точки завершения работы. |
clog-checkpoint-done | (bool) | Зонд, срабатывающий по окончании части контрольной точки CLOG. Arg0 имеет тот же смысл, что и для clog-checkpoint-start . |
subtrans-checkpoint-start | (bool) | Зонд, срабатывающий при старте части контрольной точки SUBTRANS. Arg0 равен true для обычной контрольной точки и false для контрольной точки завершения работы. |
subtrans-checkpoint-done | (bool) | Зонд, срабатывающий по окончании части контрольной точки SUBTRANS. Arg0 имеет тот же смысл, что и для subtrans-checkpoint-start . |
multixact-checkpoint-start | (bool) | Зонд, срабатывающий при старте части контрольной точки MultiXact. Arg0 равен true для обычной контрольной точки и false для контрольной точки завершения работы. |
multixact-checkpoint-done | (bool) | Зонд, срабатывающий по окончании части контрольной точки MultiXact. Arg0 имеет тот же смысл, что и для multixact-checkpoint-start . |
buffer-checkpoint-start | (int) | Зонд, срабатывающий при старте записи буферов в рамках контрольной точки. В аргументе arg0 содержатся битовые флаги, используемые для различения различных типов контрольных точек, таких как завершение работы, немедленная или принудительная. |
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 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. Arg4 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. Arg5 --- это количество блоков, на которое вызывающая сторона хотела бы расширить. |
buffer-extend-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int, BlockNumber) | Зонд, срабатывающий при полном расширении отношения. Arg0 содержит вилку, подлежащую расширению. Arg1, arg2 и arg3 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. Arg4 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. Arg5 --- это количество блоков, на которое было расширено отношение; оно может быть меньше числа из buffer-extend-start из-за ограничений ресурсов. Arg6 содержит BlockNumber первого нового блока. |
buffer-read-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Зонд, срабатывающий при чтении буфера. Arg0 и arg1 содержат номера вилки и блока страницы. Arg2, arg3 и arg4 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. Arg5 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. |
buffer-read-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) | Зонд, срабатывающий по завершении чтения буфера. Arg0 и arg1 содержат номера вилки и блока страницы. Arg2, arg3 и arg4 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. Arg5 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. Arg6 равен true, если буфер был найден в пуле, иначе --- false. |
buffer-flush-start | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Зонд, срабатывающий непосредственно перед отправкой любого запроса на запись для общего буфера. Arg0 и arg1 содержат номера вилки и блока страницы. Arg2, arg3 и arg4 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. |
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 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. |
smgr-md-read-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) | Зонд, срабатывающий по завершении чтения блока. Arg0 и arg1 содержат номера вилки и блока страницы. Arg2, arg3 и arg4 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. Arg5 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. Arg6 --- это количество байтов, фактически прочитанных, а arg7 --- запрошенное количество (если они различаются, это указывает на неполное чтение). |
smgr-md-write-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Зонд, срабатывающий при начале записи блока в отношении. Arg0 и arg1 содержат номера вилки и блока страницы. Arg2, arg3 и arg4 содержат идентификаторы пространства таблиц, базы данных и отношения, определяющие отношение. Arg5 --- это идентификатор бэкенда, который создал временное отношение для локального буфера, или INVALID_PROC_NUMBER (-1) для общего буфера. |
smgr-md-write-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) | Зонд, срабатывающий при завершении записи блока. Аргументы arg0 и arg1 содержат идентификаторы вилки и номера блока страницы. Аргументы arg2, arg3 и arg4 содержат OID табличного пространства, базы данных и отношения, определяющие отношение. Arg5 --- это идентификатор бэкенда, который создал временную таблицу для локального буфера, или INVALID_PROC_NUMBER (-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-wait-start . |
deadlock-found | () | Зонд, срабатывающий, когда детектор взаимоблокировок обнаруживает тупиковую ситуацию. |
Предопределенные типы, используемые в параметрах зондирования:
Тип | Определение |
---|---|
LocalTransactionId | unsigned int |
LWLockMode | int |
LOCKMODE | int |
BlockNumber | unsigned int |
Oid | unsigned int |
ForkNumber | int |
bool | unsigned char |
Использование точек трассировки
В приведенном ниже примере показан сценарий DTrace для анализа количества транзакций в системе в качестве альтернативы созданию моментальных снимков pg_stat_database
до и после теста производительности:
#!/usr/sbin/dtrace -qs
postgresql$1:::transaction-start
{
@start["Start"] = count();
self->ts = timestamp;
}
postgresql$1:::transaction-abort
{
@abort["Abort"] = count();
}
postgresql$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 postgres` or ./txn_count.d <PID>
^C
Start 71
Commit 70
Total time (ns) 2312105013
SystemTap использует другую нотацию для скриптов трассировки, чем DTrace, хотя основные точки трассировки совместимы. Одним из моментов, которые стоит отметить, является то, что на момент написания этой статьи сценарии SystemTap должны ссылаться на имена датчиков с использованием двойных подчеркиваний вместо дефисов. Ожидается, что это будет исправлено в будущих версиях SystemTap.
Следует помнить, что сценарии DTrace необходимо тщательно писать и отлаживать, иначе собранная информация о трассировке может оказаться бессмысленной. В большинстве случаев, когда возникают проблемы, виновато инструментальное средство, а не основная система. При обсуждении информации, полученной с помощью динамической трассировки, убедитесь, что сценарий, используемый для этого, также заключен в скобки, чтобы его тоже можно было проверить и обсудить.
Определение новых точек трассировки
Новые зонды могут быть определены в коде везде, где разработчику это угодно, хотя для этого потребуется перекомпиляция. Ниже приведены шаги для вставки новых зондов:
- Определите имена датчиков и данные, которые будут доступны через датчики
- Добавьте определения датчиков в
src/backend/utils/probes.d
- Включите
pg_trace.h
, если он еще не присутствует в модуле(ях), содержащем точки датчика, и вставьтеTRACE_POSTGRESQL
макросы датчика в желаемые места в исходном коде - Пересоберите и убедитесь, что новые датчики доступны.
Пример добавление точки для трассировки всех новых транзакций по идентификатору транзакции:
-
Пробник будет называться
transaction-start
и принимать параметр типаLocalTransactionId
. -
Добавьте определение пробника к
src/backend/utils/probes.d
:probe transaction__start(LocalTransactionId);
Обратите внимание на использование двойного подчеркивания в имени пробника. В сценарии DTrace, использующем зонд, двойное подчеркивание необходимо заменить дефисом, поэтому
transaction-start
является именем, которое следует задокументировать для пользователей. -
Во время компиляции
transaction__start
преобразуется в макроопределение под названиемTRACE_POSTGRESQL_TRANSACTION_START
(обратите внимание, что здесь подчеркивание одинарное), которое доступно при включенииpg_trace.h
. Добавьте вызов макроса в соответствующее место исходного кода. В данном случае это выглядит следующим образом:TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
-
После перекомпиляции и запуска нового двоичного файла проверьте, доступен ли недавно добавленный зонд, выполнив следующую команду DTrace. Вывод должен быть аналогичен:
# dtrace -ln transaction-start
ID PROVIDER MODULE FUNCTION NAME
18705 postgresql49878 postgres StartTransactionCommand transaction-start
18755 postgresql49877 postgres StartTransactionCommand transaction-start
18805 postgresql49876 postgres StartTransactionCommand transaction-start
18855 postgresql49875 postgres StartTransactionCommand transaction-start
18986 postgresql49873 postgres StartTransactionCommand transaction-start
Есть несколько моментов, о которых следует помнить при добавлении макросов трассировки к коду на языке Си:
-
Нужно убедиться, что типы данных, указанные для параметров зонда, соответствуют типам данных переменных, используемых в макросе. В противном случае будут получены ошибки компиляции.
-
На большинстве платформ, если PostgreSQL построен с
--enable-dtrace
, аргументы трассируемого макроса будут оцениваться каждый раз при прохождении через этот макрос, даже если нет никакого отслеживания. Обычно об этом не стоит беспокоиться, если просто сообщаете значения нескольких локальных переменных. Но остерегайтесь помещать дорогостоящие вызовы функций в аргументы. Если нужно сделать это, рассмотрите возможность защиты макроса проверкой, чтобы увидеть, действительно ли включен трассировщик:if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));Каждый трассируемый макрос имеет соответствующий
ENABLED
макрос.