性能分析¶
LNT 支持存储和显示性能分析。这些分析的目的是为了揭示测试样本之间代码生成的差异,并允许轻松识别代码的热点部分。
LNT 中分析的原理¶
LNT 中的分析以自定义格式表示。用户界面完全基于对此自定义格式的查询进行操作。编写适配器以将其他格式转换为 LNT 的分析格式。分析数据作为正常 JSON 报告的一部分上传到 LNT 服务器。
生成分析数据¶
分析生成可以直接通过 python API 调用(lnt profile
是其包装器)或使用 lnt runtests
工具驱动。
通过 lnt runtests test-suite
生成分析数据¶
注意
通过 LNT 收集分析目前仅在 Linux 系统上受支持,因为目前编写的唯一适配器使用了 Linux 的 perf
基础设施。当编写更多适配器后,LNT 可以扩展对它们的支持。
如果您的测试系统已经使用 lnt runtests
来构建和运行测试,那么生成分析的最简单方法是简单地添加一个参数
--use-perf=all
--use-perf
选项指定了 Linux Perf 的用途。选项包括
none
:不为任何事情使用perf
time
:使用perf
来测量编译和执行时间。这可以比time
更准确。profile
:仅使用perf
进行分析。all
:使用perf
进行分析和计时。
生成的分析与每个测试可执行文件一起存在,命名为 $TEST.perf_data
。这些分析在测试执行结束时被处理并转换为 LNT 的分析格式,并插入到生成的 report.json
中。
不通过 lnt runtests test-suite
生成分析数据¶
LNT 的一个受支持的用例是使用 LNT 服务器进行性能跟踪,但使用与 lnt runtests
不同的测试驱动程序来实际构建、运行和收集测试的统计信息。
分析数据位于提交给 LNT 的 JSON 报告中。本节将描述如何向已存在的 JSON 报告添加分析数据;有关 JSON 报告的总体结构的详细信息,请参阅 导入数据。
第一步是以 LNT 格式生成分析数据本身,以便通过 JSON 发送。要导入分析,请使用 lnt profile upgrade
命令
lnt profile upgrade my_profile.perf_data /tmp/my_profile.lntprof
my_profile.perf_data
在这里被假定为 Linux Perf 格式,但可以是任何已注册适配器的格式(目前只有 Linux Perf,但预计随着时间的推移会添加更多格式)。
/tmp/my_profile.lntprof
现在是以节省空间的二进制形式存在的 LNT 分析。为了准备好通过 JSON 发送它,我们必须对其进行 base-64 编码
base64 -i /tmp/my_profile.lntprof > /tmp/my_profile.txt
现在我们只需要将其添加到报告中。分析看起来类似于哈希,因为它们是带有字符串数据的样本
{
"format_version": "2",
"machine": {
...
},
"run": {
...
},
"tests": [
{
"name": "nts.suite1/program1",
"execution_time": [ 0.1056, 0.1055 ],
"profile": "eJxNj8EOgjAMhu99Cm9wULMOEHgBE888QdkASWCQFWJ8e1v04JIt+9f//7qmfkVoEj8yMXdzO70v/RJn2hJYrRQiveSWATdJvwe3jUtgecgh9Wsh9T6gyJvKUjm0kegK0mmt9UCjJUSgB5q8KsobUJOQ96dozr8tAbRApPbssOeCcm83ddoLC7ijMcA/RGUUwXt7iviPEDLJN92yh62LR7I8aBUMysgLnaKNFNzzMo8y7uGplQ4sa/j6rfn60WYaGdRhtT9fP5+JUW4="
}
]
}
支持的格式¶
Linux Perf¶
Perf 分析直接从二进制 perf.data
文件读取,而无需使用 perf
包装器工具或任何 Linux/GPL 标头。这使其可以在非 Linux 平台上运行,尽管这实际上仅对调试有用,因为被分析的二进制文件/库应该是可读的。
perf 导入代码使用了一个名为 cPerf 的 C++ 扩展,它是为 LNT 项目编写的。它的功能不如 perf annotate
或 perf report
,但以机器可读的形式生成的数据大致相同,速度快约 6 倍。它用 C++ 编写是因为很难编写可读的 Python,以便在二进制数据上高效地执行。一旦事件流被聚合,就会创建一个 python 字典对象,并将处理返回给 Python。速度在这个阶段很重要,因为分析导入可能在较旧或性能较低的硬件上运行,并且 LLVM 的测试套件包含数百个必须导入的测试!
注意
在最近版本的 Perf 中,存在一个新的子命令:perf data
。这以 CTF 格式 输出事件跟踪,然后可以使用 babeltrace 及其 Python 绑定进行查询。只要它的性能相似,这将允许删除 LNT 中的大量自定义代码。
为新的分析格式添加支持¶
要创建新的分析适配器,必须在 lnt.testing.profile
包中创建一个新的 Python 类,该类继承 ProfileImpl
类
- class lnt.testing.profile.profile.ProfileImpl¶
- static checkFile(fname)¶
如果 ‘fname’ 是此分析实现的序列化版本,则返回 True。
- static deserialize(fobj)¶
从 ‘fobj’ 读取分析,返回一个新的分析对象。这可以是惰性的。
- getCodeForFunction(fname)¶
返回一个生成器,它将为每次调用返回一个三元组
(counters, address, text)
其中 counters 是一个字典:(例如)
{'cycles': 50.0}
,text 的格式与 getDisassemblyFormat() 返回的格式相同,address 是一个整数。计数器值必须是百分比(函数总计的百分比),而不是绝对数字。
- getDisassemblyFormat()¶
返回 getCodeForFunction() 返回的反汇编字符串的格式。可能的值是
raw
- 没有可用的解释;纯字符串。
marked-up-disassembly
- LLVM 标记的反汇编格式。
- getFunctions()¶
返回一个字典,其中包含函数名称以及有关该函数的信息。
信息字典包含
counters
- 函数的计数器值。length
- 调用 getCodeForFunction 以获取所有指令的次数。
字典不应包含反汇编/函数内容。计数器值必须是百分比,而不是绝对数字。
例如。
{'main': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 200}, 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 4} }
- getTopLevelCounters()¶
返回一个字典,其中包含整个分析的计数器。这些将是绝对数字:例如
{'cycles': 5000.0}
。
- getVersion()¶
返回分析版本。
- serialize(fname=None)¶
将分析序列化为给定的文件名(base)。如果 fname 为 None,则作为字节实例返回。
- static upgrade(old)¶
接受 ‘old’ 中的先前分析实现,并为此版本返回一个新的 ProfileImpl。唯一必须支持的旧版本是紧邻的先前版本(例如,版本 3 只需要处理从版本 2 的升级)。
您的子类可以实现所有指定的函数,或者执行 perf.py
所做的事情,即仅实现 checkFile()
和 deserialize()
静态函数。在这种模型中,在 deserialize()
内部,您将把您的分析数据解析为简单的字典结构,并从中创建一个 ProfileV1Impl
对象。这是一个非常简单的分析实现,它仅从字典表示形式工作
- class lnt.testing.profile.profilev1impl.ProfileV1(data)¶
ProfileV1 文件在任何方面都不聪明。它们是简单的 Python 对象,其中分析数据以最明显的生产/消费方式布局,然后进行 pickle 序列化和压缩。
它们应该通过简单地存储到
self.data
成员中来创建。self.data
成员具有以下格式{ counters: {'cycles': 12345.0, 'branch-misses': 200.0}, # absolute values. disassembly-format: 'raw', functions: { name: { counters: {'cycles': 45.0, ...}, # Note counters are now percentages. data: [ [463464, {'cycles': 23.0, ...}, ' add r0, r0, r1'}], ... ] } } }
- static checkFile(fn)¶
如果 ‘fname’ 是此分析实现的序列化版本,则返回 True。
- static deserialize(fobj)¶
从 ‘fobj’ 读取分析,返回一个新的分析对象。这可以是惰性的。
- getCodeForFunction(fname)¶
返回一个生成器,它将为每次调用返回一个三元组
(counters, address, text)
其中 counters 是一个字典:(例如)
{'cycles': 50.0}
,text 的格式与 getDisassemblyFormat() 返回的格式相同,address 是一个整数。计数器值必须是百分比(函数总计的百分比),而不是绝对数字。
- getDisassemblyFormat()¶
返回 getCodeForFunction() 返回的反汇编字符串的格式。可能的值是
raw
- 没有可用的解释;纯字符串。
marked-up-disassembly
- LLVM 标记的反汇编格式。
- getFunctions()¶
返回一个字典,其中包含函数名称以及有关该函数的信息。
信息字典包含
counters
- 函数的计数器值。length
- 调用 getCodeForFunction 以获取所有指令的次数。
字典不应包含反汇编/函数内容。计数器值必须是百分比,而不是绝对数字。
例如。
{'main': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 200}, 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 4} }
- getTopLevelCounters()¶
返回一个字典,其中包含整个分析的计数器。这些将是绝对数字:例如
{'cycles': 5000.0}
。
- getVersion()¶
返回分析版本。
- serialize(fname=None)¶
将分析序列化为给定的文件名(base)。如果 fname 为 None,则作为字节实例返回。
- static upgrade(old)¶
接受 ‘old’ 中的先前分析实现,并为此版本返回一个新的 ProfileImpl。唯一必须支持的旧版本是紧邻的先前版本(例如,版本 3 只需要处理从版本 2 的升级)。
查看分析¶
一旦分析被提交到 LNT,它们就可以通过手动 URL 或 “runs” 页面获得。
在运行结果页面上,当鼠标悬停在表格行上时,如果分析数据可用,则应显示“查看分析”链接。
注意
已知这种悬停效果对触摸屏不友好,并且可能不够直观。此页面应尽快修改,以使分析数据链接更加明显。
或者,可以通过手动构建 URL 来查看分析
db_default/v4/nts/profile/<test-id>/<run1-id>/<run2-id>
其中
test-id
是要显示的测试的数据库 TestIDrun1-id
是要显示在显示屏左侧的运行的数据库 RunIDrun2-id
是要显示在显示屏右侧的运行的数据库 RunID
显然,此 URL 有点难以构建,因此建议使用上面运行页面中的链接。