/* * Copyright (C) 2019, Alex Bennée * * Hot Pages - show which pages saw the most memory accesses. * * License: GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include #include #include #include #include #include #include #include #include QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) static uint64_t page_size = 4096; static uint64_t page_mask; static int limit = 50; static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW; static bool track_io; enum sort_type { SORT_RW = 0, SORT_R, SORT_W, SORT_A }; static int sort_by = SORT_RW; typedef struct { uint64_t page_address; int cpu_read; int cpu_write; uint64_t reads; uint64_t writes; } PageCounters; static GMutex lock; static GHashTable *pages; static gint cmp_access_count(gconstpointer a, gconstpointer b) { PageCounters *ea = (PageCounters *) a; PageCounters *eb = (PageCounters *) b; int r; switch (sort_by) { case SORT_RW: r = (ea->reads + ea->writes) > (eb->reads + eb->writes) ? -1 : 1; break; case SORT_R: r = ea->reads > eb->reads ? -1 : 1; break; case SORT_W: r = ea->writes > eb->writes ? -1 : 1; break; case SORT_A: r = ea->page_address > eb->page_address ? -1 : 1; break; default: g_assert_not_reached(); } return r; } static void plugin_exit(qemu_plugin_id_t id, void *p) { g_autoptr(GString) report = g_string_new("Addr, RCPUs, Reads, WCPUs, Writes\n"); int i; GList *counts; counts = g_hash_table_get_values(pages); if (counts && g_list_next(counts)) { GList *it; it = g_list_sort(counts, cmp_access_count); for (i = 0; i < limit && it->next; i++, it = it->next) { PageCounters *rec = (PageCounters *) it->data; g_string_append_printf(report, "%#016"PRIx64", 0x%04x, %"PRId64 ", 0x%04x, %"PRId64"\n", rec->page_address, rec->cpu_read, rec->reads, rec->cpu_write, rec->writes); } g_list_free(it); } qemu_plugin_outs(report->str); } static void plugin_init(void) { page_mask = (page_size - 1); pages = g_hash_table_new(NULL, g_direct_equal); } static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo, uint64_t vaddr, void *udata) { struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr); uint64_t page; PageCounters *count; /* We only get a hwaddr for system emulation */ if (track_io) { if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) { page = vaddr; } else { return; } } else { if (hwaddr && !qemu_plugin_hwaddr_is_io(hwaddr)) { page = (uint64_t) qemu_plugin_hwaddr_device_offset(hwaddr); } else { page = vaddr; } } page &= ~page_mask; g_mutex_lock(&lock); count = (PageCounters *) g_hash_table_lookup(pages, GUINT_TO_POINTER(page)); if (!count) { count = g_new0(PageCounters, 1); count->page_address = page; g_hash_table_insert(pages, GUINT_TO_POINTER(page), (gpointer) count); } if (qemu_plugin_mem_is_store(meminfo)) { count->writes++; count->cpu_write |= (1 << cpu_index); } else { count->reads++; count->cpu_read |= (1 << cpu_index); } g_mutex_unlock(&lock); } static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) { size_t n = qemu_plugin_tb_n_insns(tb); size_t i; for (i = 0; i < n; i++) { struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr, QEMU_PLUGIN_CB_NO_REGS, rw, NULL); } } QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, int argc, char **argv) { int i; for (i = 0; i < argc; i++) { char *opt = argv[i]; if (g_strcmp0(opt, "reads") == 0) { sort_by = SORT_R; } else if (g_strcmp0(opt, "writes") == 0) { sort_by = SORT_W; } else if (g_strcmp0(opt, "address") == 0) { sort_by = SORT_A; } else if (g_strcmp0(opt, "io") == 0) { track_io = true; } else if (g_str_has_prefix(opt, "pagesize=")) { page_size = g_ascii_strtoull(opt + 9, NULL, 10); } else { fprintf(stderr, "option parsing failed: %s\n", opt); return -1; } } plugin_init(); qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); return 0; }