#include <pthread.h>
#include <math.h>
-#if defined(__i386__) || defined(__x86_64__)
-#include <asm/svm.h>
-#include <asm/vmx.h>
-#include <asm/kvm.h>
-
-struct event_key {
- #define INVALID_KEY (~0ULL)
- u64 key;
- int info;
-};
-
-struct kvm_event_stats {
- u64 time;
- struct stats stats;
-};
-
-struct kvm_event {
- struct list_head hash_entry;
- struct rb_node rb;
-
- struct event_key key;
-
- struct kvm_event_stats total;
-
- #define DEFAULT_VCPU_NUM 8
- int max_vcpu;
- struct kvm_event_stats *vcpu;
-};
-
-typedef int (*key_cmp_fun)(struct kvm_event*, struct kvm_event*, int);
-
-struct kvm_event_key {
- const char *name;
- key_cmp_fun key;
-};
-
-
-struct perf_kvm_stat;
-
-struct kvm_events_ops {
- bool (*is_begin_event)(struct perf_evsel *evsel,
- struct perf_sample *sample,
- struct event_key *key);
- bool (*is_end_event)(struct perf_evsel *evsel,
- struct perf_sample *sample, struct event_key *key);
- void (*decode_key)(struct perf_kvm_stat *kvm, struct event_key *key,
- char decode[20]);
- const char *name;
-};
-
-struct exit_reasons_table {
- unsigned long exit_code;
- const char *reason;
-};
+#ifdef HAVE_KVM_STAT_SUPPORT
+#include <asm/kvm_perf.h>
+#include "util/kvm-stat.h"
-#define EVENTS_BITS 12
-#define EVENTS_CACHE_SIZE (1UL << EVENTS_BITS)
-
-struct perf_kvm_stat {
- struct perf_tool tool;
- struct record_opts opts;
- struct perf_evlist *evlist;
- struct perf_session *session;
-
- const char *file_name;
- const char *report_event;
- const char *sort_key;
- int trace_vcpu;
-
- struct exit_reasons_table *exit_reasons;
- int exit_reasons_size;
- const char *exit_reasons_isa;
-
- struct kvm_events_ops *events_ops;
- key_cmp_fun compare;
- struct list_head kvm_events_cache[EVENTS_CACHE_SIZE];
-
- u64 total_time;
- u64 total_count;
- u64 lost_events;
- u64 duration;
-
- const char *pid_str;
- struct intlist *pid_list;
-
- struct rb_root result;
-
- int timerfd;
- unsigned int display_time;
- bool live;
-};
-
-
-static void exit_event_get_key(struct perf_evsel *evsel,
- struct perf_sample *sample,
- struct event_key *key)
+void exit_event_get_key(struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct event_key *key)
{
key->info = 0;
- key->key = perf_evsel__intval(evsel, sample, "exit_reason");
+ key->key = perf_evsel__intval(evsel, sample, KVM_EXIT_REASON);
}
-static bool kvm_exit_event(struct perf_evsel *evsel)
+bool kvm_exit_event(struct perf_evsel *evsel)
{
- return !strcmp(evsel->name, "kvm:kvm_exit");
+ return !strcmp(evsel->name, KVM_EXIT_TRACE);
}
-static bool exit_event_begin(struct perf_evsel *evsel,
- struct perf_sample *sample, struct event_key *key)
+bool exit_event_begin(struct perf_evsel *evsel,
+ struct perf_sample *sample, struct event_key *key)
{
if (kvm_exit_event(evsel)) {
exit_event_get_key(evsel, sample, key);
return false;
}
-static bool kvm_entry_event(struct perf_evsel *evsel)
+bool kvm_entry_event(struct perf_evsel *evsel)
{
- return !strcmp(evsel->name, "kvm:kvm_entry");
+ return !strcmp(evsel->name, KVM_ENTRY_TRACE);
}
-static bool exit_event_end(struct perf_evsel *evsel,
- struct perf_sample *sample __maybe_unused,
- struct event_key *key __maybe_unused)
+bool exit_event_end(struct perf_evsel *evsel,
+ struct perf_sample *sample __maybe_unused,
+ struct event_key *key __maybe_unused)
{
return kvm_entry_event(evsel);
}
-static struct exit_reasons_table vmx_exit_reasons[] = {
- VMX_EXIT_REASONS
-};
-
-static struct exit_reasons_table svm_exit_reasons[] = {
- SVM_EXIT_REASONS
-};
-
-static const char *get_exit_reason(struct perf_kvm_stat *kvm, u64 exit_code)
+static const char *get_exit_reason(struct perf_kvm_stat *kvm,
+ struct exit_reasons_table *tbl,
+ u64 exit_code)
{
- int i = kvm->exit_reasons_size;
- struct exit_reasons_table *tbl = kvm->exit_reasons;
-
- while (i--) {
+ while (tbl->reason != NULL) {
if (tbl->exit_code == exit_code)
return tbl->reason;
tbl++;
return "UNKNOWN";
}
-static void exit_event_decode_key(struct perf_kvm_stat *kvm,
- struct event_key *key,
- char decode[20])
-{
- const char *exit_reason = get_exit_reason(kvm, key->key);
-
- scnprintf(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 mmio events, 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...).
- */
-static void mmio_event_get_key(struct perf_evsel *evsel, struct perf_sample *sample,
- struct event_key *key)
-{
- key->key = perf_evsel__intval(evsel, sample, "gpa");
- key->info = perf_evsel__intval(evsel, sample, "type");
-}
-
-#define KVM_TRACE_MMIO_READ_UNSATISFIED 0
-#define KVM_TRACE_MMIO_READ 1
-#define KVM_TRACE_MMIO_WRITE 2
-
-static bool mmio_event_begin(struct perf_evsel *evsel,
- struct perf_sample *sample, struct event_key *key)
-{
- /* MMIO read begin event in kernel. */
- if (kvm_exit_event(evsel))
- return true;
-
- /* MMIO write begin event in kernel. */
- if (!strcmp(evsel->name, "kvm:kvm_mmio") &&
- perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_WRITE) {
- mmio_event_get_key(evsel, sample, key);
- return true;
- }
-
- return false;
-}
-
-static bool mmio_event_end(struct perf_evsel *evsel, struct perf_sample *sample,
- struct event_key *key)
+void exit_event_decode_key(struct perf_kvm_stat *kvm,
+ struct event_key *key,
+ char *decode)
{
- /* MMIO write end event in kernel. */
- if (kvm_entry_event(evsel))
- return true;
+ const char *exit_reason = get_exit_reason(kvm, key->exit_reasons,
+ key->key);
- /* MMIO read end event in kernel.*/
- if (!strcmp(evsel->name, "kvm:kvm_mmio") &&
- perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_READ) {
- mmio_event_get_key(evsel, sample, key);
- return true;
- }
-
- return false;
+ scnprintf(decode, DECODE_STR_LEN, "%s", exit_reason);
}
-static void mmio_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
- struct event_key *key,
- char decode[20])
-{
- scnprintf(decode, 20, "%#lx:%s", (unsigned long)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"
-};
-
- /* The time of emulation pio access is from kvm_pio to kvm_entry. */
-static void ioport_event_get_key(struct perf_evsel *evsel,
- struct perf_sample *sample,
- struct event_key *key)
+static bool register_kvm_events_ops(struct perf_kvm_stat *kvm)
{
- key->key = perf_evsel__intval(evsel, sample, "port");
- key->info = perf_evsel__intval(evsel, sample, "rw");
-}
+ struct kvm_reg_events_ops *events_ops = kvm_reg_events_ops;
-static bool ioport_event_begin(struct perf_evsel *evsel,
- struct perf_sample *sample,
- struct event_key *key)
-{
- if (!strcmp(evsel->name, "kvm:kvm_pio")) {
- ioport_event_get_key(evsel, sample, key);
- return true;
+ for (events_ops = kvm_reg_events_ops; events_ops->name; events_ops++) {
+ if (!strcmp(events_ops->name, kvm->report_event)) {
+ kvm->events_ops = events_ops->ops;
+ return true;
+ }
}
return false;
}
-static bool ioport_event_end(struct perf_evsel *evsel,
- struct perf_sample *sample __maybe_unused,
- struct event_key *key __maybe_unused)
-{
- return kvm_entry_event(evsel);
-}
-
-static void ioport_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
- struct event_key *key,
- char decode[20])
-{
- scnprintf(decode, 20, "%#llx:%s", (unsigned long long)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 bool register_kvm_events_ops(struct perf_kvm_stat *kvm)
-{
- bool ret = true;
-
- if (!strcmp(kvm->report_event, "vmexit"))
- kvm->events_ops = &exit_events;
- else if (!strcmp(kvm->report_event, "mmio"))
- kvm->events_ops = &mmio_events;
- else if (!strcmp(kvm->report_event, "ioport"))
- kvm->events_ops = &ioport_events;
- else {
- pr_err("Unknown report event:%s\n", kvm->report_event);
- ret = false;
- }
-
- return ret;
-}
-
struct vcpu_event_record {
int vcpu_id;
u64 start_time;
return true;
}
+static bool is_child_event(struct perf_kvm_stat *kvm,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct event_key *key)
+{
+ struct child_event_ops *child_ops;
+
+ child_ops = kvm->events_ops->child_ops;
+
+ if (!child_ops)
+ return false;
+
+ for (; child_ops->name; child_ops++) {
+ if (!strcmp(evsel->name, child_ops->name)) {
+ child_ops->get_key(evsel, sample, key);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool handle_child_event(struct perf_kvm_stat *kvm,
+ struct vcpu_event_record *vcpu_record,
+ struct event_key *key,
+ struct perf_sample *sample __maybe_unused)
+{
+ struct kvm_event *event = NULL;
+
+ if (key->key != INVALID_KEY)
+ event = find_create_kvm_event(kvm, key);
+
+ vcpu_record->last_event = event;
+
+ return true;
+}
+
+static bool skip_event(const char *event)
+{
+ const char * const *skip_events;
+
+ for (skip_events = kvm_skip_events; *skip_events; skip_events++)
+ if (!strcmp(event, *skip_events))
+ return true;
+
+ return false;
+}
+
static bool handle_end_event(struct perf_kvm_stat *kvm,
struct vcpu_event_record *vcpu_record,
struct event_key *key,
time_diff = sample->time - time_begin;
if (kvm->duration && time_diff > kvm->duration) {
- char decode[32];
+ char decode[DECODE_STR_LEN];
kvm->events_ops->decode_key(kvm, &event->key, decode);
- if (strcmp(decode, "HLT")) {
+ if (!skip_event(decode)) {
pr_info("%" PRIu64 " VM %d, vcpu %d: %s event took %" PRIu64 "usec\n",
sample->time, sample->pid, vcpu_record->vcpu_id,
decode, time_diff/1000);
return NULL;
}
- vcpu_record->vcpu_id = perf_evsel__intval(evsel, sample, "vcpu_id");
+ vcpu_record->vcpu_id = perf_evsel__intval(evsel, sample, VCPU_ID);
thread->priv = vcpu_record;
}
struct perf_sample *sample)
{
struct vcpu_event_record *vcpu_record;
- struct event_key key = {.key = INVALID_KEY};
+ struct event_key key = { .key = INVALID_KEY,
+ .exit_reasons = kvm->exit_reasons };
vcpu_record = per_vcpu_record(thread, evsel, sample);
if (!vcpu_record)
if (kvm->events_ops->is_begin_event(evsel, sample, &key))
return handle_begin_event(kvm, vcpu_record, &key, sample->time);
+ if (is_child_event(kvm, evsel, sample, &key))
+ return handle_child_event(kvm, vcpu_record, &key, sample);
+
if (kvm->events_ops->is_end_event(evsel, sample, &key))
return handle_end_event(kvm, vcpu_record, &key, sample);
static void print_result(struct perf_kvm_stat *kvm)
{
- char decode[20];
+ char decode[DECODE_STR_LEN];
struct kvm_event *event;
int vcpu = kvm->trace_vcpu;
pr_info("\n\n");
print_vcpu_info(kvm);
- pr_info("%20s ", kvm->events_ops->name);
+ pr_info("%*s ", DECODE_STR_LEN, kvm->events_ops->name);
pr_info("%10s ", "Samples");
pr_info("%9s ", "Samples%");
pr_info("%9s ", "Time%");
- pr_info("%10s ", "Min Time");
- pr_info("%10s ", "Max Time");
+ pr_info("%11s ", "Min Time");
+ pr_info("%11s ", "Max Time");
pr_info("%16s ", "Avg time");
pr_info("\n\n");
min = get_event_min(event, vcpu);
kvm->events_ops->decode_key(kvm, &event->key, decode);
- pr_info("%20s ", decode);
+ pr_info("%*s ", DECODE_STR_LEN, decode);
pr_info("%10llu ", (unsigned long long)ecount);
pr_info("%8.2f%% ", (double)ecount / kvm->total_count * 100);
pr_info("%8.2f%% ", (double)etime / kvm->total_time * 100);
- pr_info("%8" PRIu64 "us ", min / 1000);
- pr_info("%8" PRIu64 "us ", max / 1000);
+ pr_info("%9.2fus ", (double)min / 1e3);
+ pr_info("%9.2fus ", (double)max / 1e3);
pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3,
kvm_event_rel_stddev(vcpu, event));
pr_info("\n");
static int cpu_isa_config(struct perf_kvm_stat *kvm)
{
char buf[64], *cpuid;
- int err, isa;
+ int err;
if (kvm->live) {
err = get_cpuid(buf, sizeof(buf));
if (err != 0) {
- pr_err("Failed to look up CPU type (Intel or AMD)\n");
+ pr_err("Failed to look up CPU type\n");
return err;
}
cpuid = buf;
} else
cpuid = kvm->session->header.env.cpuid;
- if (strstr(cpuid, "Intel"))
- isa = 1;
- else if (strstr(cpuid, "AMD"))
- isa = 0;
- else {
- pr_err("CPU %s is not supported.\n", cpuid);
- return -ENOTSUP;
+ if (!cpuid) {
+ pr_err("Failed to look up CPU type\n");
+ return -EINVAL;
}
- if (isa == 1) {
- kvm->exit_reasons = vmx_exit_reasons;
- kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons);
- kvm->exit_reasons_isa = "VMX";
- }
+ err = cpu_isa_init(kvm, cpuid);
+ if (err == -ENOTSUP)
+ pr_err("CPU %s is not supported.\n", cpuid);
- return 0;
+ return err;
}
static bool verify_vcpu(int vcpu)
return -1;
}
- err = perf_session_queue_event(kvm->session, event, &sample, 0);
+ err = perf_session_queue_event(kvm->session, event, &kvm->tool, &sample, 0);
/*
* FIXME: Here we can't consume the event, as perf_session_queue_event will
* point to it, and it'll get possibly overwritten by the kernel.
/* flush queue after each round in which we processed events */
if (ntotal) {
- kvm->session->ordered_samples.next_flush = flush_time;
+ kvm->session->ordered_events.next_flush = flush_time;
err = kvm->tool.finished_round(&kvm->tool, NULL, kvm->session);
if (err) {
if (kvm->lost_events)
return 0;
}
-static
-int perf_kvm__handle_stdin(struct termios *tc_now, struct termios *tc_save)
+static int perf_kvm__handle_stdin(void)
{
int c;
- tcsetattr(0, TCSANOW, tc_now);
c = getc(stdin);
- tcsetattr(0, TCSAFLUSH, tc_save);
-
if (c == 'q')
return 1;
{
struct pollfd *pollfds = NULL;
int nr_fds, nr_stdin, ret, err = -EINVAL;
- struct termios tc, save;
+ struct termios save;
/* live flag must be set first */
kvm->live = true;
goto out;
}
+ set_term_quiet_input(&save);
init_kvm_event_record(kvm);
- tcgetattr(0, &save);
- tc = save;
- tc.c_lflag &= ~(ICANON | ECHO);
- tc.c_cc[VMIN] = 0;
- tc.c_cc[VTIME] = 0;
-
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
goto out;
if (pollfds[nr_stdin].revents & POLLIN)
- done = perf_kvm__handle_stdin(&tc, &save);
+ done = perf_kvm__handle_stdin();
if (!rc && !done)
err = poll(pollfds, nr_fds, 100);
if (kvm->timerfd >= 0)
close(kvm->timerfd);
+ tcsetattr(0, TCSAFLUSH, &save);
free(pollfds);
return err;
}
struct perf_tool eops = {
.sample = process_sample_event,
.comm = perf_event__process_comm,
- .ordered_samples = true,
+ .ordered_events = true,
};
struct perf_data_file file = {
.path = kvm->file_name,
return -EINVAL;
}
+ symbol__init(&kvm->session->header.env);
+
if (!perf_session__has_traces(kvm->session, "kvm record"))
return -EINVAL;
return ret;
}
-static const char * const kvm_events_tp[] = {
- "kvm:kvm_entry",
- "kvm:kvm_exit",
- "kvm:kvm_mmio",
- "kvm:kvm_pio",
-};
-
#define STRDUP_FAIL_EXIT(s) \
({ char *_p; \
_p = strdup(s); \
static int
kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv)
{
- unsigned int rec_argc, i, j;
+ unsigned int rec_argc, i, j, events_tp_size;
const char **rec_argv;
const char * const record_args[] = {
"record",
"-m", "1024",
"-c", "1",
};
+ const char * const *events_tp;
+ events_tp_size = 0;
+
+ for (events_tp = kvm_events_tp; *events_tp; events_tp++)
+ events_tp_size++;
rec_argc = ARRAY_SIZE(record_args) + argc + 2 +
- 2 * ARRAY_SIZE(kvm_events_tp);
+ 2 * events_tp_size;
rec_argv = calloc(rec_argc + 1, sizeof(char *));
if (rec_argv == NULL)
for (i = 0; i < ARRAY_SIZE(record_args); i++)
rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]);
- for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) {
+ for (j = 0; j < events_tp_size; j++) {
rec_argv[i++] = "-e";
rec_argv[i++] = STRDUP_FAIL_EXIT(kvm_events_tp[j]);
}
{
const struct option kvm_events_report_options[] = {
OPT_STRING(0, "event", &kvm->report_event, "report event",
- "event for reporting: vmexit, mmio, ioport"),
+ "event for reporting: vmexit, "
+ "mmio (x86 only), ioport (x86 only)"),
OPT_INTEGER(0, "vcpu", &kvm->trace_vcpu,
"vcpu id to report"),
OPT_STRING('k', "key", &kvm->sort_key, "sort-key",
NULL
};
- symbol__init();
-
if (argc) {
argc = parse_options(argc, argv,
kvm_events_report_options,
{
struct perf_evlist *evlist;
char *tp, *name, *sys;
- unsigned int j;
int err = -1;
+ const char * const *events_tp;
evlist = perf_evlist__new();
if (evlist == NULL)
return NULL;
- for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) {
+ for (events_tp = kvm_events_tp; *events_tp; events_tp++) {
- tp = strdup(kvm_events_tp[j]);
+ tp = strdup(*events_tp);
if (tp == NULL)
goto out;
name = strchr(tp, ':');
if (name == NULL) {
pr_err("Error parsing %s tracepoint: subsystem delimiter not found\n",
- kvm_events_tp[j]);
+ *events_tp);
free(tp);
goto out;
}
name++;
if (perf_evlist__add_newtp(evlist, sys, name, NULL)) {
- pr_err("Failed to add %s tracepoint to the list\n", kvm_events_tp[j]);
+ pr_err("Failed to add %s tracepoint to the list\n", *events_tp);
free(tp);
goto out;
}
"key for sorting: sample(sort by samples number)"
" time (sort by avg time)"),
OPT_U64(0, "duration", &kvm->duration,
- "show events other than HALT that take longer than duration usecs"),
+ "show events other than"
+ " HLT (x86 only) or Wait state (s390 only)"
+ " that take longer than duration usecs"),
OPT_END()
};
const char * const live_usage[] = {
kvm->tool.exit = perf_event__process_exit;
kvm->tool.fork = perf_event__process_fork;
kvm->tool.lost = process_lost_event;
- kvm->tool.ordered_samples = true;
+ kvm->tool.ordered_events = true;
perf_tool__fill_defaults(&kvm->tool);
/* set defaults */
kvm->opts.target.uid_str = NULL;
kvm->opts.target.uid = UINT_MAX;
- symbol__init();
+ symbol__init(NULL);
disable_buildid_cache();
use_browser = 0;
.report_event = "vmexit",
.sort_key = "sample",
- .exit_reasons = svm_exit_reasons,
- .exit_reasons_size = ARRAY_SIZE(svm_exit_reasons),
- .exit_reasons_isa = "SVM",
};
if (argc == 1) {
perf_stat:
return cmd_stat(argc, argv, NULL);
}
-#endif
+#endif /* HAVE_KVM_STAT_SUPPORT */
static int __cmd_record(const char *file_name, int argc, const char **argv)
{
return cmd_top(argc, argv, NULL);
else if (!strncmp(argv[0], "buildid-list", 12))
return __cmd_buildid_list(file_name, argc, argv);
-#if defined(__i386__) || defined(__x86_64__)
+#ifdef HAVE_KVM_STAT_SUPPORT
else if (!strncmp(argv[0], "stat", 4))
return kvm_cmd_stat(file_name, argc, argv);
#endif