qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

tests/plugin: add hotpages to analyse memory access patterns

This plugin gives a summary of access patterns grouped by "pages" and
showing read/write patterns by vCPUS.

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>

+192
+1
tests/plugin/Makefile
··· 12 12 NAMES += mem 13 13 NAMES += hotblocks 14 14 NAMES += howvec 15 + NAMES += hotpages 15 16 16 17 SONAMES := $(addsuffix .so,$(addprefix lib,$(NAMES))) 17 18
+191
tests/plugin/hotpages.c
··· 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 + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 22 + 23 + static uint64_t page_size = 4096; 24 + static uint64_t page_mask; 25 + static int limit = 50; 26 + static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW; 27 + static bool track_io; 28 + 29 + enum sort_type { 30 + SORT_RW = 0, 31 + SORT_R, 32 + SORT_W, 33 + SORT_A 34 + }; 35 + 36 + static int sort_by = SORT_RW; 37 + 38 + typedef struct { 39 + uint64_t page_address; 40 + int cpu_read; 41 + int cpu_write; 42 + uint64_t reads; 43 + uint64_t writes; 44 + } PageCounters; 45 + 46 + static GMutex lock; 47 + static GHashTable *pages; 48 + 49 + static gint cmp_access_count(gconstpointer a, gconstpointer b) 50 + { 51 + PageCounters *ea = (PageCounters *) a; 52 + PageCounters *eb = (PageCounters *) b; 53 + int r; 54 + switch (sort_by) { 55 + case SORT_RW: 56 + r = (ea->reads + ea->writes) > (eb->reads + eb->writes) ? -1 : 1; 57 + break; 58 + case SORT_R: 59 + r = ea->reads > eb->reads ? -1 : 1; 60 + break; 61 + case SORT_W: 62 + r = ea->writes > eb->writes ? -1 : 1; 63 + break; 64 + case SORT_A: 65 + r = ea->page_address > eb->page_address ? -1 : 1; 66 + break; 67 + default: 68 + g_assert_not_reached(); 69 + } 70 + return r; 71 + } 72 + 73 + 74 + static void plugin_exit(qemu_plugin_id_t id, void *p) 75 + { 76 + g_autoptr(GString) report = g_string_new("Addr, RCPUs, Reads, WCPUs, Writes\n"); 77 + int i; 78 + GList *counts; 79 + 80 + counts = g_hash_table_get_values(pages); 81 + if (counts && g_list_next(counts)) { 82 + GList *it; 83 + 84 + it = g_list_sort(counts, cmp_access_count); 85 + 86 + for (i = 0; i < limit && it->next; i++, it = it->next) { 87 + PageCounters *rec = (PageCounters *) it->data; 88 + g_string_append_printf(report, 89 + "%#016"PRIx64", 0x%04x, %"PRId64 90 + ", 0x%04x, %"PRId64"\n", 91 + rec->page_address, 92 + rec->cpu_read, rec->reads, 93 + rec->cpu_write, rec->writes); 94 + } 95 + g_list_free(it); 96 + } 97 + 98 + qemu_plugin_outs(report->str); 99 + } 100 + 101 + static void plugin_init(void) 102 + { 103 + page_mask = (page_size - 1); 104 + pages = g_hash_table_new(NULL, g_direct_equal); 105 + } 106 + 107 + static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo, 108 + uint64_t vaddr, void *udata) 109 + { 110 + struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr); 111 + uint64_t page; 112 + PageCounters *count; 113 + 114 + /* We only get a hwaddr for system emulation */ 115 + if (track_io) { 116 + if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) { 117 + page = vaddr; 118 + } else { 119 + return; 120 + } 121 + } else { 122 + if (hwaddr && !qemu_plugin_hwaddr_is_io(hwaddr)) { 123 + page = (uint64_t) qemu_plugin_hwaddr_device_offset(hwaddr); 124 + } else { 125 + page = vaddr; 126 + } 127 + } 128 + page &= ~page_mask; 129 + 130 + g_mutex_lock(&lock); 131 + count = (PageCounters *) g_hash_table_lookup(pages, GUINT_TO_POINTER(page)); 132 + 133 + if (!count) { 134 + count = g_new0(PageCounters, 1); 135 + count->page_address = page; 136 + g_hash_table_insert(pages, GUINT_TO_POINTER(page), (gpointer) count); 137 + } 138 + if (qemu_plugin_mem_is_store(meminfo)) { 139 + count->writes++; 140 + count->cpu_write |= (1 << cpu_index); 141 + } else { 142 + count->reads++; 143 + count->cpu_read |= (1 << cpu_index); 144 + } 145 + 146 + g_mutex_unlock(&lock); 147 + } 148 + 149 + static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb) 150 + { 151 + size_t n = qemu_plugin_tb_n_insns(tb); 152 + size_t i; 153 + 154 + for (i = 0; i < n; i++) { 155 + struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i); 156 + qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr, 157 + QEMU_PLUGIN_CB_NO_REGS, 158 + rw, NULL); 159 + } 160 + } 161 + 162 + QEMU_PLUGIN_EXPORT 163 + int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, 164 + int argc, char **argv) 165 + { 166 + int i; 167 + 168 + for (i = 0; i < argc; i++) { 169 + char *opt = argv[i]; 170 + if (g_strcmp0(opt, "reads") == 0) { 171 + sort_by = SORT_R; 172 + } else if (g_strcmp0(opt, "writes") == 0) { 173 + sort_by = SORT_W; 174 + } else if (g_strcmp0(opt, "address") == 0) { 175 + sort_by = SORT_A; 176 + } else if (g_strcmp0(opt, "io") == 0) { 177 + track_io = true; 178 + } else if (g_str_has_prefix(opt, "pagesize=")) { 179 + page_size = g_ascii_strtoull(opt + 9, NULL, 10); 180 + } else { 181 + fprintf(stderr, "option parsing failed: %s\n", opt); 182 + return -1; 183 + } 184 + } 185 + 186 + plugin_init(); 187 + 188 + qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans); 189 + qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); 190 + return 0; 191 + }