Skip to content

27.11 DTrace

DTrace 源于 Sun Solaris,以 CDDL 许可证开源。FreeBSD 自 GENERIC 内核起内置支持,由内核模块实现。

警告

由于提交 commit/1ca7542a1b31fffefaac5a2ff45ef23f908b33c2 的变更,本节内容仅适用于 16.0-CURRENT。

27.11.1 概述

DTrace 可用于诊断系统性能问题,调查和调试 FreeBSD 内核及用户空间程序中的意外行为,支持运行预编写的脚本,也可使用 D 语言编写自定义工具以满足特定分析需求。

FreeBSD 实现提供了对内核 DTrace 的完整支持,以及对用户空间 DTrace 的实验性支持。通过用户空间 DTrace 的 pid 提供者,可跟踪用户空间程序的函数边界,并在用户空间程序中插入静态探针以供后续跟踪。某些 Port,如 databases/postgresql18-server 和 lang/php85,提供了 DTrace 选项以启用静态探针。

Port databases/postgresql18-server 的 Makefile 部分源代码:

makefile
DTRACE_CONFIGURE_ENABLE=dtrace
DTRACE_LDFLAGS=		-lelf
DTRACE_INSTALL_TARGET=	install

Port lang/php85 的 Makefile 部分源代码:

makefile
OPTIONS_DEFINE+=CGI CLI DEBUG DTRACE EMBED FPM IPV6 LINKTHR \
				MYSQLND NOASLR PCRE PHPDBG ZTS JIT

DTrace 的官方指南由 Illumos 项目维护,网址为 illumos DTrace Guide

27.11.2 实现上的差异

虽然 FreeBSD 中的 DTrace 与 Solaris 类似,但存在一些差异。主要区别是,FreeBSD 中的 DTrace 由一组内核模块实现。自 FreeBSD 10.0-RELEASE 起,运行 dtrace 时会自动加载所需模块。

FreeBSD 使用内核选项 DDB_CTF 以支持从内核模块及内核本身加载 CTF 数据。CTF 是 Solaris 的 Compact C Type Format,用于封装简化的调试信息,类似 DWARF 或传统的 stabs。CTF 数据由 ctfconvert 和 ctfmerge 构建工具添加到二进制文件中。ctfconvert 工具解析编译程序生成的 DWARF ELF 调试段,而 ctfmerge 将对象文件中的 CTF ELF 段合并到可执行文件或共享库中。

FreeBSD 提供的 provider 与 Solaris 略有不同。dtmalloc provider 较为特别,它能在 FreeBSD 内核中按类型跟踪 malloc。某些 Solaris 中的 provider 在 FreeBSD 中不存在。同时,两者均存在的 provider 可能不兼容,探针的参数类型不同。因此,在 Solaris 上编写的 D 脚本在 FreeBSD 上可能需要修改才能运行,反之亦然。

因安全机制差异,FreeBSD 上仅 root 用户可使用 DTrace。Solaris 中存在一些低级安全检查,而 FreeBSD 尚未实现。因此,/dev/dtrace/dtrace 设备文件严格限定为 root 用户可访问。

DTrace 本身以通用开发与分发许可证(CDDL)发布。支持 DTrace 的 FreeBSD 内核本身采用 BSD 许可,但 DTrace 内核模块以二进制形式分发或加载时适用 CDDL(参见源代码文件 dtrace_cddl.h)。

27.11.3 开启 DTrace 支持

DTrace 支持 已内置于 GENERIC 内核中。自 FreeBSD 10.0-RELEASE 起,运行 dtrace 时会自动加载所需模块。如需确保所有 DTrace 模块均已加载(某些脚本可能依赖未自动加载的 provider),可手动执行:

sh
kldload dtraceall

可安装 DTrace 工具包 sysutils/dtrace-toolkit。

使用 pkg 安装:

sh
# pkg install dtrace-toolkit

还可以使用 Ports 安装:

sh
# cd /usr/ports/sysutils/dtrace-toolkit/
# make install clean

该工具包内置了用于收集系统信息的现成脚本,包括检查打开的文件、内存、CPU 使用情况等。FreeBSD 基本系统中也内置了若干脚本,存放在 /usr/share/dtrace,在源代码中位于 share/dtrace/

注意

/usr/share/dtrace 中的脚本是专门为 FreeBSD 移植的。DTrace 工具包中的脚本并非全部可直接在 FreeBSD 上运行,个别脚本需要一定修改才能使用。

DTrace 工具包使用 DTrace 特有的脚本语言 D 语言,与 C++ 相似。本节不对 D 语言进行深入讲解。如需概览,请参阅 FreeBSD 的 d 手册页。D 语言在 illumos 动态跟踪指南 中也有详细介绍。

如需将 DTrace 静态编译进内核,应在自定义内核配置文件中添加以下行,并重新编译内核:

ini
options         KDTRACE_HOOKS
options         DDB_CTF
makeoptions	DEBUG=-g
makeoptions	WITH_CTF=1

AMD64 架构的用户还应添加以下行:

ini
options         KDTRACE_FRAME

此选项提供了对 dtrace_fbt 的支持。虽然没有该选项 DTrace 仍可工作,但函数边界跟踪功能将受限。

要为内核外模块添加 DTrace 支持,可在模块的 Makefile 中加入以下行:

cpp
CFLAGS+= -DKDTRACE_HOOKS

此标志在编译过程中启用 DTrace 钩子,从而便于调试和监控模块。修改后,确保重新编译该模块以激活 DTrace 功能。

27.11.4 使用 DTrace

DTrace 脚本由一个或多个 探针(probes) 组成,每个探针都是一个可监控的点,并且与一个动作关联。每当探针的条件满足,关联的动作便会执行。例如,动作可能发生在文件打开、进程启动或代码行执行时。动作可以记录信息或修改上下文变量。上下文变量的读写允许探针共享信息,从而协作分析不同事件之间的关联。

要查看所有探针,需要以 root 身份执行以下命令:

sh
# dtrace -l | more
   ID   PROVIDER            MODULE                          FUNCTION NAME
    1     dtrace                                                     BEGIN
    2     dtrace                                                     END
    3     dtrace                                                     ERROR
    4        fbt            kernel                camstatusentrycomp entry
    5        fbt            kernel                camstatusentrycomp return
    6        fbt            kernel            cam_compat_handle_0x18 entry
    7        fbt            kernel            cam_compat_handle_0x18 return

……以下省略……

每个探针都有一个 ID、一个 PROVIDER(例如 dtracefbt)、一个 MODULE 和一个 FUNCTION NAME

普通用户无法运行 DTrace:

sh
$ dtrace -l
dtrace: failed to initialize dtrace: DTrace requires additional privileges

示例:hotkernel 脚本用于识别消耗最多内核时间的函数。

sh
# cd /usr/local/share/dtrace-toolkit
# ./hotkernel
Sampling... Hit Ctrl-C to end.

按 Ctrl+C 停止执行。脚本结束后,会显示内核函数及其时间信息,并按时间升序排序:

sh
dtrace: buffer size lowered to 1m
dtrace: aggregation size lowered to 1m
^C
FUNCTION                                                COUNT   PCNT
kernel`vm_page_alloc_domain_iter                            1   0.0%
kernel`cpu_idle                                             7   0.0%
kernel`em_update_stats_counters                             8   0.0%
kernel`spinlock_exit                                       33   0.0%
kernel`acpi_cpu_c1                                     128203 100.0%

该脚本也支持内核模块。使用 -m 选项运行:

sh
# cd /usr/local/share/dtrace-toolkit
# ./hotkernel -m
Sampling... Hit Ctrl-C to end.
dtrace: buffer size lowered to 1m
dtrace: aggregation size lowered to 1m
^C
MODULE                                                  COUNT   PCNT
kernel                                                  80153 100.0%

示例:procsystime 脚本捕获并打印指定进程 IDPID)或进程名称的系统调用时间。例如,启动一个新的 /bin/sh 实例后运行:

sh
# ./procsystime -n sh
Tracing... Hit Ctrl-C to end...
^C

Elapsed Times for processes sh,

         SYSCALL          TIME (ns)
         geteuid               4203
       sigreturn              14858
        __sysctl              61708
           write             640999
            read         2507609226

如上所示,read 系统调用耗时最长,geteuid 耗时最短。

示例:基本系统内置的 hotopen 脚本会定期输出表格,列出哪些 UID 正在打开文件。

sh
# cd /usr/share/dtrace
# ./hotopen
dtrace: buffer size lowered to 1m
dtrace: aggregation size lowered to 1m
Files opened per UID in the last second.
2026 May  2 19:20:52
uid 0	count 2
2026 May  2 19:20:53
uid 0	count 2
2026 May  2 19:20:54
uid 0	count 1
^C
2026 May  2 19:20:55
uid 0	count 1

借此可快速判断特定用户是否正在通过频繁调用 open/close 使系统进入“抖动”(thrashing)状态。