27.4. 动态跟踪

PostgreSQL允许对数据库服务器进行动态跟踪。 这样就允许在代码内特定的点上调用外部工具来跟踪执行过程。

许多跟踪点(也被称为"探头")已经插入在源代码中了, 这些探针的目的是被用于数据库开发者和管理员,默认情况下, 探头不编译成PostgreSQL; 用户必须运行配置脚本时明确启用它们。

目前,只有DTrace支持实用工具,在写这的时候, 它可在Solaris, Mac OS X, FreeBSD, NetBSD和Oracle Linux上使用。 SystemTap 项目为Linux还提供了一个DTrace的等效并且也是可用的。 通过改变src/include/utils/probes.h 中的宏命令定义为支持其他的动态跟踪工具在理论上是可能的 。

27.4.1. 编译动态跟踪支持

跟踪点是默认禁止的,你必须明确告诉配置脚本以使得PostgreSQL中的探头可用。 使用--enable-dtrace选项来启用DTrace支持。 参见第 15.4 节获取更多信息。

27.4.2. 内置跟踪点

表 27-15显示的是在源代码中提供的标准跟踪点, 表 27-16显示探测中使用的类型。 更多探测可以被添加以提高PostgreSQL的观测性。

表 27-15. 内置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可以逐位标记以区分不同的检查点类型, 如;shutdown,immediate,或force。
checkpoint-done(int, int, int, int, int)检查点完成时触发的探测器(触发探测器列出检查点处理过程序列中的下一个探测器)。 arg0表示要写入的缓冲区的数目。arg1表示总的缓冲区的数目。 arg2,arg3和arg4包含了增加,删除和循环回收的xlog文件的数目。
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持有逐位标识以区分不同的检查点类型,如shutdown,immediate或force。
buffer-sync-start(int, int)检查点期间,开始写脏缓冲区时触发的探测器(在识别出那个缓冲区必须写之后)。 arg0表示总缓冲区数,arg1表示当前脏的,需要写的缓冲区数。
buffer-sync-written(int)在检查点期间,每个缓冲区都被写了之后触发的探测器,arg0表示缓冲区的ID号。
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)当开始一次缓冲区读时触发的探测器。 arg0和arg1包含page块的锁和派生的子进程数(如果是一个关系扩展请求,arg1会是-1)。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5是为局部缓冲创建临时关系时后端ID,或者共享缓冲区InvalidBackendId (-1)。 arg6对关系扩展请求表示真,对正常读表示假。
buffer-read-done(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool, bool)当完成一次缓冲区读时触发的探测器。 arg0和arg1包含page块的锁和派生的子进程数 (如果是一个关系扩展请求,arg1会表示新增锁的数目)。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5是为局部缓冲创建临时关系时后端ID,或者共享缓冲区InvalidBackendId (-1)。 arg6对关系扩展请求表示真, 对正常读表示假。如果池中有缓冲区, 则arg7表示真,反之表示假。
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太小, 或需要调整bgwriter控制参数。 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写时触发的探测器。
xlog-insert(unsigned char, unsigned char)当插入一条WAL记录时触发的探测器。 arg0表示记录的rm id。 arg1包含信息标志。
xlog-switch()当要求进行WAL切换时触发的探测器。
smgr-md-read-start(ForkNumber, BlockNumber, Oid, Oid, Oid, int)开始从一个关系中读取锁时触发的探测器。 arg0和arg1包含page块的锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID, 以识别关系。arg5是为局部缓冲创建临时关系时的后端ID,或者共享缓冲InvalidBackendId (-1)
smgr-md-read-done(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)当一个锁读取完成时触发的探测器。 arg0和arg1包含page块的锁和派生的子进程数。 arg2,arg3和arg4包含表空间, 数据库和关系OID,以识别关系。 arg5是为局部缓冲创建临时关系时的后端ID,或者共享缓冲InvalidBackendId (-1), 而arg6表示实际读取的字节数,而arg7是要求数(如果不一样会报错)
smgr-md-write-start(ForkNumber, BlockNumber, Oid, Oid, Oid, int)当向一个关系中写入锁时触发的探测器。 arg0和arg1包含page块的锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5是为局部缓冲创建临时关系时的后端ID,或者共享缓冲InvalidBackendId (-1)。
smgr-md-write-done(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)当一个锁写进程完成时触发的探测器。 arg0和arg1表示page块的锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5表示为局部缓冲创建临时关系时的后端ID,或者共享缓冲InvalidBackendId (-1), 而arg6表示实际读取的字节数,而arg7是要求数(如果不一样会报错)。
sort-start(int, bool, int, int, bool)排序操作开始时触发的探测器。 arg0表示堆,索引或者基准点。 arg1对强制唯一值表示真。arg2表示键列的数目。 arg3表示允许使用的内存数目(以千字节为单位)。 如果要求随机访问排序结果,那么arg4表示真。
sort-done(bool, long)排序操作结束时触发的探测器。 arg0对外部排序表示真,内部排序表示假。 arg1表示用于一个外部排序的磁盘锁的数目, 或用于一个内部排序的,以千字节为单位的内存数目。
lwlock-acquire(LWLockId, LWLockMode)当成功获得一个LWLock时触发的探测器。 arg0是LWLock的ID号,arg1表明请求的锁模式,要么独占要么共享
lwlock-release(LWLockId)LWLock释放时触发的探测器 (但是请注意任何发布的等待者还未觉醒)。 arg0表示LWLock的ID号
lwlock-wait-start(LWLockId, LWLockMode)当不能立即获得LWLock锁,同时服务进程进入等待时触发的探测器。 arg0是LWLock的ID号,arg1表明请求的锁模式,要么独占要么共享。
lwlock-wait-done(LWLockId, LWLockMode)当从一个LWLock锁中释放服务进程时触发的探测器(实际上没有进行锁)。 arg0是LWLock的ID号,arg1表明请求的锁模式,要么独占要么共享。
lwlock-condacquire(LWLockId, LWLockMode)当成功获得一个LWLock时触发的探测器(已声明调用无需等待)。 arg0是LWLock的ID号,arg1表明请求的锁的模式,要么独占要么共享。
lwlock-condacquire-fail(LWLockId, LWLockMode)当没有成功获得一个LWLock时触发的探测器(已声明调用无需等待)。 arg0是LWLock的ID号,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-16. 定义用于探测器参数的类型

类型定义
LocalTransactionIdunsigned int
LWLockIdint
LWLockModeint
LOCKMODEint
BlockNumberunsigned int
Oidunsigned int
ForkNumberint
boolchar

27.4.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

注意: SystemTap为跟踪脚本使用一个不同的标记而不是Dtrace, 即使底层的跟踪点是兼容的。有一点需要注意, 在这样写的时候,SystemTap脚本必须使用双下划线代替连字符来指向探测器名。 希望在未来SystemTap的版本中修复。

你应该记住DTrace脚本需要仔细的编写和充分的调试, 否则收集到的跟踪信息可能毫无意义。 大多数情况下问题是手段是错误的而不是底层系统。 在讨论使用动态跟踪发现的信息时,应确保包含允许检查和讨论使用的脚本。

更多的示例脚本可以在PgFoundry dtrace project 中找到。

27.4.4. 定义新的跟踪点

开发者可以在代码中任意位置定义新的跟踪点, 当然这要重新编译之后才能生效。下面是用于新探测器插入步骤:

  1. 通过探头决定探头名字和可利用数据。

  2. 新增探头定义为src/backend/utils/probes.d

  3. 包括pg_trace.h,如果已经不在模块中包含探测点, 并且在所需源代码中期望的位置插入TRACE_POSTGRESQL探测宏。

  4. 重新编译和验证新探头是可用的。

例子: 下面是一个例子,你将如何添加一个探头通过事务ID追踪所有新的事务。

  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

向C代码中添加跟踪宏时,有一些注意事项,见下文: