XRay 飞行数据记录器跟踪格式

版本:

1,截至 2017-07-20

简介

当在飞行数据记录器模式下收集 XRay 跟踪数据时,应用程序的每个线程将声明缓冲区以填充跟踪数据,这些数据在某个时刻被最终确定并刷新。

性能分析器的一个目标是最小化开销,刷新后的数据直接对应于缓冲区。

本文档描述了跟踪文件的格式。

概述

每个跟踪文件对应于特定线程中的一系列事件。

该文件具有头部,后跟一系列可区分的记录类型。

字节字段的字节序与生成跟踪文件的平台的字节序相匹配。

头部区段

跟踪文件以 32 字节的头部开始。

字段

大小(字节)

描述

version

2

预期版本化的读取器。本文档描述了当 version == 1 时的格式

type

2

一个枚举,编码跟踪的类型。飞行数据记录器模式跟踪的类型为 type == 1

bitfield

4

保存未字节对齐的参数。在下面进一步描述。

cycle_frequency

8

CPU 振荡器的频率(以赫兹为单位),用于测量事件持续时间(以时钟周期为单位)。

buffer_size

8

头部之后跟踪数据部分的大小(以字节为单位)。

reserved

8

保留供将来使用。

文件头部的 bitfield 参数由以下字段组成。

字段

大小(位)

描述

constant_tsc

1

平台的时间戳计数器是否以恒定频率(尽管 CPU 频率会发生变化)来记录事件之间的时间刻度。0 == 非恒定。1 == 恒定。

nonstop_tsc

1

无论 CPU 是否处于低功耗状态,tsc 是否继续计数。0 == 停止。1 == 不停止。

reserved

30

无意义。

数据区段

跟踪中的头部之后是数据区段,其大小与头部中的 buffer_size 字段匹配。

数据区段是不同类型元素的流。

序列中有几个类别的数据。

  • 函数记录:函数记录包含进入和退出函数执行的时间信息。函数记录每个 8 字节。

  • 元数据记录:元数据记录服务于多种目的。主要是,它们捕获对于每个函数来说记录成本可能太高,但对于情境化细粒度计时是必需的信息。它们也用作用户定义的事件数据负载的标记。元数据记录每个 16 字节。

  • 事件数据:自由格式数据可能与二进制文件跟踪的事件相关联,并编码由处理函数定义的数据。事件数据始终以标记记录开头,该标记记录指示其大小。

  • 函数参数:某些函数的参数包含在跟踪中。这些参数要么是指针地址,要么是在高级语言中独立于其类型读取和记录的原始类型。对于跟踪器来说,它们都是数字。具有附加参数的函数记录将在函数入口记录上指示它们的存在。我们仅支持记录从参数零开始的连续函数参数序列,这将是成员函数调用的 “this” 指针。例如,我们不支持记录第一个和第三个参数。

内存格式的读取器必须维护状态机。该格式未尝试填充以进行对齐,并且不可寻址。

函数记录

函数记录具有 8 字节布局。此布局编码信息以重建已检测函数的调用堆栈及其持续时间。

字段

大小(位)

描述

discriminant

1

指示读取器应读取函数记录还是元数据记录。对于函数记录,设置为 0

action

3

指定函数是正在进入、退出,还是由优化产生的非标准入口或出口。

function_id

28

函数的数字 ID。通过 xray instrumentation map 解析为名称。instrumentation map 由 xray 在编译时构建到目标文件中,并将函数 ID 与地址配对。它用于修补,并用作查找二进制文件符号以获取名称的工具。

tsc_delta

32

自上一个记录记录增量或其他 TSC 重置事件以来,时间戳计数器的时钟周期数。

在小端机器上,位字段从最低有效位到最高有效位排序。读取器可以读取 8 位值并应用掩码 0x01 来获取 discriminant。类似地,他们可以读取 32 位并无符号右移 0x04 以获得 function_id 字段。

在大端机器上,位字段从最高有效位到最低有效位按顺序写入。读取器将读取 8 位值并无符号右移 7 位以获得 discriminant。function_id 字段可以通过读取 32 位值并应用掩码 0x0FFFFFFF 来获得。

函数动作类型如下。

类型

编号

描述

Entry

0

典型的函数入口。

Exit

1

典型的函数出口。

Tail_Exit

2

由于尾调用优化而从函数退出的出口。

Entry_Args

3

记录参数的函数入口。

Entry_Args 记录不包含参数本身。相反,每个记录的参数的元数据记录都跟随流中的函数记录。

元数据记录

在整个缓冲区中散布着 16 字节的元数据记录。对于通常检测的二进制文件,它们将比函数记录更稀疏,并且它们提供了二进制文件执行状态的更完整的画面。

元数据记录布局部分取决于记录,但它们共享一个共同的结构。

为函数记录描述的相同位字段规则适用于 MetadataRecords 的第一个字节。在此字节内,小端机器使用 lsb 到 msb 排序,大端机器使用 msb 到 lsb 排序。

字段

大小

描述

discriminant

1

指示读取器应读取函数记录还是元数据记录。对于元数据记录,设置为 1

record_kind

7

元数据记录的类型。

data

15 字节

一个数据字段,每种记录类型使用方式不同。

以下是枚举的记录类型表。

编号

类型

0

NewBuffer

1

EndOfBuffer

2

NewCPUId

3

TSCWrap

4

WallTimeMarker

5

CustomEventMarker

6

CallArgument

NewBuffer 记录

每个缓冲区都以紧跟在头部之后的 NewBuffer 记录开始。它记录跟踪所属线程的线程 ID。

其数据段如下所示。

字段

大小(字节)

描述

thread_Id

2

缓冲区的线程 ID。

reserved

13

未使用。

WallClockTime 记录

在 NewBuffer 记录之后,每个缓冲区记录一个绝对时间,作为时间戳计数器增量记录的持续时间的参考框架。

其数据段如下所示。

字段

大小(字节)

描述

seconds

8

绝对时间刻度上的秒数。起点未指定,取决于跟踪器配置的实现和平台。

microseconds

4

时间的微秒分量。

reserved

3

未使用。

NewCpuId 记录

每个函数入口都会调用例程来确定正在执行的 CPU。通常,这是通过 readtscp 完成的,它同时读取时间戳计数器。

如果跟踪检测到执行已切换 CPU,或者这是第一个检测的入口点,则跟踪器将输出 NewCpuId 记录。

其数据段如下所示。

字段

大小(字节)

描述

cpu_id

2

CPU ID。

absolute_tsc

8

时间戳计数器的绝对值。

reserved

5

未使用。

TSCWrap 记录

由于每个函数记录都使用 32 位值来表示自上次引用以来时间戳计数器的时钟周期数,因此此值可能会溢出,特别是对于稀疏检测的二进制文件。

当此增量不适合 32 位表示时,将以 TSCWrap 记录的形式写入引用绝对时间戳计数器记录。

其数据段如下所示。

字段

大小(字节)

描述

absolute_tsc

8

时间戳计数器值。

reserved

7

未使用。

CallArgument 记录

紧跟在 Entry_Args 类型函数记录之后,可能有一个或多个 CallArgument 记录,其中包含跟踪函数的参数值。

CallArgument 记录序列的顺序与函数参数的顺序一一对应。

CallArgument 数据段

字段

大小(字节)

描述

argument

8

数值参数(可能是指针地址)。

reserved

7

未使用。

CustomEventMarker 记录

XRay 提供了记录自定义事件的功能。这可以用于记录 RPC 的跟踪信息或类似的应用特定跟踪数据。

自定义事件本身是缓冲区内具有任意大小的非结构化(应用程序定义)内存段。它们前面是 CustomEventMarkers,以指示它们的存在和大小。

CustomEventMarker 数据段

字段

大小(字节)

描述

event_size

4

前置事件的大小。

absolute_tsc

8

事件的时间戳计数器。

reserved

3

未使用。

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。缓冲区可能耗尽空间,或者程序可能在检测函数退出之前请求跟踪器完成以返回缓冲区。