Динамическая трассировка#

Примечание

Эта страница переведена при помощи нейросети 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 *)

Зонд, который срабатывает при запуске обработки запроса. Аргумент 0 - это строка запроса

query-done

(const char *)

Зонд, который срабатывает при завершении обработки запроса. Аргумент 0 - это строка запроса

query-parse-start

(const char *)

Зонд, который срабатывает при начале разбора запроса. Аргумент 0 - это строка запроса

query-parse-done

(const char *)

Зонд, который срабатывает при завершении разбора запроса. Аргумент 0 - это строка запроса

query-rewrite-start

(const char *)

Зонд, который срабатывает при начале перезаписи запроса. Аргумент 0 - это строка запроса

query-rewrite-done

(const char *)

Зонд, который срабатывает при завершении перезаписи запроса. Аргумент 0 - это строка запроса

query-plan-start

()

Зонд, который срабатывает при начале планирования запроса

query-plan-done

()

Зонд, который срабатывает при завершении планирования запроса

query-execute-start

()

Зонд, который срабатывает при запуске выполнения запроса

query-execute-done

()

Зонд, который срабатывает при завершении выполнения запроса

statement-status

(const char *)

Зонд, который срабатывает всякий раз, когда серверный процесс обновляет свой pg_stat_activity.status. Аргумент 0 содержит новую строку состояния

checkpoint-start

(int)

Зонд, который срабатывает при начале контрольной точки. Аргумент 0 содержит битовую маску, используемую для различения различных типов контрольных точек, таких как завершение работы, немедленное или принудительное

checkpoint-done

(int, int, int, int, int)

Зонд, который срабатывает при завершении контрольной точки. (Следующие зонды срабатывают последовательно во время обработки контрольной точки.) Arg0 - это количество записанных буферов. Arg1 - общее количество буферов. Arg2, arg3 и arg4 содержат количество добавленных, удаленных и переработанных файлов WAL соответственно

clog-checkpoint-start

(bool)

Зонд, который срабатывает, когда начинается часть CLOG контрольной точки. Arg0 истинно для нормальной контрольной точки, ложно для контрольной точки завершения работы

clog-checkpoint-done

(bool)

Зонд, который срабатывает, когда завершена часть CLOG контрольной точки. Arg0 имеет то же значение, что и для clog-checkpoint-start

subtrans-checkpoint-start

(bool)

Зонд, который срабатывает, когда запускается часть SUBTRANS контрольной точки. Arg0 истинно для нормальной контрольной точки, ложно для контрольной точки завершения работы

subtrans-checkpoint-done

(bool)

Зонд, который срабатывает при завершении части SUBTRANS контрольной точки. arg0 имеет то же значение, что и для subtrans-checkpoint-start

multixact-checkpoint-start

(bool)

Зонд, который срабатывает, когда начинается часть MultiXact контрольной точки. arg0 истинно для нормальной контрольной точки, ложно для контрольной точки завершения работы

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-read-start

(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool)

Зонд, который срабатывает при запуске чтения буфера. Аргументы 0 и 1 содержат номера вилки и блока страницы (но аргумент 1 будет равен -1, если это запрос расширения отношения). Аргументы 2, 3 и 4 содержат OID пространства таблиц, базы данных и отношения, идентифицирующие отношение. Аргумент 5 - это идентификатор бэкенда, который создал временное отношение для локального буфера или InvalidBackendId (-1) для общего буфера. Аргумент 6 истинен для запроса расширения отношения, ложен для обычного чтения

buffer-read-done

(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool, bool)

Зонд, который срабатывает при завершении чтения буфера. Аргументы 0 и 1 содержат номера вилки и блока страницы (если это запрос расширения отношения, то теперь аргумент 1 содержит номер блока нового добавленного блока). Аргументы 2, 3 и 4 содержат OID пространства таблиц, базы данных и отношения, идентифицирующие отношение. Аргумент 5 - это идентификатор бэкенда, который создал временное отношение для локального буфера или InvalidBackendId (-1) для общего буфера. Аргумент 6 истинен для запроса расширения отношения, ложен для нормального чтения. Аргумент 7 истинен, если буфер был найден в пуле, ложен, если нет

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

buffer-write-dirty-start

(ForkNumber, BlockNumber, Oid, Oid, Oid)

Зонд, который срабатывает, когда серверный процесс начинает записывать поврежденный буфер. (Если это происходит часто, это означает, что shared_buffers слишком мал или параметры управления фоновым писателем нуждаются в корректировке.) Аргументы 0 и 1 содержат номера вилки и блока страницы. Аргументы 2, 3 и 4 содержат OID пространства таблиц, базы данных и отношения, идентифицирующие отношение

buffer-write-dirty-done

(ForkNumber, BlockNumber, Oid, Oid, Oid)

Зонд, который срабатывает при завершении записи поврежденного буфера. Аргументы такие же, как для buffer-write-dirty-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)

Зонд, который срабатывает при начале чтения блока из отношения. Аргументы 0 и 1 содержат номера вилки и блока страницы. Аргументы 2, 3 и 4 содержат OID пространства таблиц, базы данных и отношения, идентифицирующие отношение. Аргумент 5 - это идентификатор бэкенда, который создал временное отношение для локального буфера или InvalidBackendId (-1) для общего буфера

smgr-md-read-done

(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)

Зонд, который срабатывает при завершении чтения блока. Аргументы 0 и 1 содержат номера вилки и блока страницы. Аргументы 2, 3 и 4 содержат OID пространства таблиц, базы данных и отношения, идентифицирующие отношение. Аргумент 5 - это идентификатор бэкенда, который создал временное отношение для локального буфера или InvalidBackendId (-1) для общего буфера. Аргумент 6 - это количество фактически прочитанных байтов, а аргумент 7 - запрошенное количество (если они различаются, это указывает на проблему)

smgr-md-write-start

(ForkNumber, BlockNumber, Oid, Oid, Oid, int)

Зонд, который срабатывает при начале записи блока в отношение. Аргументы 0 и 1 содержат номера вилки и блока страницы. Аргументы 2, 3 и 4 содержат OID пространства таблиц, базы данных и отношения, идентифицирующие отношение. Аргумент 5 - это идентификатор бэкенда, который создал временное отношение для локального буфера или InvalidBackendId (-1) для общего буфера

smgr-md-write-done

(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)

Зонд срабатывает при завершении записи блока. Аргументы 0 и 1 содержат вилку и номера блоков страницы. Аргументы 2, 3 и 4 содержат OID таблицы пространства, базы данных и отношения, идентифицирующие отношение. Аргумент 5 - это идентификатор бэкенда, который создал временное отношение для локального буфера или InvalidBackendId (-1) для общего буфера. Аргумент 6 - это количество байтов, фактически записанных, тогда как аргумент 7 - это запрошенное количество (если они разные, это указывает на проблему)

sort-start

(int, bool, int, int, bool, int)

Зонд срабатывает, когда начинается операция сортировки. Аргумент 0 указывает кучу, индекс или сортировку данных. Аргумент 1 истинен для обеспечения уникальности значений. Аргумент 2 - это количество столбцов ключей. Аргумент 3 - это количество килобайт рабочей памяти, разрешенной для работы. Аргумент 4 истинен, если требуется произвольный доступ к результату сортировки. Аргумент 5 указывает последовательный режим, когда 0, рабочий параллельный процесс, когда 1, или ведущий параллельный процесс, когда 2

sort-done

(bool, long)

Зонд срабатывает, когда сортировка завершена. Аргумент 0 истинен для внешней сортировки, ложен для внутренней сортировки. Аргумент 1 - это количество дисковых блоков, используемых для внешней сортировки, или килобайты памяти, используемые для внутренней сортировки

lwlock-acquire

(char *, LWLockMode)

Зонд срабатывает, когда приобретается LWLock. Аргумент 0 - это траншея LWLock. Аргумент 1 - это запрашиваемый режим блокировки, либо исключительный, либо общий

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) начинает ожидать, потому что блокировка недоступна. Аргументы от 0 до 3 являются полями тега, идентифицирующими заблокированный объект. 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 необходимо тщательно писать и отлаживать, иначе собранная информация о трассировке может оказаться бессмысленной. В большинстве случаев, когда возникают проблемы, виновато инструментальное средство, а не основная система. При обсуждении информации, полученной с помощью динамической трассировки, убедитесь, что сценарий, используемый для этого, также заключен в скобки, чтобы его тоже можно было проверить и обсудить.

Определение новых точек трассировки#

Новые зонды могут быть определены в коде везде, где разработчику это угодно, хотя для этого потребуется перекомпиляция. Ниже приведены шаги для вставки новых зондов:

  1. Определите имена датчиков и данные, которые будут доступны через датчики

  2. Добавьте определения датчиков в src/backend/utils/probes.d

  3. Включите pg_trace.h, если он еще не присутствует в модуле(ях), содержащем точки датчика, и вставьте TRACE_POSTGRESQL макросы датчика в желаемые места в исходном коде

  4. Пересоберите и убедитесь, что новые датчики доступны.

Пример добавление точки для трассировки всех новых транзакций по идентификатору транзакции:

  1. Пробник будет называться transaction-start и принимать параметр типа LocalTransactionId.

  2. Добавьте определение пробника к src/backend/utils/probes.d:

    probe transaction__start(LocalTransactionId);
    

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

  3. Во время компиляции transaction__start преобразуется в макроопределение под названием TRACE_POSTGRESQL_TRANSACTION_START (обратите внимание, что здесь подчеркивание одинарное), которое доступно при включении pg_trace.h. Добавьте вызов макроса в соответствующее место исходного кода. В данном случае это выглядит следующим образом:

    TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
    
  4. После перекомпиляции и запуска нового двоичного файла проверьте, доступен ли недавно добавленный зонд, выполнив следующую команду 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 макрос.