1 /*
2 * Copyright (C) 2019, Alex Bennée <alex.bennee@linaro.org>
3 *
4 * Hot Pages - show which pages saw the most memory accesses.
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 static uint64_t page_size = 4096;
26 static uint64_t page_mask;
27 static int limit = 50;
28 static enum qemu_plugin_mem_rw rw = QEMU_PLUGIN_MEM_RW;
29 static bool track_io;
30
31 enum sort_type {
32 SORT_RW = 0,
33 SORT_R,
34 SORT_W,
35 SORT_A
36 };
37
38 static int sort_by = SORT_RW;
39
40 typedef struct {
41 uint64_t page_address;
42 int cpu_read;
43 int cpu_write;
44 uint64_t reads;
45 uint64_t writes;
46 } PageCounters;
47
48 static GMutex lock;
49 static GHashTable *pages;
50
cmp_access_count(gconstpointer a,gconstpointer b)51 static gint cmp_access_count(gconstpointer a, gconstpointer b)
52 {
53 PageCounters *ea = (PageCounters *) a;
54 PageCounters *eb = (PageCounters *) b;
55 int r;
56 switch (sort_by) {
57 case SORT_RW:
58 r = (ea->reads + ea->writes) > (eb->reads + eb->writes) ? -1 : 1;
59 break;
60 case SORT_R:
61 r = ea->reads > eb->reads ? -1 : 1;
62 break;
63 case SORT_W:
64 r = ea->writes > eb->writes ? -1 : 1;
65 break;
66 case SORT_A:
67 r = ea->page_address > eb->page_address ? -1 : 1;
68 break;
69 default:
70 g_assert_not_reached();
71 }
72 return r;
73 }
74
75
plugin_exit(qemu_plugin_id_t id,void * p)76 static void plugin_exit(qemu_plugin_id_t id, void *p)
77 {
78 g_autoptr(GString) report = g_string_new("Addr, RCPUs, Reads, WCPUs, Writes\n");
79 int i;
80 GList *counts;
81
82 counts = g_hash_table_get_values(pages);
83 if (counts && g_list_next(counts)) {
84 GList *it;
85
86 it = g_list_sort(counts, cmp_access_count);
87
88 for (i = 0; i < limit && it->next; i++, it = it->next) {
89 PageCounters *rec = (PageCounters *) it->data;
90 g_string_append_printf(report,
91 "0x%016"PRIx64", 0x%04x, %"PRId64
92 ", 0x%04x, %"PRId64"\n",
93 rec->page_address,
94 rec->cpu_read, rec->reads,
95 rec->cpu_write, rec->writes);
96 }
97 g_list_free(it);
98 }
99
100 qemu_plugin_outs(report->str);
101 }
102
plugin_init(void)103 static void plugin_init(void)
104 {
105 page_mask = (page_size - 1);
106 pages = g_hash_table_new(NULL, g_direct_equal);
107 }
108
vcpu_haddr(unsigned int cpu_index,qemu_plugin_meminfo_t meminfo,uint64_t vaddr,void * udata)109 static void vcpu_haddr(unsigned int cpu_index, qemu_plugin_meminfo_t meminfo,
110 uint64_t vaddr, void *udata)
111 {
112 struct qemu_plugin_hwaddr *hwaddr = qemu_plugin_get_hwaddr(meminfo, vaddr);
113 uint64_t page;
114 PageCounters *count;
115
116 /* We only get a hwaddr for system emulation */
117 if (track_io) {
118 if (hwaddr && qemu_plugin_hwaddr_is_io(hwaddr)) {
119 page = vaddr;
120 } else {
121 return;
122 }
123 } else {
124 if (hwaddr && !qemu_plugin_hwaddr_is_io(hwaddr)) {
125 page = (uint64_t) qemu_plugin_hwaddr_phys_addr(hwaddr);
126 } else {
127 page = vaddr;
128 }
129 }
130 page &= ~page_mask;
131
132 g_mutex_lock(&lock);
133 count = (PageCounters *) g_hash_table_lookup(pages, GUINT_TO_POINTER(page));
134
135 if (!count) {
136 count = g_new0(PageCounters, 1);
137 count->page_address = page;
138 g_hash_table_insert(pages, GUINT_TO_POINTER(page), (gpointer) count);
139 }
140 if (qemu_plugin_mem_is_store(meminfo)) {
141 count->writes++;
142 count->cpu_write |= (1 << cpu_index);
143 } else {
144 count->reads++;
145 count->cpu_read |= (1 << cpu_index);
146 }
147
148 g_mutex_unlock(&lock);
149 }
150
vcpu_tb_trans(qemu_plugin_id_t id,struct qemu_plugin_tb * tb)151 static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb *tb)
152 {
153 size_t n = qemu_plugin_tb_n_insns(tb);
154 size_t i;
155
156 for (i = 0; i < n; i++) {
157 struct qemu_plugin_insn *insn = qemu_plugin_tb_get_insn(tb, i);
158 qemu_plugin_register_vcpu_mem_cb(insn, vcpu_haddr,
159 QEMU_PLUGIN_CB_NO_REGS,
160 rw, NULL);
161 }
162 }
163
164 QEMU_PLUGIN_EXPORT
qemu_plugin_install(qemu_plugin_id_t id,const qemu_info_t * info,int argc,char ** argv)165 int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info,
166 int argc, char **argv)
167 {
168 int i;
169
170 for (i = 0; i < argc; i++) {
171 char *opt = argv[i];
172 g_auto(GStrv) tokens = g_strsplit(opt, "=", -1);
173
174 if (g_strcmp0(tokens[0], "sortby") == 0) {
175 if (g_strcmp0(tokens[1], "reads") == 0) {
176 sort_by = SORT_R;
177 } else if (g_strcmp0(tokens[1], "writes") == 0) {
178 sort_by = SORT_W;
179 } else if (g_strcmp0(tokens[1], "address") == 0) {
180 sort_by = SORT_A;
181 } else {
182 fprintf(stderr, "invalid value to sortby: %s\n", tokens[1]);
183 return -1;
184 }
185 } else if (g_strcmp0(tokens[0], "io") == 0) {
186 if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &track_io)) {
187 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
188 return -1;
189 }
190 } else if (g_strcmp0(tokens[0], "pagesize") == 0) {
191 page_size = g_ascii_strtoull(tokens[1], NULL, 10);
192 } else {
193 fprintf(stderr, "option parsing failed: %s\n", opt);
194 return -1;
195 }
196 }
197
198 plugin_init();
199
200 qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
201 qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
202 return 0;
203 }
204