参考RHEL7 PerformanceTuningGuide
全编时使用O0编译KVM模块
make CFLAGS_kvm_main.o='-O0 -ftree-ter'
或单独编译时使用O0编译
make CONFIG_KVM=y CONFIG_INTEL_KVM=y M=arch/x86/kvm CFLAGS_kvm_main.o='-O0 -ftree-ter'
进入文档目录
cd /path/to/qemu/docs
mkdir _build
sphinx-build . _build
生成文件
_build/index.html
在内核目录执行
cd /path/to/src/kernel/
make htmldocs
生成文件
Documentation/output/index.html
FindingOriginsOfLatenciesUsingFtrace
延时的定义
一个事件实际执行的时间点和本应该要在哪个时间点执行的时间差
内核中导致延时的四个情景
- 禁止中断(导致任务无法处理)
- 禁止抢占(阻止激活的任务无法运行)
- 调度延时
- 中断倒转(中断优先级比其中断的任务优先级低)
使用trace来测量相应的延时
- 禁止中断(irqoff) 追踪关中断时间
- 禁止抢占(preemptoff) 追踪抢占/中断任一被关闭的最大延迟时间。
- 调度延时(wakeup, wakeup_rt) 追踪普通进程从被唤醒到真正得到执行之间的延迟
- 中断倒转
查看Qemu启动后都有那些线程
./scripts/find_thread_name.sh
pid(14912) qemu-system-x86
pid(14913) call_rcu
pid(14914) trace-thread
pid(14916) IO mon_iothread
pid(14917) CPU 0/KVM
pid(14918) CPU 1/KVM
pid(14921) SPICE Worker
pid(14922) vnc_worker
isolcpus
-
isolate one or more CPUs from the scheduler with the isolcpus boot parameter. This prevents the scheduler from scheduling any user-space threads on this CPU.
-
Once a CPU is isolated, you must manually assign processes to the isolated CPU, either with the CPU affinity system calls or the numactl command
tickless and dynamic tickless kernel(CONFIG_NO_HZ_FULL)
- tickless does not interrupt idle CPUs in order to reduce power usage and allow newer processors to take advantage of deep sleep states
- dynamic tickless is useful for very latency-sensitive workloads, such as high performance computing or realtime computing
开启内核配置选项(dynamic tickless config)
CONFIG_NO_HZ_FULL
通过在启动参数中设置isolcpus来配置cpu隔离和内核tick
在/etc/default/grub中设置
GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=1,3 nohz_full=1,3"
重新生成grub.cfg
grub-mkconfig -o /boot/grub/grub.cfg
查看修改是否成功
cat /proc/cmdline
测试程序
# ./isol_cpu_test.sh
对测试结果解读
grep 'Local timer interrupts' /proc/interrupts
未配置隔离核和全时钟中断的的情况下每个核上的中断一般在1000ticks左右
配置后在隔离核上理论上只有1个ticks(实际测试平均都在10ticks以内)
x2apic优点
-
x2apic improves guest performance by reducing the overhead of APIC access, which is used to program timers and for issuing inter-processor interrupts. By exposing x2apic to guests, and by enabling the guest to utilize x2apic, we improve guest performance
-
improved guest performance and lower cpu utilization
x2APIC是硬件特性,内核提供一些参数来控制
默认开启,可以在/etc/default/grub中禁止该功能
GRUB_CMDLINE_LINUX_DEFAULT="nox2apic"
开机后查看是否由该功能
cat /proc/cpuinfo | ag x2apic
dmesg | ag x2apic
xAPIC模式
- APIC寄存器被映射到4KB大小的内存区,因此访问APIC是通过MMIO
x2APIC模式下
- 一部分MSR地址区间为APIC寄存器预留,访问APIC是通过MSR
测试方法
- measure the overhead of an IPI with and without x2apic
模拟中断和直接分配设备产生的中断示意图
Balancing Interrupts Manually
-
找出需要配置的中断
-
确认平台是否支持中断分发 check BIOS
-
确认APIC的工作模式 disable x2apic
设置smp_affinity
echo mask > /proc/irq/irq_number/smp_affinity
content of smp_affinity files can be obtained by
for i in $(seq 0 300); do grep . /proc/irq/$i/smp_affinity /dev/null 2>/dev/null; done
查看中断N绑定的cpu
cat /proc/irq/N/smp_affinity_list
irqbalance(v1.0.7)的主函数10s一个周期做以下事情
- 清除上次统计结果
- 分析中断情况
- 分析中断的负载情况
- 计算如何平衡中断
- 实施上面指定的方案
通过/proc/stat来计算中断负载(irq+softirq)
以下时cpu0的数据
数值 | 参数 | 含义 |
---|---|---|
200118431 | user | 处于用户态的运行时间,不包含 nice值为负进程 |
1258 | nice | nice值为负的进程所占用的CPU时间 |
112897097 | system | 处于核心态的运行时间 |
1062445972 | idle | 除IO等待时间以外的其它等待时间 |
321829 | iowait | IO等待时间(since 2.5.41) |
0 | irq | 硬中断时间 |
1048436 | softirq | 软中断时间 |
0 | steal | - |
0 | guest | - |
0 | guest_nice | - |
cpu->last_load = (irq_load + softirq_load)
- 每个CORE的负载是附在上面的中断的负载的总和
- 每个DOMAIN是包含的CORE的总和
- 每个PACKAGE包含的DOMAIN的总和,就像树层次一样的计算
查看cpu各cache的信息
tree -L 1 /sys/devices/system/cpu/cpu0/cache/
/sys/devices/system/cpu/cpu0/cache/
├── index0 -> L1 data缓存
├── index1 -> L1 Instruction缓存
├── index2 -> L2 缓存
└── index3 -> L3 缓存
RSS(ResidentSetSize)表示进程占用的物理内存大小, 但是将各进程的RSS值相加,通常会超出整个系统的内存消耗,这是因为RSS中包含了各进程间共享的内存
PSS(ProportionalSet size)所有使用某共享库的程序均分该共享库占用的内存时,每个进程占用的内存,显然所有进程的PSS之和就是系统的内存使用量.它会更准确一些,它将共享内存的大小进行平均后,再分摊到各进程上去.
USS(UniqueSetSize)进程独自占用的内存,它是PSS中自己的部分(PSS = USS + sharelib)
,它只计算了进程独自占用的内存大小,不包含任何共享的部分
- VSS(VirtualSetSize)虚拟耗用内存(包含共享库占用的内存)
- RSS(ResidentSetSize)实际使用物理内存(包含共享库占用的内存)
- PSS(ProportionalSetSize)实际使用的物理内存(比例分配共享库占用的内存)
- USS(UniqueSetSize)进程独自占用的物理内存(不包含共享库占用的内存)
根据rss查看内存使用情况饼图
smem --pie name -s rss
根据uss查看内存使用情况柱形图
smem --bar name -s uss
将主机A的信息采集后打包
smemcap > memorycapture.tar
在主机B上查看相应的结果
smem -S memorycapture.tar --bar name -s uss
发现使用同样的perf.data在不同机器上生成图片不一致
获取flame源码
git clone https://github.com/brendangregg/FlameGraph.git
采集qemu数据
perf record -a -g -p `pidof qemu-system-x86_64` sleep 30
用perf script工具对perf.data进行解析
perf script -i perf.data &> perf.unfold
将perf.unfold中的符号进行折叠
./stackcollapse-perf.pl perf.unfold &> perf.folded
生成svg图片
./flamegraph.pl perf.folded > perf.svg
或者将perf.data拷贝到FlameGraph目录中,执行脚本genfg.sh
编译Qemu的时候配置相关的选项
./configure --enable-trace-backends=simple
在gentoo中手动修改ebuild文件后通过localoverlay安装
手动创建一个需要trace的事件列表(可选)
echo bdrv_aio_readv > /tmp/events
echo bdrv_aio_writev >> /tmp/events
启动Qemu时设定events,trace file, monitor
qemu -trace events=/tmp/events,file=trace.bin -monitor stdio ...
使用qemu源码中的脚本分析trace file(trace.bin)
cd qemu-source/
./simpletrace.py ../trace-events /path/to/trace.bin
./simpletrace.py ../trace-events-all /path/to/trace.bin
输出格式
eventname delta_ns/1000 pid args...
事件名字 耗时(us) 进程ID 函数参数
在Qemu monitor中设置event
(qemu) trace-event xxx-event <on | off>
开关trace file功能
(qemu) trace-file <on | off>
在Qemu源码中每个目录都有一个trace-events文件 在这个文件中定义了trace中如何打印参数(对应trace.bin中的输出)
qcow2镜像大小是on demand的 qcow2的组成单元是clusters(default 64KB)
手动修改cluster size大小为128KB
qemu-img create -f qcow2 -o cluster_size=128K hd.qcow2 4G
qcow2 image通过两级表来映射给guest
一个disk image对应一个L1,且L1一直存在内存中 L2的张数则和disk image的大小有关 Qemu中为了加速对L2表的访问在内存中提供了L2的cache
L2cache大小对性能的影响
可映射到guest的磁盘大小计算公式如下
disk_size = l2_cache_size * cluster_size / 8
如果cluster_size默认64KB的话
disk_size = l2_cache_size * 8192
如果要映射nGB磁盘大小的话l2 cache大小计算公式如下
l2_cache_size = disk_size_GB * 131072
Qemu默认的L2 cache大小是1MB,所以对应的8GB的默认磁盘大小
如何配置cache size
- l2-cache-size: maximum size of the L2 table cache
- refcount-cache-size: maximum size of the refcount block cache
- cache-size: maximum size of both caches combined
配置时需要注意的两点
- L2 cache和refcount block cache都要是cluster size的倍数
- 设之三面三个中的一个参数,qemu会自动调整其他两个参数保证(L2cache 大于4倍的refcount cache)
所以下面的三条设置是等效的
-drive file=hd.qcow2,l2-cache-size=2097152
-drive file=hd.qcow2,refcount-cache-size=524288
-drive file=hd.qcow2,cache-size=2621440
减少内存使用(多快照情况下)
-drive file=hd.qcow2,cache-clean-interval=900
多个快照的情况下,可以将不需要使用的cache定时释放,减少内存使用
Tracing
# trace-cmd record -b 20000 -e kvm
run your workload, then stop trace-cmd with ctrl-C. trace-cmd will write a trace.dat file
Analyzing
trace-cmd report
or using kernelshark
开始测试
iozone -a > result.txt
绘3D图
./Generate_Graphs result.txt
现象:虚拟机卡顿,延时大,拖动窗口有残影,主机端qemu进程的cpu占用率非常高
主机上收集qemu程序的信息
perf record -p `pidof qemu-system-x86_64`
分析相关信息
perf report -i perf.data
# Overhead Command Shared Object Symbol
# ........ ............... .......................... ...................................................
#
8.41% qemu-system-x86 [kernel.kallsyms] [k] native_sched_clock
7.46% qemu-system-x86 [kernel.kallsyms] [k] trace_graph_entry
6.50% qemu-system-x86 [kernel.kallsyms] [k] trace_graph_return
5.20% qemu-system-x86 [kernel.kallsyms] [k] ring_buffer_lock_reserve
5.01% qemu-system-x86 [kernel.kallsyms] [k] __rb_reserve_next
4.38% qemu-system-x86 [kernel.kallsyms] [k] rb_commit
3.19% qemu-system-x86 [kernel.kallsyms] [k] tracing_generic_entry_update
2.94% CPU 0/KVM [kernel.kallsyms] [k] trace_graph_entry
2.90% CPU 0/KVM [kernel.kallsyms] [k] native_sched_clock
2.66% CPU 0/KVM [kernel.kallsyms] [k] trace_graph_return
2.29% qemu-system-x86 [kernel.kallsyms] [k] ftrace_push_return_trace
2.10% qemu-system-x86 [kernel.kallsyms] [k] ring_buffer_event_data
1.90% qemu-system-x86 [kernel.kallsyms] [k] return_to_handler
1.89% qemu-system-x86 [kernel.kallsyms] [k] ftrace_return_to_handler
看到结果中调用trace相关的地方非常频繁
查看后发现是因为开机了trace
cat /sys/kernel/debug/tracing/tracing_on
1
关闭后qemu进程的cpu占用率降低,虚拟机不卡顿,但是perf.data里还是有相关trace调用
echo 0 > /sys/kernel/debug/tracing/tracing_on
设置trace为nop关闭所有trace(此时的perf.data里就看不到相关的trace内容了)
echo nop > /sys/kernel/debug/tracing/current_tracer
参考文章: 记一次虚拟化环境下Windows IO性能的解析
首先使用kvm_stat确认KVM中哪些事件频繁
linux/tools/kvm/kvm_stat/kvm_stat
分析vmx_handle_exit退出原因
- 访问IO Port(handle_pio)
- 访问MMIO(handle_apic_access)
- 其他
使用trace获取相应的事件信息(得到trace.dat也可以使用kernelshark查看)
trace-cmd record -e kvm_pio -e kvm_mmio
trace-cmd report
trace.dat中有如下信息
pio_read at 0x071 size 1 count 1 val 0xc0
pio_write at 0x70 size 1 count 1 val 0xc
获取mtree信息(这里vm是domain name)
virsh qemu-monitor-command vm --hmp info mtree > mtree.txt
在mtree.txt中查找相应的IOport(可以看到是rtc)
address-space: I/O
0000000000000070-0000000000000071 (prio 0, RW): rtc
使用trace获取相应的事件信息(得到trace.dat也可以使用kernelshark查看)
trace-cmd record -e kvm_entry -e kvm_exit
trace-cmd report
使用qemu源码中的工具
cd qemu/scripts/
gdb --args qemu-system-x86_64 -enable-kvm -cpu host -smp $(nproc) -m 2048 -boot d -hda /path/to/vm.img
(gdb) source qemu-gdb.py
(gdb) help qemu
准备ramdisk(linux-0.2.img)
下载linux源码使用下面的配置编译内核
make kvmtool_defconfig
make
下载lkvm
git clone git://git.kernel.org/pub/scm/linux/kernel/git/will/kvmtool.git
make
启动虚拟机
$ ./lkvm run --disk /path/to/linux-0.2.img --kernel /path/to/arch/x86/boot/bzImage
带网络启动虚拟机
# ./lkvm run --disk /path/to/linux-0.2.img --kernel /path/to/arch/x86/boot/bzImage --network virtio