RFC-0084:向 zx_info_task_runtime_t 添加更多指标 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 向 zx_info_task_runtime_t 添加了更多指标,用于调试媒体性能问题。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-03-09 |
审核日期(年-月-日) | 2021-04-06 |
摘要
ZX_INFO_TASK_RUNTIME
主题提供了一种方法来检索
任务在 CPU 上运行或排队等待运行的时间。为了诊断
我们提议增加额外的精细运行时
特别是等待页面错误发生所花费的时间
等待内核互斥所用的时间。
设计初衷
实时任务有截止日期。有时,由于要完成的任务而错过截止日期 在内核中停留的时间超出预期。要调试这些情况, 有助于了解任务被阻止的原因。例如,如果某个任务 而等待页面错误需要 11 毫秒,我们可以得出结论, 由于页面错误太慢,因此错过了截止日期。
具体而言,我们希望改进由以下媒体子系统产生的诊断结果,
audio_core
。在 audio_core
中,每个 Mixer 任务都必须在 10 毫秒内完成。如果
任务用时超过 10 毫秒,用户会听到杂音。最近,我们
因页面错误缓慢而错过截止日期(其中 audio_core
可执行文件
需要重新分页)和内核堆互斥量上的争用。
这些问题无法通过快照进行诊断,而是必须
跟踪是一个单调乏味的过程,有时会因
在本地重现问题(因为 bug 可能只有在特定情形下才会触发)
应用)。这些是高优先级问题,
对媒体效果造成严重负面影响。
我们的目标是从内核中导出足够的诊断信息,
可让您通过快照诊断这些问题,而无需深入了解
执行跟踪记录本文档建议将新统计信息添加到
zx_info_task_runtime_t
,以便更完整地回答以下问题:“为什么
“任务无法运行?”。
Zircon 线程截止时间的背景
Zircon 截止时间配置文件包含三个组成部分:期限、容量和截止时间。 为线程分配截止期限配置文件后,Zircon 可保证 period 时,线程会被分配至最高 capacity CPU 在 deadline 内 每个时间段的开始时间。举例说明:
- 周期 = 10 毫秒
- 容量 = 2 毫秒
- 时限 = 5 毫秒
在接下来的 5 毫秒内,为线程分配 2 毫秒的 CPU。还有 有以下两种可能错过截止日期:
任务安排得很晚。例如,如果下一时间段的开始时间是 T,但任务在 T+4 毫秒之后才被调度, 任务的截止时间(该任务不得晚于 T+3 毫秒)。内核可以检测到这种错过的截止时间,但实际上 除非调度程序有错误或订阅过多,否则绝不会发生这种情况。
完成任务耗时超过 2 毫秒。内核无法知道 是因为它不了解任务边界。例如,如果 任务安排在 T+1 毫秒,总运行 1 毫秒,然后阻塞 9 毫秒, 内核无法得知任务是否错过了截止时间(因为 或者任务要求 2 毫秒,但只需要 1 毫秒即可 完成(然后睡眠 9 毫秒以等待下一个周期)。
我们的目标是帮助诊断第二种错过截止日期。目前,如果
任务运行时间过长,可以查询 ZX_INFO_TASK_RUNTIME
以了解
在用户空间中的 CPU 上运行所花费的时间。如果该 cpu_time
大于
预期运行时,任务就会知道它的运行时间过长。不过,
如果 cpu_time
只占总任务时间的一小部分,则该任务
它的大部分时间都花在内核中,很可能处于阻塞状态,无法运行。目标
是为了帮助您了解这些时间花在哪些方面。
设计空间
我们的目标是回答“为什么此任务无法运行?”这一问题。我们必须 做一些决定:
我们的回答应有多完整?具体而言,我们是否应 任务被阻止的原因,还是仅仅是几个看上去很重要的原因?
我们应该以怎样的详细程度回答这个问题?例如,我们应该 仅报告用户一级易于理解的简单事件,例如 还是应该添加级别较低的事件 是否特定于 Zircon 的当前实现?
如果我们报告 N 条统计信息,是否应要求这 N 条统计信息 还是应该允许它们以任意方式重叠?
最简单、最直接的方法是,枚举我们关心的一些事件 并生成这些事件的统计信息鉴于媒体子系统 页面故障和内核锁争用方面的问题, “页面错误所花时间”的统计信息以及“在内核上阻塞花费的时间” 。
可能还存在这两个无法捕获到的其他问题 统计信息。因此,完整的解决方案具有吸引力。一种想法是 线程进入内核的所有方式这包括 N 硬件 中断(计时器和设备中断)和 K 软件中断(系统中断) 调用和故障)。然后,我们会生成 N+K 的统计信息, 中断。计时器在线程进入内核时启动,在线程进入内核时停止。 将控制权返还给用户空间线程。不过,用户级别 计算“在系统调用 X 中花费的时间”,让内核计算该信息 是多余的
另一种方法是生成统计信息, 内核模式”以及“在 X 上阻塞的时间”,其中 X 是 基元,如“内核锁定”或“channel”[频道]。这种做法存在导致膨胀的风险 随着内核基元集随时间变化而发生变化。
回过头来,我们真正想要的是从实际使用的设备获取跟踪记录。
理想情况下,我们可以连续将轨迹记录到环形缓冲区中,并上传该轨迹文件
缓冲区
TRACE_ALERT
。
设计
虽然需要一个全面的解决方案,但可能需要很长时间 设计和构建。我们迫切需要诊断性能下降问题 大自然。因此,我建议我们添加两个有针对性的指标, 眼前的问题:等待页面错误所花费的时间, 等待内核锁定所用的时间
对于上述设计空间问题:
我们不会力求完整性。
粒度是任意的(我们会记录我们认为所需的任何值)
统计信息可能会以任意方式重叠
内核变更
// This struct contains a partial breakdown of time spent by this task since
// creation. The breakdown is not complete and individual fields may overlap:
// there is no expectation that these fields should sum to an equivalent
// "wall time".
typedef struct zx_info_task_runtime {
// Existing fields
zx_duration_t cpu_time;
zx_duration_t queue_time;
// New fields below here
// The total amount of time this task and its children spent handling page faults.
zx_duration_t page_fault_time;
// The total amount of time this task and its children spent waiting on contended
// kernel locks.
zx_duration_t lock_contention_time;
} zx_info_task_runtime_t;
这两个字段都将按线程计算,然后跨进程求和
和作业。cpu_time
queue_time
请注意,
媒体子系统不需要按进程和作业汇总,
为了与
zx_info_task_runtime_t
。
存在多种类型的页面错误和多种内核锁定。
page_fault_time
表示处理各种网页所花费的总时间
错误。通过涵盖所有页面错误,我们无需说明
页面故障,这可能很难,因为内核可能会添加或
随着实现随时间的推移而发生变化,并消除某些类型的页面错误
因为其支持新架构
lock_contention_time
涵盖所有争用锁。不过,“竞争”一词
故意未指定规格,因此内核可以改进其实现
以平衡衡量争用的成本和
报告竞争时间。有关其他讨论,请参阅“实现”(见下文)。
用户空间如何诊断错过截止日期
鉴于这些新字段,用户空间可以使用如下代码来诊断 错过的截止日期:
for (;;) {
zx_object_get_info(current_thread, ZX_TASK_RUNTIME_INFO, &start_info, ...)
deadline_task()
if (current_time() > deadline) {
zx_object_get_info(current_thread, ZX_TASK_RUNTIME_INFO, &end_info, ...)
// ...
// report stats from (end_info - start_info)
// ...
}
}
实现
page_fault_time
将计算所有页面错误处理程序占用的总时间。
在当前的实现中,这包括 vmm_page_fault_handler
和
vmm_accessed_fault_handler
。
lock_contention_time
会计算
Mutex::AcquireContendedMutex
和BrwLock::Block
。此方法已有
访问当前的 Thread
和 current_ticks()
。实现
不会涵盖自旋锁。虽然自旋锁可能会争用,但我们会忽略自旋锁
因为测量自旋锁的争用可能让人望而却步,
价格高昂。
为了尽可能减少开销,我们会将这些时长记录为 tick 计数,
在 zx_object_get_info
系统调用期间转换为 zx_duration_t
。
该实现的其他详情将遵循
cpu_time
和queue_time
。原型实现可在以下位置找到:
fxrev.dev/469818。
性能
我们将运行 Zircon 互斥基准,以验证没有回归问题。我们将 在原始硬件(x86 和 ARM)上运行这些基准测试。此外,要验证 在虚拟化环境中不会出现回归问题, 在 QEMU(x86 和 ARM)上进行基准测试。
向后兼容性
zx_info_task_runtime_t
结构体将进行版本控制,这与
已针对其他 zx_info_*
结构体完成(有关示例,请参阅 fxrev.dev/406754)。
安全注意事项
ZX_INFO_TASK_RUNTIME
是一种边信道,可能会泄露
检查任务。例如,page_fault_time
可用于衡量任务的
内存访问模式作为此类泄露的缓解措施,
ZX_INFO_TASK_RUNTIME
主题已要求使用ZX_RIGHT_INSPECT
。拥有
则可以假定有权访问任务的私有数据。
ZX_INFO_TASK_RUNTIME
还可能会泄露有关其他任务的间接信息。对于
例如,如果任务知道自己的 page_fault_time
,或许能够推断出
其他任务的内存访问模式。同样,如果任务知道
就能够推断出
其他任务使用共享内核资源。将来,我们可能会构建
zx_info_task_runtime_t
正在使用低分辨率计时器。这并不一定
可以防止定时攻击,但可能会限制其有效性。
另一项防御措施是限制特殊开发者访问 zx_info_task_runtime_t
build。但是,这会极大地限制此功能的实用性:
在开发环境中,我们往往难以重现性能错误。
我们需要一个可在正式版 build 中启用的解决方案。
为了彻底避免出现这种边信道,我们需要使用单独的报告功能
和指标检查等功能。例如,如果我们
连续将轨迹记录到环形缓冲区中,并将该缓冲区上传到
特定通道或端口
TRACE_ALERT
、
则不需要提供触发 TRACE_ALERT
的任务
访问跟踪记录,从而消除边信道。如前所述
这样的解决方案需要很长时间来设计和构建,而我们
需要立即解决。
隐私注意事项
无。
文档
需要更新 Zircon 系统调用文档,以纳入新的
zx_info_task_runtime_t
字段。
先验技术和参考资料
在 Linux 中,相关度最高的先前技术是 getrusage
,它会报告用户
和系统 CPU 时间和页面故障数、I/O 操作数和上下文切换次数。
Windows 具有 GetThreadTimes
,用于报告用户和系统 CPU 时间。五金件
性能计数器(如 x86 上的 RDPMC
)可提供类似的信息和
拥有
类似的安全问题。
测试
我们将通过 audio_core
记录新的运行时信息来手动测试
(我们已经完成了原型实现:请参阅
fxrev.dev/469819)。
我们将更新 cpu_time
和 queue_time
的现有测试,以测试旧测试
名为“zx_info_task_runtime_t
”的版本
zx_info_task_runtime_v1_t
。此外,Zircon 的
abi_type_validator.h
将进行更新,以验证新旧 ABI。这样可以确保
并保持向后兼容性
为此功能添加集成测试并不容易,因为 没有 API 可强制内核遇到锁争用或触发页面错误 (进程终止分段错误除外)。