XRay 飞行数据记录器跟踪格式¶
- 版本:
1,截至 2017-07-20
简介¶
当在飞行数据记录器模式下收集 XRay 跟踪数据时,应用程序的每个线程将声明缓冲区以填充跟踪数据,这些数据在某个时刻被最终确定并刷新。
性能分析器的一个目标是最小化开销,刷新后的数据直接对应于缓冲区。
本文档描述了跟踪文件的格式。
概述¶
每个跟踪文件对应于特定线程中的一系列事件。
该文件具有头部,后跟一系列可区分的记录类型。
字节字段的字节序与生成跟踪文件的平台的字节序相匹配。
头部区段¶
跟踪文件以 32 字节的头部开始。
字段 |
大小(字节) |
描述 |
---|---|---|
version |
|
预期版本化的读取器。本文档描述了当 version == 1 时的格式 |
type |
|
一个枚举,编码跟踪的类型。飞行数据记录器模式跟踪的类型为 type == 1 |
bitfield |
|
保存未字节对齐的参数。在下面进一步描述。 |
cycle_frequency |
|
CPU 振荡器的频率(以赫兹为单位),用于测量事件持续时间(以时钟周期为单位)。 |
buffer_size |
|
头部之后跟踪数据部分的大小(以字节为单位)。 |
reserved |
|
保留供将来使用。 |
文件头部的 bitfield 参数由以下字段组成。
字段 |
大小(位) |
描述 |
---|---|---|
constant_tsc |
|
平台的时间戳计数器是否以恒定频率(尽管 CPU 频率会发生变化)来记录事件之间的时间刻度。0 == 非恒定。1 == 恒定。 |
nonstop_tsc |
|
无论 CPU 是否处于低功耗状态,tsc 是否继续计数。0 == 停止。1 == 不停止。 |
reserved |
|
无意义。 |
数据区段¶
跟踪中的头部之后是数据区段,其大小与头部中的 buffer_size 字段匹配。
数据区段是不同类型元素的流。
序列中有几个类别的数据。
函数记录
:函数记录包含进入和退出函数执行的时间信息。函数记录每个 8 字节。元数据记录
:元数据记录服务于多种目的。主要是,它们捕获对于每个函数来说记录成本可能太高,但对于情境化细粒度计时是必需的信息。它们也用作用户定义的事件数据负载的标记。元数据记录每个 16 字节。事件数据
:自由格式数据可能与二进制文件跟踪的事件相关联,并编码由处理函数定义的数据。事件数据始终以标记记录开头,该标记记录指示其大小。函数参数
:某些函数的参数包含在跟踪中。这些参数要么是指针地址,要么是在高级语言中独立于其类型读取和记录的原始类型。对于跟踪器来说,它们都是数字。具有附加参数的函数记录将在函数入口记录上指示它们的存在。我们仅支持记录从参数零开始的连续函数参数序列,这将是成员函数调用的 “this” 指针。例如,我们不支持记录第一个和第三个参数。
内存格式的读取器必须维护状态机。该格式未尝试填充以进行对齐,并且不可寻址。
函数记录¶
函数记录具有 8 字节布局。此布局编码信息以重建已检测函数的调用堆栈及其持续时间。
字段 |
大小(位) |
描述 |
---|---|---|
discriminant |
|
指示读取器应读取函数记录还是元数据记录。对于函数记录,设置为 |
action |
|
指定函数是正在进入、退出,还是由优化产生的非标准入口或出口。 |
function_id |
|
函数的数字 ID。通过 xray instrumentation map 解析为名称。instrumentation map 由 xray 在编译时构建到目标文件中,并将函数 ID 与地址配对。它用于修补,并用作查找二进制文件符号以获取名称的工具。 |
tsc_delta |
|
自上一个记录记录增量或其他 TSC 重置事件以来,时间戳计数器的时钟周期数。 |
在小端机器上,位字段从最低有效位到最高有效位排序。读取器可以读取 8 位值并应用掩码 0x01
来获取 discriminant。类似地,他们可以读取 32 位并无符号右移 0x04
以获得 function_id 字段。
在大端机器上,位字段从最高有效位到最低有效位按顺序写入。读取器将读取 8 位值并无符号右移 7 位以获得 discriminant。function_id 字段可以通过读取 32 位值并应用掩码 0x0FFFFFFF
来获得。
函数动作类型如下。
类型 |
编号 |
描述 |
---|---|---|
Entry |
|
典型的函数入口。 |
Exit |
|
典型的函数出口。 |
Tail_Exit |
|
由于尾调用优化而从函数退出的出口。 |
Entry_Args |
|
记录参数的函数入口。 |
Entry_Args 记录不包含参数本身。相反,每个记录的参数的元数据记录都跟随流中的函数记录。
元数据记录¶
在整个缓冲区中散布着 16 字节的元数据记录。对于通常检测的二进制文件,它们将比函数记录更稀疏,并且它们提供了二进制文件执行状态的更完整的画面。
元数据记录布局部分取决于记录,但它们共享一个共同的结构。
为函数记录描述的相同位字段规则适用于 MetadataRecords 的第一个字节。在此字节内,小端机器使用 lsb 到 msb 排序,大端机器使用 msb 到 lsb 排序。
字段 |
大小 |
描述 |
---|---|---|
discriminant |
|
指示读取器应读取函数记录还是元数据记录。对于元数据记录,设置为 |
record_kind |
|
元数据记录的类型。 |
data |
|
一个数据字段,每种记录类型使用方式不同。 |
以下是枚举的记录类型表。
编号 |
类型 |
---|---|
0 |
NewBuffer |
1 |
EndOfBuffer |
2 |
NewCPUId |
3 |
TSCWrap |
4 |
WallTimeMarker |
5 |
CustomEventMarker |
6 |
CallArgument |
NewBuffer 记录¶
每个缓冲区都以紧跟在头部之后的 NewBuffer 记录开始。它记录跟踪所属线程的线程 ID。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
thread_Id |
|
缓冲区的线程 ID。 |
reserved |
|
未使用。 |
WallClockTime 记录¶
在 NewBuffer 记录之后,每个缓冲区记录一个绝对时间,作为时间戳计数器增量记录的持续时间的参考框架。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
seconds |
|
绝对时间刻度上的秒数。起点未指定,取决于跟踪器配置的实现和平台。 |
microseconds |
|
时间的微秒分量。 |
reserved |
|
未使用。 |
NewCpuId 记录¶
每个函数入口都会调用例程来确定正在执行的 CPU。通常,这是通过 readtscp 完成的,它同时读取时间戳计数器。
如果跟踪检测到执行已切换 CPU,或者这是第一个检测的入口点,则跟踪器将输出 NewCpuId 记录。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
cpu_id |
|
CPU ID。 |
absolute_tsc |
|
时间戳计数器的绝对值。 |
reserved |
|
未使用。 |
TSCWrap 记录¶
由于每个函数记录都使用 32 位值来表示自上次引用以来时间戳计数器的时钟周期数,因此此值可能会溢出,特别是对于稀疏检测的二进制文件。
当此增量不适合 32 位表示时,将以 TSCWrap 记录的形式写入引用绝对时间戳计数器记录。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
absolute_tsc |
|
时间戳计数器值。 |
reserved |
|
未使用。 |
CallArgument 记录¶
紧跟在 Entry_Args 类型函数记录之后,可能有一个或多个 CallArgument 记录,其中包含跟踪函数的参数值。
CallArgument 记录序列的顺序与函数参数的顺序一一对应。
CallArgument 数据段
字段 |
大小(字节) |
描述 |
---|---|---|
argument |
|
数值参数(可能是指针地址)。 |
reserved |
|
未使用。 |
CustomEventMarker 记录¶
XRay 提供了记录自定义事件的功能。这可以用于记录 RPC 的跟踪信息或类似的应用特定跟踪数据。
自定义事件本身是缓冲区内具有任意大小的非结构化(应用程序定义)内存段。它们前面是 CustomEventMarkers,以指示它们的存在和大小。
CustomEventMarker 数据段
字段 |
大小(字节) |
描述 |
---|---|---|
event_size |
|
前置事件的大小。 |
absolute_tsc |
|
事件的时间戳计数器。 |
reserved |
|
未使用。 |
EndOfBuffer 记录¶
EndOfBuffer 记录类型指示此缓冲区中没有更多跟踪数据。读取器应查找过去缓冲区开始之前表示的剩余 buffer_size,并查找另一个头部或 EOF。
格式语法和不变量¶
并非所有元数据记录和函数记录的序列都是有效数据。序列应解析为状态机。有效格式的期望可以用 EBNF 格式的语句表示。
这是尝试用 EBNF 格式的语句解释格式。
Format := Header ThreadBuffer* EOF
ThreadBuffer := NewBuffer WallClockTime NewCPUId BodySequence* End
BodySequence := NewCPUId | TSCWrap | Function | CustomEvent
Function := (Function_Entry_Args CallArgument*) | Function_Other_Type
CustomEvent := CustomEventMarker CustomEventUnstructuredMemory
End := EndOfBuffer RemainingBufferSizeToSkip
函数记录顺序¶
以下是一些可能有助于理解函数记录期望的澄清。
具有 Exit 的函数应在跟踪中具有相应的 Entry 或 Entry_Args 函数记录在它们之前。
Tail_Exit 函数记录记录程序计数器将要采用的返回地址的函数的函数 ID。换句话说,如果未使用尾调用优化,则将从调用堆栈中弹出的最终函数。
并非所有标记为检测的函数都一定在跟踪中。跟踪器使用启发式方法来保留非平凡函数的跟踪。
并非每个入口都必须具有跟踪的 Exit 或 Tail Exit。缓冲区可能耗尽空间,或者程序可能在检测函数退出之前请求跟踪器完成以返回缓冲区。