27.4. 动态跟踪

PostgreSQL允许对数据库服务器进行动态跟踪。这样就允许在代码内特 定的点上调用外部工具来跟踪执行过程。目前此功能主要目的是为了提供给数据库开发者使用,它要求使用者对代码非常熟悉。

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

目前,只有 DTrace 支持实用工具,它可 在OpenSolaris,Solaris10操作系统,和MacOSXLeopard的。据预计, DTrace将可以在FreeBSD上的未来和其他可能的 操作系统。“ SystemTap项目 Linux还提供了一个DTrace的等效。支持其他动态, 通过改变src/include/utils/probes.h中的定义为跟踪实用程序在理论上是可能的 。

27.4.1. 编译动态跟踪支持

跟踪点是默认禁止的,你必须在运行configure脚本在PostgreSQL时明确使 用--enable-dtrace选项来启用DTrace支持。参见Section 15.5获取更多信息。

27.4.2. 内置跟踪点

Table 27-3显示的是在源代码中提 供的标准跟踪点,此外,也可以根据特定的具体问题添加其它跟踪点。 更多可以肯定会增加,以提高PostgreSQL的 观测性。

Table 27-3. 内置跟踪点

名字参数概述
transaction-start(LocalTransactionId) 开始新事务,arg0是事务ID。
transaction-commit(LocalTransactionId) 事务成功完成,arg0是事务ID。
transaction-abort(LocalTransactionId) 当交易完成失败将触发的探测器。 arg0是事务ID。
query-start(constchar*) 开始处理查询时将触发的探测器。 arg0是查询字符串。
query-done(constchar*) 查询处理完成时将触发的探测器。 arg0是查询字符串。
query-parse-start(constchar*) 解析查询开始时将触发的探测器。 arg0是查询字符串。
query-parse-done(constchar*) 完整解析查询时触发的探测器。 arg0是查询字符串。
query-rewrite-start(constchar*) 启动触发的探测器时,查询重写。 arg0是查询字符串。
query-rewrite-done(constchar*) 探头触发时,查询重写是完整的。 arg0是查询字符串。
query-plan-start() 查询规划开始时将触发的探测器
query-plan-done()查询规划完成时将触发的探测器
query-execute-start()执行规划开始时将触发的探测器
query-execute-done()执行规划完成时将触发的探测器
statement-status(constchar*)服务进程随时更新pg_stat_activity.current_query 状态时触发的探测器。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 of buffer-sync-start); 任何的不同会导致另一个进程在检查点发生时刷写缓冲区。
buffer-checkpoint-sync-start()当完成将脏缓冲区写入到内核,并且还没有发出fsync请求之前触发的探测器。
buffer-checkpoint-done()当同步缓冲区到磁盘完成时触发的探测器。
twophase-checkpoint-start()当一个检查点的两相阶段状态部分开始时触发的探测器。
twophase-checkpoint-done()当一个检查点的两相阶段状态部分完成时触发的探测器。
buffer-read-start(ForkNumber,BlockNumber,O id,O id,O id,bool,bool)当开始一次缓冲区读时触发的探测器。 arg0和arg1包含page块中锁和派生的子进程数(如果是一个关系扩展请求,arg1会是-1)。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5对本地缓冲区表示真,对共享缓冲区表示假。 arg6对关系扩展请求表示真,对正常读表示假。
buffer-read-done(ForkNumber,BlockNumber,O id,O id,O id,bool,bool,bool)当完成一次缓冲区读时触发的探测器。 arg0和arg1包含page块中锁和派生的子进程数(如果是一个关系扩展请求,arg1会表示新增锁的数目)。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5对本地缓冲区表示真,对共享缓冲区表示假。 arg6对关系扩展请求表示真,对正常读表示假。 如果池中有缓冲区,则arg7表示真,反之表示假。
buffer-flush-start(ForkNumber,BlockNumber,O id,O id,O id)在发出共享缓冲区的任意写入请求时触发的探测器。 arg0和arg1包含page块中锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。
buffer-flush-done(ForkNumber,BlockNumber,O id,O id,O id)当完成一条写要求时触发的探测器。 需要注意的是,它只影响将数据传递到内核参数的时间; 实际上,它不会写到磁盘上。这个参数与buffer-flush-start一致。
buffer-write-dirty-start(ForkNumber,BlockNumber,O id,O id,O id)当服务器进程开始写脏缓冲区时触发的探测电器。如果经常发生,表示shared_buffers太小 ,或需要调整bgwriter控制参数。 arg0和arg1包含page块中锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。
buffer-write-dirty-done(ForkNumber,BlockNumber,O id,O id,O id)当完成脏缓冲区写时触发的探测器。 参数与buffer-write-dirty-start一样。
wal-buffer-write-dirty-start()当服务器进程开始写脏读时触发的探测器(此时WAL缓冲区已满)。 如果经常发生,应该是wal_buffers设置的太小了。
wal-buffer-write-dirty-done()当完成一次WAL脏写时触发的探测器。
xlog-insert(unsignedchar,unsignedchar)当插入一条WAL记录时触发的探测器。 arg0表示记录的rm id。 arg1包含信息标志。
xlog-switch()当要求进行WAL切换时触发的探测器。
smgr-md-read-start(ForkNumber,BlockNumber,O id,O id,O id)开始从一个关系中写锁时触发的探测器。 arg0和arg1包含page块中锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。
smgr-md-read-done(ForkNumber,BlockNumber,O id,O id,O id,int,int)当一个锁写完成时触发的探测器。 arg0和arg1包含page块中锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。 arg5表示实际读的字节数,而arg6表示要求的字节数(如果不一样会报错)。
smgr-md-write-start(ForkNumber,BlockNumber,O id,O id,O id)当向一个关系中写入锁时触发的探测器。 arg0和arg1包含page块中锁和派生子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID,以识别关系。
smgr-md-write-done(ForkNumber,BlockNumber,O id,O id,O id,int,int)当一个锁写进程完成时触发的探测器。 arg0和arg1表示page块的锁和派生的子进程数。 arg2,arg3和arg4包含表空间,数据库和关系OID。 arg5表示实际写的字节数,而arg6表示要求的数目(如果不一样会报错)。
sort-start(int,bool,int,int,bool)排序操作开始时触发的探测器。 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(unsignedint,unsignedint,unsignedint,unsignedint,unsignedint,LOCKMODE)当一个重量级锁(lmgr锁)的请求开始等待(因为无法获得锁)时触发的探测器。 arg0到arg3是辨别被锁定对象的标签字段。arg4指出被锁对象的类型。 arg5表示请求的锁类型。
lock-wait-done(unsignedint,unsignedint,unsignedint,unsignedint,unsignedint,LOCKMODE)当一个重量级锁(lmgr锁)的请求结束等待时触发的探测器,参数与lock-wait-start一样。
deadlock-found()当死锁探测器发现死锁是触发的探测器

Table 27-4. 定义用于探测器参数的类型

类型定义
LocalTransactionIdunsignedint
LWLockIdint
LWLockModeint
LOCKMODEint
BlockNumberunsignedint
O idunsignedint
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["Totaltime(ns)"]=sum(timestamp-self->ts);
self->ts=0;
}

例如示范D脚本执行时,如输出:

#./txn_count.d`pgrep-npostgres`or./txn_count.d<PID>
^C

Start71
Commit70
Totaltime(ns)2312105013

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

必须在实际使用跟踪程序前进行仔细的编写和充分的调试,否则收集到的跟踪信息可能毫 无意义。大多数问题是由于外部跟踪程序错误导致的而不是底层系统。在讨论使 用动态跟踪发现的信息时,应确保在其中包含你使用的跟踪脚本。

更多的示例脚本,可以发现在PgFoundrydtraceproject.

27.4.4. 定义跟踪点

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

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

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

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

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

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

  1. 决定,探测器将被命名为transaction-start并且 需要LocalTransactionId类型参数

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

    probetransaction__start(LocalTransactionId);

    注意在探测器名字中的双下划线。在一个DTrace使用探测器,需要用一个连字符来替换双下滑线,因此 ,对用户而言,transaction-start是文档名。

  3. 在编译时,transaction__start被转换成一个宏调用TRACE_POSTGRESQL_TRANSACTION_START (注意这里是单下划线),可以从pg_trace.h中获得。将宏调用放在源代码中的合适位置。 在这种情况下,类似下面:

    TRACE_POSTGRESQL_TRANSACTION_START(vx id.localTransactionId);

  4. 在重新编译和运行新的二进制文件之后,通过运行下面的DTrace命令来检查新增的探测器是否可用。 应该得到类似下面的结果:

    #dtrace-lntransaction-start
    IDPROVIDERMODULEFUNCTIONNAME
    18705postgresql49878postgresStartTransactionCommandtransaction-start
    18755postgresql49877postgresStartTransactionCommandtransaction-start
    18805postgresql49876postgresStartTransactionCommandtransaction-start
    18855postgresql49875postgresStartTransactionCommandtransaction-start
    18986postgresql49873postgresStartTransactionCommandtransaction-start

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