DTrace 提供了内置格式化函数 printf() 和 printa(),可在 D 程序中使用这些函数来设置输出的格式。D 编译器提供了 printf(3C) 库例程中没有的功能,所以,即使您已经熟悉 printf(),也应该阅读本章内容。本章还将讨论 trace() 函数的格式化行为以及 dtrace(1M) 用于显示聚合的缺省输出格式。
printf() 函数将跟踪数据的功能(就像先前使用的 trace() 函数一样)与以描述的特定格式输出数据和其他文本的功能组合在一起。printf() 函数会指示 DTrace 跟踪与第一个参数之后的每个参数关联的数据,然后使用第一个 printf() 参数说明的规则(称为格式字符串)来格式化结果。
格式字符串是包含任意数量的格式转换的规则字符串,其中的每个转换都以 % 字符开头,该字符说明如何设置对应的参数格式。格式字符串中的第一个转换对应于第二个 printf() 参数,第二个转换对应于第三个参数,依此类推。转换之间的所有文本将逐字列显。% 转换字符之后的字符说明要用于对应参数的格式。
与 printf(3C) 不同,DTrace printf() 是 D 编译器可识别的内置函数。D 编译器为 DTrace printf() 提供了几项在 C 库 printf() 中未提供的有用服务:
D 编译器可将参数与格式字符串中的转换进行比较。如果参数类型与格式转换不兼容,则 D 编译器会提供错误消息来说明该问题。
D 编译器在 printf() 格式转换中不需要使用大小前缀。C printf() 例程要求通过添加前缀(如用 %ld 表示 long,或者用 %lld 表示 long long)来指示参数的大小。由于 D 编译器已知参数的大小和类型,因此 D printf() 语句中不需要这些前缀。
DTrace 为调试和观察功能提供了其他有用的格式化字符。例如,%a 格式转换可用于将指针列显为符号名和偏移。
要实现这些功能,必须在 D 程序中将 DTrace printf() 函数中的格式字符串指定为字符串常量。格式字符串不能是 string 类型的动态变量。
格式字符串中的每种转换规范都由 % 字符引入,在该字符后按顺序显示以下信息:
零个或多个标志(以任意顺序显示),用于修改转换规范的含义,如下节所述。
可选最小字段宽度。如果转换的值的字节数小于字段宽度,则缺省情况下将在该值的左侧填充空格,如果指定了左对齐标志 (-),则将在该值的右侧填充空格。还可将字段宽度指定为星号 (*),在此情况下,将根据 int 类型的另一参数的值动态设置字段宽度。
精度(可选),用于指示 d、i、o、u、x 和 X 转换要显示的最小位数(将对字段填充前导零);e、E 和 f 转换的基数字符之后要显示的位数,g 和 G 转换要显示的最大有效位数;或者进行 s 转换后字符串中要列显的最大字节数。精度采用句点 (.) 后跟星号 (*) 的形式(如下所述),或十进制数字字符串的形式。
大小前缀序列(可选),用于指示对应参数的大小,如大小前缀中所述。大小前缀在 D 中不是必需的,提供它是为了与 C printf() 函数兼容。
转换说明符,用于指示要应用于参数的转换类型。
printf(3C) 函数还支持 % n$ 形式的转换规范,其中 n 是十进制整数;DTrace printf() 不支持此类型的转换规范。
可通过指定下列一个或多个字符(可以按任何顺序显示)来启用 printf() 转换标志:
十进制转换结果的整数部分(%i、%d、%u、%f、%g 或 %G)的格式是通过使用非货币分组字符的千位分组字符设置的。某些语言环境(包括 POSIX C 语言环境)未提供用于此标志的非货币分组字符。
转换结果将在字段中左对齐。如果未指定此标志,则转换将右对齐。
带符号的转换结果始终以符号(+ 或 -)开头。如果未指定此标志,则仅当转换负值时,转换才以符号开头。
如果带符号的转换的第一个字符不是符号或者未产生任何字符,则会在结果前放置空格。如果同时出现空格和 + 标志,则将忽略空格标志。
如果为所选转换定义了替换形式,则会将该值转换为替换形式。转换的替换格式将与对应转换一起说明。
对于 d、i、o、u、x、X、e、E、f、g 和 G 转换,前导零(跟在任何符号或基数表示之后)用于填充字段宽度。不会执行任何空格填充。如果同时出现 0 和 - 标志,则将忽略 0 标志。对于 d、i、o、u、x 和 X 转换,如果指定了精度,则将忽略 0 标志。如果同时出现 0 和 ' 标志,则会在进行零填充之前插入分组字符。
可将最小字段宽度指定为任意标志说明符后跟十进制数字字符串,在此情况下,字段宽度将设置为指定的列数。字段宽度还可以指定为星号 (*),在此情况下,将访问 int 类型的另一参数来确定字段宽度。例如,要在字段中列显整数 x(由 int 变量 w 的值确定),应编写如下 D 语句:
printf("%*d", w, x);
字段宽度也可使用 ? 字符来指定,以指定应根据设置地址格式(该地址在操作系统内核的数据模型中使用十六进制)所需的字符数来设置字段宽度。如果内核使用 32 位数据模型,则宽度设置为 8,如果内核使用 64 位数据模型,则宽度设置为 16。
转换的精度可指定为跟在句点 (.) 之后的十进制数字字符串,或者跟在句点之后的星号 (*)。如果使用星号来指定精度,则会访问转换参数之前的类型为 int 的另一参数来确定精度。如果宽度和精度都指定为星号,则对应转换的 printf() 参数的顺序应按以下顺序显示:宽度、精度、值。
在使用 printf(3C) 指示转换参数的大小和类型的 ANSI-C 程序中,需要使用大小前缀。因为 D 编译器会自动对 printf() 调用执行此处理,所以不需要大小前缀。尽管提供大小前缀是为了与 C 兼容,但因为在使用派生类型时它们会将代码绑定到特定数据模型,所以明确建议不要在 D 程序中使用它们。例如,如果根据数据模型将 typedef 重新定义为其他整数基本类型,则在未明确知道两种基础类型并且包括强制转换表达式(或定义多个格式字符串)的情况下,不能使用单个 C 转换来同时处理两种数据模型。D 编译器通过允许省略大小前缀并自动确定参数大小来自动解决此问题。
大小前缀只能放在格式转换名称之前和所有标志、宽度以及精度说明符之后。大小前缀如下所示:
h(可选),用于指定后续的 d、i、o、u、x 或 X 转换将应用于 short 或 unsigned short。
l(可选),用于指定后续的 d、i、o、u、x 或 X 转换将应用于 long 或 unsigned long。
Il(可选),用于指定后续的 d、i、o、u、x 或 X 转换将应用于 long long 或 unsigned long long。
L(可选),用于指定后续的 e、E、f、g 或 G 转换将应用于 long double。
l(可选),用于指定后续的 c 转换将应用于 wint_t 参数,并且后续的 s 转换字符将应用于指向 wchar_t 参数的指针。
每个转换字符序列都将导致提取零个或多个参数。如果没有为格式字符串提供足够的参数,或者格式字符串已用完但仍有参数剩余,则 D 编译器将发出相应的错误消息。如果指定了未定义的转换格式,则 D 编译器将发出相应的错误消息。转换字符序列为:
指针或 uintptr_t 参数将列显为如下形式的内核符号名:module `symbol-name 加上十六进制字节偏移(可选)。如果该值不在已知内核符号定义的范围内,则该值将列显为十六进制整数。
char、short 或 int 参数将列显为 ASCII 字符。
如果字符为可列显的 ASCII 字符,则 char、short 或 int 参数将列显为 ASCII 字符。如果该字符不是可列显字符,则将使用表 2–5 中说明的对应转义序列将其列显。
char、short、int、long 或 long long 参数将列显为十进制(以 10 为基数)整数。如果参数类型为 signed,则将列显为带符号的值。如果参数类型为 unsigned,则将列显为无符号值。此转换与 i 具有相同的含义。
float、double 或 long double 参数将转换为 [-] d.ddde± dd 样式,其中基数字符前有一位,基数字符后的位数等于精度。如果参数不为零,则基数字符也不为零。如果未指定精度,则缺省精度值为 6。如果精度为 0 并且未指定 # 标志,则不会显示任何基数字符。E 转换格式将生成一个数字,该数字将使用 E 而不是 e 来引入指数。指数总是至少包含两位数。该值将向上舍入为相应的位数。
float、double 或 long double 参数将转换为 [-] ddd.ddd 样式,其中基数字符之后的位数等于指定的精度。如果未指定精度,则缺省精度值为 6。如果精度为 0 并且未指定 # 标志,则不会显示任何基数字符。如果显示基数字符,则它之前至少会显示一位数字。该值将向上舍入为相应的位数。
float、double 或 long double 参数将列显为 f 或 e 样式(如果使用 G 转换字符则为 E 样式),并且使用指定有效数字位数的精度。如果显式精度为 0,则会将其视为 1。使用的样式取决于转换的值:仅当转换生成的指数小于 -4 或大于等于精度时, 才会使用 e(或 E)样式。结果的小数部分中的结尾零将删除。仅当基数字符后跟数字时,才会显示基数字符。如果指定了 # 标志,则不会从结果中删除结尾零。
char、short、int、long 或 long long 参数将列显为十进制(以 10 为基数)整数。如果参数类型为 signed,则将列显为带符号的值。如果参数类型为 unsigned,则将列显为无符号值。此转换与 d 具有相同的含义。
char、short、int、long 或 long long 参数将列显为无符号的八进制(以 8 为基数)整数。signed 类型或 unsigned 类型的参数可用于与此转换。如果指定了 # 标志,则在需要将结果的第一位强制为零时,结果的精度将增加。
指针或 uintptr_t 参数将列显为十六进制(以 16 为基数)整数。D 接受任何类型的指针参数。如果指定了 # 标志,则将在非零结果的开头加上 0x。
参数必须为 char 数组或 string。将一直读取数组或 string 中的字节,直到达到终止空字符或数据的结尾,并将其解释和列显为 ASCII 字符。如果未指定精度,则会将其视为无穷的,所以将列显所有字符,直到达到第一个空字符。如果指定了精度,则仅会列显在对应屏幕列数中显示的那一部分字符数组。如果要格式化 char * 类型的参数,则应将该参数强制转换为 string,或者使用 D stringof 运算符为前缀,以指示 DTrace 应跟踪的字符串字节数并设置这些字节的格式。
参数必须为 char 数组或 string。参数将按类似 %s 转换的方式进行处理,但不可列显的所有 ASCII 字符都将替换为表 2–5 中说明的对应转义序列。
char、short、int、long 或 long long 参数将列显为无符号十进制(以 10 为基数)整数。signed 类型或 unsigned 类型的参数可用于此转换,并且结果都将格式化为 unsigned。
int 参数将转换为宽字符 (wchar_t) 并且将列显生成的宽字符。
参数必须为 wchar_t 数组。会一直读取数组中的字节,直到达到终止空字符或数据结尾,并将其解释和列显为宽字符。如果未指定精度,则会将其视为是无穷的,所以将列显所有宽字符,直到达到第一个空字符。如果指定了精度,则仅会列显在对应屏幕列数中显示的那一部分宽字符数组。
char、short、int、long 或 long long 参数将列显为无符号十六进制(以 16 为基数)整数。signed 类型或 unsigned 类型的参数可用于与此转换。如果使用 x 形式的转换,则将使用字母数字 abcdef。如果使用 X 形式的转换,则将使用字母数字 ABCDEF。如果指定了 # 标志,则将在非零结果的开头加上 0x(对于 %x)或 0X(对于 %X)。
uint64_t 参数将解释为自 1970 年 1 月 1 日 00:00 世界标准时间以来的纳秒数,并且按以下 cftime(3C) 形式列显:"%Y %a %b %e %T %Z"。自 1970 年 1 月 1 日 00:00 世界标准时间以来的当前纳秒数将通过 walltimestamp 变量提供。
列显文字 % 字符。不会转换任何参数。完整的转换规范必须为 %%。
printa() 函数用于格式化 D 程序中聚合的结果。可通过下列两种形式之一来调用该函数:
printa(@aggregation-name); printa(format-string, @aggregation-name);
如果使用函数的第一种形式,则 dtrace(1M) 命令将提取聚合数据的一致快照并生成输出,该输出与用于聚合的缺省输出格式等效,如第 9 章中所述。
如果使用函数的第二种形式,则 dtrace(1M) 命令将提取聚合数据的一致快照,并根据 format string 中指定的转换以及下列规则来生成输出:
格式转换必须与用于创建聚合的元组签名相匹配。每个元组元素仅能显示一次。例如,如果使用下列 D 语句来聚合计数:
@a["hello", 123] = count(); @a["goodbye", 456] = count();
然后将 D 语句 printa(format-string, @a) 添加到探测器子句,则 dtrace 将提取聚合数据的快照并生成输出,就像输入了下列语句:
printf(format-string, "hello", 123); printf(format-string, "goodbye", 456);
及类似语句一样(对于聚合中定义的每个元组)。
与 printf() 不同,用于 printa() 的格式字符串无需包括元组的所有元素。即,元组的长度可以为 3,并且仅进行一次格式转换。因此,可以通过更改聚合声明将要在 printa() 输出中省略的任何元组键移到元组的结尾来将其省略,然后在 printa() 格式字符串中省略其对应的转换说明符。
可通过使用附加的 @ 格式标志字符(仅在用于 printa() 时有效)将聚合结果包括在输出中。@ 标志可与任何相应的格式转换说明符组合,并且可在一个格式字符串中多次显示,以便元组结果可在输出中的任何位置多次显示。可用于聚合函数的一组转换说明符隐含在聚合函数的结果类型中。聚合结果类型包括:
avg() |
uint64_t |
count() |
uint64_t |
lquantize() |
int64_t |
max() |
uint64_t |
min() |
uint64_t |
quantize() |
int64_t |
sum() |
uint64_t |
例如,要格式化 avg() 的结果,可应用 %d、%i、%o、%u 或 %x 格式转换。quantize() 和 lquantize() 函数会将其结果格式化为 ASCII 表而不是单个值。
以下 D 程序显示 printa() 的完整示例,通过使用 profile 提供器来对 caller 的值进行采样,然后将结果格式化为简单的表:
profile:::profile-997 { @a[caller] = count(); } END { printa("%@8u %a\n", @a); }
如果使用 dtrace 来执行此程序,请等待几秒钟,然后按 Ctrl-C 组合键,将会看到与以下示例类似的输出:
# dtrace -s printa.d ^C CPU ID FUNCTION:NAME 1 2 :END 1 0x1 1 ohci`ohci_handle_root_hub_status_change+0x148 1 specfs`spec_write+0xe0 1 0xff14f950 1 genunix`cyclic_softint+0x588 1 0xfef2280c 1 genunix`getf+0xdc 1 ufs`ufs_icheck+0x50 1 genunix`infpollinfo+0x80 1 genunix`kmem_log_enter+0x1e8 ... |
如果使用 trace() 函数而不是 printf() 函数来捕获数据,则 dtrace 命令将使用缺省输出格式来设置结果的格式。如果数据大小为 1、2、4 个字节或 8 个字节,则结果格式将设置为十进制整数值。如果数据为任何其他大小并且是可列显字符序列(如果解释为字节序列),则它将列显为 ASCII 字符串。如果数据为任何其他大小并且不是可列显字符序列,则它将列显为一系列十六进制整数格式的字节值。