性能分析¶
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 报告通用结构的详细信息,请参阅 导入数据。
第一步是生成适合通过 JSON 发送的 LNT 格式的分析数据。要导入分析,请使用 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 发送,我们必须对其进行 Base64 编码
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)¶
将概要文件序列化到给定的文件名(基础)。如果 fname 为 None,则作为字节实例返回。
- static upgrade(old)¶
获取“old”中的先前概要文件实现,并返回此版本的新 ProfileImpl。必须支持的唯一旧版本是紧接其前的版本(例如,版本 3 只需处理来自版本 2 的升级)。
您的子类可以实现所有指定的功能,或者像 perf.py
一样,只实现 checkFile()
和 deserialize()
静态函数。在此模型中,在 deserialize()
内部,您将把概要文件数据解析成一个简单的字典结构,并从中创建一个 ProfileV1Impl
对象。这是一个非常简单的概要文件实现,只需从字典表示中工作即可。
- class lnt.testing.profile.profilev1impl.ProfileV1(data)¶
ProfileV1 文件在任何方面都不聪明。它们是简单的 Python 对象,其中概要文件数据以最明显的方式(用于生产/使用)布局,然后被腌制并压缩。
预计它们将通过简单地存储到
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)¶
将概要文件序列化到给定的文件名(基础)。如果 fname 为 None,则作为字节实例返回。
- static upgrade(old)¶
获取“old”中的先前概要文件实现,并返回此版本的新 ProfileImpl。必须支持的唯一旧版本是紧接其前的版本(例如,版本 3 只需处理来自版本 2 的升级)。
查看概要文件¶
将概要文件提交到 LNT 后,可以通过手动 URL 或“运行”页面访问它们。
在运行结果页面上,如果提供了概要文件数据,则将鼠标悬停在表格行上时,会显示“查看概要文件”链接。
注意
已知此悬停效果不适合触摸屏,并且可能不直观。此页面应尽快修改,以使概要文件数据链接更明显。
或者,可以通过手动构建 URL 来查看概要文件
db_default/v4/nts/profile/<test-id>/<run1-id>/<run2-id>
其中
test-id
是要显示的测试的数据库 TestIDrun1-id
是显示在显示屏左侧的运行的数据库 RunIDrun2-id
是显示在显示屏右侧的运行的数据库 RunID
显然,此 URL 有点难以构建,因此建议使用上述运行页面中的链接。