1 /* 2 * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org> 3 * 4 * Hot Pages - show which pages saw the most memory accesses. 5 * 6 * License: GNU GPL, version 2 or later. 7 * See the COPYING file in the top-level directory. 8 */ 9 10 #include <inttypes.h> 11 #include <assert.h> 12 #include <stdlib.h> 13 #include <inttypes.h> 14 #include <string.h> 15 #include <unistd.h> 16 #include <stdio.h> 17 #include <glib.h> 18 19 #include <qemu-plugin.h> 20 21 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; 22 23 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 24 25 static uint64_t page_size = 4096; 26 static uint64_t page_mask; 27 static int limit = 50; 28 static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW; 29 static bool track_io; 30 31 enum sort_type { 32 SORT_RW = 0, 33 SORT_R, 34 SORT_W, 35 SORT_A 36 }; 37 38 static int sort_by = SORT_RW; 39 40 typedef struct { 41 uint64_t page_address; 42 int cpu_read; 43 int cpu_write; 44 uint64_t reads; 45 uint64_t writes; 46 } PageCounters; 47 48 static GMutex lock; 49 static GHashTable *pages; 50 51 static gint cmp_access_count(gconstpointer a, gconstpointer b) 52 { 53 PageCounters *ea = (PageCounters *) a; 54 PageCounters *eb = (PageCounters *) b; 55 int r; 56 switch (sort_by) { 57 case SORT_RW: 58 r = (ea->reads + ea->writes) > (eb->reads + eb->writes) ? -1 : 1; 59 break; 60 case SORT_R: 61 r = ea->reads > eb->reads ? -1 : 1; 62 break; 63 case SORT_W: 64 r = ea->writes > eb->writes ? -1 : 1; 65 break; 66 case SORT_A: 67 r = ea->page_address > eb->page_address ? -1 : 1; 68 break; 69 default: 70 g_assert_not_reached(); 71 } 72 return r; 73 } 74 75 76 static void plugin_exit(qemu_plugin_id_t id, void *p) 77 { 78 g_autoptr(GString) report = g_string_new("Addr, RCPUs, Reads, WCPUs, Writes\n"); 79 int i; 80 GList *counts; 81 82 counts = g_hash_table_get_values(pages); 83 if (counts && g_list_next(counts)) { 84 GList *it; 85 86 it = g_list_sort(counts, cmp_access_count); 87 88 for (i = 0; i < limit && it->next; i++, it = it->next) { 89 PageCounters *rec = (PageCounters *) it->data; 90 g_string_append_printf(report, 91 "0x%016"PRIx64", 0x%04x, %"PRId64 92 ", 0x%04x, %"PRId64"\n", 93 rec->page_address, 94 rec->cpu_read, rec->reads, 95 rec->cpu_write, rec->writes); 96 } 97 g_list_free(it); 98 } 99 100 qemu_plugin_outs(report->str); 101 } 102 103 static void plugin_init(void) 104 { 105 page_mask = (page_size - 1); 106 pages = g_hash_table_new(NULL, g_direct_equal); 107 } 108 109 static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo, 110 uint64_t vaddr, void *udata) 111 { 112 struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr); 113 uint64_t page; 114 PageCounters *count; 115 116 /* We only get a hwaddr for system emulation */ 117 if (track_io) { 118 if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) { 119 page = vaddr; 120 } else { 121 return; 122 } 123 } else { 124 if (hwaddr && !qemu_plugin_hwaddr_is_io(hwaddr)) { 125 page = (uint64_t) qemu_plugin_hwaddr_phys_addr(hwaddr); 126 } else { 127 page = vaddr; 128 } 129 } 130 page &= ~page_mask; 131 132 g_mutex_lock(&lock); 133 count = (PageCounters *) g_hash_table_lookup(pages, GUINT_TO_POINTER(page)); 134 135 if (!count) { 136 count = g_new0(PageCounters, 1); 137 count->page_address = page; 138 g_hash_table_insert(pages, GUINT_TO_POINTER(page), (gpointer) count); 139 } 140 if (qemu_plugin_mem_is_store(meminfo)) { 141 count->writes++; 142 count->cpu_write |= (1 << cpu_index); 143 } else { 144 count->reads++; 145 count->cpu_read |= (1 << cpu_index); 146 } 147 148 g_mutex_unlock(&lock); 149 } 150 151 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) 152 { 153 size_t n = qemu_plugin_tb_n_insns(tb); 154 size_t i; 155 156 for (i = 0; i < n; i++) { 157 struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); 158 qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr, 159 QEMU_PLUGIN_CB_NO_REGS, 160 rw, NULL); 161 } 162 } 163 164 QEMU_PLUGIN_EXPORT 165 int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, 166 int argc, char **argv) 167 { 168 int i; 169 170 for (i = 0; i < argc; i++) { 171 char *opt = argv[i]; 172 g_autofree char **tokens = g_strsplit(opt, "=", -1); 173 174 if (g_strcmp0(tokens[0], "sortby") == 0) { 175 if (g_strcmp0(tokens[1], "reads") == 0) { 176 sort_by = SORT_R; 177 } else if (g_strcmp0(tokens[1], "writes") == 0) { 178 sort_by = SORT_W; 179 } else if (g_strcmp0(tokens[1], "address") == 0) { 180 sort_by = SORT_A; 181 } else { 182 fprintf(stderr, "invalid value to sortby: %s\n", tokens[1]); 183 return -1; 184 } 185 } else if (g_strcmp0(tokens[0], "io") == 0) { 186 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &track_io)) { 187 fprintf(stderr, "boolean argument parsing failed: %s\n", opt); 188 return -1; 189 } 190 } else if (g_strcmp0(tokens[0], "pagesize") == 0) { 191 page_size = g_ascii_strtoull(tokens[1], NULL, 10); 192 } else { 193 fprintf(stderr, "option parsing failed: %s\n", opt); 194 return -1; 195 } 196 } 197 198 plugin_init(); 199 200 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); 201 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); 202 return 0; 203 } 204