On this page
28.5. 动态跟踪
PostgreSQL 提供了支持数据库服务器动态跟踪的功能。这允许在代码中的特定点调用外部 Util,从而跟踪执行。
许多探针或跟踪点已经插入到源代码中。这些探针旨在供数据库开发人员和 Management 员使用。默认情况下,探针不会编译到 PostgreSQL 中。用户需要明确告诉 configure 脚本以使探针可用。
当前支持DTraceUtil,在撰写本文时,该 Util 可在 Solaris,macOS,FreeBSD,NetBSD 和 Oracle Linux 上使用。 Linux 的SystemTap项目提供了 DTrace 等效项,也可以使用。理论上,通过更改src/include/utils/probes.h
中宏的定义,可以支持其他动态跟踪 Util。
28 .5.1. 进行动态跟踪编译
默认情况下,探针不可用,因此您需要明确地告诉 configure 脚本以使探针在 PostgreSQL 中可用。要包括 DTrace 支持,请指定--enable-dtrace
进行配置。有关更多信息,请参见Section 16.4。
28 .5.2. 内置探头
源代码中提供了许多标准探针,如Table 28.23所示; Table 28.24显示了探针中使用的类型。当然可以添加更多探针以增强 PostgreSQL 的可观察性。
表 28.23. 内置 DTrace 探头
Name | Parameters | Description |
---|---|---|
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-read-start |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) |
开始读取缓冲区时将触发的探针。 arg0 和 arg1 包含页面的派生号和块号(但如果这是一个关系扩展请求,则 arg1 将为-1)。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 OID。 arg5 是后端的 ID,该后端为本地缓冲区创建了临时关系,为共享缓冲区创建了InvalidBackendId (-1)。 arg6 对于关系扩展请求为 true,对于正常读取为 false。 |
buffer-read-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool, bool) |
缓冲区读取完成时将触发的探针。 arg0 和 arg1 包含页面的派生号和块号(如果这是一个关系扩展请求,则 arg1 现在包含新添加的块的块号)。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 OID。 arg5 是后端的 ID,该后端为本地缓冲区创建了临时关系,为共享缓冲区创建了InvalidBackendId (-1)。 arg6 对于关系扩展请求为 true,对于正常读取为 false。如果在池中找到了缓冲区,则 arg7 为 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 相同。 |
buffer-write-dirty-start |
(ForkNumber, BlockNumber, Oid, Oid, Oid) |
服务器进程开始写入脏缓冲区时将触发的探针。 (如果这种情况经常发生,则意味着shared_buffers太小或需要后台编写器控制参数进行调整。)arg0 和 arg1 包含页面的派生号和块号。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 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 是记录的资源 Management 器(rmid)。 arg1 包含信息标志。 |
wal-switch |
() |
请求 WAL 段切换时将触发的探测器。 |
smgr-md-read-start |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int) |
开始从关系读取块时将触发的探针。 arg0 和 arg1 包含页面的派生号和块号。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 OID。 arg5 是后端的 ID,该后端为本地缓冲区创建了临时关系,为共享缓冲区创建了InvalidBackendId (-1)。 |
smgr-md-read-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) |
块读取完成时将触发的探针。 arg0 和 arg1 包含页面的派生号和块号。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 OID。 arg5 是后端的 ID,该后端为本地缓冲区创建了临时关系,为共享缓冲区创建了InvalidBackendId (-1)。 arg6 是实际读取的字节数,而 arg7 是请求的数目(如果它们不同,则表示有问题)。 |
smgr-md-write-start |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int) |
开始将块写入关系时将触发的探针。 arg0 和 arg1 包含页面的派生号和块号。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 OID。 arg5 是后端的 ID,该后端为本地缓冲区创建了临时关系,为共享缓冲区创建了InvalidBackendId (-1)。 |
smgr-md-write-done |
(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) |
块写入完成时将触发的探针。 arg0 和 arg1 包含页面的派生号和块号。 arg2,arg3 和 arg4 包含表空间,数据库和标识关系的关系 OID。 arg5 是后端的 ID,该后端为本地缓冲区创建了临时关系,为共享缓冲区创建了InvalidBackendId (-1)。 arg6 是实际写入的字节数,而 arg7 是请求的数目(如果它们不同,则表示有问题)。 |
sort-start |
(int, bool, int, int, bool) |
开始排序操作时将触发的探针。 arg0 指示堆,索引或基准排序。 arg1 对于唯一值强制实施为 true。 arg2 是键列的数量。 arg3 是允许的工作内存的千字节数。如果需要随机访问排序结果,则 arg4 为 true。 |
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 不立即可用且服务器进程已开始 await 锁可用时触发的探测器。 arg0 是 LWLock 的一部分。 arg1 是请求的锁定模式,可以是独占模式或共享模式。 |
lwlock-wait-done |
(char *, LWLockMode) |
从 awaitLWLock 释放服务器进程时触发的探测器(它实际上还没有锁)。 arg0 是 LWLock 的一部分。 arg1 是请求的锁定模式,可以是独占模式或共享模式。 |
lwlock-condacquire |
(char *, LWLockMode) |
当调用者指定不 await 时,成功获取 LWLock 时将触发的探测器。 arg0 是 LWLock 的一部分。 arg1 是请求的锁定模式,可以是独占模式或共享模式。 |
lwlock-condacquire-fail |
(char *, LWLockMode) |
当调用者指定不 await 时,未成功获取 LWLock 时将触发的探测器。 arg0 是 LWLock 的一部分。 arg1 是请求的锁定模式,可以是独占模式或共享模式。 |
lock-wait-start |
(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) |
由于无法使用重型锁(lmgr 锁)的请求而开始 await 该锁时将触发的探测器。 arg0 到 arg3 是标识被锁定对象的标记字段。 arg4 指示被锁定的对象的类型。 arg5 指示正在请求的锁定类型。 |
lock-wait-done |
(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) |
对重量级锁(lmgr 锁)的请求完成 await(即已获得锁)时将触发的探测器。参数与lock-wait-start 相同。 |
deadlock-found |
() |
当死锁检测器发现死锁时将触发的探测器。 |
表 28.24. 探针参数中使用的已定义类型
Type | Definition |
---|---|
LocalTransactionId |
unsigned int |
LWLockMode |
int |
LOCKMODE |
int |
BlockNumber |
unsigned int |
Oid |
unsigned int |
ForkNumber |
int |
bool |
char |
28 .5.3. 使用探针
下面的示例显示了一个 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
Note
即使基础跟踪点兼容,SystemTap 对跟踪脚本也使用与 DTrace 不同的符号。值得注意的一点是,在撰写本文时,SystemTap 脚本必须使用双下划线代替连字符来引用探针名称。预期在将来的 SystemTap 版本中将修复此问题。
您应该记住,DTrace 脚本需要仔细编写和调试,否则收集的跟踪信息可能毫无意义。在大多数情况下,发现问题的是仪器故障,而不是基础系统。在讨论使用动态跟踪找到的信息时,请确保将用于检查和讨论该脚本的内容包含在内。
28 .5.4. 定义新探针
可以在开发人员希望的任何地方在代码中定义新的探针,尽管这需要重新编译。以下是插入新探针的步骤:
确定探针名称和将通过探针提供的数据
将探针定义添加到
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
构建的,则只要控制通过宏,即使没有完成跟踪,也会对 trace 宏的参数进行评估。如果仅报告一些局部变量的值,通常就不必担心。但是要注意不要将昂贵的函数调用放入参数中。如果需要这样做,请考虑通过检查是否实际启用跟踪来保护宏:
if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));
每个跟踪宏都有一个对应的ENABLED
宏。