qemu/contrib/plugins/hwprofile.c
<<
>>
Prefs
   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
  21QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
  22
  23#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
  24
  25typedef struct {
  26    uint64_t cpu_read;
  27    uint64_t cpu_write;
  28    uint64_t reads;
  29    uint64_t writes;
  30} IOCounts;
  31
  32typedef struct {
  33    uint64_t off_or_pc;
  34    IOCounts counts;
  35} IOLocationCounts;
  36
  37typedef struct {
  38    const char *name;
  39    uint64_t   base;
  40    IOCounts   totals;
  41    GHashTable *detail;
  42} DeviceCounts;
  43
  44static GMutex lock;
  45static GHashTable *devices;
  46
  47/* track the access pattern to a piece of HW */
  48static bool pattern;
  49/* track the source address of access to HW */
  50static bool source;
  51/* track only matched regions of HW */
  52static bool check_match;
  53static gchar **matches;
  54
  55static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW;
  56
  57static inline bool track_reads(void)
  58{
  59    return rw == QEMU_PLUGIN_MEM_RW || rw == QEMU_PLUGIN_MEM_R;
  60}
  61
  62static inline bool track_writes(void)
  63{
  64    return rw == QEMU_PLUGIN_MEM_RW || rw == QEMU_PLUGIN_MEM_W;
  65}
  66
  67static void plugin_init(void)
  68{
  69    devices = g_hash_table_new(NULL, NULL);
  70}
  71
  72static 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
  80static 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
  87static 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
  99static 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
 107static 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
 156static 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
 168static 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
 176static 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
 184static 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
 195static 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
 243static 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
 257QEMU_PLUGIN_EXPORT
 258int 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