diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c --- a/arch/powerpc/kernel/asm-offsets.c +++ b/arch/powerpc/kernel/asm-offsets.c @@ -346,6 +346,11 @@ DEFINE(VCPU_LAST_INST, offsetof(struct kvm_vcpu, arch.last_inst)); DEFINE(VCPU_FAULT_DEAR, offsetof(struct kvm_vcpu, arch.fault_dear)); DEFINE(VCPU_FAULT_ESR, offsetof(struct kvm_vcpu, arch.fault_esr)); + + DEFINE(VCPU_TIMING_EXIT_TBU, offsetof(struct kvm_vcpu, arch.timing_exit.tv32.tbu)); + DEFINE(VCPU_TIMING_EXIT_TBL, offsetof(struct kvm_vcpu, arch.timing_exit.tv32.tbl)); + DEFINE(VCPU_TIMING_LAST_ENTER_TBU, offsetof(struct kvm_vcpu, arch.timing_last_enter.tv32.tbu)); + DEFINE(VCPU_TIMING_LAST_ENTER_TBL, offsetof(struct kvm_vcpu, arch.timing_last_enter.tv32.tbl)); #endif return 0; diff --git a/arch/powerpc/kvm/booke_guest.c b/arch/powerpc/kvm/booke_guest.c --- a/arch/powerpc/kvm/booke_guest.c +++ b/arch/powerpc/kvm/booke_guest.c @@ -56,6 +56,14 @@ { "halt_wakeup", VCPU_STAT(halt_wakeup) }, { NULL } }; + +u32 last_exit_type; +u32 timing_count_type[__NUMBER_OF_KVM_EXIT_TYPES]; +u64 timing_sum_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +u64 timing_sum_quad_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +u64 timing_min_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +u64 timing_max_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +u64 timing_last_exit; static const u32 interrupt_msr_mask[16] = { [BOOKE_INTERRUPT_CRITICAL] = MSR_ME, @@ -261,6 +269,9 @@ enum emulation_result er; int r = RESUME_HOST; + /* update before a new last_exit_type is written */ + update_exit_timing(&(vcpu->arch)); + local_irq_enable(); run->exit_reason = KVM_EXIT_UNKNOWN; @@ -285,10 +296,13 @@ * misses before ceding control. */ if (need_resched()) cond_resched(); - if (exit_nr == BOOKE_INTERRUPT_DECREMENTER) + if (exit_nr == BOOKE_INTERRUPT_DECREMENTER) { vcpu->stat.dec_exits++; - else + set_last_exit_type(DEC_EXITS); + } else { vcpu->stat.ext_intr_exits++; + set_last_exit_type(EXT_INTR_EXITS); + } r = RESUME_GUEST; break; @@ -299,6 +313,7 @@ vcpu->arch.esr = vcpu->arch.fault_esr; kvmppc_queue_exception(vcpu, BOOKE_INTERRUPT_PROGRAM); r = RESUME_GUEST; + set_last_exit_type(USR_PR_INST); break; } @@ -308,10 +323,12 @@ /* Future optimization: only reload non-volatiles if * they were actually modified by emulation. */ vcpu->stat.emulated_inst_exits++; + set_last_exit_type(EMULATED_INST_EXITS); r = RESUME_GUEST_NV; break; case EMULATE_DO_DCR: run->exit_reason = KVM_EXIT_DCR; + set_last_exit_type(DCR_EXITS); r = RESUME_HOST; break; case EMULATE_FAIL: @@ -331,6 +348,7 @@ case BOOKE_INTERRUPT_FP_UNAVAIL: kvmppc_queue_exception(vcpu, exit_nr); + set_last_exit_type(FP_UNAVAIL); r = RESUME_GUEST; break; @@ -339,6 +357,7 @@ vcpu->arch.esr = vcpu->arch.fault_esr; kvmppc_queue_exception(vcpu, exit_nr); vcpu->stat.dsi_exits++; + set_last_exit_type(DSI_EXITS); r = RESUME_GUEST; break; @@ -346,6 +365,7 @@ vcpu->arch.esr = vcpu->arch.fault_esr; kvmppc_queue_exception(vcpu, exit_nr); vcpu->stat.isi_exits++; + set_last_exit_type(ISI_EXITS); r = RESUME_GUEST; break; @@ -353,9 +373,11 @@ if (vcpu->arch.last_inst == KVM_HYPERCALL_BIN) { kvmppc_do_hypercall(vcpu); vcpu->stat.hcall_exits++; + set_last_exit_type(HCALL_EXITS); } else { kvmppc_queue_exception(vcpu, exit_nr); vcpu->stat.syscall_exits++; + set_last_exit_type(SYSCALL_EXITS); } r = RESUME_GUEST; break; @@ -370,6 +392,7 @@ vcpu->arch.pvmem_gpaddr >> KVM_PPCPV_MAGIC_PAGE_SHIFT, 0, KVM_PPCPV_MAGIC_PAGE_FLAGS); vcpu->stat.dtlb_pvmem_miss_exits++; + set_last_exit_type(DTLB_PVMEM_MISS_EXITS); r = RESUME_GUEST; break; } @@ -382,6 +405,7 @@ vcpu->arch.dear = vcpu->arch.fault_dear; vcpu->arch.esr = vcpu->arch.fault_esr; vcpu->stat.dtlb_real_miss_exits++; + set_last_exit_type(DTLB_REAL_MISS_EXITS); r = RESUME_GUEST; break; } @@ -399,11 +423,13 @@ kvmppc_mmu_map(vcpu, eaddr, gfn, gtlbe->tid, gtlbe->word2); vcpu->stat.dtlb_virt_miss_exits++; + set_last_exit_type(DTLB_VIRT_MISS_EXITS); r = RESUME_GUEST; } else { /* Guest has mapped and accessed a page which is not * actually RAM. */ r = kvmppc_emulate_mmio(run, vcpu); + set_last_exit_type(MMIO_EXITS); } break; @@ -422,10 +448,12 @@ /* The guest didn't have a mapping for it. */ kvmppc_queue_exception(vcpu, exit_nr); vcpu->stat.itlb_real_miss_exits++; + set_last_exit_type(ITLB_REAL_MISS_EXITS); break; } vcpu->stat.itlb_virt_miss_exits++; + set_last_exit_type(ITLB_VIRT_MISS_EXITS); gfn = tlb_xlate(gtlbe, eaddr) >> PAGE_SHIFT; @@ -458,6 +486,7 @@ mtspr(SPRN_DBSR, dbsr); run->exit_reason = KVM_EXIT_DEBUG; + set_last_exit_type(DEBUG_EXITS); r = RESUME_HOST; break; } @@ -482,6 +511,7 @@ r = (-EINTR << 2) | RESUME_HOST | (r & RESUME_FLAG_NV); vcpu->stat.signal_exits++; + set_last_exit_type(SIGNAL_EXITS); } else { vcpu->stat.light_exits++; } @@ -495,6 +525,7 @@ break; case KVM_EXIT_INTR: vcpu->stat.signal_exits++; + set_last_exit_type(SIGNAL_EXITS); break; } } @@ -535,6 +566,8 @@ * real timebase frequency. Accordingly, it must see the state of * CCR1[TCS]. */ vcpu->arch.ccr1 = mfspr(SPRN_CCR1); + + init_exit_timing(&(vcpu->arch)); return 0; } diff --git a/arch/powerpc/kvm/booke_interrupts.S b/arch/powerpc/kvm/booke_interrupts.S --- a/arch/powerpc/kvm/booke_interrupts.S +++ b/arch/powerpc/kvm/booke_interrupts.S @@ -109,6 +109,16 @@ li r6, 1 slw r6, r6, r5 + + /* save exit time */ +..exit_tbu_overflow_loop: + mfspr r7, SPRN_TBRU + mfspr r8, SPRN_TBRL + mfspr r9, SPRN_TBRU + cmpw r9, r7 + bne ..exit_tbu_overflow_loop + stw r8, VCPU_TIMING_EXIT_TBL(r4) + stw r9, VCPU_TIMING_EXIT_TBU(r4) /* Save the faulting instruction and all GPRs for emulation. */ andi. r7, r6, NEED_INST_MASK @@ -415,6 +425,17 @@ lwz r3, VCPU_SPRG7(r4) mtspr SPRN_SPRG7, r3 + /* save enter time */ + +..enter_tbu_overflow_loop: + mfspr r6, SPRN_TBRU + mfspr r7, SPRN_TBRL + mfspr r8, SPRN_TBRU + cmpw r8, r6 + bne ..enter_tbu_overflow_loop + stw r7, VCPU_TIMING_LAST_ENTER_TBL(r4) + stw r8, VCPU_TIMING_LAST_ENTER_TBU(r4) + /* Finish loading guest volatiles and jump to guest. */ lwz r3, VCPU_CTR(r4) mtctr r3 diff --git a/include/asm-powerpc/kvm_host.h b/include/asm-powerpc/kvm_host.h --- a/include/asm-powerpc/kvm_host.h +++ b/include/asm-powerpc/kvm_host.h @@ -25,6 +25,8 @@ #include #include #include +#include +#include #define KVM_MAX_VCPUS 1 #define KVM_MEMORY_SLOTS 32 @@ -66,6 +68,53 @@ u32 halt_wakeup; }; +enum kvm_exit_types { + MMIO_EXITS, + DCR_EXITS, + SIGNAL_EXITS, + ITLB_REAL_MISS_EXITS, + ITLB_VIRT_MISS_EXITS, + DTLB_REAL_MISS_EXITS, + DTLB_PVMEM_MISS_EXITS, + DTLB_VIRT_MISS_EXITS, + SYSCALL_EXITS, + HCALL_EXITS, + ISI_EXITS, + DSI_EXITS, + EMULATED_INST_EXITS, + DEC_EXITS, + EXT_INTR_EXITS, + HALT_WAKEUP, + USR_PR_INST, + FP_UNAVAIL, + DEBUG_EXITS, + TIMEINGUEST, + __NUMBER_OF_KVM_EXIT_TYPES +}; + +static const char* kvm_exit_names[__NUMBER_OF_KVM_EXIT_TYPES] = { + "MMIO", + "DCR", + "SIGNAL", + "ITLBREAL", + "ITLBVIRT", + "DTLBREAL", + "DTLBPV", + "DTLBVIRT", + "SYSCALL", + "HCALL", + "ISI", + "DSI", + "EMULINST", + "DEC", + "EXTINT", + "HALT", + "USR_PR_INST", + "FP_UNAVAIL", + "DEBUG", + "TIMEINGUEST" +}; + struct tlbe { u32 tid; /* Only the low 8 bits are used. */ u32 word0; @@ -75,6 +124,14 @@ struct kvm_arch { }; + +/* allow access to big endian 32bit upper/lower parts and 64bit var */ +typedef union { + u64 tv64; + struct { + u32 tbu, tbl; + } tv32; +} exit_timing_t; struct kvm_vcpu_arch { /* Unmodified copy of the guest's TLB. */ @@ -139,6 +196,10 @@ u32 dbcr0; u32 dbcr1; + /* to trace exit timing */ + exit_timing_t timing_exit; + exit_timing_t timing_last_enter; + u32 last_inst; u32 fault_dear; u32 fault_esr; @@ -165,4 +226,121 @@ int singlestep; }; +/* helpers for instruction timing, allocated in arch/powerpc/kvm/powerpc.c */ +extern u32 last_exit_type; +extern u32 timing_count_type[__NUMBER_OF_KVM_EXIT_TYPES]; +extern u64 timing_sum_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +extern u64 timing_sum_quad_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +extern u64 timing_min_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +extern u64 timing_max_duration[__NUMBER_OF_KVM_EXIT_TYPES]; +extern u64 timing_last_exit; + +static inline void init_exit_timing(struct kvm_vcpu_arch* vcpu_arch) +{ + int i; + last_exit_type=0xDEAD; + for (i=0; i<__NUMBER_OF_KVM_EXIT_TYPES; i++) { + timing_count_type[i]=0; + timing_max_duration[i]=0; + timing_min_duration[i]=0xFFFFFFFF; + timing_sum_duration[i]=0; + timing_sum_quad_duration[i]=0; + } + vcpu_arch->timing_exit.tv64 = 0; + timing_last_exit = 0; + vcpu_arch->timing_last_enter.tv64 = 0; + printk(KERN_ERR"### initialized exit timing (print start timestamp to log) ###\n"); +} + +static inline void set_last_exit_type(int type) +{ + last_exit_type=type; +} + +static inline void print_exit_timing(void) +{ + int i; + u64 min, max; + + printk(KERN_ERR"### start exit timing statistics ###\n"); + for (i=0; i<__NUMBER_OF_KVM_EXIT_TYPES; i++) { + if (timing_min_duration[i]==0xFFFFFFFF) + min = 0; + else + min = timing_min_duration[i]; + if (timing_max_duration[i]==0) + max = 0; + else + max = timing_max_duration[i]; + + printk(KERN_ERR"exit type %2d (%12s) count %10d min %10lld max %10lld sum %20lld sum_quad %20lld\n", + i, kvm_exit_names[i], timing_count_type[i], timing_min_duration[i], timing_max_duration[i], + timing_sum_duration[i], timing_sum_quad_duration[i]); + } + printk(KERN_ERR"please post calc: avg = sum/count; stddev = sqrt((sum_quad/count)-(sum_dur/count)������)\n"); + printk(KERN_ERR"### end exit timing statistics ###\n"); +} + +static inline void add_exit_timing(u64 duration, int type) +{ + u64 old; + + do_div(duration, tb_ticks_per_usec); + if (unlikely(duration > 0xFFFFFFFF)) { + printk(KERN_ERR"%s - duration too big, **2 would overflow duration %lld max 4294967295 type %d exit # of type %d (tb_ticks_per_usec=%ld)\n", + __func__, duration, type, timing_count_type[type], tb_ticks_per_usec); + print_exit_timing(); + } + + + timing_count_type[type]++; + + /* sum */ + old = timing_sum_duration[type]; + timing_sum_duration[type] += duration; + if (unlikely(old > timing_sum_duration[type])) { + printk(KERN_ERR"%s - wraparound adding sum of durations old %lld new %lld type %d exit # of type %d\n", + __func__, old, timing_sum_duration[type], type, timing_count_type[type]); + print_exit_timing(); + } + + /* square sum */ + old = timing_sum_quad_duration[type]; + timing_sum_quad_duration[type] += (duration*duration); + if (unlikely(old > timing_sum_quad_duration[type])) { + printk(KERN_ERR"%s - wraparound adding sum of squared durations old %lld new %lld type %d exit # of type %d\n", + __func__, old, timing_sum_quad_duration[type], type, timing_count_type[type]); + print_exit_timing(); + } + + /* set min/max */ + if (unlikely(duration < timing_min_duration[type])) + timing_min_duration[type] = duration; + if (unlikely(duration > timing_max_duration[type])) + timing_max_duration[type] = duration; +} + +static inline void update_exit_timing(struct kvm_vcpu_arch* vcpu_arch) +{ + u64 exit = timing_last_exit; + u64 enter = vcpu_arch->timing_last_enter.tv64; + + /* save exit time to use it next exit when the related enter time is known */ + timing_last_exit = vcpu_arch->timing_exit.tv64; + + if (unlikely(last_exit_type==0xDEAD)) + return; /* skip first incomplete enter/exit cycle */ + + /* update statistics for average and standard deviation */ + add_exit_timing((enter - exit), last_exit_type); + /* enter -> timing_last_exit is time spent in guest - log this too */ + add_exit_timing((timing_last_exit - enter), TIMEINGUEST); + + /* report regularly every 100000 exits*/ + if (unlikely(timing_count_type[TIMEINGUEST] % 1000000 == 0)) { + printk(KERN_ERR"%s - %d exits report timing statistics\n", __func__,timing_count_type[TIMEINGUEST]); + print_exit_timing(); + } +} + #endif /* __POWERPC_KVM_HOST_H__ */