1 /* 2 * Copyright (C) 2020, Alex Bennée <alex.bennee@linaro.org> 3 * 4 * HW Profile - breakdown access patterns for IO to devices 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 typedef struct { 26 uint64_t cpu_read; 27 uint64_t cpu_write; 28 uint64_t reads; 29 uint64_t writes; 30 } IOCounts; 31 32 typedef struct { 33 uint64_t off_or_pc; 34 IOCounts counts; 35 } IOLocationCounts; 36 37 typedef struct { 38 const char *name; 39 uint64_t base; 40 IOCounts totals; 41 GHashTable *detail; 42 } DeviceCounts; 43 44 static GMutex lock; 45 static GHashTable *devices; 46 47 /* track the access pattern to a piece of HW */ 48 static bool pattern; 49 /* track the source address of access to HW */ 50 static bool source; 51 /* track only matched regions of HW */ 52 static bool check_match; 53 static gchar **matches; 54 55 static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW; 56 57 static inline bool track_reads(void) 58 { 59 return rw == QEMU_PLUGIN_MEM_RW || rw == QEMU_PLUGIN_MEM_R; 60 } 61 62 static inline bool track_writes(void) 63 { 64 return rw == QEMU_PLUGIN_MEM_RW || rw == QEMU_PLUGIN_MEM_W; 65 } 66 67 static void plugin_init(void) 68 { 69 devices = g_hash_table_new(NULL, NULL); 70 } 71 72 static gint sort_cmp(gconstpointer a, gconstpointer b) 73 { 74 DeviceCounts *ea = (DeviceCounts *) a; 75 DeviceCounts *eb = (DeviceCounts *) b; 76 return ea->totals.reads + ea->totals.writes > 77 eb->totals.reads + eb->totals.writes ? -1 : 1; 78 } 79 80 static gint sort_loc(gconstpointer a, gconstpointer b) 81 { 82 IOLocationCounts *ea = (IOLocationCounts *) a; 83 IOLocationCounts *eb = (IOLocationCounts *) b; 84 return ea->off_or_pc > eb->off_or_pc; 85 } 86 87 static void fmt_iocount_record(GString *s, IOCounts *rec) 88 { 89 if (track_reads()) { 90 g_string_append_printf(s, ", %"PRIx64", %"PRId64, 91 rec->cpu_read, rec->reads); 92 } 93 if (track_writes()) { 94 g_string_append_printf(s, ", %"PRIx64", %"PRId64, 95 rec->cpu_write, rec->writes); 96 } 97 } 98 99 static void fmt_dev_record(GString *s, DeviceCounts *rec) 100 { 101 g_string_append_printf(s, "%s, 0x%"PRIx64, 102 rec->name, rec->base); 103 fmt_iocount_record(s, &rec->totals); 104 g_string_append_c(s, '\n'); 105 } 106 107 static void plugin_exit(qemu_plugin_id_t id, void *p) 108 { 109 g_autoptr(GString) report = g_string_new(""); 110 GList *counts; 111 112 if (!(pattern || source)) { 113 g_string_printf(report, "Device, Address"); 114 if (track_reads()) { 115 g_string_append_printf(report, ", RCPUs, Reads"); 116 } 117 if (track_writes()) { 118 g_string_append_printf(report, ", WCPUs, Writes"); 119 } 120 g_string_append_c(report, '\n'); 121 } 122 123 counts = g_hash_table_get_values(devices); 124 if (counts && g_list_next(counts)) { 125 GList *it; 126 127 it = g_list_sort(counts, sort_cmp); 128 129 while (it) { 130 DeviceCounts *rec = (DeviceCounts *) it->data; 131 if (rec->detail) { 132 GList *accesses = g_hash_table_get_values(rec->detail); 133 GList *io_it = g_list_sort(accesses, sort_loc); 134 const char *prefix = pattern ? "off" : "pc"; 135 g_string_append_printf(report, "%s @ 0x%"PRIx64"\n", 136 rec->name, rec->base); 137 while (io_it) { 138 IOLocationCounts *loc = (IOLocationCounts *) io_it->data; 139 g_string_append_printf(report, " %s:%08"PRIx64, 140 prefix, loc->off_or_pc); 141 fmt_iocount_record(report, &loc->counts); 142 g_string_append_c(report, '\n'); 143 io_it = io_it->next; 144 } 145 } else { 146 fmt_dev_record(report, rec); 147 } 148 it = it->next; 149 }; 150 g_list_free(it); 151 } 152 153 qemu_plugin_outs(report->str); 154 } 155 156 static DeviceCounts *new_count(const char *name, uint64_t base) 157 { 158 DeviceCounts *count = g_new0(DeviceCounts, 1); 159 count->name = name; 160 count->base = base; 161 if (pattern || source) { 162 count->detail = g_hash_table_new(NULL, NULL); 163 } 164 g_hash_table_insert(devices, (gpointer) name, count); 165 return count; 166 } 167 168 static IOLocationCounts *new_location(GHashTable *table, uint64_t off_or_pc) 169 { 170 IOLocationCounts *loc = g_new0(IOLocationCounts, 1); 171 loc->off_or_pc = off_or_pc; 172 g_hash_table_insert(table, (gpointer) off_or_pc, loc); 173 return loc; 174 } 175 176 static void hwprofile_match_hit(DeviceCounts *rec, uint64_t off) 177 { 178 g_autoptr(GString) report = g_string_new("hwprofile: match @ offset"); 179 g_string_append_printf(report, "%"PRIx64", previous hits\n", off); 180 fmt_dev_record(report, rec); 181 qemu_plugin_outs(report->str); 182 } 183 184 static void inc_count(IOCounts *count, bool is_write, unsigned int cpu_index) 185 { 186 if (is_write) { 187 count->writes++; 188 count->cpu_write |= (1 << cpu_index); 189 } else { 190 count->reads++; 191 count->cpu_read |= (1 << cpu_index); 192 } 193 } 194 195 static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo, 196 uint64_t vaddr, void *udata) 197 { 198 struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr); 199 200 if (!hwaddr || !qemu_plugin_hwaddr_is_io(hwaddr)) { 201 return; 202 } else { 203 const char *name = qemu_plugin_hwaddr_device_name(hwaddr); 204 uint64_t off = qemu_plugin_hwaddr_phys_addr(hwaddr); 205 bool is_write = qemu_plugin_mem_is_store(meminfo); 206 DeviceCounts *counts; 207 208 g_mutex_lock(&lock); 209 counts = (DeviceCounts *) g_hash_table_lookup(devices, name); 210 211 if (!counts) { 212 uint64_t base = vaddr - off; 213 counts = new_count(name, base); 214 } 215 216 if (check_match) { 217 if (g_strv_contains((const char * const *)matches, counts->name)) { 218 hwprofile_match_hit(counts, off); 219 inc_count(&counts->totals, is_write, cpu_index); 220 } 221 } else { 222 inc_count(&counts->totals, is_write, cpu_index); 223 } 224 225 /* either track offsets or source of access */ 226 if (source) { 227 off = (uint64_t) udata; 228 } 229 230 if (pattern || source) { 231 IOLocationCounts *io_count = g_hash_table_lookup(counts->detail, 232 (gpointer) off); 233 if (!io_count) { 234 io_count = new_location(counts->detail, off); 235 } 236 inc_count(&io_count->counts, is_write, cpu_index); 237 } 238 239 g_mutex_unlock(&lock); 240 } 241 } 242 243 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) 244 { 245 size_t n = qemu_plugin_tb_n_insns(tb); 246 size_t i; 247 248 for (i = 0; i < n; i++) { 249 struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); 250 gpointer udata = (gpointer) (source ? qemu_plugin_insn_vaddr(insn) : 0); 251 qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr, 252 QEMU_PLUGIN_CB_NO_REGS, 253 rw, udata); 254 } 255 } 256 257 QEMU_PLUGIN_EXPORT 258 int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, 259 int argc, char **argv) 260 { 261 int i; 262 g_autoptr(GString) matches_raw = g_string_new(""); 263 264 for (i = 0; i < argc; i++) { 265 char *opt = argv[i]; 266 g_autofree char **tokens = g_strsplit(opt, "=", 2); 267 268 if (g_strcmp0(tokens[0], "track") == 0) { 269 if (g_strcmp0(tokens[1], "read") == 0) { 270 rw = QEMU_PLUGIN_MEM_R; 271 } else if (g_strcmp0(tokens[1], "write") == 0) { 272 rw = QEMU_PLUGIN_MEM_W; 273 } else { 274 fprintf(stderr, "invalid value for track: %s\n", tokens[1]); 275 return -1; 276 } 277 } else if (g_strcmp0(tokens[0], "pattern") == 0) { 278 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &pattern)) { 279 fprintf(stderr, "boolean argument parsing failed: %s\n", opt); 280 return -1; 281 } 282 } else if (g_strcmp0(tokens[0], "source") == 0) { 283 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &source)) { 284 fprintf(stderr, "boolean argument parsing failed: %s\n", opt); 285 return -1; 286 } 287 } else if (g_strcmp0(tokens[0], "match") == 0) { 288 check_match = true; 289 g_string_append_printf(matches_raw, "%s,", tokens[1]); 290 } else { 291 fprintf(stderr, "option parsing failed: %s\n", opt); 292 return -1; 293 } 294 } 295 if (check_match) { 296 matches = g_strsplit(matches_raw->str, ",", -1); 297 } 298 299 if (source && pattern) { 300 fprintf(stderr, "can only currently track either source or pattern.\n"); 301 return -1; 302 } 303 304 if (!info->system_emulation) { 305 fprintf(stderr, "hwprofile: plugin only useful for system emulation\n"); 306 return -1; 307 } 308 309 /* Just warn about overflow */ 310 if (info->system.smp_vcpus > 64 || 311 info->system.max_vcpus > 64) { 312 fprintf(stderr, "hwprofile: can only track up to 64 CPUs\n"); 313 } 314 315 plugin_init(); 316 317 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); 318 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); 319 return 0; 320 } 321