xref: /qemu/contrib/plugins/hwprofile.c (revision 06b40d250ecfa1633209c2e431a7a38acfd03a98)
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 
track_reads(void)59 static inline bool track_reads(void)
60 {
61     return rw == QEMU_PLUGIN_MEM_RW || rw == QEMU_PLUGIN_MEM_R;
62 }
63 
track_writes(void)64 static inline bool track_writes(void)
65 {
66     return rw == QEMU_PLUGIN_MEM_RW || rw == QEMU_PLUGIN_MEM_W;
67 }
68 
plugin_init(void)69 static void plugin_init(void)
70 {
71     devices = g_hash_table_new(NULL, NULL);
72 }
73 
sort_cmp(gconstpointer a,gconstpointer b,gpointer d)74 static gint sort_cmp(gconstpointer a, gconstpointer b, gpointer d)
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 
sort_loc(gconstpointer a,gconstpointer b,gpointer d)82 static gint sort_loc(gconstpointer a, gconstpointer b, gpointer d)
83 {
84     IOLocationCounts *ea = (IOLocationCounts *) a;
85     IOLocationCounts *eb = (IOLocationCounts *) b;
86     return ea->off_or_pc > eb->off_or_pc;
87 }
88 
fmt_iocount_record(GString * s,IOCounts * rec)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 
fmt_dev_record(GString * s,DeviceCounts * rec)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 
plugin_exit(qemu_plugin_id_t id,void * p)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_with_data(counts, sort_cmp, NULL);
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_with_data(accesses, sort_loc, NULL);
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 
new_count(const char * name,uint64_t base)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 
new_location(GHashTable * table,uint64_t off_or_pc)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 
hwprofile_match_hit(DeviceCounts * rec,uint64_t off)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 
inc_count(IOCounts * count,bool is_write,unsigned int cpu_index)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 
vcpu_haddr(unsigned int cpu_index,qemu_plugin_meminfo_t meminfo,uint64_t vaddr,void * udata)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 
vcpu_tb_trans(qemu_plugin_id_t id,struct qemu_plugin_tb * tb)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
qemu_plugin_install(qemu_plugin_id_t id,const qemu_info_t * info,int argc,char ** argv)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