mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-17 05:17:45 +07:00
5e25f5db6a
After guest live migration on xen, steal time in /proc/stat (cpustat[CPUTIME_STEAL]) might decrease because steal returned by xen_steal_lock() might be less than this_rq()->prev_steal_time which is derived from previous return value of xen_steal_clock(). For instance, steal time of each vcpu is 335 before live migration. cpu 198 0 368 200064 1962 0 0 1340 0 0 cpu0 38 0 81 50063 492 0 0 335 0 0 cpu1 65 0 97 49763 634 0 0 335 0 0 cpu2 38 0 81 50098 462 0 0 335 0 0 cpu3 56 0 107 50138 374 0 0 335 0 0 After live migration, steal time is reduced to 312. cpu 200 0 370 200330 1971 0 0 1248 0 0 cpu0 38 0 82 50123 500 0 0 312 0 0 cpu1 65 0 97 49832 634 0 0 312 0 0 cpu2 39 0 82 50167 462 0 0 312 0 0 cpu3 56 0 107 50207 374 0 0 312 0 0 Since runstate times are cumulative and cleared during xen live migration by xen hypervisor, the idea of this patch is to accumulate runstate times to global percpu variables before live migration suspend. Once guest VM is resumed, xen_get_runstate_snapshot_cpu() would always return the sum of new runstate times and previously accumulated times stored in global percpu variables. Comment above HYPERVISOR_suspend() has been removed as it is inaccurate: the call can return an error code (e.g., possibly -EPERM in the future). Similar and more severe issue would impact prior linux 4.8-4.10 as discussed by Michael Las at https://0xstubs.org/debugging-a-flaky-cpu-steal-time-counter-on-a-paravirtualized-xen-guest, which would overflow steal time and lead to 100% st usage in top command for linux 4.8-4.10. A backport of this patch would fix that issue. [boris: added linux/slab.h to driver/xen/time.c, slightly reformatted commit message] References: https://0xstubs.org/debugging-a-flaky-cpu-steal-time-counter-on-a-paravirtualized-xen-guest Signed-off-by: Dongli Zhang <dongli.zhang@oracle.com> Reviewed-by: Boris Ostrovsky <boris.ostrovsky@oracle.com> Signed-off-by: Boris Ostrovsky <boris.ostrovsky@oracle.com>
183 lines
4.1 KiB
C
183 lines
4.1 KiB
C
/*
|
|
* Xen stolen ticks accounting.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/kernel_stat.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <asm/paravirt.h>
|
|
#include <asm/xen/hypervisor.h>
|
|
#include <asm/xen/hypercall.h>
|
|
|
|
#include <xen/events.h>
|
|
#include <xen/features.h>
|
|
#include <xen/interface/xen.h>
|
|
#include <xen/interface/vcpu.h>
|
|
#include <xen/xen-ops.h>
|
|
|
|
/* runstate info updated by Xen */
|
|
static DEFINE_PER_CPU(struct vcpu_runstate_info, xen_runstate);
|
|
|
|
static DEFINE_PER_CPU(u64[4], old_runstate_time);
|
|
|
|
/* return an consistent snapshot of 64-bit time/counter value */
|
|
static u64 get64(const u64 *p)
|
|
{
|
|
u64 ret;
|
|
|
|
if (BITS_PER_LONG < 64) {
|
|
u32 *p32 = (u32 *)p;
|
|
u32 h, l, h2;
|
|
|
|
/*
|
|
* Read high then low, and then make sure high is
|
|
* still the same; this will only loop if low wraps
|
|
* and carries into high.
|
|
* XXX some clean way to make this endian-proof?
|
|
*/
|
|
do {
|
|
h = READ_ONCE(p32[1]);
|
|
l = READ_ONCE(p32[0]);
|
|
h2 = READ_ONCE(p32[1]);
|
|
} while(h2 != h);
|
|
|
|
ret = (((u64)h) << 32) | l;
|
|
} else
|
|
ret = READ_ONCE(*p);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void xen_get_runstate_snapshot_cpu_delta(
|
|
struct vcpu_runstate_info *res, unsigned int cpu)
|
|
{
|
|
u64 state_time;
|
|
struct vcpu_runstate_info *state;
|
|
|
|
BUG_ON(preemptible());
|
|
|
|
state = per_cpu_ptr(&xen_runstate, cpu);
|
|
|
|
do {
|
|
state_time = get64(&state->state_entry_time);
|
|
rmb(); /* Hypervisor might update data. */
|
|
*res = READ_ONCE(*state);
|
|
rmb(); /* Hypervisor might update data. */
|
|
} while (get64(&state->state_entry_time) != state_time ||
|
|
(state_time & XEN_RUNSTATE_UPDATE));
|
|
}
|
|
|
|
static void xen_get_runstate_snapshot_cpu(struct vcpu_runstate_info *res,
|
|
unsigned int cpu)
|
|
{
|
|
int i;
|
|
|
|
xen_get_runstate_snapshot_cpu_delta(res, cpu);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
res->time[i] += per_cpu(old_runstate_time, cpu)[i];
|
|
}
|
|
|
|
void xen_manage_runstate_time(int action)
|
|
{
|
|
static struct vcpu_runstate_info *runstate_delta;
|
|
struct vcpu_runstate_info state;
|
|
int cpu, i;
|
|
|
|
switch (action) {
|
|
case -1: /* backup runstate time before suspend */
|
|
if (unlikely(runstate_delta))
|
|
pr_warn_once("%s: memory leak as runstate_delta is not NULL\n",
|
|
__func__);
|
|
|
|
runstate_delta = kmalloc_array(num_possible_cpus(),
|
|
sizeof(*runstate_delta),
|
|
GFP_ATOMIC);
|
|
if (unlikely(!runstate_delta)) {
|
|
pr_warn("%s: failed to allocate runstate_delta\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
xen_get_runstate_snapshot_cpu_delta(&state, cpu);
|
|
memcpy(runstate_delta[cpu].time, state.time,
|
|
sizeof(runstate_delta[cpu].time));
|
|
}
|
|
|
|
break;
|
|
|
|
case 0: /* backup runstate time after resume */
|
|
if (unlikely(!runstate_delta)) {
|
|
pr_warn("%s: cannot accumulate runstate time as runstate_delta is NULL\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
for (i = 0; i < 4; i++)
|
|
per_cpu(old_runstate_time, cpu)[i] +=
|
|
runstate_delta[cpu].time[i];
|
|
}
|
|
|
|
break;
|
|
|
|
default: /* do not accumulate runstate time for checkpointing */
|
|
break;
|
|
}
|
|
|
|
if (action != -1 && runstate_delta) {
|
|
kfree(runstate_delta);
|
|
runstate_delta = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Runstate accounting
|
|
*/
|
|
void xen_get_runstate_snapshot(struct vcpu_runstate_info *res)
|
|
{
|
|
xen_get_runstate_snapshot_cpu(res, smp_processor_id());
|
|
}
|
|
|
|
/* return true when a vcpu could run but has no real cpu to run on */
|
|
bool xen_vcpu_stolen(int vcpu)
|
|
{
|
|
return per_cpu(xen_runstate, vcpu).state == RUNSTATE_runnable;
|
|
}
|
|
|
|
u64 xen_steal_clock(int cpu)
|
|
{
|
|
struct vcpu_runstate_info state;
|
|
|
|
xen_get_runstate_snapshot_cpu(&state, cpu);
|
|
return state.time[RUNSTATE_runnable] + state.time[RUNSTATE_offline];
|
|
}
|
|
|
|
void xen_setup_runstate_info(int cpu)
|
|
{
|
|
struct vcpu_register_runstate_memory_area area;
|
|
|
|
area.addr.v = &per_cpu(xen_runstate, cpu);
|
|
|
|
if (HYPERVISOR_vcpu_op(VCPUOP_register_runstate_memory_area,
|
|
xen_vcpu_nr(cpu), &area))
|
|
BUG();
|
|
}
|
|
|
|
void __init xen_time_setup_guest(void)
|
|
{
|
|
bool xen_runstate_remote;
|
|
|
|
xen_runstate_remote = !HYPERVISOR_vm_assist(VMASST_CMD_enable,
|
|
VMASST_TYPE_runstate_update_flag);
|
|
|
|
pv_time_ops.steal_clock = xen_steal_clock;
|
|
|
|
static_key_slow_inc(¶virt_steal_enabled);
|
|
if (xen_runstate_remote)
|
|
static_key_slow_inc(¶virt_steal_rq_enabled);
|
|
}
|