diff options
Diffstat (limited to 'tools/perf/builtin-kvm.c')
-rw-r--r-- | tools/perf/builtin-kvm.c | 754 |
1 files changed, 716 insertions, 38 deletions
diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c index 24b78aecc928..935d52216c89 100644 --- a/tools/perf/builtin-kvm.c +++ b/tools/perf/builtin-kvm.c | |||
@@ -2,22 +2,26 @@ | |||
2 | #include "perf.h" | 2 | #include "perf.h" |
3 | 3 | ||
4 | #include "util/evsel.h" | 4 | #include "util/evsel.h" |
5 | #include "util/evlist.h" | ||
5 | #include "util/util.h" | 6 | #include "util/util.h" |
6 | #include "util/cache.h" | 7 | #include "util/cache.h" |
7 | #include "util/symbol.h" | 8 | #include "util/symbol.h" |
8 | #include "util/thread.h" | 9 | #include "util/thread.h" |
9 | #include "util/header.h" | 10 | #include "util/header.h" |
10 | #include "util/session.h" | 11 | #include "util/session.h" |
11 | 12 | #include "util/intlist.h" | |
12 | #include "util/parse-options.h" | 13 | #include "util/parse-options.h" |
13 | #include "util/trace-event.h" | 14 | #include "util/trace-event.h" |
14 | #include "util/debug.h" | 15 | #include "util/debug.h" |
15 | #include <lk/debugfs.h> | 16 | #include <lk/debugfs.h> |
16 | #include "util/tool.h" | 17 | #include "util/tool.h" |
17 | #include "util/stat.h" | 18 | #include "util/stat.h" |
19 | #include "util/top.h" | ||
18 | 20 | ||
19 | #include <sys/prctl.h> | 21 | #include <sys/prctl.h> |
22 | #include <sys/timerfd.h> | ||
20 | 23 | ||
24 | #include <termios.h> | ||
21 | #include <semaphore.h> | 25 | #include <semaphore.h> |
22 | #include <pthread.h> | 26 | #include <pthread.h> |
23 | #include <math.h> | 27 | #include <math.h> |
@@ -82,6 +86,8 @@ struct exit_reasons_table { | |||
82 | 86 | ||
83 | struct perf_kvm_stat { | 87 | struct perf_kvm_stat { |
84 | struct perf_tool tool; | 88 | struct perf_tool tool; |
89 | struct perf_record_opts opts; | ||
90 | struct perf_evlist *evlist; | ||
85 | struct perf_session *session; | 91 | struct perf_session *session; |
86 | 92 | ||
87 | const char *file_name; | 93 | const char *file_name; |
@@ -96,10 +102,20 @@ struct perf_kvm_stat { | |||
96 | struct kvm_events_ops *events_ops; | 102 | struct kvm_events_ops *events_ops; |
97 | key_cmp_fun compare; | 103 | key_cmp_fun compare; |
98 | struct list_head kvm_events_cache[EVENTS_CACHE_SIZE]; | 104 | struct list_head kvm_events_cache[EVENTS_CACHE_SIZE]; |
105 | |||
99 | u64 total_time; | 106 | u64 total_time; |
100 | u64 total_count; | 107 | u64 total_count; |
108 | u64 lost_events; | ||
109 | u64 duration; | ||
110 | |||
111 | const char *pid_str; | ||
112 | struct intlist *pid_list; | ||
101 | 113 | ||
102 | struct rb_root result; | 114 | struct rb_root result; |
115 | |||
116 | int timerfd; | ||
117 | unsigned int display_time; | ||
118 | bool live; | ||
103 | }; | 119 | }; |
104 | 120 | ||
105 | 121 | ||
@@ -320,6 +336,28 @@ static void init_kvm_event_record(struct perf_kvm_stat *kvm) | |||
320 | INIT_LIST_HEAD(&kvm->kvm_events_cache[i]); | 336 | INIT_LIST_HEAD(&kvm->kvm_events_cache[i]); |
321 | } | 337 | } |
322 | 338 | ||
339 | static void clear_events_cache_stats(struct list_head *kvm_events_cache) | ||
340 | { | ||
341 | struct list_head *head; | ||
342 | struct kvm_event *event; | ||
343 | unsigned int i; | ||
344 | int j; | ||
345 | |||
346 | for (i = 0; i < EVENTS_CACHE_SIZE; i++) { | ||
347 | head = &kvm_events_cache[i]; | ||
348 | list_for_each_entry(event, head, hash_entry) { | ||
349 | /* reset stats for event */ | ||
350 | event->total.time = 0; | ||
351 | init_stats(&event->total.stats); | ||
352 | |||
353 | for (j = 0; j < event->max_vcpu; ++j) { | ||
354 | event->vcpu[j].time = 0; | ||
355 | init_stats(&event->vcpu[j].stats); | ||
356 | } | ||
357 | } | ||
358 | } | ||
359 | } | ||
360 | |||
323 | static int kvm_events_hash_fn(u64 key) | 361 | static int kvm_events_hash_fn(u64 key) |
324 | { | 362 | { |
325 | return key & (EVENTS_CACHE_SIZE - 1); | 363 | return key & (EVENTS_CACHE_SIZE - 1); |
@@ -436,7 +474,7 @@ static bool update_kvm_event(struct kvm_event *event, int vcpu_id, | |||
436 | static bool handle_end_event(struct perf_kvm_stat *kvm, | 474 | static bool handle_end_event(struct perf_kvm_stat *kvm, |
437 | struct vcpu_event_record *vcpu_record, | 475 | struct vcpu_event_record *vcpu_record, |
438 | struct event_key *key, | 476 | struct event_key *key, |
439 | u64 timestamp) | 477 | struct perf_sample *sample) |
440 | { | 478 | { |
441 | struct kvm_event *event; | 479 | struct kvm_event *event; |
442 | u64 time_begin, time_diff; | 480 | u64 time_begin, time_diff; |
@@ -472,9 +510,25 @@ static bool handle_end_event(struct perf_kvm_stat *kvm, | |||
472 | vcpu_record->last_event = NULL; | 510 | vcpu_record->last_event = NULL; |
473 | vcpu_record->start_time = 0; | 511 | vcpu_record->start_time = 0; |
474 | 512 | ||
475 | BUG_ON(timestamp < time_begin); | 513 | /* seems to happen once in a while during live mode */ |
514 | if (sample->time < time_begin) { | ||
515 | pr_debug("End time before begin time; skipping event.\n"); | ||
516 | return true; | ||
517 | } | ||
518 | |||
519 | time_diff = sample->time - time_begin; | ||
520 | |||
521 | if (kvm->duration && time_diff > kvm->duration) { | ||
522 | char decode[32]; | ||
523 | |||
524 | kvm->events_ops->decode_key(kvm, &event->key, decode); | ||
525 | if (strcmp(decode, "HLT")) { | ||
526 | pr_info("%" PRIu64 " VM %d, vcpu %d: %s event took %" PRIu64 "usec\n", | ||
527 | sample->time, sample->pid, vcpu_record->vcpu_id, | ||
528 | decode, time_diff/1000); | ||
529 | } | ||
530 | } | ||
476 | 531 | ||
477 | time_diff = timestamp - time_begin; | ||
478 | return update_kvm_event(event, vcpu, time_diff); | 532 | return update_kvm_event(event, vcpu, time_diff); |
479 | } | 533 | } |
480 | 534 | ||
@@ -521,7 +575,7 @@ static bool handle_kvm_event(struct perf_kvm_stat *kvm, | |||
521 | return handle_begin_event(kvm, vcpu_record, &key, sample->time); | 575 | return handle_begin_event(kvm, vcpu_record, &key, sample->time); |
522 | 576 | ||
523 | if (kvm->events_ops->is_end_event(evsel, sample, &key)) | 577 | if (kvm->events_ops->is_end_event(evsel, sample, &key)) |
524 | return handle_end_event(kvm, vcpu_record, &key, sample->time); | 578 | return handle_end_event(kvm, vcpu_record, &key, sample); |
525 | 579 | ||
526 | return true; | 580 | return true; |
527 | } | 581 | } |
@@ -550,6 +604,8 @@ static int compare_kvm_event_ ## func(struct kvm_event *one, \ | |||
550 | GET_EVENT_KEY(time, time); | 604 | GET_EVENT_KEY(time, time); |
551 | COMPARE_EVENT_KEY(count, stats.n); | 605 | COMPARE_EVENT_KEY(count, stats.n); |
552 | COMPARE_EVENT_KEY(mean, stats.mean); | 606 | COMPARE_EVENT_KEY(mean, stats.mean); |
607 | GET_EVENT_KEY(max, stats.max); | ||
608 | GET_EVENT_KEY(min, stats.min); | ||
553 | 609 | ||
554 | #define DEF_SORT_NAME_KEY(name, compare_key) \ | 610 | #define DEF_SORT_NAME_KEY(name, compare_key) \ |
555 | { #name, compare_kvm_event_ ## compare_key } | 611 | { #name, compare_kvm_event_ ## compare_key } |
@@ -639,43 +695,81 @@ static struct kvm_event *pop_from_result(struct rb_root *result) | |||
639 | return container_of(node, struct kvm_event, rb); | 695 | return container_of(node, struct kvm_event, rb); |
640 | } | 696 | } |
641 | 697 | ||
642 | static void print_vcpu_info(int vcpu) | 698 | static void print_vcpu_info(struct perf_kvm_stat *kvm) |
643 | { | 699 | { |
700 | int vcpu = kvm->trace_vcpu; | ||
701 | |||
644 | pr_info("Analyze events for "); | 702 | pr_info("Analyze events for "); |
645 | 703 | ||
704 | if (kvm->live) { | ||
705 | if (kvm->opts.target.system_wide) | ||
706 | pr_info("all VMs, "); | ||
707 | else if (kvm->opts.target.pid) | ||
708 | pr_info("pid(s) %s, ", kvm->opts.target.pid); | ||
709 | else | ||
710 | pr_info("dazed and confused on what is monitored, "); | ||
711 | } | ||
712 | |||
646 | if (vcpu == -1) | 713 | if (vcpu == -1) |
647 | pr_info("all VCPUs:\n\n"); | 714 | pr_info("all VCPUs:\n\n"); |
648 | else | 715 | else |
649 | pr_info("VCPU %d:\n\n", vcpu); | 716 | pr_info("VCPU %d:\n\n", vcpu); |
650 | } | 717 | } |
651 | 718 | ||
719 | static void show_timeofday(void) | ||
720 | { | ||
721 | char date[64]; | ||
722 | struct timeval tv; | ||
723 | struct tm ltime; | ||
724 | |||
725 | gettimeofday(&tv, NULL); | ||
726 | if (localtime_r(&tv.tv_sec, <ime)) { | ||
727 | strftime(date, sizeof(date), "%H:%M:%S", <ime); | ||
728 | pr_info("%s.%06ld", date, tv.tv_usec); | ||
729 | } else | ||
730 | pr_info("00:00:00.000000"); | ||
731 | |||
732 | return; | ||
733 | } | ||
734 | |||
652 | static void print_result(struct perf_kvm_stat *kvm) | 735 | static void print_result(struct perf_kvm_stat *kvm) |
653 | { | 736 | { |
654 | char decode[20]; | 737 | char decode[20]; |
655 | struct kvm_event *event; | 738 | struct kvm_event *event; |
656 | int vcpu = kvm->trace_vcpu; | 739 | int vcpu = kvm->trace_vcpu; |
657 | 740 | ||
741 | if (kvm->live) { | ||
742 | puts(CONSOLE_CLEAR); | ||
743 | show_timeofday(); | ||
744 | } | ||
745 | |||
658 | pr_info("\n\n"); | 746 | pr_info("\n\n"); |
659 | print_vcpu_info(vcpu); | 747 | print_vcpu_info(kvm); |
660 | pr_info("%20s ", kvm->events_ops->name); | 748 | pr_info("%20s ", kvm->events_ops->name); |
661 | pr_info("%10s ", "Samples"); | 749 | pr_info("%10s ", "Samples"); |
662 | pr_info("%9s ", "Samples%"); | 750 | pr_info("%9s ", "Samples%"); |
663 | 751 | ||
664 | pr_info("%9s ", "Time%"); | 752 | pr_info("%9s ", "Time%"); |
753 | pr_info("%10s ", "Min Time"); | ||
754 | pr_info("%10s ", "Max Time"); | ||
665 | pr_info("%16s ", "Avg time"); | 755 | pr_info("%16s ", "Avg time"); |
666 | pr_info("\n\n"); | 756 | pr_info("\n\n"); |
667 | 757 | ||
668 | while ((event = pop_from_result(&kvm->result))) { | 758 | while ((event = pop_from_result(&kvm->result))) { |
669 | u64 ecount, etime; | 759 | u64 ecount, etime, max, min; |
670 | 760 | ||
671 | ecount = get_event_count(event, vcpu); | 761 | ecount = get_event_count(event, vcpu); |
672 | etime = get_event_time(event, vcpu); | 762 | etime = get_event_time(event, vcpu); |
763 | max = get_event_max(event, vcpu); | ||
764 | min = get_event_min(event, vcpu); | ||
673 | 765 | ||
674 | kvm->events_ops->decode_key(kvm, &event->key, decode); | 766 | kvm->events_ops->decode_key(kvm, &event->key, decode); |
675 | pr_info("%20s ", decode); | 767 | pr_info("%20s ", decode); |
676 | pr_info("%10llu ", (unsigned long long)ecount); | 768 | pr_info("%10llu ", (unsigned long long)ecount); |
677 | pr_info("%8.2f%% ", (double)ecount / kvm->total_count * 100); | 769 | pr_info("%8.2f%% ", (double)ecount / kvm->total_count * 100); |
678 | pr_info("%8.2f%% ", (double)etime / kvm->total_time * 100); | 770 | pr_info("%8.2f%% ", (double)etime / kvm->total_time * 100); |
771 | pr_info("%8" PRIu64 "us ", min / 1000); | ||
772 | pr_info("%8" PRIu64 "us ", max / 1000); | ||
679 | pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3, | 773 | pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3, |
680 | kvm_event_rel_stddev(vcpu, event)); | 774 | kvm_event_rel_stddev(vcpu, event)); |
681 | pr_info("\n"); | 775 | pr_info("\n"); |
@@ -683,6 +777,29 @@ static void print_result(struct perf_kvm_stat *kvm) | |||
683 | 777 | ||
684 | pr_info("\nTotal Samples:%" PRIu64 ", Total events handled time:%.2fus.\n\n", | 778 | pr_info("\nTotal Samples:%" PRIu64 ", Total events handled time:%.2fus.\n\n", |
685 | kvm->total_count, kvm->total_time / 1e3); | 779 | kvm->total_count, kvm->total_time / 1e3); |
780 | |||
781 | if (kvm->lost_events) | ||
782 | pr_info("\nLost events: %" PRIu64 "\n\n", kvm->lost_events); | ||
783 | } | ||
784 | |||
785 | static int process_lost_event(struct perf_tool *tool, | ||
786 | union perf_event *event __maybe_unused, | ||
787 | struct perf_sample *sample __maybe_unused, | ||
788 | struct machine *machine __maybe_unused) | ||
789 | { | ||
790 | struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat, tool); | ||
791 | |||
792 | kvm->lost_events++; | ||
793 | return 0; | ||
794 | } | ||
795 | |||
796 | static bool skip_sample(struct perf_kvm_stat *kvm, | ||
797 | struct perf_sample *sample) | ||
798 | { | ||
799 | if (kvm->pid_list && intlist__find(kvm->pid_list, sample->pid) == NULL) | ||
800 | return true; | ||
801 | |||
802 | return false; | ||
686 | } | 803 | } |
687 | 804 | ||
688 | static int process_sample_event(struct perf_tool *tool, | 805 | static int process_sample_event(struct perf_tool *tool, |
@@ -691,10 +808,14 @@ static int process_sample_event(struct perf_tool *tool, | |||
691 | struct perf_evsel *evsel, | 808 | struct perf_evsel *evsel, |
692 | struct machine *machine) | 809 | struct machine *machine) |
693 | { | 810 | { |
694 | struct thread *thread = machine__findnew_thread(machine, sample->tid); | 811 | struct thread *thread; |
695 | struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat, | 812 | struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat, |
696 | tool); | 813 | tool); |
697 | 814 | ||
815 | if (skip_sample(kvm, sample)) | ||
816 | return 0; | ||
817 | |||
818 | thread = machine__findnew_thread(machine, sample->pid, sample->tid); | ||
698 | if (thread == NULL) { | 819 | if (thread == NULL) { |
699 | pr_debug("problem processing %d event, skipping it.\n", | 820 | pr_debug("problem processing %d event, skipping it.\n", |
700 | event->header.type); | 821 | event->header.type); |
@@ -707,10 +828,20 @@ static int process_sample_event(struct perf_tool *tool, | |||
707 | return 0; | 828 | return 0; |
708 | } | 829 | } |
709 | 830 | ||
710 | static int get_cpu_isa(struct perf_session *session) | 831 | static int cpu_isa_config(struct perf_kvm_stat *kvm) |
711 | { | 832 | { |
712 | char *cpuid = session->header.env.cpuid; | 833 | char buf[64], *cpuid; |
713 | int isa; | 834 | int err, isa; |
835 | |||
836 | if (kvm->live) { | ||
837 | err = get_cpuid(buf, sizeof(buf)); | ||
838 | if (err != 0) { | ||
839 | pr_err("Failed to look up CPU type (Intel or AMD)\n"); | ||
840 | return err; | ||
841 | } | ||
842 | cpuid = buf; | ||
843 | } else | ||
844 | cpuid = kvm->session->header.env.cpuid; | ||
714 | 845 | ||
715 | if (strstr(cpuid, "Intel")) | 846 | if (strstr(cpuid, "Intel")) |
716 | isa = 1; | 847 | isa = 1; |
@@ -718,10 +849,361 @@ static int get_cpu_isa(struct perf_session *session) | |||
718 | isa = 0; | 849 | isa = 0; |
719 | else { | 850 | else { |
720 | pr_err("CPU %s is not supported.\n", cpuid); | 851 | pr_err("CPU %s is not supported.\n", cpuid); |
721 | isa = -ENOTSUP; | 852 | return -ENOTSUP; |
853 | } | ||
854 | |||
855 | if (isa == 1) { | ||
856 | kvm->exit_reasons = vmx_exit_reasons; | ||
857 | kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons); | ||
858 | kvm->exit_reasons_isa = "VMX"; | ||
859 | } | ||
860 | |||
861 | return 0; | ||
862 | } | ||
863 | |||
864 | static bool verify_vcpu(int vcpu) | ||
865 | { | ||
866 | if (vcpu != -1 && vcpu < 0) { | ||
867 | pr_err("Invalid vcpu:%d.\n", vcpu); | ||
868 | return false; | ||
869 | } | ||
870 | |||
871 | return true; | ||
872 | } | ||
873 | |||
874 | /* keeping the max events to a modest level to keep | ||
875 | * the processing of samples per mmap smooth. | ||
876 | */ | ||
877 | #define PERF_KVM__MAX_EVENTS_PER_MMAP 25 | ||
878 | |||
879 | static s64 perf_kvm__mmap_read_idx(struct perf_kvm_stat *kvm, int idx, | ||
880 | u64 *mmap_time) | ||
881 | { | ||
882 | union perf_event *event; | ||
883 | struct perf_sample sample; | ||
884 | s64 n = 0; | ||
885 | int err; | ||
886 | |||
887 | *mmap_time = ULLONG_MAX; | ||
888 | while ((event = perf_evlist__mmap_read(kvm->evlist, idx)) != NULL) { | ||
889 | err = perf_evlist__parse_sample(kvm->evlist, event, &sample); | ||
890 | if (err) { | ||
891 | pr_err("Failed to parse sample\n"); | ||
892 | return -1; | ||
893 | } | ||
894 | |||
895 | err = perf_session_queue_event(kvm->session, event, &sample, 0); | ||
896 | if (err) { | ||
897 | pr_err("Failed to enqueue sample: %d\n", err); | ||
898 | return -1; | ||
899 | } | ||
900 | |||
901 | /* save time stamp of our first sample for this mmap */ | ||
902 | if (n == 0) | ||
903 | *mmap_time = sample.time; | ||
904 | |||
905 | /* limit events per mmap handled all at once */ | ||
906 | n++; | ||
907 | if (n == PERF_KVM__MAX_EVENTS_PER_MMAP) | ||
908 | break; | ||
909 | } | ||
910 | |||
911 | return n; | ||
912 | } | ||
913 | |||
914 | static int perf_kvm__mmap_read(struct perf_kvm_stat *kvm) | ||
915 | { | ||
916 | int i, err, throttled = 0; | ||
917 | s64 n, ntotal = 0; | ||
918 | u64 flush_time = ULLONG_MAX, mmap_time; | ||
919 | |||
920 | for (i = 0; i < kvm->evlist->nr_mmaps; i++) { | ||
921 | n = perf_kvm__mmap_read_idx(kvm, i, &mmap_time); | ||
922 | if (n < 0) | ||
923 | return -1; | ||
924 | |||
925 | /* flush time is going to be the minimum of all the individual | ||
926 | * mmap times. Essentially, we flush all the samples queued up | ||
927 | * from the last pass under our minimal start time -- that leaves | ||
928 | * a very small race for samples to come in with a lower timestamp. | ||
929 | * The ioctl to return the perf_clock timestamp should close the | ||
930 | * race entirely. | ||
931 | */ | ||
932 | if (mmap_time < flush_time) | ||
933 | flush_time = mmap_time; | ||
934 | |||
935 | ntotal += n; | ||
936 | if (n == PERF_KVM__MAX_EVENTS_PER_MMAP) | ||
937 | throttled = 1; | ||
938 | } | ||
939 | |||
940 | /* flush queue after each round in which we processed events */ | ||
941 | if (ntotal) { | ||
942 | kvm->session->ordered_samples.next_flush = flush_time; | ||
943 | err = kvm->tool.finished_round(&kvm->tool, NULL, kvm->session); | ||
944 | if (err) { | ||
945 | if (kvm->lost_events) | ||
946 | pr_info("\nLost events: %" PRIu64 "\n\n", | ||
947 | kvm->lost_events); | ||
948 | return err; | ||
949 | } | ||
950 | } | ||
951 | |||
952 | return throttled; | ||
953 | } | ||
954 | |||
955 | static volatile int done; | ||
956 | |||
957 | static void sig_handler(int sig __maybe_unused) | ||
958 | { | ||
959 | done = 1; | ||
960 | } | ||
961 | |||
962 | static int perf_kvm__timerfd_create(struct perf_kvm_stat *kvm) | ||
963 | { | ||
964 | struct itimerspec new_value; | ||
965 | int rc = -1; | ||
966 | |||
967 | kvm->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); | ||
968 | if (kvm->timerfd < 0) { | ||
969 | pr_err("timerfd_create failed\n"); | ||
970 | goto out; | ||
971 | } | ||
972 | |||
973 | new_value.it_value.tv_sec = kvm->display_time; | ||
974 | new_value.it_value.tv_nsec = 0; | ||
975 | new_value.it_interval.tv_sec = kvm->display_time; | ||
976 | new_value.it_interval.tv_nsec = 0; | ||
977 | |||
978 | if (timerfd_settime(kvm->timerfd, 0, &new_value, NULL) != 0) { | ||
979 | pr_err("timerfd_settime failed: %d\n", errno); | ||
980 | close(kvm->timerfd); | ||
981 | goto out; | ||
982 | } | ||
983 | |||
984 | rc = 0; | ||
985 | out: | ||
986 | return rc; | ||
987 | } | ||
988 | |||
989 | static int perf_kvm__handle_timerfd(struct perf_kvm_stat *kvm) | ||
990 | { | ||
991 | uint64_t c; | ||
992 | int rc; | ||
993 | |||
994 | rc = read(kvm->timerfd, &c, sizeof(uint64_t)); | ||
995 | if (rc < 0) { | ||
996 | if (errno == EAGAIN) | ||
997 | return 0; | ||
998 | |||
999 | pr_err("Failed to read timer fd: %d\n", errno); | ||
1000 | return -1; | ||
1001 | } | ||
1002 | |||
1003 | if (rc != sizeof(uint64_t)) { | ||
1004 | pr_err("Error reading timer fd - invalid size returned\n"); | ||
1005 | return -1; | ||
1006 | } | ||
1007 | |||
1008 | if (c != 1) | ||
1009 | pr_debug("Missed timer beats: %" PRIu64 "\n", c-1); | ||
1010 | |||
1011 | /* update display */ | ||
1012 | sort_result(kvm); | ||
1013 | print_result(kvm); | ||
1014 | |||
1015 | /* reset counts */ | ||
1016 | clear_events_cache_stats(kvm->kvm_events_cache); | ||
1017 | kvm->total_count = 0; | ||
1018 | kvm->total_time = 0; | ||
1019 | kvm->lost_events = 0; | ||
1020 | |||
1021 | return 0; | ||
1022 | } | ||
1023 | |||
1024 | static int fd_set_nonblock(int fd) | ||
1025 | { | ||
1026 | long arg = 0; | ||
1027 | |||
1028 | arg = fcntl(fd, F_GETFL); | ||
1029 | if (arg < 0) { | ||
1030 | pr_err("Failed to get current flags for fd %d\n", fd); | ||
1031 | return -1; | ||
1032 | } | ||
1033 | |||
1034 | if (fcntl(fd, F_SETFL, arg | O_NONBLOCK) < 0) { | ||
1035 | pr_err("Failed to set non-block option on fd %d\n", fd); | ||
1036 | return -1; | ||
1037 | } | ||
1038 | |||
1039 | return 0; | ||
1040 | } | ||
1041 | |||
1042 | static | ||
1043 | int perf_kvm__handle_stdin(struct termios *tc_now, struct termios *tc_save) | ||
1044 | { | ||
1045 | int c; | ||
1046 | |||
1047 | tcsetattr(0, TCSANOW, tc_now); | ||
1048 | c = getc(stdin); | ||
1049 | tcsetattr(0, TCSAFLUSH, tc_save); | ||
1050 | |||
1051 | if (c == 'q') | ||
1052 | return 1; | ||
1053 | |||
1054 | return 0; | ||
1055 | } | ||
1056 | |||
1057 | static int kvm_events_live_report(struct perf_kvm_stat *kvm) | ||
1058 | { | ||
1059 | struct pollfd *pollfds = NULL; | ||
1060 | int nr_fds, nr_stdin, ret, err = -EINVAL; | ||
1061 | struct termios tc, save; | ||
1062 | |||
1063 | /* live flag must be set first */ | ||
1064 | kvm->live = true; | ||
1065 | |||
1066 | ret = cpu_isa_config(kvm); | ||
1067 | if (ret < 0) | ||
1068 | return ret; | ||
1069 | |||
1070 | if (!verify_vcpu(kvm->trace_vcpu) || | ||
1071 | !select_key(kvm) || | ||
1072 | !register_kvm_events_ops(kvm)) { | ||
1073 | goto out; | ||
1074 | } | ||
1075 | |||
1076 | init_kvm_event_record(kvm); | ||
1077 | |||
1078 | tcgetattr(0, &save); | ||
1079 | tc = save; | ||
1080 | tc.c_lflag &= ~(ICANON | ECHO); | ||
1081 | tc.c_cc[VMIN] = 0; | ||
1082 | tc.c_cc[VTIME] = 0; | ||
1083 | |||
1084 | signal(SIGINT, sig_handler); | ||
1085 | signal(SIGTERM, sig_handler); | ||
1086 | |||
1087 | /* copy pollfds -- need to add timerfd and stdin */ | ||
1088 | nr_fds = kvm->evlist->nr_fds; | ||
1089 | pollfds = zalloc(sizeof(struct pollfd) * (nr_fds + 2)); | ||
1090 | if (!pollfds) { | ||
1091 | err = -ENOMEM; | ||
1092 | goto out; | ||
722 | } | 1093 | } |
1094 | memcpy(pollfds, kvm->evlist->pollfd, | ||
1095 | sizeof(struct pollfd) * kvm->evlist->nr_fds); | ||
1096 | |||
1097 | /* add timer fd */ | ||
1098 | if (perf_kvm__timerfd_create(kvm) < 0) { | ||
1099 | err = -1; | ||
1100 | goto out; | ||
1101 | } | ||
1102 | |||
1103 | pollfds[nr_fds].fd = kvm->timerfd; | ||
1104 | pollfds[nr_fds].events = POLLIN; | ||
1105 | nr_fds++; | ||
1106 | |||
1107 | pollfds[nr_fds].fd = fileno(stdin); | ||
1108 | pollfds[nr_fds].events = POLLIN; | ||
1109 | nr_stdin = nr_fds; | ||
1110 | nr_fds++; | ||
1111 | if (fd_set_nonblock(fileno(stdin)) != 0) | ||
1112 | goto out; | ||
1113 | |||
1114 | /* everything is good - enable the events and process */ | ||
1115 | perf_evlist__enable(kvm->evlist); | ||
1116 | |||
1117 | while (!done) { | ||
1118 | int rc; | ||
1119 | |||
1120 | rc = perf_kvm__mmap_read(kvm); | ||
1121 | if (rc < 0) | ||
1122 | break; | ||
1123 | |||
1124 | err = perf_kvm__handle_timerfd(kvm); | ||
1125 | if (err) | ||
1126 | goto out; | ||
1127 | |||
1128 | if (pollfds[nr_stdin].revents & POLLIN) | ||
1129 | done = perf_kvm__handle_stdin(&tc, &save); | ||
1130 | |||
1131 | if (!rc && !done) | ||
1132 | err = poll(pollfds, nr_fds, 100); | ||
1133 | } | ||
1134 | |||
1135 | perf_evlist__disable(kvm->evlist); | ||
1136 | |||
1137 | if (err == 0) { | ||
1138 | sort_result(kvm); | ||
1139 | print_result(kvm); | ||
1140 | } | ||
1141 | |||
1142 | out: | ||
1143 | if (kvm->timerfd >= 0) | ||
1144 | close(kvm->timerfd); | ||
1145 | |||
1146 | if (pollfds) | ||
1147 | free(pollfds); | ||
723 | 1148 | ||
724 | return isa; | 1149 | return err; |
1150 | } | ||
1151 | |||
1152 | static int kvm_live_open_events(struct perf_kvm_stat *kvm) | ||
1153 | { | ||
1154 | int err, rc = -1; | ||
1155 | struct perf_evsel *pos; | ||
1156 | struct perf_evlist *evlist = kvm->evlist; | ||
1157 | |||
1158 | perf_evlist__config(evlist, &kvm->opts); | ||
1159 | |||
1160 | /* | ||
1161 | * Note: exclude_{guest,host} do not apply here. | ||
1162 | * This command processes KVM tracepoints from host only | ||
1163 | */ | ||
1164 | list_for_each_entry(pos, &evlist->entries, node) { | ||
1165 | struct perf_event_attr *attr = &pos->attr; | ||
1166 | |||
1167 | /* make sure these *are* set */ | ||
1168 | perf_evsel__set_sample_bit(pos, TID); | ||
1169 | perf_evsel__set_sample_bit(pos, TIME); | ||
1170 | perf_evsel__set_sample_bit(pos, CPU); | ||
1171 | perf_evsel__set_sample_bit(pos, RAW); | ||
1172 | /* make sure these are *not*; want as small a sample as possible */ | ||
1173 | perf_evsel__reset_sample_bit(pos, PERIOD); | ||
1174 | perf_evsel__reset_sample_bit(pos, IP); | ||
1175 | perf_evsel__reset_sample_bit(pos, CALLCHAIN); | ||
1176 | perf_evsel__reset_sample_bit(pos, ADDR); | ||
1177 | perf_evsel__reset_sample_bit(pos, READ); | ||
1178 | attr->mmap = 0; | ||
1179 | attr->comm = 0; | ||
1180 | attr->task = 0; | ||
1181 | |||
1182 | attr->sample_period = 1; | ||
1183 | |||
1184 | attr->watermark = 0; | ||
1185 | attr->wakeup_events = 1000; | ||
1186 | |||
1187 | /* will enable all once we are ready */ | ||
1188 | attr->disabled = 1; | ||
1189 | } | ||
1190 | |||
1191 | err = perf_evlist__open(evlist); | ||
1192 | if (err < 0) { | ||
1193 | printf("Couldn't create the events: %s\n", strerror(errno)); | ||
1194 | goto out; | ||
1195 | } | ||
1196 | |||
1197 | if (perf_evlist__mmap(evlist, kvm->opts.mmap_pages, false) < 0) { | ||
1198 | ui__error("Failed to mmap the events: %s\n", strerror(errno)); | ||
1199 | perf_evlist__close(evlist); | ||
1200 | goto out; | ||
1201 | } | ||
1202 | |||
1203 | rc = 0; | ||
1204 | |||
1205 | out: | ||
1206 | return rc; | ||
725 | } | 1207 | } |
726 | 1208 | ||
727 | static int read_events(struct perf_kvm_stat *kvm) | 1209 | static int read_events(struct perf_kvm_stat *kvm) |
@@ -749,28 +1231,24 @@ static int read_events(struct perf_kvm_stat *kvm) | |||
749 | * Do not use 'isa' recorded in kvm_exit tracepoint since it is not | 1231 | * Do not use 'isa' recorded in kvm_exit tracepoint since it is not |
750 | * traced in the old kernel. | 1232 | * traced in the old kernel. |
751 | */ | 1233 | */ |
752 | ret = get_cpu_isa(kvm->session); | 1234 | ret = cpu_isa_config(kvm); |
753 | |||
754 | if (ret < 0) | 1235 | if (ret < 0) |
755 | return ret; | 1236 | return ret; |
756 | 1237 | ||
757 | if (ret == 1) { | ||
758 | kvm->exit_reasons = vmx_exit_reasons; | ||
759 | kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons); | ||
760 | kvm->exit_reasons_isa = "VMX"; | ||
761 | } | ||
762 | |||
763 | return perf_session__process_events(kvm->session, &kvm->tool); | 1238 | return perf_session__process_events(kvm->session, &kvm->tool); |
764 | } | 1239 | } |
765 | 1240 | ||
766 | static bool verify_vcpu(int vcpu) | 1241 | static int parse_target_str(struct perf_kvm_stat *kvm) |
767 | { | 1242 | { |
768 | if (vcpu != -1 && vcpu < 0) { | 1243 | if (kvm->pid_str) { |
769 | pr_err("Invalid vcpu:%d.\n", vcpu); | 1244 | kvm->pid_list = intlist__new(kvm->pid_str); |
770 | return false; | 1245 | if (kvm->pid_list == NULL) { |
1246 | pr_err("Error parsing process id string\n"); | ||
1247 | return -EINVAL; | ||
1248 | } | ||
771 | } | 1249 | } |
772 | 1250 | ||
773 | return true; | 1251 | return 0; |
774 | } | 1252 | } |
775 | 1253 | ||
776 | static int kvm_events_report_vcpu(struct perf_kvm_stat *kvm) | 1254 | static int kvm_events_report_vcpu(struct perf_kvm_stat *kvm) |
@@ -778,6 +1256,9 @@ static int kvm_events_report_vcpu(struct perf_kvm_stat *kvm) | |||
778 | int ret = -EINVAL; | 1256 | int ret = -EINVAL; |
779 | int vcpu = kvm->trace_vcpu; | 1257 | int vcpu = kvm->trace_vcpu; |
780 | 1258 | ||
1259 | if (parse_target_str(kvm) != 0) | ||
1260 | goto exit; | ||
1261 | |||
781 | if (!verify_vcpu(vcpu)) | 1262 | if (!verify_vcpu(vcpu)) |
782 | goto exit; | 1263 | goto exit; |
783 | 1264 | ||
@@ -801,16 +1282,11 @@ exit: | |||
801 | return ret; | 1282 | return ret; |
802 | } | 1283 | } |
803 | 1284 | ||
804 | static const char * const record_args[] = { | 1285 | static const char * const kvm_events_tp[] = { |
805 | "record", | 1286 | "kvm:kvm_entry", |
806 | "-R", | 1287 | "kvm:kvm_exit", |
807 | "-f", | 1288 | "kvm:kvm_mmio", |
808 | "-m", "1024", | 1289 | "kvm:kvm_pio", |
809 | "-c", "1", | ||
810 | "-e", "kvm:kvm_entry", | ||
811 | "-e", "kvm:kvm_exit", | ||
812 | "-e", "kvm:kvm_mmio", | ||
813 | "-e", "kvm:kvm_pio", | ||
814 | }; | 1290 | }; |
815 | 1291 | ||
816 | #define STRDUP_FAIL_EXIT(s) \ | 1292 | #define STRDUP_FAIL_EXIT(s) \ |
@@ -826,8 +1302,15 @@ kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) | |||
826 | { | 1302 | { |
827 | unsigned int rec_argc, i, j; | 1303 | unsigned int rec_argc, i, j; |
828 | const char **rec_argv; | 1304 | const char **rec_argv; |
1305 | const char * const record_args[] = { | ||
1306 | "record", | ||
1307 | "-R", | ||
1308 | "-m", "1024", | ||
1309 | "-c", "1", | ||
1310 | }; | ||
829 | 1311 | ||
830 | rec_argc = ARRAY_SIZE(record_args) + argc + 2; | 1312 | rec_argc = ARRAY_SIZE(record_args) + argc + 2 + |
1313 | 2 * ARRAY_SIZE(kvm_events_tp); | ||
831 | rec_argv = calloc(rec_argc + 1, sizeof(char *)); | 1314 | rec_argv = calloc(rec_argc + 1, sizeof(char *)); |
832 | 1315 | ||
833 | if (rec_argv == NULL) | 1316 | if (rec_argv == NULL) |
@@ -836,6 +1319,11 @@ kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) | |||
836 | for (i = 0; i < ARRAY_SIZE(record_args); i++) | 1319 | for (i = 0; i < ARRAY_SIZE(record_args); i++) |
837 | rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]); | 1320 | rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]); |
838 | 1321 | ||
1322 | for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) { | ||
1323 | rec_argv[i++] = "-e"; | ||
1324 | rec_argv[i++] = STRDUP_FAIL_EXIT(kvm_events_tp[j]); | ||
1325 | } | ||
1326 | |||
839 | rec_argv[i++] = STRDUP_FAIL_EXIT("-o"); | 1327 | rec_argv[i++] = STRDUP_FAIL_EXIT("-o"); |
840 | rec_argv[i++] = STRDUP_FAIL_EXIT(kvm->file_name); | 1328 | rec_argv[i++] = STRDUP_FAIL_EXIT(kvm->file_name); |
841 | 1329 | ||
@@ -856,6 +1344,8 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) | |||
856 | OPT_STRING('k', "key", &kvm->sort_key, "sort-key", | 1344 | OPT_STRING('k', "key", &kvm->sort_key, "sort-key", |
857 | "key for sorting: sample(sort by samples number)" | 1345 | "key for sorting: sample(sort by samples number)" |
858 | " time (sort by avg time)"), | 1346 | " time (sort by avg time)"), |
1347 | OPT_STRING('p', "pid", &kvm->pid_str, "pid", | ||
1348 | "analyze events only for given process id(s)"), | ||
859 | OPT_END() | 1349 | OPT_END() |
860 | }; | 1350 | }; |
861 | 1351 | ||
@@ -878,6 +1368,190 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) | |||
878 | return kvm_events_report_vcpu(kvm); | 1368 | return kvm_events_report_vcpu(kvm); |
879 | } | 1369 | } |
880 | 1370 | ||
1371 | static struct perf_evlist *kvm_live_event_list(void) | ||
1372 | { | ||
1373 | struct perf_evlist *evlist; | ||
1374 | char *tp, *name, *sys; | ||
1375 | unsigned int j; | ||
1376 | int err = -1; | ||
1377 | |||
1378 | evlist = perf_evlist__new(); | ||
1379 | if (evlist == NULL) | ||
1380 | return NULL; | ||
1381 | |||
1382 | for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) { | ||
1383 | |||
1384 | tp = strdup(kvm_events_tp[j]); | ||
1385 | if (tp == NULL) | ||
1386 | goto out; | ||
1387 | |||
1388 | /* split tracepoint into subsystem and name */ | ||
1389 | sys = tp; | ||
1390 | name = strchr(tp, ':'); | ||
1391 | if (name == NULL) { | ||
1392 | pr_err("Error parsing %s tracepoint: subsystem delimiter not found\n", | ||
1393 | kvm_events_tp[j]); | ||
1394 | free(tp); | ||
1395 | goto out; | ||
1396 | } | ||
1397 | *name = '\0'; | ||
1398 | name++; | ||
1399 | |||
1400 | if (perf_evlist__add_newtp(evlist, sys, name, NULL)) { | ||
1401 | pr_err("Failed to add %s tracepoint to the list\n", kvm_events_tp[j]); | ||
1402 | free(tp); | ||
1403 | goto out; | ||
1404 | } | ||
1405 | |||
1406 | free(tp); | ||
1407 | } | ||
1408 | |||
1409 | err = 0; | ||
1410 | |||
1411 | out: | ||
1412 | if (err) { | ||
1413 | perf_evlist__delete(evlist); | ||
1414 | evlist = NULL; | ||
1415 | } | ||
1416 | |||
1417 | return evlist; | ||
1418 | } | ||
1419 | |||
1420 | static int kvm_events_live(struct perf_kvm_stat *kvm, | ||
1421 | int argc, const char **argv) | ||
1422 | { | ||
1423 | char errbuf[BUFSIZ]; | ||
1424 | int err; | ||
1425 | |||
1426 | const struct option live_options[] = { | ||
1427 | OPT_STRING('p', "pid", &kvm->opts.target.pid, "pid", | ||
1428 | "record events on existing process id"), | ||
1429 | OPT_UINTEGER('m', "mmap-pages", &kvm->opts.mmap_pages, | ||
1430 | "number of mmap data pages"), | ||
1431 | OPT_INCR('v', "verbose", &verbose, | ||
1432 | "be more verbose (show counter open errors, etc)"), | ||
1433 | OPT_BOOLEAN('a', "all-cpus", &kvm->opts.target.system_wide, | ||
1434 | "system-wide collection from all CPUs"), | ||
1435 | OPT_UINTEGER('d', "display", &kvm->display_time, | ||
1436 | "time in seconds between display updates"), | ||
1437 | OPT_STRING(0, "event", &kvm->report_event, "report event", | ||
1438 | "event for reporting: vmexit, mmio, ioport"), | ||
1439 | OPT_INTEGER(0, "vcpu", &kvm->trace_vcpu, | ||
1440 | "vcpu id to report"), | ||
1441 | OPT_STRING('k', "key", &kvm->sort_key, "sort-key", | ||
1442 | "key for sorting: sample(sort by samples number)" | ||
1443 | " time (sort by avg time)"), | ||
1444 | OPT_U64(0, "duration", &kvm->duration, | ||
1445 | "show events other than HALT that take longer than duration usecs"), | ||
1446 | OPT_END() | ||
1447 | }; | ||
1448 | const char * const live_usage[] = { | ||
1449 | "perf kvm stat live [<options>]", | ||
1450 | NULL | ||
1451 | }; | ||
1452 | |||
1453 | |||
1454 | /* event handling */ | ||
1455 | kvm->tool.sample = process_sample_event; | ||
1456 | kvm->tool.comm = perf_event__process_comm; | ||
1457 | kvm->tool.exit = perf_event__process_exit; | ||
1458 | kvm->tool.fork = perf_event__process_fork; | ||
1459 | kvm->tool.lost = process_lost_event; | ||
1460 | kvm->tool.ordered_samples = true; | ||
1461 | perf_tool__fill_defaults(&kvm->tool); | ||
1462 | |||
1463 | /* set defaults */ | ||
1464 | kvm->display_time = 1; | ||
1465 | kvm->opts.user_interval = 1; | ||
1466 | kvm->opts.mmap_pages = 512; | ||
1467 | kvm->opts.target.uses_mmap = false; | ||
1468 | kvm->opts.target.uid_str = NULL; | ||
1469 | kvm->opts.target.uid = UINT_MAX; | ||
1470 | |||
1471 | symbol__init(); | ||
1472 | disable_buildid_cache(); | ||
1473 | |||
1474 | use_browser = 0; | ||
1475 | setup_browser(false); | ||
1476 | |||
1477 | if (argc) { | ||
1478 | argc = parse_options(argc, argv, live_options, | ||
1479 | live_usage, 0); | ||
1480 | if (argc) | ||
1481 | usage_with_options(live_usage, live_options); | ||
1482 | } | ||
1483 | |||
1484 | kvm->duration *= NSEC_PER_USEC; /* convert usec to nsec */ | ||
1485 | |||
1486 | /* | ||
1487 | * target related setups | ||
1488 | */ | ||
1489 | err = perf_target__validate(&kvm->opts.target); | ||
1490 | if (err) { | ||
1491 | perf_target__strerror(&kvm->opts.target, err, errbuf, BUFSIZ); | ||
1492 | ui__warning("%s", errbuf); | ||
1493 | } | ||
1494 | |||
1495 | if (perf_target__none(&kvm->opts.target)) | ||
1496 | kvm->opts.target.system_wide = true; | ||
1497 | |||
1498 | |||
1499 | /* | ||
1500 | * generate the event list | ||
1501 | */ | ||
1502 | kvm->evlist = kvm_live_event_list(); | ||
1503 | if (kvm->evlist == NULL) { | ||
1504 | err = -1; | ||
1505 | goto out; | ||
1506 | } | ||
1507 | |||
1508 | symbol_conf.nr_events = kvm->evlist->nr_entries; | ||
1509 | |||
1510 | if (perf_evlist__create_maps(kvm->evlist, &kvm->opts.target) < 0) | ||
1511 | usage_with_options(live_usage, live_options); | ||
1512 | |||
1513 | /* | ||
1514 | * perf session | ||
1515 | */ | ||
1516 | kvm->session = perf_session__new(NULL, O_WRONLY, false, false, &kvm->tool); | ||
1517 | if (kvm->session == NULL) { | ||
1518 | err = -ENOMEM; | ||
1519 | goto out; | ||
1520 | } | ||
1521 | kvm->session->evlist = kvm->evlist; | ||
1522 | perf_session__set_id_hdr_size(kvm->session); | ||
1523 | |||
1524 | |||
1525 | if (perf_target__has_task(&kvm->opts.target)) | ||
1526 | perf_event__synthesize_thread_map(&kvm->tool, | ||
1527 | kvm->evlist->threads, | ||
1528 | perf_event__process, | ||
1529 | &kvm->session->machines.host); | ||
1530 | else | ||
1531 | perf_event__synthesize_threads(&kvm->tool, perf_event__process, | ||
1532 | &kvm->session->machines.host); | ||
1533 | |||
1534 | |||
1535 | err = kvm_live_open_events(kvm); | ||
1536 | if (err) | ||
1537 | goto out; | ||
1538 | |||
1539 | err = kvm_events_live_report(kvm); | ||
1540 | |||
1541 | out: | ||
1542 | exit_browser(0); | ||
1543 | |||
1544 | if (kvm->session) | ||
1545 | perf_session__delete(kvm->session); | ||
1546 | kvm->session = NULL; | ||
1547 | if (kvm->evlist) { | ||
1548 | perf_evlist__delete_maps(kvm->evlist); | ||
1549 | perf_evlist__delete(kvm->evlist); | ||
1550 | } | ||
1551 | |||
1552 | return err; | ||
1553 | } | ||
1554 | |||
881 | static void print_kvm_stat_usage(void) | 1555 | static void print_kvm_stat_usage(void) |
882 | { | 1556 | { |
883 | printf("Usage: perf kvm stat <command>\n\n"); | 1557 | printf("Usage: perf kvm stat <command>\n\n"); |
@@ -885,6 +1559,7 @@ static void print_kvm_stat_usage(void) | |||
885 | printf("# Available commands:\n"); | 1559 | printf("# Available commands:\n"); |
886 | printf("\trecord: record kvm events\n"); | 1560 | printf("\trecord: record kvm events\n"); |
887 | printf("\treport: report statistical data of kvm events\n"); | 1561 | printf("\treport: report statistical data of kvm events\n"); |
1562 | printf("\tlive: live reporting of statistical data of kvm events\n"); | ||
888 | 1563 | ||
889 | printf("\nOtherwise, it is the alias of 'perf stat':\n"); | 1564 | printf("\nOtherwise, it is the alias of 'perf stat':\n"); |
890 | } | 1565 | } |
@@ -914,6 +1589,9 @@ static int kvm_cmd_stat(const char *file_name, int argc, const char **argv) | |||
914 | if (!strncmp(argv[1], "rep", 3)) | 1589 | if (!strncmp(argv[1], "rep", 3)) |
915 | return kvm_events_report(&kvm, argc - 1 , argv + 1); | 1590 | return kvm_events_report(&kvm, argc - 1 , argv + 1); |
916 | 1591 | ||
1592 | if (!strncmp(argv[1], "live", 4)) | ||
1593 | return kvm_events_live(&kvm, argc - 1 , argv + 1); | ||
1594 | |||
917 | perf_stat: | 1595 | perf_stat: |
918 | return cmd_stat(argc, argv, NULL); | 1596 | return cmd_stat(argc, argv, NULL); |
919 | } | 1597 | } |