2 * Copyright (C) 2020, Alex Bennée <alex.bennee@linaro.org>
4 * HW Profile - breakdown access patterns for IO to devices
6 * License: GNU GPL, version 2 or later.
7 * See the COPYING file in the top-level directory.
19 #include <qemu-plugin.h>
21 QEMU_PLUGIN_EXPORT
int qemu_plugin_version
= QEMU_PLUGIN_VERSION
;
23 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
45 static GHashTable
*devices
;
47 /* track the access pattern to a piece of HW */
49 /* track the source address of access to HW */
51 /* track only matched regions of HW */
52 static bool check_match
;
53 static gchar
**matches
;
55 static enum qemu_plugin_mem_rw rw
= QEMU_PLUGIN_MEM_RW
;
57 static inline bool track_reads(void)
59 return rw
== QEMU_PLUGIN_MEM_RW
|| rw
== QEMU_PLUGIN_MEM_R
;
62 static inline bool track_writes(void)
64 return rw
== QEMU_PLUGIN_MEM_RW
|| rw
== QEMU_PLUGIN_MEM_W
;
67 static void plugin_init(void)
69 devices
= g_hash_table_new(NULL
, NULL
);
72 static gint
sort_cmp(gconstpointer a
, gconstpointer b
)
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;
80 static gint
sort_loc(gconstpointer a
, gconstpointer b
)
82 IOLocationCounts
*ea
= (IOLocationCounts
*) a
;
83 IOLocationCounts
*eb
= (IOLocationCounts
*) b
;
84 return ea
->off_or_pc
> eb
->off_or_pc
;
87 static void fmt_iocount_record(GString
*s
, IOCounts
*rec
)
90 g_string_append_printf(s
, ", %"PRIx64
", %"PRId64
,
91 rec
->cpu_read
, rec
->reads
);
94 g_string_append_printf(s
, ", %"PRIx64
", %"PRId64
,
95 rec
->cpu_write
, rec
->writes
);
99 static void fmt_dev_record(GString
*s
, DeviceCounts
*rec
)
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');
107 static void plugin_exit(qemu_plugin_id_t id
, void *p
)
109 g_autoptr(GString
) report
= g_string_new("");
112 if (!(pattern
|| source
)) {
113 g_string_printf(report
, "Device, Address");
115 g_string_append_printf(report
, ", RCPUs, Reads");
117 if (track_writes()) {
118 g_string_append_printf(report
, ", WCPUs, Writes");
120 g_string_append_c(report
, '\n');
123 counts
= g_hash_table_get_values(devices
);
124 if (counts
&& g_list_next(counts
)) {
127 it
= g_list_sort(counts
, sort_cmp
);
130 DeviceCounts
*rec
= (DeviceCounts
*) it
->data
;
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
);
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');
146 fmt_dev_record(report
, rec
);
153 qemu_plugin_outs(report
->str
);
156 static DeviceCounts
*new_count(const char *name
, uint64_t base
)
158 DeviceCounts
*count
= g_new0(DeviceCounts
, 1);
161 if (pattern
|| source
) {
162 count
->detail
= g_hash_table_new(NULL
, NULL
);
164 g_hash_table_insert(devices
, (gpointer
) name
, count
);
168 static IOLocationCounts
*new_location(GHashTable
*table
, uint64_t off_or_pc
)
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
);
176 static void hwprofile_match_hit(DeviceCounts
*rec
, uint64_t off
)
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
);
184 static void inc_count(IOCounts
*count
, bool is_write
, unsigned int cpu_index
)
188 count
->cpu_write
|= (1 << cpu_index
);
191 count
->cpu_read
|= (1 << cpu_index
);
195 static void vcpu_haddr(unsigned int cpu_index
, qemu_plugin_meminfo_t meminfo
,
196 uint64_t vaddr
, void *udata
)
198 struct qemu_plugin_hwaddr
*hwaddr
= qemu_plugin_get_hwaddr(meminfo
, vaddr
);
200 if (!hwaddr
|| !qemu_plugin_hwaddr_is_io(hwaddr
)) {
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
;
209 counts
= (DeviceCounts
*) g_hash_table_lookup(devices
, name
);
212 uint64_t base
= vaddr
- off
;
213 counts
= new_count(name
, base
);
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
);
222 inc_count(&counts
->totals
, is_write
, cpu_index
);
225 /* either track offsets or source of access */
227 off
= (uint64_t) udata
;
230 if (pattern
|| source
) {
231 IOLocationCounts
*io_count
= g_hash_table_lookup(counts
->detail
,
234 io_count
= new_location(counts
->detail
, off
);
236 inc_count(&io_count
->counts
, is_write
, cpu_index
);
239 g_mutex_unlock(&lock
);
243 static void vcpu_tb_trans(qemu_plugin_id_t id
, struct qemu_plugin_tb
*tb
)
245 size_t n
= qemu_plugin_tb_n_insns(tb
);
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
,
258 int qemu_plugin_install(qemu_plugin_id_t id
, const qemu_info_t
*info
,
259 int argc
, char **argv
)
262 g_autoptr(GString
) matches_raw
= g_string_new("");
264 for (i
= 0; i
< argc
; i
++) {
266 g_autofree
char **tokens
= g_strsplit(opt
, "=", 2);
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
;
274 fprintf(stderr
, "invalid value for track: %s\n", tokens
[1]);
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
);
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
);
287 } else if (g_strcmp0(tokens
[0], "match") == 0) {
289 g_string_append_printf(matches_raw
, "%s,", tokens
[1]);
291 fprintf(stderr
, "option parsing failed: %s\n", opt
);
296 matches
= g_strsplit(matches_raw
->str
, ",", -1);
299 if (source
&& pattern
) {
300 fprintf(stderr
, "can only currently track either source or pattern.\n");
304 if (!info
->system_emulation
) {
305 fprintf(stderr
, "hwprofile: plugin only useful for system emulation\n");
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");
317 qemu_plugin_register_vcpu_tb_trans_cb(id
, vcpu_tb_trans
);
318 qemu_plugin_register_atexit_cb(id
, plugin_exit
, NULL
);