动态跟踪

PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 27. 监控数据库的活动 | 下一页

# 27.4. 动态跟踪

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

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

目前，只有DTrace支持实用工具，在写这的时候， 它可在Solaris, 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-16显示的是在源代码中提供的标准跟踪点， 表 27-17显示探测中使用的类型。 更多探测可以被添加以提高PostgreSQL的观测性。

表 27-16. 内置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 | (char *, int, LWLockMode) | 当成功获得一个LWLock时触发的探测器。 arg0是LWLock的一部分。arg1是LWLock部分的偏差。 arg2表明请求的锁模式，要么独占要么共享。
lwlock-release | (char *, int) | LWLock释放时触发的探测器 (但是请注意任何发布的等待者还未觉醒)。 arg0是LWLock的一部分。arg1是LWLock部分的偏差。
lwlock-wait-start | (char *, int, LWLockMode) | 当不能立即获得LWLock锁，同时服务进程进入等待时触发的探测器。 arg0是LWLock的一部分。arg1是LWLock部分的偏差。 arg2表明请求的锁模式，要么独占要么共享。
lwlock-wait-done | (char *, int, LWLockMode) | 当从一个LWLock锁中释放服务进程时触发的探测器（实际上没有进行锁）。 arg0是LWLock的一部分。arg1是LWLock部分的偏差。 arg2表明请求的锁模式，要么独占要么共享。
lwlock-condacquire | (char *, int, LWLockMode) | 当成功获得一个LWLock时触发的探测器（已声明调用无需等待）。 arg0是LWLock的一部分。arg1是LWLock部分的偏差。 arg2表明请求的锁模式，要么独占要么共享。
lwlock-condacquire-fail | (char *, int, LWLockMode) | 当没有成功获得一个LWLock时触发的探测器（已声明调用无需等待）。 arg0是LWLock的一部分。arg1是LWLock部分的偏差。 arg2表明请求的锁模式，要么独占要么共享。
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-17. 定义用于探测器参数的类型

类型 | 定义
--- | ---
LocalTransactionId | unsigned int
LWLockMode | int
LOCKMODE | int
BlockNumber | unsigned int
Oid | unsigned int
ForkNumber | int
bool | char

## 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代码中添加跟踪宏时，有一些注意事项，见下文：

- 需要注意的是，为探测器参数声明的数据类型要匹配宏中可用的数据类型， 否则会发生编译错误。

- 在大多数平台上，如果编译PostgreSQL 时带有`--enable-dtrace`选项， 无论何时通过宏来控制时，都会估算该跟踪宏的参数， 即使没有进行跟踪。通常不需要担心是否你只是报告一些局部变量的值。 但要注意将重要的函数调用放置在参数中。如果需要这么做， 考虑通过检查是否真的开启跟踪来保护宏：

      if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
          TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));

  每个跟踪宏都有一个相应的`ENABLED`宏。

上一页 | 起始页 | 下一页
查看锁 | 上一级 | 监控磁盘使用情况