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