## 28.5.动态跟踪 [28.5.1. 动态跟踪编译](dynamic-trace.html#COMPILING-FOR-TRACE) [28.5.2. 内置探测器](dynamic-trace.html#TRACE-POINTS) [28.5.3. 使用探针](dynamic-trace.html#USING-TRACE-POINTS) [28.5.4. 定义新探针](dynamic-trace.html#DEFINING-TRACE-POINTS) [](<>) PostgreSQL提供了支持数据库服务器动态跟踪的工具。这允许在代码中的特定点调用外部实用程序,从而跟踪执行。 源代码中已经插入了许多探测或跟踪点。这些探测器旨在供数据库开发人员和管理员使用。默认情况下,探测不会编译成PostgreSQL;用户需要明确地告诉configure脚本,以使探针可用。 目前[动态跟踪技术](https://en.wikipedia.org/wiki/DTrace)在撰写本文时,Solaris、macOS、FreeBSD、NetBSD和Oracle Linux上都支持该实用程序。这个[SystemTap](https://sourceware.org/systemtap/)project for Linux提供了与DTrace等效的功能,也可以使用。通过更改中宏的定义,理论上可以支持其他动态跟踪实用程序`src/include/utils/probe。H`. ### 28.5.1.动态跟踪编译 默认情况下,探测不可用,因此需要显式地告诉配置脚本,使探测在PostgreSQL中可用。要包含DTrace支持,请指定`--启用dtrace`配置。看见[第17.4节](install-procedure.html)了解更多信息。 ### 28.5.2.内置探测器 源代码中提供了许多标准探针,如中所示[表28.45](dynamic-trace.html#DTRACE-PROBE-POINT-TABLE);[表28.46](dynamic-trace.html#TYPEDEFS-TABLE)显示了探针中使用的类型。当然,可以添加更多的探针来增强PostgreSQL的可观察性。 **表28.45.内置DTrace探测器** | 名称 | 参数 | 描述 | | --- | --- | --- | | `事务启动` | `(LocalTransactionId)` | 在新事务开始时触发的探测器。arg0是事务ID。 | | `事务提交` | `(LocalTransactionId)` | 当事务成功完成时触发的探测器。arg0是事务ID。 | | `事务中止` | `(LocalTransactionId)` | 当事务未成功完成时触发的探测器。arg0是事务ID。 | | `查询开始` | `(常量字符*)` | 在开始处理查询时激发的探测器。arg0是查询字符串。 | | `查询完成` | `(常量字符*)` | 当查询处理完成时激发的探测器。arg0是查询字符串。 | | `查询解析开始` | `(常量字符*)` | 在查询分析启动时激发的探测。arg0是查询字符串。 | | `查询解析完成` | `(常量字符*)` | 当查询解析完成时激发的探测。arg0是查询字符串。 | | `查询重写开始` | `(常量字符*)` | 在开始重写查询时激发的探测。arg0是查询字符串。 | | `查询重写完成` | `(常量字符*)` | 当查询重写完成时触发的探测。arg0是查询字符串。 | | `查询计划开始` | `()` | 启动查询计划时激发的探测器。 | | `查询计划完成` | `()` | 当查询计划完成时触发的探测器。 | | `查询执行开始` | `()` | 在开始执行查询时激发的探测器。 | | `查询执行完成` | `()` | 在查询执行完成时激发的探测器。 | | `报表状态` | `(常量字符*)` | 在服务器进程更新其`pg_统计活动`.`地位`.arg0是新的状态字符串。 | | `检查点启动` | `(国际)` | 当检查点启动时触发的探测器。arg0包含用于区分不同检查点类型(如关机、立即或强制)的按位标志。 | | `检查点完成了` | `(int,int,int,int,int,int)` | 检查点完成时触发的探测器。(在检查点处理过程中,列出的探测器将按顺序发射。)arg0是写入的缓冲区数。arg1是缓冲区的总数。arg2、arg3和arg4分别包含添加、删除和回收的WAL文件数。 | | `阻塞检查点启动` | `(布尔)` | 当检查点的阻塞部分启动时触发的探测器。arg0对于正常检查点为true,对于关闭检查点为false。 | | `堵塞检查站完成` | `(布尔)` | 当检查点的阻塞部分完成时触发的探测器。arg0与for的含义相同`阻塞检查点启动`. | | `子传输检查点启动` | `(布尔)` | 当检查点的子传输部分启动时激发的探测器。arg0对于正常检查点为true,对于关闭检查点为false。 | | `subtrans检查点已完成` | `(布尔)` | 当检查点的子事务部分完成时触发的探测器。arg0与for的含义相同`子传输检查点启动`. | | `多X射线检查点启动` | `(布尔)` | 当检查点的MultiXact部分启动时触发的探测器。arg0对于正常检查点为true,对于关闭检查点为false。 | | `多层面扫描检查点完成` | `(布尔)` | 当检查点的MultiXact部分完成时触发的探测器。arg0与for的含义相同`多X射线检查点启动`. | | `缓冲区检查点启动` | `(国际)` | 当检查点的缓冲区写入部分启动时触发的探测器。arg0包含用于区分不同检查点类型(如关机、立即或强制)的按位标志。 | | `缓冲区同步启动` | `(int,int)` | 在检查点期间(在确定必须写入哪些缓冲区之后)开始写入脏缓冲区时触发的探测器。arg0是缓冲区的总数。arg1是当前不干净且需要写入的数字。 | | `缓冲区同步写入` | `(国际)` | 在检查点期间写入每个缓冲区后激发的探测器。arg0是缓冲区的ID号。 | | `缓冲区同步完成` | `(int,int,int)` | 当所有脏缓冲区都已写入时触发的探测器。arg0是缓冲区的总数。arg1是检查点进程实际写入的缓冲区数。arg2是预期要写入的数字(arg1)`缓冲区同步启动`); 任何差异都反映了在检查点期间刷新缓冲区的其他进程。 | | `缓冲区检查点同步启动` | `()` | 在将脏缓冲区写入内核后,在开始发出fsync请求之前触发的探测。 | | `缓冲区检查点完成` | `()` | 当缓冲区与磁盘同步完成时触发的探测器。 | | `两阶段检查点启动` | `()` | 当检查点的两阶段部分启动时触发的探测器。 | | `两阶段检查点完成` | `()` | 当检查点的两阶段部分完成时触发的探测器。 | | `缓冲区读取启动` | `(ForkNumber,BlockNumber,Oid,Oid,Oid,int,bool)` | 当缓冲区读取开始时触发的探测器。arg0和arg1包含页面的叉号和块号(但如果这是关系扩展请求,则arg1将为-1)。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。arg5是为本地缓冲区创建临时关系的后端的ID,或`InvalidBackendId`(-1)用于共享缓冲区。arg6对于关系扩展请求为true,对于正常读取为false。 | | `缓冲区读取完成` | `(ForkNumber,BlockNumber,Oid,Oid,Oid,int,bool,bool)` | 当缓冲区读取完成时触发的探测器。arg0和arg1包含页面的fork和block编号(如果这是关系扩展请求,则arg1现在包含新添加块的block编号)。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。arg5是为本地缓冲区创建临时关系的后端的ID,或`InvalidBackendId`(-1)用于共享缓冲区。arg6对于关系扩展请求为true,对于正常读取为false。如果在池中找到缓冲区,则arg7为true,否则为false。 | | `缓冲区刷新启动` | `(ForkNumber,BlockNumber,Oid,Oid,Oid)` | 在发出任何共享缓冲区写入请求之前触发的探测器。arg0和arg1包含页面的叉号和块号。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。 | | `缓冲区刷新完成` | `(ForkNumber,BlockNumber,Oid,Oid,Oid)` | 写入请求完成时触发的探测器。(请注意,这只是反映了将数据传递到内核的时间;它通常还没有实际写入磁盘。)这些论点与我们的论点相同`缓冲区刷新启动`. | | `缓冲区写脏启动` | `(ForkNumber,BlockNumber,Oid,Oid,Oid)` | 当服务器进程开始写入脏缓冲区时触发的探测。(如果这种情况经常发生,这意味着[共享\_缓冲区](runtime-config-resource.html#GUC-SHARED-BUFFERS)太小或后台写入程序控制参数需要调整。)arg0和arg1包含页面的叉号和块号。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。 | | `缓冲区写脏完成` | `(ForkNumber,BlockNumber,Oid,Oid,Oid)` | 当脏缓冲区写入完成时触发的探测器。这些论点与我们的论点相同`缓冲区写脏启动`. | | `wal缓冲区写脏开始` | `()` | 当服务器进程因为没有更多WAL缓冲区空间而开始写入脏WAL缓冲区时触发的探测。(如果这种情况经常发生,这意味着[沃尔\_缓冲区](runtime-config-wal.html#GUC-WAL-BUFFERS)太小了。) | | `wal缓冲区写脏完成` | `()` | 当脏WAL缓冲区写入完成时触发的探测器。 | | `沃尔插页` | `(无符号字符,无符号字符)` | 插入WAL记录时触发的探测器。arg0是记录的资源管理器(rmid)。arg1包含信息标志。 | | `沃尔开关` | `()` | 当请求WA段开关时触发的探测器。 | | `smgr md读取启动` | `(ForkNumber、BlockNumber、Oid、Oid、Oid、int)` | 当开始从关系中读取块时触发的探测器。arg0和arg1包含页面的叉号和块号。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。arg5是为本地缓冲区创建临时关系的后端的ID,或`InvalidBackendId`(-1)用于共享缓冲区。 | | `smgr md读取完毕` | `(ForkNumber,BlockNumber,Oid,Oid,Oid,int,int,int)` | 当块读取完成时触发的探测器。arg0和arg1包含页面的叉号和块号。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。arg5是为本地缓冲区创建临时关系的后端的ID,或`InvalidBackendId`(-1)用于共享缓冲区。arg6是实际读取的字节数,而arg7是请求的字节数(如果这些字节数不同,则表示有问题)。 | | `smgr md写启动` | `(ForkNumber、BlockNumber、Oid、Oid、Oid、int)` | 开始向关系写入块时触发的探测器。arg0和arg1包含页面的叉号和块号。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。arg5是为本地缓冲区创建临时关系的后端的ID,或`InvalidBackendId`(-1)用于共享缓冲区。 | | `smgr md写入完成` | `(ForkNumber,BlockNumber,Oid,Oid,Oid,int,int,int)` | 当块写入完成时触发的探测器。arg0和arg1包含页面的叉号和块号。arg2、arg3和arg4包含标识关系的表空间、数据库和关系OID。arg5是为本地缓冲区创建临时关系的后端的ID,或`InvalidBackendId`(-1)用于共享缓冲区。arg6是实际写入的字节数,而arg7是请求的字节数(如果这些字节数不同,则表示有问题)。 | | `排序开始` | `(int,bool,int,int,bool,int)` | 启动排序操作时激发的探测器。arg0表示堆、索引或数据排序。arg1适用于唯一值强制。arg2是键列数。arg3是允许的工作内存的千字节数。如果需要随机访问排序结果,则arg4为真。arg5表示串行模式`0`,平行工`1.`,或当`2.`. | | `差不多了` | `(布尔,朗)` | 完成排序时触发的探测器。arg0对于外部排序为true,对于内部排序为false。arg1是用于外部排序的磁盘块数,或用于内部排序的千字节内存。 | | `lwlock acquire` | `(字符*,LWLockMode)` | 获取LWLock时触发的探测器。arg0是LWLock的部分。arg1是请求的锁定模式,可以是独占模式,也可以是共享模式。 | | `lwlock释放` | `(字符*)` | 当LWLock被释放时触发的探测器(但请注意,任何被释放的服务员尚未被唤醒)。arg0是LWLock的部分。 | | `lwlock等待启动` | `(字符*,LWLockMode)` | 当LWLock不立即可用且服务器进程已开始等待该锁可用时触发的探测器。arg0是LWLock的部分。arg1是请求的锁定模式,可以是独占模式,也可以是共享模式。 | | `等一下` | `(字符*,LWLockMode)` | 当服务器进程从等待LWLock的过程中释放时触发的探测器(它实际上还没有锁)。arg0是LWLock的部分。arg1是请求的锁定模式,可以是独占模式,也可以是共享模式。 | | `lwlock CONDARGER` | `(字符*,LWLockMode)` | 当调用方指定无等待时成功获取LWLock时激发的探测。arg0是LWLock的部分。arg1是请求的锁定模式,可以是独占模式,也可以是共享模式。 | | `lwlock条件获取失败` | `(字符*,LWLockMode)` | 当调用方指定无等待时,LWLock未成功获取时激发的探测。arg0是LWLock的部分。arg1是请求的锁定模式,可以是独占模式,也可以是共享模式。 | | `锁定等待启动` | `(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,LOCKMODE)` | 当由于锁不可用而请求重量级锁(lmgr锁)开始等待时触发的探测器。arg0到arg3是标识被锁定对象的标记字段。arg4表示要锁定的对象的类型。arg5表示请求的锁类型。 | | `锁定等待完成` | `(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,LOCKMODE)` | 当对重量级锁(lmgr锁)的请求已完成等待(即已获得锁)时触发的探测器。这些论点与我们的论点相同`锁定等待启动`. | | `发现死锁` | `()` | 当死锁检测器发现死锁时触发的探测器。 | **表28.46.探针参数中使用的定义类型** | 类型 | 释义 | | --- | --- | | `LocalTransactionId` | `无符号整型` | | `LWLockMode` | `智力` | | `锁定模式` | `智力` | | `区块号` | `无符号整型` | | `老年人` | `无符号整型` | | `叉号` | `智力` | | `布尔` | `无符号字符` | ### 28.5.3.使用探针 下面的示例显示了用于分析系统中事务计数的DTrace脚本,作为快照的替代方案`pg_统计数据库`性能测试前后: ``` #!/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 ^C Start 71 Commit 70 Total time (ns) 2312105013 ``` ### 笔记 SystemTap对跟踪脚本使用的符号与DTrace不同,尽管底层跟踪点是兼容的。值得注意的一点是,在本文中,SystemTap脚本必须使用双下划线代替连字符引用探测器名称。这将在未来的SystemTap版本中修复。 您应该记住,DTrace脚本需要仔细编写和调试,否则收集的跟踪信息可能毫无意义。在大多数情况下,发现问题的是仪器,而不是底层系统。在讨论使用动态跟踪找到的信息时,请确保附上用于检查和讨论该信息的脚本。 ### 28.5.4.定义新探针 新的探测可以在开发人员希望的任何地方在代码中定义,尽管这需要重新编译。以下是插入新探针的步骤: 1. 决定探针名称和通过探针提供的数据 2. 将探测定义添加到`src/backend/utils/probe。D` 3. 包括`pg_跟踪。H`如果包含探测点的模块中还没有,则插入`TRACE_POSTGRESQL`在源代码中所需的位置探测宏 4. 重新编译并验证新探针是否可用 **例子:**下面是一个示例,说明如何添加一个探测器,以按事务ID跟踪所有新事务。 1. 确定探测器的名称`事务启动`并且需要类型为的参数`LocalTransactionId` 2. 将探测定义添加到`src/backend/utils/probe。D`: ``` probe transaction__start(LocalTransactionId); ``` 注意探针名称中使用了双下划线。在使用探测的DTrace脚本中,双下划线需要替换为连字符,因此`事务启动`为用户提供文档的名称。 3. 在编译时,`交易开始`转换为一个名为`跟踪\u POSTGRESQL\u事务\u启动`(注意这里的下划线是单数),可以通过包括`pg_跟踪。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是用`--启用dtrace`,每当控件通过宏时,跟踪宏的参数都将被计算,*即使没有追踪*。如果你只是报告几个局部变量的值,这通常不值得担心。但要注意不要在参数中加入昂贵的函数调用。如果需要这样做,请考虑用检查来保护宏,以查看是否实际启用了跟踪: ``` if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED()) TRACE_POSTGRESQL_TRANSACTION_START(some_function(...)); ``` 每个跟踪宏都有一个对应的`启用`宏。