PostgreSQL 提供了支持数据库服务器动态追踪的工具。这允许在代码中的特定点调用外部实用程序,从而跟踪执行。
许多探针或跟踪点已经插入到源代码中。这些探针旨在供数据库开发人员和管理员使用。默认情况下,这些探针不会编译到 PostgreSQL 中;用户需要显式地告知 configure 脚本使这些探针可用。
目前,支持 DTrace 实用程序,在撰写本文时,它在 Solaris、macOS、FreeBSD、NetBSD 和 Oracle Linux 上可用。Linux 的 SystemTap 项目提供了 DTrace 的等效功能,也可以使用。理论上,通过更改 src/include/utils/probes.h
中的宏定义,可以支持其他动态跟踪实用程序。
默认情况下,探针不可用,因此您需要显式地告知 configure 脚本使探针在 PostgreSQL 中可用。要包含 DTrace 支持,请指定 --enable-dtrace
给 configure。有关详细信息,请参阅 第 17.3.3.6 节。
源代码中提供了一些标准探针,如 表 27.49 所示;表 27.50 显示了探针中使用的类型。当然可以添加更多探针来增强 PostgreSQL 的可观察性。
表 27.49. 内置 DTrace 探针
名称 | 参数 | 描述 |
---|---|---|
transaction-start |
(LocalTransactionId) |
在新事务开始时触发的探针。arg0 是事务 ID。 |
transaction-commit |
(LocalTransactionId) |
在事务成功完成时触发的探针。arg0 是事务 ID。 |
transaction-abort |
(LocalTransactionId) |
在事务未成功完成时触发的探针。arg0 是事务 ID。 |
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 是缓冲区的 ID 号。 |
buffer-sync-done |
(int, int, int) |
在所有脏缓冲区都已写入时触发的探针。arg0 是缓冲区的总数。arg1 是检查点进程实际写入的缓冲区数量。arg2 是预期写入的数量(buffer-sync-start 的 arg1);任何差异都反映了检查点期间其他进程刷新缓冲区的情况。 |
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 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-1)。arg5 是调用者希望扩展的块数。 |
buffer-extend-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int, BlockNumber) |
在关系扩展完成时触发的探针。arg0 包含要扩展的分支。arg1、arg2 和 arg3 包含标识关系的表空间、数据库和关系 OID。arg4 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-1)。arg5 是关系扩展的块数,由于资源限制,这可以小于 buffer-extend-start 中的数量。arg6 包含第一个新块的 BlockNumber。 |
buffer-read-start |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int) |
在缓冲区读取开始时触发的探针。arg0 和 arg1 包含页面的分支和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-1)。 |
buffer-read-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) |
在缓冲区读取完成时触发的探针。arg0 和 arg1 包含页面的分支和块号。arg2、arg3 和 arg4 包含标识关系的表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-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 包含页面的 fork 和块号。arg2、arg3 和 arg4 包含标识该关系表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-1)。 |
smgr-md-read-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) |
当块读取完成时触发的探针。arg0 和 arg1 包含页面的 fork 和块号。arg2、arg3 和 arg4 包含标识该关系表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-1)。arg6 是实际读取的字节数,而 arg7 是请求的字节数(如果这些不同,则表示读取不足)。 |
smgr-md-write-start |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int) |
当开始向关系写入块时触发的探针。arg0 和 arg1 包含页面的 fork 和块号。arg2、arg3 和 arg4 包含标识该关系表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 INVALID_PROC_NUMBER (-1)。 |
smgr-md-write-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) |
当块写入完成时触发的探针。arg0 和 arg1 包含页面的 fork 和块号。arg2、arg3 和 arg4 包含标识该关系表空间、数据库和关系 OID。arg5 是为本地缓冲区创建临时关系的后端的 ID,对于共享缓冲区,则为 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 |
() |
当死锁检测器发现死锁时触发的探针。 |
表 27.50. 探针参数中使用的已定义类型
类型 | 定义 |
---|---|
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
探针宏
重新编译并验证新探针是否可用
示例: 以下示例说明如何添加探针以按事务 ID 跟踪所有新事务。
确定探针将被命名为 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
在将跟踪宏添加到 C 代码时,需要注意一些事项
您应注意,为探针参数指定的数据类型与宏中使用的变量的数据类型匹配。否则,您将收到编译错误。
在大多数平台上,如果 PostgreSQL 是使用 --enable-dtrace
构建的,则无论何时控制通过宏,都会评估跟踪宏的参数,即使未进行任何跟踪。如果您只是报告一些局部变量的值,通常不必担心这一点。但是,请注意不要将昂贵的函数调用放入参数中。如果您需要这样做,请考虑使用检查来保护宏,以查看是否实际启用了跟踪
if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED()) TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));
每个跟踪宏都有一个对应的 ENABLED
宏。
如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。