qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

hwprofile.c (9627B)


      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 }