- 背景
一般我们通过 kvm guest 中的%steal 指标来看 CPU 的争抢。但是目前在Host上并没有相关的手段或者指标来监控Guest 是否发生了争抢。
本文主要介绍如何在Host 判断争抢的发生,及 steal time的产生的内核代码分析。
- CPU Steal Time
Sar manual中对 %steal 的描述如下。
Steal time is the percentage of time a virtual CPU waits for a real CPU while the hypervisor is servicing another virtual processor.
当kvm/xen这些hypervisor进行VCPU之间调度时,VCPU用于等待PCPU的时间所占的百分比,就是CPU Steal Time。 VCPU 本质上是一个线程,所以可以认为是线程的调度延迟导致的。
Steal time是一个PV(ParaVirtual)实现,所以编译内核需要CONFIG_PARAVIRT=y 选项打开。
下面针对Centos_6.3的内核源码进行分析。
-
- 测试
环境:
Kernel version | HT | CPUs | Mem | |
Host | 3.10.0-693.25.4.el7.x86_64 | ON | 24 | 125G |
Gues | 2.6.32-279.19.1.el6_sn.62.x86_64 | / | 8 | 16G |
Guest Benchmark
# lookbusy -m 12GB -d 20GB -b 8KB -f /opt/test.data
Guest 各个vcpu 加50% 负荷, 内存占用12GB,同时以100ms间隔8KB写文件 。
Pattern 1:
# cat /proc/sys/kernel/sched_domain/cpu*/domain*/flags
687
4655
25647
# cat /proc/sys/kernel/sched_domain/cpu*/domain*/flags
4143
No. | 负荷Guest数 | vCPUs | Host(avg) | Guest(avg) | |||||||
%usr | %sys | %guest | %idle | %usr | %sys | %idle | %steal | ||||
1 | 4 | 32 | 0.02 | 5.03 | 67.69 | 27.25 | 49.91 | 0.31 | 46.63 | 3.13 | |
2 | 5 | 40 | 0.01 | 6.02 | 83.73 | 10.23 | 49.07 | 0.23 | 40.67 | 10.01 | |
- Steal time 的计算
在kvm_vcpu_arch 结构体中有steal的一个结构体。
Kvm_steal_time结构体如下:
下面主要依次介绍下面三个函数。
No. | 函数名 | 作用 |
1 | kvm_register_steal_time | 用于注册各个vcpu steal time 到虚拟寄存器MSR_KVM_STEAL_TIME |
2 | accumulate_steal_time | 用于累加steal time |
3 | record_steal_time | 用于记录 steal time |
-
- kvm_register_steal_time (注册steal time)
kvm内核代码调用路径如下
setup_arch -> kvmclock_init -> kvm_register_steal_time -> kvm_guest_init
其中 kvmclock_init 将kvm_steal clock注册进steal_clock
时steal_time的内存地址写入到MSR_KVM_STEAL_TIME中,kvm中初始化部分完成。
-
- accumulate_steal_time(计算函数)
在kvm_arch_vcpu_load 和 kvm_set_msr_common 两个函数中都会调用accumulate_steal_time。即,在VM_Entry/VM_Exit时都会计算steal time。
- kvm_arch_vcpu_load 会调用kvm_guest_enter函数,所以可以认为是VM_Entry时的调用。
kvm_vcpu_ioctl->kvm_arch_vcpu_ioctl_run-> vcpu_load-> kvm_arch_vcpu_load->vm_x86_ops->vcpu_load(vcpu, cpu)-> accumulate_steal_time
- kvm_set_msr_common 是处理寄存器VM_Exit的通用函数。
[EXIT_REASON_MSR_WRITE]= handle_wrmsr-> vmx_set_msr-> kvm_set_msr_common
accum_steal的计算代码非常简单,就是 run_delay 的delta。代码中run_delay 的解释是 “time spent waiting on a runqueue”花在等待运行队列的时间。也就是在vcpu 线程调度等待消耗的时间。有4个地方会修改run_delay 的值。
-
- run_delay 的来源
- run_delay 的来源之一是由于interactive tasks 可能会直接重新给运行队列排队(通过nice值等),会造成原本从expired 队列进入空闲的Active 队列的线程产生延迟。
dequeue_task -> sched_info_dequeued -> rq_sched_info_dequeued
- run_delay 的另一个来源离开CPU后等待CPU运行task的时间。
schedule -> __sched_info_switch -> sched_info_arrive -> rq_sched_info_arrive
Last_queued 是在sched_info_queued 中计算的,也就是离开CPU的时间。调用路径是,
schedule -> __sched_info_switch -> sched_info_depart -> sched_info_queued
-
- record_steal_time(记录函数)
说完accumulate_steal_time,我们再来说record_steal_time。调用路径如下。kvm_vcpu_ioctl->kvm_arch_vcpu_ioctl_run-> __vcpu_run-> vcpu_enter_guest->record_steal_time-> kvm_guest_enter
在kvm_arch_vcpu_ioctl_run中运行完vcpu_load 再执行__vcpu_run 。所以先计算accum_steal 。
注意 Centos7.3 3.10.9-693 中accumulate_steal_time 和 record_steal_time 合并成了一个函数record_steal_time 。
steal_account_process_tick 则是用来计算 /proc/stat 中的steal 。
- Host 监控指标
知道了steal time 本质上就是来源于cpu 调度系统的task 的run_delay 。
那么我们只要去监控KVM 中 VCPU 对应的线程号的 run_delay 即可。
/proc/PID/schedstat 中的第二个参数正好是run_delay 。
/proc/PID/schedstat中有3个值
NO. | Value | 描述 |
| sum_exec_runtime |
|
| run_delay |
|
| pcount |
|
具体监控某个VCPU 的 steal time 可以通过latency.c 计算。
# gcc –o lat tatency.c
# ./lat –s 1 –c 5 $vcpupid
- ref