随着云计算技术与服务的发展和进步,越来越多的客户选择将业务部署到云端。但由于引入了虚拟化层,在业务部署过程中经常会遇到 IO 问题,通常也不易调试。本文主要介绍利用 perf、systemtap 等工具,帮助一位托管云客户调试 IO 性能问题,来分析虚拟环境下 Windows IO 的性能。
问题出现
有一次,托管云客户自己搭建了虚拟化环境,在同一台宿主机上创建 Windows 2008 R2 和 Centos6.5 虚拟机,用 fio 分别测试其随机读性能,Windows 2008 R2 的 IOPS 大约在 18K,而 Linux 的 IOPS 却可以达到 100K 左右。
客户测试用的 fio 配置
测试结果
云主机 IO 栈
云主机环境下,整个 IO 栈相对较长,涉及到 Guest OS 中的应用层/文件系统/Block 层以及驱动层、虚拟化层、宿主机 OS 文件系统/Block 层以及驱动层。因为涉及面多,所以其中任何一个环节出现问题都会造成性能下降,也为做 IO 的 Tracing 增加了难度。
从这次得到的信息来看,首先排除了宿主机文件系统和 Block 层以及驱动层的问题,因为同样情况的配置,Linux 系统并没有问题。
所以目前主要集中于两点。
1.Guest OS(Windows 系统)
2.fio 程序
3.文件系统/Block layer
4.VirtIO Block 驱动 虚拟机为 Guest OS 提供的是 Virtio Block 设备
5.QEMU
如何排除 QEMU 的嫌疑?
对于 IOPS 的性能问题,很容易想到两种可能性:
1.IO 延时过高
2.设备支持 IO 队列太短
在队列的问题方面,Linux 和 Windows 虚拟机对应的 Virtio Block 设备都是一样的,那么就需要确认延时问题。
QEMU 完成 Block IO 花了多长时间?
幸运的是,Stefan Hajnoczi 已经为 QEMU 添加了 Tracing 的特性,因此可以很方便地统计出 QEMU 从接收到一个 IO 请求到完成所用的具体时长。
从上述统计来看,平均 IO 完成时间在 130 us,由此暂时排除 QEMU 层造成太高延时的影响。另外,如果关注这种动态 Tracing 的 overhead,从测试观察上大致接近 20%。
排除队列和延时问题,可能造成影响的也只有 Guest OS 了。
1.VirtIO Block 驱动的问题?
至少更新到最新稳定版本的 Virtio-Win 驱动,仍然存在同样的问题。
2.Windows 文件系统/Block 层的问题?
原生 Windows 系统在确认后并没有做任何配置上的修改。
fio 测试程序的问题
为什么 Linux 上 fio 没有问题呢?
两种可能性
在性能排查过程中,总是很容易陷入死局,经常会问到底是哪儿出了问题?因此一切可能影响的因素似乎都没有做任何变动。从经验来看,大部分性能问题都可以分成两种可能:
1.on CPU
2.off CPU
重新来看这个问题 ,在基本排除 IO 延时问题后,对应的问题还有两种可能性:
1.CPU 极其忙碌,但是大部分时间并不是在做 IO 处理;
2.CPU 经常处于空闲状态,那相应的也没有主要在处理 IO。
注:之所以说到目前为止并不能排除 IO 延时的影响,是因为只排除了 QEMU Block 层可能的影响,但是还有 Guest OS(这次暂时忽略 Guest OS)。
先看测试过程中,虚拟机的 CPU 消耗情况。
从上图来看,QEMU 主线程的 CPU 负载已经达到 90%以上,似乎符合 on CPU 类问题。通常来说,解决这类问题最好的办法就是用 perf 进程采样,然后生成火焰图,因为首先查看 CPU 具体消耗在什么地方是一个不错的选择。
生成火焰图:
可以清楚的看到,CPU 大部分消耗都是 KVM 的操作,其中最主要的消耗是 vmx_handle_exit。(真实的火焰图是一个矢量图,用浏览器查看很容易确认)。这里引起 vmx_handle_exit 主要有两点:
1.访问 IO Port(handle_pio)
2.访问 MMIO(handle_apic_access)
既然 KVM 模块占了大部分,那就更希望了解测试时 KVM 的真实行为,通过另一个工具(kvm_stat)可以达到。
除 VM Entry 和 VM Exit 事件外,最高的就是 kvm_pio 和 kvm_mmio,说明 Windows 确实有大量 IO Port 和 MMIO 操作,这也验证了在火焰图上所得出的结论。
在虚拟化里,IO Port 或者 MMIO 都可能引起 VM Exit,甚至是 Heavy Exit。如果需要改善性能,一般都会尽量避免这种情况,至少避免 Heavy Exit。
具体访问哪些 IO Port 和 MMIO 导致的 VM Exit?
对于这个问题,KVM 模块已经加了很多 trace event,上面的 kvm_stat 也是利用这些 trace event,只是并没有把具体 trace event 信息打印出来。为了获取 trace-event 的信息,有很多前端工具,如 trace-cmd、perf,都是不错的选择。
查看所有 kvm 模块的 trace event。
KVM 模块添加了许多 trace event 的点,这里只抓起其中两个:kvm:kvm_pio 和 kvm:kvm_mmio。
通过统计发现主要访问的:
1.IO Port 是 0x608 和 0xc050
2.MMIO 是 0xFEE003xx
经由 qemu info mtree 命令,可以查看 IO Port 608、c050 以及 FEE003xx 分别对应的具体设备。
IO Port
MMIO
c050 可以忽略,这个被 Virtio Block 来做 VM Exit。
到目前为止,可以判断出 windows 大量读取 ACPI Power Manager Timer 以及访问 APIC 寄存器,进而导致过多 vm exit 产生,消耗大量 CPU 资源,因此就可以具体讨论两个问题:
1.如何减少读取 ACPI PM Timer 寄存器而引起的 VM Exit;
2.如何减少访问 APIC MMIO 导致的 VM Exit。
如何减少读取 ACPI PM Timer 而引起的 VM Exit?
从虚拟化层优化的思路来说,减少 IO Port 引发的 VM Exit 通常会考虑是否可以利用 Paravirtulization 替换 Full-virtualization 以达到目的,来看 Windows 在这方面是如何做的。
从 Windows 7 开始,微软为了使 Windows 操作系统能够在 HyperV 得到更好性能,特意为 Windows 系统做了很多虚拟化方面的增强工作,其中就包括这里可以利用到的 HyperV Timer,这个特性类似于 Linux 中的 kvmclock。
从当前的支持情况来看:
1.Windows 7
2.Windows 7 SP1
3.Windows Server 2008 R2
4.Windows Server 2008 R2 SP1/SP2
5.Windows 8/8.1/10
6.Windows Server 2012
7.Windows Server 2012 R2
这些 Windows 系统都包含虚拟化增强功能,更多的信息在微软官方网站。
2014 年,RedHat 工程师 Vadim Rozenfeld 和 Peter Krempa 分别为 qemu 和 libvirt 添加了 HyperV Timer 的支持,所以可以直接通过 libvirt 使能 HyperV Timer。
另外,KVM 里很早也支持了 HyperV Timer,只是客户的宿主机内核版本并不支持该功能,所以需要为客户升级 UCloud 自己维护的内核版本。
如何减少 APIC ACCESS 而引起 VM Exit?
Intel CPU 也已经支持 apic-v,同样升级到 UCloud 自己维护的内核版本来解决。
最终效果
总结
从这个案例可以看出,跟物理环境相比,在虚拟化环境下,Windows IO 性能较差时,并不一定真正是 IO 路径出现问题,可能是一些虚拟化性能的问题对 IO 性能造成了很大影响。
本文转载自公众号 UCloud 技术(ID:ucloud_tech)。
原文链接:
https://mp.weixin.qq.com/s/nkfSoeV2zbgiw5wInwFPmw
活动推荐:
2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。
评论