KVM: perf: kvm events analysis tool
Subsystems (5)
v4 cover KVM: perf: kvm events analysis tool
Changlog:
There are some changes from David Ahern's review:
- let the tool to be off-box analysis by getting cpu isa from HEADER_CPUID feature and removing max-vcpu related code.
- attach per vcpu record structure to the thread by adding a void pointer in "struct thread".
- remove unnecessary tool argument(-v).
The output example is following:
# ./perf kvm-events report --event mmio --vcpu 3 Warning: Error: expected type 5 but read 4 Warning: Error: expected type 5 but read 0 Warning: unknown op '}'
Analyze events for VCPU 3:
MMIO Access Samples Samples% Time% Avg time
0xfee00380:W 45534 78.00% 84.76% 5.27us ( +- 0.77% )
0xfee00300:W 4280 7.33% 9.37% 6.20us ( +- 1.91% )
0xfee00300:R 4280 7.33% 3.34% 2.21us ( +- 1.53% )
0xfee00310:W 4280 7.33% 2.52% 1.67us ( +- 0.71% )Total Samples:58374, Total events handled time:283257.68us.
PATCH v4 1/3 KVM: x86: export svm/vmx exit code and vector code to userspace
They will be needed by 'perf kvm-events'
--- arch/x86/include/asm/kvm_host.h | 36 ++++--- arch/x86/include/asm/svm.h | 205 +++++++++++++++++++++++++-------------- arch/x86/include/asm/vmx.h | 125 ++++++++++++++++-------- arch/x86/kvm/trace.h | 89 ----------------- 4 files changed, 233 insertions(+), 222 deletions(-)
arch/x86/include/asm/kvm_host.h +20 -16
@@ -11,6 +11,24 @@#ifndef _ASM_X86_KVM_HOST_H#define _ASM_X86_KVM_HOST_H +#define DE_VECTOR 0 +#define DB_VECTOR 1 +#define BP_VECTOR 3 +#define OF_VECTOR 4 +#define BR_VECTOR 5 +#define UD_VECTOR 6 +#define NM_VECTOR 7 +#define DF_VECTOR 8 +#define TS_VECTOR 10 +#define NP_VECTOR 11 +#define SS_VECTOR 12 +#define GP_VECTOR 13 +#define PF_VECTOR 14 +#define MF_VECTOR 16 +#define MC_VECTOR 18 + +#ifdef __KERNEL__ +#include <linux/types.h> #include <linux/mm.h> #include <linux/mmu_notifier.h>
@@ -73,22 +91,6 @@#define KVM_HPAGE_MASK (x) (~(KVM_HPAGE_SIZE(x) - 1))#define KVM_PAGES_PER_HPAGE (x) (KVM_HPAGE_SIZE(x) / PAGE_SIZE) -#define DE_VECTOR 0 -#define DB_VECTOR 1 -#define BP_VECTOR 3 -#define OF_VECTOR 4 -#define BR_VECTOR 5 -#define UD_VECTOR 6 -#define NM_VECTOR 7 -#define DF_VECTOR 8 -#define TS_VECTOR 10 -#define NP_VECTOR 11 -#define SS_VECTOR 12 -#define GP_VECTOR 13 -#define PF_VECTOR 14 -#define MF_VECTOR 16 -#define MC_VECTOR 18 -#define SELECTOR_TI_MASK (1 << 2)#define SELECTOR_RPL_MASK 0x03
@@ -939,4 +941,6 @@ int kvm_pmu_read_pmc(struct kvm_vcpu *vcpu, unsigned pmc, u64 *data);void kvm_handle_pmu_event (struct kvm_vcpu *vcpu);void kvm_deliver_pmi (struct kvm_vcpu *vcpu); +#endif +#endif /* _ASM_X86_KVM_HOST_H */
arch/x86/include/asm/svm.h +130 -75
220 lines not on this page · raw message
arch/x86/include/asm/vmx.h +83 -42
140 lines not on this page · raw message
arch/x86/kvm/trace.h +0 -89
95 lines not on this page · raw message
PATCH v4 2/3 KVM: x86: add tracepoints to trace mmio begin and complete
In reply to Xiao Guangrong'perf kvm-events' will use kvm_exit and kvm_mmio(read...) to calculate mmio read emulated time for the old kernel, in order to trace mmio read event more exactly, we add kvm_mmio_begin to trace the time when mmio read begins
Also, add kvm_mmio_done to trace the time when mmio/pio is completed
--- arch/x86/kvm/x86.c | 21 ++++++++++++++------- include/trace/events/kvm.h | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-)
arch/x86/kvm/x86.c +14 -7
@@ -3766,9 +3766,12 @@ mmio:/* * Is this MMIO handled locally? */ +trace_kvm_mmio_begin (vcpu->vcpu_id, write, gpa); handled = ops->read_write_mmio (vcpu, gpa, bytes, val); -if (handled == bytes) +if (handled == bytes) { +trace_kvm_mmio_done (vcpu->vcpu_id);return X86EMUL_CONTINUE ; + } gpa += handled; bytes -= handled;
@@ -3934,6 +3937,7 @@ static int emulator_pio_in_out(struct kvm_vcpu *vcpu, int size, vcpu->arch.pio.size = size;if (!kernel_pio (vcpu, vcpu->arch.pio_data)) { +trace_kvm_mmio_done (vcpu->vcpu_id); vcpu->arch.pio.count =0 ;return 1 ; }
@@ -4544,9 +4548,7 @@ restart:inject_emulated_exception (vcpu); r =EMULATE_DONE ; }else if (vcpu->arch.pio.count) { -if (!vcpu->arch.pio.in) - vcpu->arch.pio.count =0 ; -else +if (vcpu->arch.pio.in) writeback = false; r =EMULATE_DO_MMIO ; }else if (vcpu->mmio_needed) {
@@ -4577,8 +4579,6 @@ int kvm_fast_pio_out(struct kvm_vcpu *vcpu, int size, unsigned short port)unsigned long val =kvm_register_read (vcpu,VCPU_REGS_RAX );int ret =emulator_pio_out_emulated (&vcpu->arch.emulate_ctxt, size, port, &val,1 ); -/* do not return to emulator after return from userspace */ - vcpu->arch.pio.count =0 ;return ret; }EXPORT_SYMBOL_GPL (kvm_fast_pio_out);
@@ -5407,6 +5407,11 @@ static int complete_mmio(struct kvm_vcpu *vcpu)if (!(vcpu->arch.pio.count || vcpu->mmio_needed))return 1 ; +if (vcpu->arch.pio.count && !vcpu->arch.pio.in) { + vcpu->arch.pio.count =0 ; + goto exit; + } +if (vcpu->mmio_needed) { vcpu->mmio_needed =0 ;if (!vcpu->mmio_is_write)
@@ -5423,7 +5428,7 @@ static int complete_mmio(struct kvm_vcpu *vcpu)return 0 ; }if (vcpu->mmio_is_write) -return 1 ; + goto exit; vcpu->mmio_read_completed =1 ; } vcpu->srcu_idx =srcu_read_lock (&vcpu->kvm->srcu);
@@ -5431,6 +5436,8 @@ static int complete_mmio(struct kvm_vcpu *vcpu)srcu_read_unlock (&vcpu->kvm->srcu, vcpu->srcu_idx);if (r !=EMULATE_DONE )return 0 ; +exit: +trace_kvm_mmio_done (vcpu->vcpu_id);return 1 ; }
include/trace/events/kvm.h +37 -0
@@ -174,6 +174,43 @@ TRACE_EVENT(kvm_mmio, __entry->len, __entry->gpa, __entry->val) ); +TRACE_EVENT (kvm_mmio_begin, + TP_PROTO(unsigned int vcpu_id, bool rw, u64 gpa), +TP_ARGS (vcpu_id, rw, gpa), + +TP_STRUCT__entry ( + __field(unsigned int, vcpu_id) + __field(int, type) +__field (u64, gpa) + ), + +TP_fast_assign ( + __entry->vcpu_id = vcpu_id; + __entry->type = rw ? KVM_TRACE_MMIO_WRITE : + KVM_TRACE_MMIO_READ; + __entry->gpa = gpa; + ), + +TP_printk ("vcpu %u mmio %s gpa 0x%llx" , __entry->vcpu_id, +__print_symbolic (__entry->type, kvm_trace_symbol_mmio), + __entry->gpa) +); + +TRACE_EVENT (kvm_mmio_done, + TP_PROTO(unsigned int vcpu_id), +TP_ARGS (vcpu_id), + +TP_STRUCT__entry ( + __field( unsigned int, vcpu_id ) + ), + +TP_fast_assign ( + __entry->vcpu_id = vcpu_id; + ), + +TP_printk ("vcpu %u" , __entry->vcpu_id) +); +#define kvm_fpu_load_symbol \ {0, "unload"}, \ {1, "load"}
Signature · 1 line
1.7.7.6
PATCH v4 3/3 KVM: perf: kvm events analysis tool
In reply to Xiao GuangrongAdd 'perf kvm-events' support to analyze kvm vmexit/mmio/ioport smartly
Usage:
- trace kvm events:
perf kvm-events record, or, if other tracepoints are also interesting, we can append the events like this:
perf kvm-events record -e timer:*
- show the result:
perf kvm-events report
--- tools/perf/Documentation/perf-kvm-events.txt | 52 ++ tools/perf/Makefile | 1 + tools/perf/builtin-kvm-events.c | 851 ++++++++++++++++++++++++++ tools/perf/builtin.h | 1 + tools/perf/command-list.txt | 1 + tools/perf/perf.c | 1 + tools/perf/util/header.c | 54 ++- tools/perf/util/header.h | 1 + tools/perf/util/thread.h | 2 + 9 files changed, 963 insertions(+), 1 deletions(-) create mode 100644 tools/perf/Documentation/perf-kvm-events.txt create mode 100644 tools/perf/builtin-kvm-events.c
tools/perf/Documentation/perf-kvm-events.txt new file +52 -0
@@ -0,0 +1,52 @@ +perf-kvm-events(1) +============ + +NAME +---- +perf-kvm-events - Analyze kvm events + +SYNOPSIS +-------- +[verse] +'perf kvm-events' {record|report} + +DESCRIPTION +----------- +You can analyze some crucial kvm events and statistics with this +'perf kvm-events' command. Currently, vmexit, mmio and ioport events +are supported. + + 'perf kvm-events record <command>' records kvm events(vmexit, + mmio and ioport) and the events between start and end <command>. + And this command produces the file "perf.data" which contains + tracing results of kvm events. + + 'perf kvm-events report' reports statistical data which includes + events handled time, samples, and so on. + +COMMON OPTIONS +-------------- + +-i:: +--input=<file>:: + Input file name. (default: perf.data unless stdin is a fifo) + +-D:: +--dump-raw-trace:: + Dump raw trace in ASCII. + +REPORT OPTIONS +-------------- +--vcpu=<value>:: + analyze events which occures on this vcpu + +--events=<value>:: + events to be analyzed. Possible values: vmexit, mmio, ioport. +-k:: +--key=<value>:: + Sorting key. Possible values: sample(default, sort by samples number), +time(sort by average time). + +SEE ALSO +-------- +linkperf:perf[1]
tools/perf/Makefile +1 -0
@@ -382,6 +382,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-probe.o BUILTIN_OBJS += $(OUTPUT)builtin-kmem.o BUILTIN_OBJS += $(OUTPUT)builtin-lock.o BUILTIN_OBJS += $(OUTPUT)builtin-kvm.o +BUILTIN_OBJS += $(OUTPUT)builtin-kvm-events.o BUILTIN_OBJS += $(OUTPUT)builtin-test.o BUILTIN_OBJS += $(OUTPUT)builtin-inject.o
tools/perf/builtin-kvm-events.c new file +851 -0
851 lines not on this page · raw message
tools/perf/builtin.h +1 -0
@@ -33,6 +33,7 @@ extern int cmd_probe(int argc, const char **argv, const char *prefix);extern int cmd_kmem (int argc,const char **argv,const char *prefix);extern int cmd_lock (int argc,const char **argv,const char *prefix);extern int cmd_kvm (int argc,const char **argv,const char *prefix); +extern int cmd_kvm_events (int argc,const char **argv,const char *prefix);extern int cmd_test (int argc,const char **argv,const char *prefix);extern int cmd_inject (int argc,const char **argv,const char *prefix);
tools/perf/command-list.txt +1 -0
@@ -22,4 +22,5 @@ perf-probe mainporcelain common perf-kmem mainporcelain common perf-lock mainporcelain common perf-kvm mainporcelain common +perf-kvm-events mainporcelain common perf-test mainporcelain common
tools/perf/perf.c +1 -0
@@ -317,6 +317,7 @@ static void handle_internal_command(int argc, const char **argv) { "kmem", cmd_kmem, 0 }, { "lock", cmd_lock, 0 }, { "kvm", cmd_kvm, 0 }, + { "kvm-events", cmd_kvm_events, 0}, { "test", cmd_test, 0 }, { "inject", cmd_inject, 0 }, };
tools/perf/util/header.c +53 -1
81 lines not on this page · raw message
tools/perf/util/header.h +1 -0
@@ -92,6 +92,7 @@ int perf_header__process_sections(struct perf_header *header, int fd, int feat, int fd, void *data));int perf_header__fprintf_info (struct perf_session *s,FILE *fp,bool full); +char *perf_header__read_feature (struct perf_session *session,int feat);int build_id_cache__add_s (const char *sbuild_id,const char *debugdir,const char *name,bool is_kallsyms);
tools/perf/util/thread.h +2 -0
@@ -16,6 +16,8 @@ struct thread {bool comm_set;char *comm;int comm_len; + +void *private; };struct machine ;
Compile fails on Fedora 16, x86_64 with latest tip-perf-core branch:
In file included from builtin-kvm-events.c:19:0:../../arch/x86/include/asm/svm.h:133:1: error: packed attribute is unnecessary for ‘vmcb_seg’ [-Werror=packed] ../../arch/x86/include/asm/svm.h:178:1: error: packed attribute is unnecessary for ‘vmcb_save_area’ [-Werror=packed] ../../arch/x86/include/asm/svm.h:183:1: error: packed attribute is unnecessary for ‘vmcb’ [-Werror=packed]
In file included from builtin-kvm-events.c:20:0:../../arch/x86/include/asm/vmx.h:334:0: error: "REG_R8" redefined [-Werror] /usr/include/sys/ucontext.h:46:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:335:0: error: "REG_R9" redefined [-Werror] /usr/include/sys/ucontext.h:48:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:336:0: error: "REG_R10" redefined [-Werror] /usr/include/sys/ucontext.h:50:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:337:0: error: "REG_R11" redefined [-Werror] /usr/include/sys/ucontext.h:52:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:338:0: error: "REG_R12" redefined [-Werror] /usr/include/sys/ucontext.h:54:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:339:0: error: "REG_R13" redefined [-Werror] /usr/include/sys/ucontext.h:56:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:340:0: error: "REG_R14" redefined [-Werror] /usr/include/sys/ucontext.h:58:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:341:0: error: "REG_R15" redefined [-Werror] /usr/include/sys/ucontext.h:60:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:443:13: error: expected declaration specifiers or ‘...’ before numeric constant
In file included from builtin-kvm-events.c:21:0:../../arch/x86/include/asm/kvm_host.h:15:22: fatal error: linux/mm.h: No such file or directory
cc1: all warnings being treated as errors
Quoted 1,103 lines · Xiao Guangrong
Add 'perf kvm-events' support to analyze kvm vmexit/mmio/ioport smartly
Usage:
- trace kvm events:
perf kvm-events record, or, if other tracepoints are also interesting, we can append the events like this:
perf kvm-events record -e timer:*- show the result:
perf kvm-events reportSigned-off-by Xiao Guangrong <[email protected]>--- tools/perf/Documentation/perf-kvm-events.txt | 52 ++ tools/perf/Makefile | 1 + tools/perf/builtin-kvm-events.c | 851 ++++++++++++++++++++++++++ tools/perf/builtin.h | 1 + tools/perf/command-list.txt | 1 + tools/perf/perf.c | 1 + tools/perf/util/header.c | 54 ++- tools/perf/util/header.h | 1 + tools/perf/util/thread.h | 2 + 9 files changed, 963 insertions(+), 1 deletions(-) create mode 100644 tools/perf/Documentation/perf-kvm-events.txt create mode 100644 tools/perf/builtin-kvm-events.ctools/perf/Documentation/perf-kvm-events.txt new file +52
-0@@ -0,0 +1,52 @@ +perf-kvm-events(1) +============ + +NAME +---- +perf-kvm-events - Analyze kvm events + +SYNOPSIS +-------- +[verse] +'perf kvm-events' {record|report} + +DESCRIPTION +----------- +You can analyze some crucial kvm events and statistics with this +'perf kvm-events' command. Currently, vmexit, mmio and ioport events +are supported. + + 'perf kvm-events record <command>' records kvm events(vmexit, + mmio and ioport) and the events between start and end <command>. + And this command produces the file "perf.data" which contains + tracing results of kvm events. + + 'perf kvm-events report' reports statistical data which includes + events handled time, samples, and so on. + +COMMON OPTIONS +-------------- + +-i:: +--input=<file>:: + Input file name. (default: perf.data unless stdin is a fifo) + +-D:: +--dump-raw-trace:: + Dump raw trace in ASCII. + +REPORT OPTIONS +-------------- +--vcpu=<value>:: + analyze events which occures on this vcpu + +--events=<value>:: + events to be analyzed. Possible values: vmexit, mmio, ioport. +-k:: +--key=<value>:: + Sorting key. Possible values: sample(default, sort by samples number), +time(sort by average time). + +SEE ALSO +-------- +linkperf:perf[1]tools/perf/Makefile +1
-0@@ -382,6 +382,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-probe.o BUILTIN_OBJS += $(OUTPUT)builtin-kmem.o BUILTIN_OBJS += $(OUTPUT)builtin-lock.o BUILTIN_OBJS += $(OUTPUT)builtin-kvm.o +BUILTIN_OBJS += $(OUTPUT)builtin-kvm-events.o BUILTIN_OBJS += $(OUTPUT)builtin-test.o BUILTIN_OBJS += $(OUTPUT)builtin-inject.otools/perf/builtin-kvm-events.c new file +851
-0@@ -0,0 +1,851 @@ +#include "builtin.h" +#include "perf.h" +#include "util/util.h" +#include "util/cache.h" +#include "util/symbol.h" +#include "util/thread.h" +#include "util/header.h" +#include "util/parse-options.h" +#include "util/trace-event.h" +#include "util/debug.h" +#include "util/debugfs.h" +#include "util/session.h" +#include "util/tool.h" + +#include <math.h> + +#include <linux/kvm.h> + +#include "../../arch/x86/include/asm/svm.h" +#include "../../arch/x86/include/asm/vmx.h" +#include "../../arch/x86/include/asm/kvm_host.h" + +struct event_key { +#define INVALID_KEY (~0ULL) +u64 key; +int info; +}; + +struct kvm_events_ops { +bool (*is_begin_event)(struct event *event,void *data, +struct event_key *key); +bool (*is_end_event)(struct event *event,void *data, +struct event_key *key); +void (*decode_key)(struct event_key *key,char decode[20 ]); +const char *name; +}; + +static int cpu_isa; + +static void exit_event_get_key (struct event *event,void *data, +struct event_key *key) +{ + key->info = cpu_isa; + key->key =raw_field_value (event,"exit_reason" , data); +} + +static bool kvm_exit_event (struct event *event) +{ +return !strcmp (event->name,"kvm_exit" ); +} + +static bool exit_event_begin (struct event *event,void *data, +struct event_key *key) +{ +if (kvm_exit_event (event)) { +exit_event_get_key (event, data, key); +return true; + } + +return false; +} + +static bool kvm_entry_event (struct event *event) +{ +return !strcmp (event->name,"kvm_entry" ); +} + +static bool exit_event_end(struct event *event, void *data __unused, + struct event_key *key __unused) +{ +return kvm_entry_event (event); +} + +struct exit_reasons_table { +unsigned long exit_code; +const char *reason; +}; + +struct exit_reasons_table vmx_exit_reasons[] = { +VMX_EXIT_REASONS +}; + +struct exit_reasons_table svm_exit_reasons[] = { +SVM_EXIT_REASONS +}; + +static const char *get_exit_reason (u64 exit_code,int isa) +{ +int table_size =ARRAY_SIZE (svm_exit_reasons); +struct exit_reasons_table *table = svm_exit_reasons; + +if (isa ==1 ) { + table = vmx_exit_reasons; + table_size =ARRAY_SIZE (vmx_exit_reasons); + } + +while (table_size--) { +if (table->exit_code == exit_code) +return table->reason; + table++; + } + +die ("unknown kvm exit code:%ld on %s\n" , exit_code, + isa ?"VMX" :"SVM" ); +} + +static void exit_event_decode_key (struct event_key *key,char decode[20 ]) +{ +const char *exit_reason =get_exit_reason (key->key, key->info); + +snprintf (decode,20 ,"%s" , exit_reason); +} + +static struct kvm_events_ops exit_events = { + .is_begin_event = exit_event_begin, + .is_end_event = exit_event_end, + .decode_key = exit_event_decode_key, + .name ="VM-EXIT" +}; + +/* +* For the old kernel, we treat: +* the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry +* the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...). +* +* For the new kernel, we use kvm_mmio_begin and kvm_mmio_done to make +* things better. +*/ +static void mmio_event_get_key (struct event *event,void *data, +struct event_key *key) +{ + key->key =raw_field_value (event,"gpa" , data); + key->info =raw_field_value (event,"type" , data); +} + +#define KVM_TRACE_MMIO_READ_UNSATISFIED 0 +#define KVM_TRACE_MMIO_READ 1 +#define KVM_TRACE_MMIO_WRITE 2 + +static bool kvm_mmio_done_event (struct event *event) +{ +return !strcmp (event->name,"kvm_mmio_done" ); +} + +static bool mmio_event_begin (struct event *event,void *data, +struct event_key *key) +{ +/* MMIO read begin in old kernel. */ +if (kvm_exit_event (event)) +return true; + +/* MMIO write begin in old kernel. */ +if (!strcmp (event->name,"kvm_mmio" ) && +raw_field_value (event,"type" , data) ==KVM_TRACE_MMIO_WRITE ) { +mmio_event_get_key (event, data, key); +return true; + } + +/* MMIO read/write begin in new kernel. */ +if (!strcmp (event->name,"kvm_mmio_begin" )) { +mmio_event_get_key (event, data, key); +return true; + } + +return false; +} + +static bool mmio_event_end (struct event *event,void *data, +struct event_key *key) +{ +/* MMIO write end in old kernel. */ +if (kvm_entry_event (event)) +return true; + +/* MMIO read end in the old kernel.*/ +if (!strcmp (event->name,"kvm_mmio" ) && +raw_field_value (event,"type" , data) ==KVM_TRACE_MMIO_READ ) { +mmio_event_get_key (event, data, key); +return true; + } + +/* MMIO read/write end event in the new kernel.*/ +return kvm_mmio_done_event (event); +} + +static void mmio_event_decode_key (struct event_key *key,char decode[20 ]) +{ +snprintf (decode,20 ,"%#lx:%s" , key->key, + key->info ==KVM_TRACE_MMIO_WRITE ?"W" :"R" ); +} + +static struct kvm_events_ops mmio_events = { + .is_begin_event = mmio_event_begin, + .is_end_event = mmio_event_end, + .decode_key = mmio_event_decode_key, + .name ="MMIO Access" +}; + +/* +* For the old kernel, the time of emulation pio access is from kvm_pio to +* kvm_entry. In the new kernel, the end time is indicated by kvm_mmio_done. +*/ +static void ioport_event_get_key (struct event *event,void *data, +struct event_key *key) +{ + key->key =raw_field_value (event,"port" , data); + key->info =raw_field_value (event,"rw" , data); +} + +static bool ioport_event_begin (struct event *event,void *data, +struct event_key *key) +{ +if (!strcmp (event->name,"kvm_pio" )) { +ioport_event_get_key (event, data, key); +return true; + } + +return false; +} + +static bool ioport_event_end(struct event *event, void *data __unused, + struct event_key *key __unused) +{ +if (kvm_entry_event (event)) +return true; + +return kvm_mmio_done_event (event); +} + +static void ioport_event_decode_key (struct event_key *key,char decode[20 ]) +{ +snprintf (decode,20 ,"%#lx:%s" , key->key, key->info ?"POUT" :"PIN" ); +} + +static struct kvm_events_ops ioport_events = { + .is_begin_event = ioport_event_begin, + .is_end_event = ioport_event_end, + .decode_key = ioport_event_decode_key, + .name ="IO Port Access" +}; + +static const char *report_event ="vmexit" ; +struct kvm_events_ops *events_ops; + +static void register_kvm_events_ops (void ) +{ +if (!strcmp (report_event,"vmexit" )) + events_ops = &exit_events; +else if (!strcmp (report_event,"mmio" )) + events_ops = &mmio_events; +else if (!strcmp (report_event,"ioport" )) + events_ops = &ioport_events; +else +die ("Unknown report event:%s\n" , report_event); +} + +struct event_stats { +u64 count; +u64 time; + +/* used to calculate stddev. */ +double mean; +double M2; +}; + +struct kvm_event { +struct list_head hash_entry; +struct rb_node rb; + +struct event_key key; + +struct event_stats total; + +#define DEFAULT_VCPU_NUM 32 +int max_vcpu; +struct event_stats *vcpu; +}; + +struct vcpu_event_record { +int vcpu_id; +u64 start_time; +struct kvm_event *last_event; +}; + +#define EVENTS_BITS 12 +#define EVENTS_CACHE_SIZE (1UL << EVENTS_BITS) + +static u64 total_time; +static u64 total_count; +static struct list_head kvm_events_cache[EVENTS_CACHE_SIZE ]; + +static void init_kvm_event_record (void ) +{ +int i; + +for (i =0 ; i < (int )EVENTS_CACHE_SIZE ; i++) +INIT_LIST_HEAD (&kvm_events_cache[i]); +} + +static int kvm_events_hash_fn (u64 key) +{ +return key & (EVENTS_CACHE_SIZE -1 ); +} + +static void kvm_event_expand (struct kvm_event *event,int vcpu_id) +{ +int old_max_vcpu = event->max_vcpu; + +if (vcpu_id < event->max_vcpu) +return ; + +while (event->max_vcpu <= vcpu_id) + event->max_vcpu +=DEFAULT_VCPU_NUM ; + + event->vcpu =realloc (event->vcpu, + event->max_vcpu *sizeof (*event->vcpu)); +if (!event->vcpu) +die ("Not enough memory\n" ); + +memset (event->vcpu + old_max_vcpu,0 , + (event->max_vcpu - old_max_vcpu) *sizeof (*event->vcpu)); +} + +static struct kvm_event *kvm_alloc_init_event (struct event_key *key) +{ +struct kvm_event *event; + + event =zalloc (sizeof (*event)); +if (!event) +die ("Not enough memory\n" ); + + event->key = *key; +return event; +} + +static struct kvm_event *find_create_kvm_event (struct event_key *key) +{ +struct kvm_event *event; +struct list_head *head; + +BUG_ON (key->key ==INVALID_KEY ); + + head = &kvm_events_cache[kvm_events_hash_fn (key->key)]; +list_for_each_entry (event, head, hash_entry) +if (event->key.key == key->key && event->key.info == key->info) +return event; + + event =kvm_alloc_init_event (key); +list_add (&event->hash_entry, head); +return event; +} + +static void handle_begin_event (struct vcpu_event_record *vcpu_record, +struct event_key *key,u64 timestamp) +{ +struct kvm_event *event =NULL ; + +if (key->key !=INVALID_KEY ) + event =find_create_kvm_event (key); + + vcpu_record->last_event = event; + vcpu_record->start_time = timestamp; +} + +static void update_event_stats (struct event_stats *stats,u64 time_diff) +{ +double delta; + + stats->count++; + stats->time += time_diff; + + delta = time_diff - stats->mean; + stats->mean += delta / stats->count; + stats->M2 += delta*(time_diff - stats->mean); +} + +static double event_stats_stddev (int vcpu_id,struct kvm_event *event) +{ +struct event_stats *stats = &event->total; +double variance, variance_mean, stddev; + +if (vcpu_id !=-1 ) + stats = &event->vcpu[vcpu_id]; + +BUG_ON (!stats->count); + + variance = stats->M2 / (stats->count -1 ); + variance_mean = variance / stats->count; + stddev =sqrt (variance_mean); + +return stddev *100 / stats->mean; +} + +static void update_kvm_event (struct kvm_event *event,int vcpu_id, +u64 time_diff) +{ +update_event_stats (&event->total, time_diff); +kvm_event_expand (event, vcpu_id); +update_event_stats (&event->vcpu[vcpu_id], time_diff); +} + +static void handle_end_event (struct vcpu_event_record *vcpu_record, +struct event_key *key,u64 timestamp) +{ +struct kvm_event *event; +u64 time_begin, time_diff; + + event = vcpu_record->last_event; + time_begin = vcpu_record->start_time; + +/* The begin event is not caught. */ +if (!time_begin) +return ; + +/* Both begin and end events did not get the key. */ +if (!event && key->key ==INVALID_KEY ) +return ; + +if (!event) + event =find_create_kvm_event (key); + + vcpu_record->last_event =NULL ; + vcpu_record->start_time =0 ; + +BUG_ON (timestamp < time_begin); + + time_diff = timestamp - time_begin; +update_kvm_event (event, vcpu_record->vcpu_id, time_diff); +} + +static struct vcpu_event_record +*per_vcpu_record (struct thread *thread,struct event *event,void *data) +{ +/* Only kvm_entry records vcpu id. */ +if (!thread->private &&kvm_entry_event (event)) { +struct vcpu_event_record *vcpu_record; + + vcpu_record =zalloc (sizeof (struct vcpu_event_record )); +if (!vcpu_record) +die ("Not enough memory\n" ); + + vcpu_record->vcpu_id =raw_field_value (event,"vcpu_id" , data); + thread->private = vcpu_record; + } + +return (struct vcpu_event_record *)thread->private; +} + +static void handle_kvm_event (struct thread *thread,struct event *event, +void *data,u64 timestamp) +{ +struct vcpu_event_record *vcpu_record; +struct event_key key = {.key =INVALID_KEY }; + + vcpu_record =per_vcpu_record (thread, event, data); +if (!vcpu_record) +return ; + +if (events_ops->is_begin_event (event, data, &key)) +return handle_begin_event (vcpu_record, &key, timestamp); + +if (events_ops->is_end_event (event, data, &key)) +return handle_end_event (vcpu_record, &key, timestamp); +} + +typedef int (*key_cmp_fun )(struct kvm_event *,struct kvm_event *,int ); +struct kvm_event_key { +const char *name; +key_cmp_fun key; +}; + +static int trace_vcpu =-1 ; +#define GET_EVENT_KEY (member) \ +static u64 get_event_ ##member(struct kvm_event *event, int vcpu) \ +{ \ + if (vcpu == -1) \ + return event->total.member; \ + \ + if (vcpu >= event->max_vcpu) \ + return 0; \ + \ + return event->vcpu[vcpu].member; \ +} + +#define COMPARE_EVENT_KEY (member) \ +GET_EVENT_KEY(member) \ +static int compare_kvm_event_ ## member(struct kvm_event *one, \ + struct kvm_event *two, int vcpu)\ +{ \ + return get_event_ ##member(one, vcpu) > \ + get_event_ ##member(two, vcpu); \ +} + +GET_EVENT_KEY (time); +COMPARE_EVENT_KEY (count); +COMPARE_EVENT_KEY (mean); + +#define DEF_SORT_NAME_KEY (name, compare_key) \ + { #name, compare_kvm_event_ ## compare_key } + +static struct kvm_event_key keys[] = { +DEF_SORT_NAME_KEY (sample, count), +DEF_SORT_NAME_KEY (time, mean), + {NULL ,NULL } +}; + +static const char *sort_key ="sample" ; +static key_cmp_fun compare; + +static void select_key (void ) +{ +int i; + +for (i =0 ; keys[i].name; i++) { +if (!strcmp (keys[i].name, sort_key)) { + compare = keys[i].key; +return ; + } + } + +die ("Unknown compare key:%s\n" , sort_key); +} + +static struct rb_root result; +static void insert_to_result (struct kvm_event *event,key_cmp_fun bigger, +int vcpu) +{ +struct rb_node **rb = &result.rb_node; +struct rb_node *parent =NULL ; +struct kvm_event *p; + +while (*rb) { + p = container_of(*rb, struct kvm_event, rb); + parent = *rb; + +if (bigger (event, p, vcpu)) + rb = &(*rb)->rb_left; +else + rb = &(*rb)->rb_right; + } + +rb_link_node (&event->rb, parent, rb); +rb_insert_color (&event->rb, &result); +} + +static void update_total_count (struct kvm_event *event,int vcpu) +{ + total_count +=get_event_count (event, vcpu); + total_time +=get_event_time (event, vcpu); +} + +static bool event_is_valid (struct kvm_event *event,int vcpu) +{ +return get_event_count (event, vcpu); +} + +static void sort_result (int vcpu) +{ +unsigned int i; +struct kvm_event *event; + +for (i =0 ; i <EVENTS_CACHE_SIZE ; i++) +list_for_each_entry (event, &kvm_events_cache[i], hash_entry) +if (event_is_valid (event, vcpu)) { +update_total_count (event, vcpu); +insert_to_result (event, compare, vcpu); + } +} + +/* returns left most element of result, and erase it */ +static struct kvm_event *pop_from_result (void ) +{ +struct rb_node *node = result.rb_node; + +if (!node) +return NULL ; + +while (node->rb_left) + node = node->rb_left; + +rb_erase (node, &result); + return container_of(node, struct kvm_event, rb); +} + +static void print_vcpu_info (int vcpu) +{ +pr_info ("Analyze events for " ); + +if (vcpu ==-1 ) +pr_info ("all VCPUs:\n\n" ); +else +pr_info ("VCPU %d:\n\n" , vcpu); +} + +static void print_result (int vcpu) +{ +char decode[20 ]; +struct kvm_event *event; + +pr_info ("\n\n" ); +print_vcpu_info (vcpu); +pr_info ("%20s " , events_ops->name); +pr_info ("%10s " ,"Samples" ); +pr_info ("%9s " ,"Samples%" ); + +pr_info ("%9s " ,"Time%" ); +pr_info ("%16s " ,"Avg time" ); +pr_info ("\n\n" ); + +while ((event =pop_from_result ())) { +u64 ecount, etime; + + ecount =get_event_count (event, vcpu); + etime =get_event_time (event, vcpu); + + events_ops->decode_key (&event->key, decode); +pr_info ("%20s " , decode); +pr_info ("%10lu " , ecount); +pr_info ("%8.2f%% " , (double )ecount / total_count *100 ); +pr_info ("%8.2f%% " , (double )etime / total_time *100 ); +pr_info ("%9.2fus ( +-%7.2f%% )" , (double )etime / ecount/1e3 , +event_stats_stddev (trace_vcpu, event)); +pr_info ("\n" ); + } + +pr_info ("\nTotal Samples:%ld, Total events handled time:%.2fus.\n\n" , + total_count, total_time /1e3 ); +} + +static void process_raw_event (struct thread *thread,void *data,u64 timestamp) +{ +struct event *event; +int type; + + type =trace_parse_common_type (data); + event =trace_find_event (type); + +return handle_kvm_event (thread, event, data, timestamp); +} + +static int process_sample_event (struct perf_tool *tool__used , +union perf_event *event, +struct perf_sample *sample, +struct perf_evsel *evsel__used , +struct machine *machine) +{ +struct thread *thread =machine__findnew_thread (machine, sample->tid); + +if (thread ==NULL ) { +pr_debug ("problem processing %d event, skipping it.\n" , + event->header.type); +return -1 ; + } + +process_raw_event (thread, sample->raw_data, sample->time); + +return 0 ; +} + +static struct perf_tool eops = { + .sample = process_sample_event, + .comm = perf_event__process_comm, + .ordered_samples = true, +}; + +static char const *input_name ="perf.data" ; + +static int get_cpu_isa (struct perf_session *session) +{ +char *cpuid; +int isa; + + cpuid =perf_header__read_feature (session,HEADER_CPUID ); + +if (!cpuid) +die ("read HEADER_CPUID failed.\n" ); + +if (strstr (cpuid,"Intel" )) + isa =1 ; +else if (strstr (cpuid,"AMD" )) + isa =0 ; +else +die ("CPU %s is not supported.\n" , cpuid); + +free (cpuid); +return isa; +} + +static int read_events (void ) +{ +struct perf_session *session; + + session =perf_session__new (input_name,O_RDONLY ,0 , false, &eops); +if (!session) +die ("Initializing perf session failed\n" ); + +if (!perf_session__has_traces (session,"kvm record" )) +return -1 ; + +/* +* Do not use 'isa' recorded in kvm_exit tracepoint since it is not +* traced in the old kernel. +*/ + cpu_isa =get_cpu_isa (session); + +return perf_session__process_events (session, &eops); +} + +static void verify_vcpu (int vcpu) +{ +if (vcpu !=-1 && vcpu <0 ) +die ("Invalid vcpu:%d.\n" , vcpu); +} + +static int kvm_events_report (int vcpu) +{ +init_kvm_event_record (); +verify_vcpu (vcpu); +select_key (); +register_kvm_events_ops (); +setup_pager (); + +read_events (); + +sort_result (vcpu); +print_result (vcpu); +return 0 ; +} + +static const char *const record_args[] = { +"record" , +"-a" , +"-R" , +"-f" , +"-m" ,"1024" , +"-c" ,"1" , +"-e" ,"kvm:kvm_entry" , +"-e" ,"kvm:kvm_exit" , +"-e" ,"kvm:kvm_mmio" , +"-e" ,"kvm:kvm_pio" , +}; + +static const char *const new_event[] = { +"kvm_mmio_begin" , +"kvm_mmio_done" +}; + +static bool kvm_events_exist (const char *event) +{ +char evt_path[MAXPATHLEN ]; +int fd; + +snprintf (evt_path,MAXPATHLEN ,"%s/kvm/%s/id" , tracing_events_path, + event); + + fd =open (evt_path,O_RDONLY ); + +if (fd <0 ) +return false; + +close (fd); + +return true; +} + +static int kvm_events_record (int argc,const char **argv) +{ +unsigned int rec_argc, i, j; +const char **rec_argv; + + rec_argc =ARRAY_SIZE (record_args) + argc -1 ; + rec_argc +=ARRAY_SIZE (new_event) *2 ; + rec_argv =calloc (rec_argc +1 ,sizeof (char *)); + +if (rec_argv ==NULL ) +return -ENOMEM ; + +for (i =0 ; i <ARRAY_SIZE (record_args); i++) + rec_argv[i] =strdup (record_args[i]); + +for (j =0 ; j <ARRAY_SIZE (new_event); j++) +if (kvm_events_exist (new_event[j])) { +char event[256 ]; + +sprintf (event,"kvm:%s" , new_event[j]); + + rec_argv[i++] =strdup ("-e" ); + rec_argv[i++] =strdup (event); + } + +for (j =1 ; j < (unsigned int )argc; j++, i++) + rec_argv[i] = argv[j]; + +return cmd_record (i, rec_argv,NULL ); +} + +static const char *const kvm_events_report_usage[] = { +"perf kvm events report [<options>]" , +NULL +}; + +static const struct option kvm_events_report_options[] = { +OPT_STRING (0 ,"event" , &report_event,"reprot event" , +"event for reporting: vmexit, mmio, ioport" ), +OPT_INTEGER (0 ,"vcpu" , &trace_vcpu, +"vcpu id to report" ), +OPT_STRING ('k' ,"key" , &sort_key,"sort-key" , +"key for sorting: sample(sort by samples number)" +" time (sort by avg time)" ), +OPT_END () +}; + +static const char *const kvm_events_usage[] = { +"perf kvm events [<options>] {record|report}" , +NULL +}; + +static const struct option kvm_events_options[] = { +OPT_STRING ('i' ,"input" , &input_name,"file" ,"input file name" ), +OPT_BOOLEAN ('D' ,"dump-raw-trace" , &dump_trace, +"dump raw trace in ASCII" ), +OPT_END () +}; + +int cmd_kvm_events (int argc,const char **argv,const char *prefix__used ) +{ + argc =parse_options (argc, argv, kvm_events_options, kvm_events_usage, +PARSE_OPT_STOP_AT_NON_OPTION ); +if (!argc) +usage_with_options (kvm_events_usage, kvm_events_options); + +symbol__init (); + +if (!strncmp (argv[0 ],"rec" ,3 )) +return kvm_events_record (argc, argv); + +if (!strncmp (argv[0 ],"rep" ,3 )) { +if (argc) { + argc =parse_options (argc, argv, + kvm_events_report_options, + kvm_events_report_usage,0 ); +if (argc) +usage_with_options (kvm_events_report_usage, + kvm_events_report_options); + } +return kvm_events_report (trace_vcpu); + } + +usage_with_options (kvm_events_usage, kvm_events_options); +return 0 ; +}tools/perf/builtin.h +1
-0@@ -33,6 +33,7 @@ extern int cmd_probe(int argc, const char **argv, const char *prefix);extern int cmd_kmem (int argc,const char **argv,const char *prefix);extern int cmd_lock (int argc,const char **argv,const char *prefix);extern int cmd_kvm (int argc,const char **argv,const char *prefix); +extern int cmd_kvm_events (int argc,const char **argv,const char *prefix);extern int cmd_test (int argc,const char **argv,const char *prefix);extern int cmd_inject (int argc,const char **argv,const char *prefix);tools/perf/command-list.txt +1
-0@@ -22,4 +22,5 @@ perf-probe mainporcelain common perf-kmem mainporcelain common perf-lock mainporcelain common perf-kvm mainporcelain common +perf-kvm-events mainporcelain common perf-test mainporcelain commontools/perf/perf.c +1
-0@@ -317,6 +317,7 @@ static void handle_internal_command(int argc, const char **argv) { "kmem", cmd_kmem, 0 }, { "lock", cmd_lock, 0 }, { "kvm", cmd_kvm, 0 }, + { "kvm-events", cmd_kvm_events, 0}, { "test", cmd_test, 0 }, { "inject", cmd_inject, 0 }, };tools/perf/util/header.c +53
-1@@ -1305,9 +1305,15 @@ static void print_cpuid(struct perf_header *ph, int fd, FILE *fp)free (str); } +static char *read_cpuid (struct perf_header *ph,int fd) +{ +return do_read_string (fd, ph); +} +struct feature_ops {int (*write)(int fd,struct perf_header *h,struct perf_evlist *evlist);void (*print)(struct perf_header *h,int fd,FILE *fp); +char *(*read)(struct perf_header *h,int fd);const char *name;bool full_only; };@@ -1316,6 +1322,8 @@ struct feature_ops { [n] = { .name = #n, .write = write_##func, .print = print_##func }#define FEAT_OPF (n, func) \ [n] = { .name = #n, .write = write_##func, .print = print_##func, .full_only = true } +#define FEAT_OPA_R (n, func) \ + [n] = { .name = #n, .write = write_##func, .print = print_##func, .read = read_##func }/* feature_ops not implemented: */ #define print_trace_info NULL@@ -1330,7 +1338,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = {FEAT_OPA (HEADER_ARCH , arch),FEAT_OPA (HEADER_NRCPUS , nrcpus),FEAT_OPA (HEADER_CPUDESC , cpudesc), -FEAT_OPA (HEADER_CPUID , cpuid), +FEAT_OPA_R (HEADER_CPUID , cpuid),FEAT_OPA (HEADER_TOTAL_MEM , total_mem),FEAT_OPA (HEADER_EVENT_DESC , event_desc), FEAT_OPA(HEADER_CMDLINE, cmdline),@@ -1383,6 +1391,50 @@ int perf_header__fprintf_info(struct perf_session *session, FILE *fp, bool full)return 0 ; } +struct header_read_data { +int feat; +char *result; +}; + +static int perf_file_section__read_feature (struct perf_file_section *section, +struct perf_header *ph, +int feat,int fd,void *data) +{ +struct header_read_data *hd = data; + +if (feat != hd->feat) +return 0 ; + +if (lseek (fd, section->offset,SEEK_SET ) == (off_t)-1 ) { +pr_debug ("Failed to lseek to %" PRIu64" offset for feature " +"%d, continuing...\n" , section->offset, feat); +return 0 ; + } + +if (feat >=HEADER_LAST_FEATURE ) { +pr_warning ("unknown feature %d\n" , feat); +return 0 ; + } + + hd->result = feat_ops[feat].read (ph, fd); +return 0 ; +} + +char *perf_header__read_feature (struct perf_session *session,int feat) +{ +struct perf_header *header = &session->header; +struct header_read_data hd; +int fd = session->fd; + + hd.feat = feat; + hd.result =NULL ; + + +perf_header__process_sections (header, fd, &hd, + perf_file_section__read_feature); +return hd.result; +} +static int do_write_feat (int fd,struct perf_header *h,int type,struct perf_file_section **p,struct perf_evlist *evlist)tools/perf/util/header.h +1
-0@@ -92,6 +92,7 @@ int perf_header__process_sections(struct perf_header *header, int fd, int feat, int fd, void *data));int perf_header__fprintf_info (struct perf_session *s,FILE *fp,bool full); +char *perf_header__read_feature (struct perf_session *session,int feat);int build_id_cache__add_s (const char *sbuild_id,const char *debugdir,const char *name,bool is_kallsyms);tools/perf/util/thread.h +2
-0@@ -16,6 +16,8 @@ struct thread {bool comm_set;char *comm;int comm_len; + +void *private; };struct machine ;
Quoted 33 lines · David Ahern
Compile fails on Fedora 16,
x86_64with latest tip-perf-core branch:In file included from builtin-kvm-events.c:19:0:
../../arch/x86/include/asm/svm.h:133:1: error: packed attribute is unnecessary for ‘vmcb_seg’ [-Werror=packed]../../arch/x86/include/asm/svm.h:178:1: error: packed attribute is unnecessary for ‘vmcb_save_area’ [-Werror=packed]../../arch/x86/include/asm/svm.h:183:1: error: packed attribute is unnecessary for ‘vmcb’ [-Werror=packed]
In file included from builtin-kvm-events.c:20:0:../../arch/x86/include/asm/vmx.h:334:0: error: "REG_R8" redefined [-Werror]/usr/include/sys/ucontext.h:46:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:335:0: error: "REG_R9" redefined [-Werror]/usr/include/sys/ucontext.h:48:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:336:0: error: "REG_R10" redefined [-Werror]/usr/include/sys/ucontext.h:50:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:337:0: error: "REG_R11" redefined [-Werror]/usr/include/sys/ucontext.h:52:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:338:0: error: "REG_R12" redefined [-Werror]/usr/include/sys/ucontext.h:54:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:339:0: error: "REG_R13" redefined [-Werror]/usr/include/sys/ucontext.h:56:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:340:0: error: "REG_R14" redefined [-Werror]/usr/include/sys/ucontext.h:58:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:341:0: error: "REG_R15" redefined [-Werror]
Quoted 9 lines
/usr/include/sys/ucontext.h:60:0: note: this is the location of the previous definition../../arch/x86/include/asm/vmx.h:443:13: error: expected declaration specifiers or ‘...’ before numeric constant
In file included from builtin-kvm-events.c:21:0:../../arch/x86/include/asm/kvm_host.h:15:22: fatal error:linux/mm.h: No such file or directory
cc1: all warnings being treated as errors
The first patch(patch 1/3) should be applied!
Thank you, David! :)
Finally got back to this. Overall nicely written command. Few comments and one for Arnaldo at the bottom.
Quoted 11 lines · Xiao Guangrong
Add 'perf kvm-events' support to analyze kvm vmexit/mmio/ioport smartly
Usage:
- trace kvm events:
perf kvm-events record, or, if other tracepoints are also interesting, we can append the events like this:
perf kvm-events record -e timer:*- show the result:
perf kvm-events report
It would be nice to have example reports in this commit message.
Quoted 30 lines
Signed-off-by Xiao Guangrong<[email protected]>--- tools/perf/Documentation/perf-kvm-events.txt | 52 ++ tools/perf/Makefile | 1 + tools/perf/builtin-kvm-events.c | 851 ++++++++++++++++++++++++++ tools/perf/builtin.h | 1 + tools/perf/command-list.txt | 1 + tools/perf/perf.c | 1 + tools/perf/util/header.c | 54 ++- tools/perf/util/header.h | 1 + tools/perf/util/thread.h | 2 + 9 files changed, 963 insertions(+), 1 deletions(-)create mode 100644
tools/perf/Documentation/perf-kvm-events.txtcreate mode 100644tools/perf/builtin-kvm-events.ctools/perf/Documentation/perf-kvm-events.txt new file +7
-0@@ -0,0 +1,52 @@ +perf-kvm-events(1) +============ + +NAME +---- +perf-kvm-events - Analyze kvm events +
tools/perf/Documentation/perf-kvm-events.txt line 17
Quoted 10 lines
tools/perf/Documentation/perf-kvm-events.txt new file +10
-0+SYNOPSIS +-------- +[verse] +'perf kvm-events' {record|report} + +DESCRIPTION +----------- +You can analyze some crucial kvm events and statistics with this +'perf kvm-events' command. Currently, vmexit, mmio and ioport events +are supported.
First sentence should be written in the 3rd person. eg., This command generates a statistical analysis of KVM events.
Quoted 18 lines
tools/perf/Documentation/perf-kvm-events.txt new file +17
-0+ + 'perf kvm-events record<command>' records kvm events(vmexit, + mmio and ioport) and the events between start and end<command>. + And this command produces the file "perf.data" which contains + tracing results of kvm events. + + 'perf kvm-events report' reports statistical data which includes + events handled time, samples, and so on. + +COMMON OPTIONS +-------------- + +-i:: +--input=<file>:: + Input file name. (default: perf.data unless stdin is a fifo) + +-D::
tools/perf/Documentation/perf-kvm-events.txt line 44
Quoted 10 lines
tools/perf/Documentation/perf-kvm-events.txt new file +10
-0+--dump-raw-trace:: + Dump raw trace in ASCII. + +REPORT OPTIONS +-------------- +--vcpu=<value>:: + analyze events which occures on this vcpu + +--events=<value>:: + events to be analyzed. Possible values: vmexit, mmio, ioport.
Add a comment stating which event type is the default.
tools/perf/Documentation/perf-kvm-events.txt line 48
Quoted 5 lines
tools/perf/Documentation/perf-kvm-events.txt new file +4
-0+-k:: +--key=<value>:: + Sorting key. Possible values: sample(default, sort by samples number), +time(sort by average time).
Space before both of the '('.
Quoted 108 lines
tools/perf/Documentation/perf-kvm-events.txt new file +4
-0+ +SEE ALSO +-------- +linkperf:perf[1]tools/perf/Makefile +1
-0@@ -382,6 +382,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-probe.o BUILTIN_OBJS += $(OUTPUT)builtin-kmem.o BUILTIN_OBJS += $(OUTPUT)builtin-lock.o BUILTIN_OBJS += $(OUTPUT)builtin-kvm.o +BUILTIN_OBJS += $(OUTPUT)builtin-kvm-events.o BUILTIN_OBJS += $(OUTPUT)builtin-test.o BUILTIN_OBJS += $(OUTPUT)builtin-inject.otools/perf/builtin-kvm-events.c new file +85
-0@@ -0,0 +1,851 @@ +#include "builtin.h" +#include "perf.h" +#include "util/util.h" +#include "util/cache.h" +#include "util/symbol.h" +#include "util/thread.h" +#include "util/header.h" +#include "util/parse-options.h" +#include "util/trace-event.h" +#include "util/debug.h" +#include "util/debugfs.h" +#include "util/session.h" +#include "util/tool.h" + +#include <math.h> + +#include <linux/kvm.h> + +#include "../../arch/x86/include/asm/svm.h" +#include "../../arch/x86/include/asm/vmx.h" +#include "../../arch/x86/include/asm/kvm_host.h" + +struct event_key { +#define INVALID_KEY (~0ULL) +u64 key; +int info; +}; + +struct kvm_events_ops { +bool (*is_begin_event)(struct event *event,void *data, +struct event_key *key); +bool (*is_end_event)(struct event *event,void *data, +struct event_key *key); +void (*decode_key)(struct event_key *key,char decode[20 ]); +const char *name; +}; + +static int cpu_isa; + +static void exit_event_get_key (struct event *event,void *data, +struct event_key *key) +{ + key->info = cpu_isa; + key->key =raw_field_value (event,"exit_reason" , data); +} + +static bool kvm_exit_event (struct event *event) +{ +return !strcmp (event->name,"kvm_exit" ); +} + +static bool exit_event_begin (struct event *event,void *data, +struct event_key *key) +{ +if (kvm_exit_event (event)) { +exit_event_get_key (event, data, key); +return true; + } + +return false; +} + +static bool kvm_entry_event (struct event *event) +{ +return !strcmp (event->name,"kvm_entry" ); +} + +static bool exit_event_end(struct event *event, void *data __unused, + struct event_key *key __unused) +{ +return kvm_entry_event (event); +} + +struct exit_reasons_table { +unsigned long exit_code; +const char *reason; +}; + +struct exit_reasons_table vmx_exit_reasons[] = { +VMX_EXIT_REASONS +}; + +struct exit_reasons_table svm_exit_reasons[] = { +SVM_EXIT_REASONS +};
tools/perf/builtin-kvm-events.c line 95
Quoted 10 lines
tools/perf/builtin-kvm-events.c new file +10
-0+ +static const char *get_exit_reason (u64 exit_code,int isa) +{ +int table_size =ARRAY_SIZE (svm_exit_reasons); +struct exit_reasons_table *table = svm_exit_reasons; + +if (isa ==1 ) { + table = vmx_exit_reasons; + table_size =ARRAY_SIZE (vmx_exit_reasons); + }
Why not use globals that are set once in read_events() after looking up cpu_isa? Then you don't have to state a preference on default init here -- AMD or Intel. And the isa argument will not be needed here.
Quoted 169 lines
tools/perf/builtin-kvm-events.c new file +169
-0+ +while (table_size--) { +if (table->exit_code == exit_code) +return table->reason; + table++; + } + +die ("unknown kvm exit code:%ld on %s\n" , exit_code, + isa ?"VMX" :"SVM" ); +} + +static void exit_event_decode_key (struct event_key *key,char decode[20 ]) +{ +const char *exit_reason =get_exit_reason (key->key, key->info); + +snprintf (decode,20 ,"%s" , exit_reason); +} + +static struct kvm_events_ops exit_events = { + .is_begin_event = exit_event_begin, + .is_end_event = exit_event_end, + .decode_key = exit_event_decode_key, + .name ="VM-EXIT" +}; + +/* +* For the old kernel, we treat: +* the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry +* the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...). +* +* For the new kernel, we use kvm_mmio_begin and kvm_mmio_done to make +* things better. +*/ +static void mmio_event_get_key (struct event *event,void *data, +struct event_key *key) +{ + key->key =raw_field_value (event,"gpa" , data); + key->info =raw_field_value (event,"type" , data); +} + +#define KVM_TRACE_MMIO_READ_UNSATISFIED 0 +#define KVM_TRACE_MMIO_READ 1 +#define KVM_TRACE_MMIO_WRITE 2 + +static bool kvm_mmio_done_event (struct event *event) +{ +return !strcmp (event->name,"kvm_mmio_done" ); +} + +static bool mmio_event_begin (struct event *event,void *data, +struct event_key *key) +{ +/* MMIO read begin in old kernel. */ +if (kvm_exit_event (event)) +return true; + +/* MMIO write begin in old kernel. */ +if (!strcmp (event->name,"kvm_mmio" )&& +raw_field_value (event,"type" , data) ==KVM_TRACE_MMIO_WRITE ) { +mmio_event_get_key (event, data, key); +return true; + } + +/* MMIO read/write begin in new kernel. */ +if (!strcmp (event->name,"kvm_mmio_begin" )) { +mmio_event_get_key (event, data, key); +return true; + } + +return false; +} + +static bool mmio_event_end (struct event *event,void *data, +struct event_key *key) +{ +/* MMIO write end in old kernel. */ +if (kvm_entry_event (event)) +return true; + +/* MMIO read end in the old kernel.*/ +if (!strcmp (event->name,"kvm_mmio" )&& +raw_field_value (event,"type" , data) ==KVM_TRACE_MMIO_READ ) { +mmio_event_get_key (event, data, key); +return true; + } + +/* MMIO read/write end event in the new kernel.*/ +return kvm_mmio_done_event (event); +} + +static void mmio_event_decode_key (struct event_key *key,char decode[20 ]) +{ +snprintf (decode,20 ,"%#lx:%s" , key->key, + key->info ==KVM_TRACE_MMIO_WRITE ?"W" :"R" ); +} + +static struct kvm_events_ops mmio_events = { + .is_begin_event = mmio_event_begin, + .is_end_event = mmio_event_end, + .decode_key = mmio_event_decode_key, + .name ="MMIO Access" +}; + +/* +* For the old kernel, the time of emulation pio access is from kvm_pio to +* kvm_entry. In the new kernel, the end time is indicated by kvm_mmio_done. +*/ +static void ioport_event_get_key (struct event *event,void *data, +struct event_key *key) +{ + key->key =raw_field_value (event,"port" , data); + key->info =raw_field_value (event,"rw" , data); +} + +static bool ioport_event_begin (struct event *event,void *data, +struct event_key *key) +{ +if (!strcmp (event->name,"kvm_pio" )) { +ioport_event_get_key (event, data, key); +return true; + } + +return false; +} + +static bool ioport_event_end(struct event *event, void *data __unused, + struct event_key *key __unused) +{ +if (kvm_entry_event (event)) +return true; + +return kvm_mmio_done_event (event); +} + +static void ioport_event_decode_key (struct event_key *key,char decode[20 ]) +{ +snprintf (decode,20 ,"%#lx:%s" , key->key, key->info ?"POUT" :"PIN" ); +} + +static struct kvm_events_ops ioport_events = { + .is_begin_event = ioport_event_begin, + .is_end_event = ioport_event_end, + .decode_key = ioport_event_decode_key, + .name ="IO Port Access" +}; + +static const char *report_event ="vmexit" ; +struct kvm_events_ops *events_ops; + +static void register_kvm_events_ops (void ) +{ +if (!strcmp (report_event,"vmexit" )) + events_ops =&exit_events; +else if (!strcmp (report_event,"mmio" )) + events_ops =&mmio_events; +else if (!strcmp (report_event,"ioport" )) + events_ops =&ioport_events; +else +die ("Unknown report event:%s\n" , report_event); +} + +struct event_stats { +u64 count; +u64 time; + +/* used to calculate stddev. */ +double mean; +double M2; +};
tools/perf/builtin-kvm-events.c line 274
Quoted 10 lines
tools/perf/builtin-kvm-events.c new file +10
-0+ +struct kvm_event { +struct list_head hash_entry; +struct rb_node rb; + +struct event_key key; + +struct event_stats total; + +#define DEFAULT_VCPU_NUM 32
Why 32 for the default number of vcpus in a guest? Seems like a lot for the typical VM. Versus something like 4 or 8.
Quoted 135 lines
tools/perf/builtin-kvm-events.c new file +134
-0+int max_vcpu; +struct event_stats *vcpu; +}; + +struct vcpu_event_record { +int vcpu_id; +u64 start_time; +struct kvm_event *last_event; +}; + +#define EVENTS_BITS 12 +#define EVENTS_CACHE_SIZE (1UL<< EVENTS_BITS) + +static u64 total_time; +static u64 total_count; +static struct list_head kvm_events_cache[EVENTS_CACHE_SIZE ]; + +static void init_kvm_event_record (void ) +{ +int i; + +for (i =0 ; i< (int )EVENTS_CACHE_SIZE ; i++) +INIT_LIST_HEAD (&kvm_events_cache[i]); +} + +static int kvm_events_hash_fn (u64 key) +{ +return key& (EVENTS_CACHE_SIZE -1 ); +} + +static void kvm_event_expand (struct kvm_event *event,int vcpu_id) +{ +int old_max_vcpu = event->max_vcpu; + +if (vcpu_id< event->max_vcpu) +return ; + +while (event->max_vcpu<= vcpu_id) + event->max_vcpu +=DEFAULT_VCPU_NUM ; + + event->vcpu =realloc (event->vcpu, + event->max_vcpu *sizeof (*event->vcpu)); +if (!event->vcpu) +die ("Not enough memory\n" ); + +memset (event->vcpu + old_max_vcpu,0 , + (event->max_vcpu - old_max_vcpu) *sizeof (*event->vcpu)); +} + +static struct kvm_event *kvm_alloc_init_event (struct event_key *key) +{ +struct kvm_event *event; + + event =zalloc (sizeof (*event)); +if (!event) +die ("Not enough memory\n" ); + + event->key = *key; +return event; +} + +static struct kvm_event *find_create_kvm_event (struct event_key *key) +{ +struct kvm_event *event; +struct list_head *head; + +BUG_ON (key->key ==INVALID_KEY ); + + head =&kvm_events_cache[kvm_events_hash_fn (key->key)]; +list_for_each_entry (event, head, hash_entry) +if (event->key.key == key->key&& event->key.info == key->info) +return event; + + event =kvm_alloc_init_event (key); +list_add (&event->hash_entry, head); +return event; +} + +static void handle_begin_event (struct vcpu_event_record *vcpu_record, +struct event_key *key,u64 timestamp) +{ +struct kvm_event *event =NULL ; + +if (key->key !=INVALID_KEY ) + event =find_create_kvm_event (key); + + vcpu_record->last_event = event; + vcpu_record->start_time = timestamp; +} + +static void update_event_stats (struct event_stats *stats,u64 time_diff) +{ +double delta; + + stats->count++; + stats->time += time_diff; + + delta = time_diff - stats->mean; + stats->mean += delta / stats->count; + stats->M2 += delta*(time_diff - stats->mean); +} + +static double event_stats_stddev (int vcpu_id,struct kvm_event *event) +{ +struct event_stats *stats =&event->total; +double variance, variance_mean, stddev; + +if (vcpu_id !=-1 ) + stats =&event->vcpu[vcpu_id]; + +BUG_ON (!stats->count); + + variance = stats->M2 / (stats->count -1 ); + variance_mean = variance / stats->count; + stddev =sqrt (variance_mean); + +return stddev *100 / stats->mean; +} + +static void update_kvm_event (struct kvm_event *event,int vcpu_id, +u64 time_diff) +{ +update_event_stats (&event->total, time_diff); +kvm_event_expand (event, vcpu_id); +update_event_stats (&event->vcpu[vcpu_id], time_diff); +} + +static void handle_end_event (struct vcpu_event_record *vcpu_record, +struct event_key *key,u64 timestamp) +{ +struct kvm_event *event; +u64 time_begin, time_diff; + + event = vcpu_record->last_event;
tools/perf/builtin-kvm-events.c line 418
Quoted 10 lines
tools/perf/builtin-kvm-events.c new file +10
-0+ time_begin = vcpu_record->start_time; + +/* The begin event is not caught. */ +if (!time_begin) +return ; + +/* Both begin and end events did not get the key. */ +if (!event&& key->key ==INVALID_KEY ) +return ; +
Should not be able to get here with event unset, so the next 2 lines should not be needed. ie., you only want to process events where the begin event was seen in which case event is defined.
Quoted 370 lines
tools/perf/builtin-kvm-events.c new file +370
-0+if (!event) + event =find_create_kvm_event (key); + + vcpu_record->last_event =NULL ; + vcpu_record->start_time =0 ; + +BUG_ON (timestamp< time_begin); + + time_diff = timestamp - time_begin; +update_kvm_event (event, vcpu_record->vcpu_id, time_diff); +} + +static struct vcpu_event_record +*per_vcpu_record (struct thread *thread,struct event *event,void *data) +{ +/* Only kvm_entry records vcpu id. */ +if (!thread->private&&kvm_entry_event (event)) { +struct vcpu_event_record *vcpu_record; + + vcpu_record =zalloc (sizeof (struct vcpu_event_record )); +if (!vcpu_record) +die ("Not enough memory\n" ); + + vcpu_record->vcpu_id =raw_field_value (event,"vcpu_id" , data); + thread->private = vcpu_record; + } + +return (struct vcpu_event_record *)thread->private; +} + +static void handle_kvm_event (struct thread *thread,struct event *event, +void *data,u64 timestamp) +{ +struct vcpu_event_record *vcpu_record; +struct event_key key = {.key =INVALID_KEY }; + + vcpu_record =per_vcpu_record (thread, event, data); +if (!vcpu_record) +return ; + +if (events_ops->is_begin_event (event, data,&key)) +return handle_begin_event (vcpu_record,&key, timestamp); + +if (events_ops->is_end_event (event, data,&key)) +return handle_end_event (vcpu_record,&key, timestamp); +} + +typedef int (*key_cmp_fun )(struct kvm_event *,struct kvm_event *,int ); +struct kvm_event_key { +const char *name; +key_cmp_fun key; +}; + +static int trace_vcpu =-1 ; +#define GET_EVENT_KEY (member) \ +static u64 get_event_ ##member(struct kvm_event *event, int vcpu) \ +{ \ + if (vcpu == -1) \ + return event->total.member; \ + \ + if (vcpu>= event->max_vcpu) \ + return 0; \ + \ + return event->vcpu[vcpu].member; \ +} + +#define COMPARE_EVENT_KEY (member) \ +GET_EVENT_KEY(member) \ +static int compare_kvm_event_ ## member(struct kvm_event *one, \ + struct kvm_event *two, int vcpu)\ +{ \ + return get_event_ ##member(one, vcpu)> \ + get_event_ ##member(two, vcpu); \ +} + +GET_EVENT_KEY (time); +COMPARE_EVENT_KEY (count); +COMPARE_EVENT_KEY (mean); + +#define DEF_SORT_NAME_KEY (name, compare_key) \ + { #name, compare_kvm_event_ ## compare_key } + +static struct kvm_event_key keys[] = { +DEF_SORT_NAME_KEY (sample, count), +DEF_SORT_NAME_KEY (time, mean), + {NULL ,NULL } +}; + +static const char *sort_key ="sample" ; +static key_cmp_fun compare; + +static void select_key (void ) +{ +int i; + +for (i =0 ; keys[i].name; i++) { +if (!strcmp (keys[i].name, sort_key)) { + compare = keys[i].key; +return ; + } + } + +die ("Unknown compare key:%s\n" , sort_key); +} + +static struct rb_root result; +static void insert_to_result (struct kvm_event *event,key_cmp_fun bigger, +int vcpu) +{ +struct rb_node **rb =&result.rb_node; +struct rb_node *parent =NULL ; +struct kvm_event *p; + +while (*rb) { + p = container_of(*rb, struct kvm_event, rb); + parent = *rb; + +if (bigger (event, p, vcpu)) + rb =&(*rb)->rb_left; +else + rb =&(*rb)->rb_right; + } + +rb_link_node (&event->rb, parent, rb); +rb_insert_color (&event->rb,&result); +} + +static void update_total_count (struct kvm_event *event,int vcpu) +{ + total_count +=get_event_count (event, vcpu); + total_time +=get_event_time (event, vcpu); +} + +static bool event_is_valid (struct kvm_event *event,int vcpu) +{ +return get_event_count (event, vcpu); +} + +static void sort_result (int vcpu) +{ +unsigned int i; +struct kvm_event *event; + +for (i =0 ; i<EVENTS_CACHE_SIZE ; i++) +list_for_each_entry (event,&kvm_events_cache[i], hash_entry) +if (event_is_valid (event, vcpu)) { +update_total_count (event, vcpu); +insert_to_result (event, compare, vcpu); + } +} + +/* returns left most element of result, and erase it */ +static struct kvm_event *pop_from_result (void ) +{ +struct rb_node *node = result.rb_node; + +if (!node) +return NULL ; + +while (node->rb_left) + node = node->rb_left; + +rb_erase (node,&result); + return container_of(node, struct kvm_event, rb); +} + +static void print_vcpu_info (int vcpu) +{ +pr_info ("Analyze events for " ); + +if (vcpu ==-1 ) +pr_info ("all VCPUs:\n\n" ); +else +pr_info ("VCPU %d:\n\n" , vcpu); +} + +static void print_result (int vcpu) +{ +char decode[20 ]; +struct kvm_event *event; + +pr_info ("\n\n" ); +print_vcpu_info (vcpu); +pr_info ("%20s " , events_ops->name); +pr_info ("%10s " ,"Samples" ); +pr_info ("%9s " ,"Samples%" ); + +pr_info ("%9s " ,"Time%" ); +pr_info ("%16s " ,"Avg time" ); +pr_info ("\n\n" ); + +while ((event =pop_from_result ())) { +u64 ecount, etime; + + ecount =get_event_count (event, vcpu); + etime =get_event_time (event, vcpu); + + events_ops->decode_key (&event->key, decode); +pr_info ("%20s " , decode); +pr_info ("%10lu " , ecount); +pr_info ("%8.2f%% " , (double )ecount / total_count *100 ); +pr_info ("%8.2f%% " , (double )etime / total_time *100 ); +pr_info ("%9.2fus ( +-%7.2f%% )" , (double )etime / ecount/1e3 , +event_stats_stddev (trace_vcpu, event)); +pr_info ("\n" ); + } + +pr_info ("\nTotal Samples:%ld, Total events handled time:%.2fus.\n\n" , + total_count, total_time /1e3 ); +} + +static void process_raw_event (struct thread *thread,void *data,u64 timestamp) +{ +struct event *event; +int type; + + type =trace_parse_common_type (data); + event =trace_find_event (type); + +return handle_kvm_event (thread, event, data, timestamp); +} + +static int process_sample_event (struct perf_tool *tool__used , +union perf_event *event, +struct perf_sample *sample, +struct perf_evsel *evsel__used , +struct machine *machine) +{ +struct thread *thread =machine__findnew_thread (machine, sample->tid); + +if (thread ==NULL ) { +pr_debug ("problem processing %d event, skipping it.\n" , + event->header.type); +return -1 ; + } + +process_raw_event (thread, sample->raw_data, sample->time); + +return 0 ; +} + +static struct perf_tool eops = { + .sample = process_sample_event, + .comm = perf_event__process_comm, + .ordered_samples = true, +}; + +static char const *input_name ="perf.data" ; + +static int get_cpu_isa (struct perf_session *session) +{ +char *cpuid; +int isa; + + cpuid =perf_header__read_feature (session,HEADER_CPUID ); + +if (!cpuid) +die ("read HEADER_CPUID failed.\n" ); + +if (strstr (cpuid,"Intel" )) + isa =1 ; +else if (strstr (cpuid,"AMD" )) + isa =0 ; +else +die ("CPU %s is not supported.\n" , cpuid); + +free (cpuid); +return isa; +} + +static int read_events (void ) +{ +struct perf_session *session; + + session =perf_session__new (input_name,O_RDONLY ,0 , false,&eops); +if (!session) +die ("Initializing perf session failed\n" ); + +if (!perf_session__has_traces (session,"kvm record" )) +return -1 ; + +/* +* Do not use 'isa' recorded in kvm_exit tracepoint since it is not +* traced in the old kernel. +*/ + cpu_isa =get_cpu_isa (session); + +return perf_session__process_events (session,&eops); +} + +static void verify_vcpu (int vcpu) +{ +if (vcpu !=-1 && vcpu<0 ) +die ("Invalid vcpu:%d.\n" , vcpu); +} + +static int kvm_events_report (int vcpu) +{ +init_kvm_event_record (); +verify_vcpu (vcpu); +select_key (); +register_kvm_events_ops (); +setup_pager (); + +read_events (); + +sort_result (vcpu); +print_result (vcpu); +return 0 ; +} + +static const char *const record_args[] = { +"record" , +"-a" , +"-R" , +"-f" , +"-m" ,"1024" , +"-c" ,"1" , +"-e" ,"kvm:kvm_entry" , +"-e" ,"kvm:kvm_exit" , +"-e" ,"kvm:kvm_mmio" , +"-e" ,"kvm:kvm_pio" , +}; + +static const char *const new_event[] = { +"kvm_mmio_begin" , +"kvm_mmio_done" +}; + +static bool kvm_events_exist (const char *event) +{ +char evt_path[MAXPATHLEN ]; +int fd; + +snprintf (evt_path,MAXPATHLEN ,"%s/kvm/%s/id" , tracing_events_path, + event); + + fd =open (evt_path,O_RDONLY ); + +if (fd<0 ) +return false; + +close (fd); + +return true; +} + +static int kvm_events_record (int argc,const char **argv) +{ +unsigned int rec_argc, i, j; +const char **rec_argv; + + rec_argc =ARRAY_SIZE (record_args) + argc -1 ; + rec_argc +=ARRAY_SIZE (new_event) *2 ; + rec_argv =calloc (rec_argc +1 ,sizeof (char *)); + +if (rec_argv ==NULL ) +return -ENOMEM ; + +for (i =0 ; i<ARRAY_SIZE (record_args); i++) + rec_argv[i] =strdup (record_args[i]); + +for (j =0 ; j<ARRAY_SIZE (new_event); j++) +if (kvm_events_exist (new_event[j])) { +char event[256 ]; + +sprintf (event,"kvm:%s" , new_event[j]); + + rec_argv[i++] =strdup ("-e" ); + rec_argv[i++] =strdup (event);
tools/perf/builtin-kvm-events.c line 798
Quoted 10 lines
tools/perf/builtin-kvm-events.c new file +10
-0+ } + +for (j =1 ; j< (unsigned int )argc; j++, i++) + rec_argv[i] = argv[j]; + +return cmd_record (i, rec_argv,NULL ); +} + +static const char *const kvm_events_report_usage[] = { +"perf kvm events report [<options>]" ,
missing '-' between kvm and events
tools/perf/builtin-kvm-events.c
Quoted 6 lines
tools/perf/builtin-kvm-events.c new file +5
-0+NULL +}; + +static const struct option kvm_events_report_options[] = { + OPT_STRING(0, "event",&report_event, "reprot event",
report is misspelled in the above line.
tools/perf/builtin-kvm-events.c line 814
Quoted 12 lines
tools/perf/builtin-kvm-events.c new file +11
-0+ "event for reporting: vmexit, mmio, ioport"), +OPT_INTEGER (0 ,"vcpu" ,&trace_vcpu, +"vcpu id to report" ), +OPT_STRING ('k' ,"key" ,&sort_key,"sort-key" , +"key for sorting: sample(sort by samples number)" +" time (sort by avg time)" ), +OPT_END () +}; + +static const char *const kvm_events_usage[] = { + "perf kvm events [<options>] {record|report}",
missing '-' between kvm and events
Quoted 178 lines
tools/perf/builtin-kvm-events.c new file +37
-0+ NULL +}; + +static const struct option kvm_events_options[] = { +OPT_STRING ('i' ,"input" ,&input_name,"file" ,"input file name" ), +OPT_BOOLEAN ('D' ,"dump-raw-trace" ,&dump_trace, +"dump raw trace in ASCII" ), +OPT_END () +}; + +int cmd_kvm_events (int argc,const char **argv,const char *prefix__used ) +{ + argc =parse_options (argc, argv, kvm_events_options, kvm_events_usage, +PARSE_OPT_STOP_AT_NON_OPTION ); +if (!argc) +usage_with_options (kvm_events_usage, kvm_events_options); + +symbol__init (); + +if (!strncmp (argv[0 ],"rec" ,3 )) +return kvm_events_record (argc, argv); + +if (!strncmp (argv[0 ],"rep" ,3 )) { +if (argc) { + argc =parse_options (argc, argv, + kvm_events_report_options, + kvm_events_report_usage,0 ); +if (argc) +usage_with_options (kvm_events_report_usage, + kvm_events_report_options); + } +return kvm_events_report (trace_vcpu); + } + +usage_with_options (kvm_events_usage, kvm_events_options); +return 0 ; +}tools/perf/builtin.h +1
-0@@ -33,6 +33,7 @@ extern int cmd_probe(int argc, const char **argv, const char *prefix);extern int cmd_kmem (int argc,const char **argv,const char *prefix);extern int cmd_lock (int argc,const char **argv,const char *prefix);extern int cmd_kvm (int argc,const char **argv,const char *prefix); +extern int cmd_kvm_events (int argc,const char **argv,const char *prefix);extern int cmd_test (int argc,const char **argv,const char *prefix);extern int cmd_inject (int argc,const char **argv,const char *prefix);tools/perf/command-list.txt +1
-0@@ -22,4 +22,5 @@ perf-probe mainporcelain common perf-kmem mainporcelain common perf-lock mainporcelain common perf-kvm mainporcelain common +perf-kvm-events mainporcelain common perf-test mainporcelain commontools/perf/perf.c +1
-0@@ -317,6 +317,7 @@ static void handle_internal_command(int argc, const char **argv) { "kmem", cmd_kmem, 0 }, { "lock", cmd_lock, 0 }, { "kvm", cmd_kvm, 0 }, + { "kvm-events", cmd_kvm_events, 0}, { "test", cmd_test, 0 }, { "inject", cmd_inject, 0 }, };tools/perf/util/header.c +53
-1@@ -1305,9 +1305,15 @@ static void print_cpuid(struct perf_header *ph, int fd, FILE *fp)free (str); } +static char *read_cpuid (struct perf_header *ph,int fd) +{ +return do_read_string (fd, ph); +} +struct feature_ops {int (*write)(int fd,struct perf_header *h,struct perf_evlist *evlist);void (*print)(struct perf_header *h,int fd,FILE *fp); +char *(*read)(struct perf_header *h,int fd);const char *name;bool full_only; };@@ -1316,6 +1322,8 @@ struct feature_ops { [n] = { .name = #n, .write = write_##func, .print = print_##func }#define FEAT_OPF (n, func) \ [n] = { .name = #n, .write = write_##func, .print = print_##func, .full_only = true } +#define FEAT_OPA_R (n, func) \ + [n] = { .name = #n, .write = write_##func, .print = print_##func, .read = read_##func }/* feature_ops not implemented: */ #define print_trace_info NULL@@ -1330,7 +1338,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = {FEAT_OPA (HEADER_ARCH , arch),FEAT_OPA (HEADER_NRCPUS , nrcpus),FEAT_OPA (HEADER_CPUDESC , cpudesc), -FEAT_OPA (HEADER_CPUID , cpuid), +FEAT_OPA_R (HEADER_CPUID , cpuid),FEAT_OPA (HEADER_TOTAL_MEM , total_mem),FEAT_OPA (HEADER_EVENT_DESC , event_desc), FEAT_OPA(HEADER_CMDLINE, cmdline),@@ -1383,6 +1391,50 @@ int perf_header__fprintf_info(struct perf_session *session, FILE *fp, bool full)return 0 ; } +struct header_read_data { +int feat; +char *result; +}; + +static int perf_file_section__read_feature (struct perf_file_section *section, +struct perf_header *ph, +int feat,int fd,void *data) +{ +struct header_read_data *hd = data; + +if (feat != hd->feat) +return 0 ; + +if (lseek (fd, section->offset,SEEK_SET ) == (off_t)-1 ) { +pr_debug ("Failed to lseek to %" PRIu64" offset for feature " +"%d, continuing...\n" , section->offset, feat); +return 0 ; + } + +if (feat>=HEADER_LAST_FEATURE ) { +pr_warning ("unknown feature %d\n" , feat); +return 0 ; + } + + hd->result = feat_ops[feat].read (ph, fd); +return 0 ; +} + +char *perf_header__read_feature (struct perf_session *session,int feat) +{ +struct perf_header *header =&session->header; +struct header_read_data hd; +int fd = session->fd; + + hd.feat = feat; + hd.result =NULL ; + + +perf_header__process_sections (header, fd,&hd, + perf_file_section__read_feature); +return hd.result; +} +static int do_write_feat (int fd,struct perf_header *h,int type,struct perf_file_section **p,struct perf_evlist *evlist)tools/perf/util/header.h +1
-0@@ -92,6 +92,7 @@ int perf_header__process_sections(struct perf_header *header, int fd, int feat, int fd, void *data));int perf_header__fprintf_info (struct perf_session *s,FILE *fp,bool full); +char *perf_header__read_feature (struct perf_session *session,int feat);int build_id_cache__add_s (const char *sbuild_id,const char *debugdir,const char *name,bool is_kallsyms);tools/perf/util/thread.h +0
-0
tools/perf/util/thread.h struct thread { · line 20
Quoted 5 lines
tools/perf/util/thread.h +2
-0@@ -16,6 +16,8 @@ struct thread {bool comm_set;char *comm;int comm_len; + +void *private;
Arnaldo: Are you ok with this design for now? I can fix this command to whatever API we agree to when it gets committed.
David
Quoted 9 lines
tools/perf/util/thread.h +0
-1};struct machine ; --To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
On 02/21/2012 07:47 AM, David Ahern wrote:
- show the result:
Quoted 4 lines
Quoted 1 line
perf kvm-events report
It would be nice to have example reports in this commit message.
Okay.
Quoted 8 lines
tools/perf/Documentation/perf-kvm-events.txt line 17
Quoted 5 lines
tools/perf/Documentation/perf-kvm-events.txt new file +5
-0+DESCRIPTION +----------- +You can analyze some crucial kvm events and statistics with this +'perf kvm-events' command. Currently, vmexit, mmio and ioport events +are supported.First sentence should be written in the 3rd person. eg., This command generates a statistical analysis of KVM events.
Okay.
Quoted 5 lines
Quoted 2 lines
+--events=<value>:: + events to be analyzed. Possible values: vmexit, mmio, ioport.Add a comment stating which event type is the default.
OK, will fix.
Quoted 7 lines
tools/perf/Documentation/perf-kvm-events.txt line 48
Quoted 5 lines
+-k:: +--key=<value>::tools/perf/Documentation/perf-kvm-events.txt new file +2
-0+ Sorting key. Possible values: sample(default, sort by samples number), +time(sort by average time).Space before both of the '('.
Yes, will fix.
Quoted 11 lines
tools/perf/builtin-kvm-events.c line 95
Quoted 9 lines
tools/perf/builtin-kvm-events.c new file +9
-0+static const char *get_exit_reason (u64 exit_code,int isa) +{ +int table_size =ARRAY_SIZE (svm_exit_reasons); +struct exit_reasons_table *table = svm_exit_reasons; + +if (isa ==1 ) { + table = vmx_exit_reasons; + table_size =ARRAY_SIZE (vmx_exit_reasons); + }Why not use globals that are set once in
read_events()after looking upcpu_isa? Then you don't have to state a preference on default init here -- AMD or Intel. And the isa argument will not be needed here.
I agree.
Quoted 3 lines
Quoted 1 line
+ #define
DEFAULT_VCPU_NUM32Why 32 for the default number of vcpus in a guest? Seems like a lot for the typical VM. Versus something like 4 or 8.
Quoted 1 line
Hmm, since 32 is the default vcpu number in the old kernel (IIRC), but your suggestion sounds good.
Quoted 5 lines
tools/perf/builtin-kvm-events.c line 418
Quoted 4 lines
tools/perf/builtin-kvm-events.c new file +4
-0+/* Both begin and end events did not get the key. */ +if (!event&& key->key ==INVALID_KEY ) +return ; +Should not be able to get here with event unset, so the next 2 lines should not be needed. ie., you only want to process events where the begin event was seen in which case event is defined.
In some case, the 'begin event' just records the start timestamp, the actually event is recognised in the 'end event'.
Take mmio-read for example, in the old kernel, we use kvm-exit as the 'begin event' and kvm_mmio(KVM_TRACE_MMIO_READ...) is the 'end event'.
Quoted 3 lines
Quoted 1 line
+ "perf kvm events report [<options>]",
missing '-' between kvm and events
...
Quoted 3 lines
tools/perf/builtin-kvm-events.c line 814
Quoted 2 lines
tools/perf/builtin-kvm-events.c new file +2
-0+static const char *const kvm_events_usage[] = { +"perf kvm events [<options>] {record|report}" ,missing '-' between kvm and events
Sorry for my careless, these will be fixed in the next version.
Thanks very much for your review, David! :)
Quoted 4 lines · Xiao Guangrong
Quoted 4 lines
Quoted 4 lines
tools/perf/builtin-kvm-events.c new file +4
-0+/* Both begin and end events did not get the key. */ +if (!event&& key->key ==INVALID_KEY ) +return ; +
Quoted 8 lines
Quoted 1 line
Should not be able to get here with event unset, so the next 2 lines should not be needed. ie., you only want to process events where the begin event was seen in which case event is defined.
In some case, the 'begin event' just records the start timestamp, the actually event is recognised in the 'end event'.
Take mmio-read for example, in the old kernel, we use kvm-exit as the 'begin event' and
kvm_mmio(KVM_TRACE_MMIO_READ...)is the 'end event'.
ah, ok. Please add a comment about this path.
David
Quoted 11 lines · David Ahern
tools/perf/util/thread.h struct thread { · line 20
Quoted 10 lines
tools/perf/util/thread.h +2
-0@@ -16,6 +16,8 @@ struct thread {bool comm_set;char *comm;int comm_len; + +void *private;Arnaldo: Are you ok with this design for now? I can fix this command to whatever API we agree to when it gets committed.
Hi Arnaldo,
What do you think of it? :)