1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
/*
* Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
*
* 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 <inttypes.h>
#include <assert.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <glib.h>
#include <qemu-plugin.h>
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,
"0x%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_phys_addr(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];
g_autofree char **tokens = g_strsplit(opt, "=", -1);
if (g_strcmp0(tokens[0], "sortby") == 0) {
if (g_strcmp0(tokens[1], "reads") == 0) {
sort_by = SORT_R;
} else if (g_strcmp0(tokens[1], "writes") == 0) {
sort_by = SORT_W;
} else if (g_strcmp0(tokens[1], "address") == 0) {
sort_by = SORT_A;
} else {
fprintf(stderr, "invalid value to sortby: %s\n", tokens[1]);
return -1;
}
} else if (g_strcmp0(tokens[0], "io") == 0) {
if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &track_io)) {
fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
return -1;
}
} else if (g_strcmp0(tokens[0], "pagesize") == 0) {
page_size = g_ascii_strtoull(tokens[1], 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;
}
|