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