Commit bcf6edcd6fdb8965290f0b635a530fa3c6c212e1
Committed by
Arnaldo Carvalho de Melo
1 parent
26bf264e87
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
perf kvm: Events analysis tool
Add 'perf kvm stat' support to analyze kvm vmexit/mmio/ioport smartly Usage: - kvm stat run a command and gather performance counter statistics, it is the alias of perf stat - trace kvm events: perf kvm stat record, or, if other tracepoints are interesting as well, we can append the events like this: perf kvm stat record -e timer:* -a If many guests are running, we can track the specified guest by using -p or --pid, -a is used to track events generated by all guests. - show the result: perf kvm stat report The output example is following: 13005 13059 total 2 guests are running on the host Then, track the guest whose pid is 13059: ^C[ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.253 MB perf.data.guest (~11065 samples) ] See the vmexit events: Analyze events for all VCPUs: VM-EXIT Samples Samples% Time% Avg time APIC_ACCESS 460 70.55% 0.01% 22.44us ( +- 1.75% ) HLT 93 14.26% 99.98% 832077.26us ( +- 10.42% ) EXTERNAL_INTERRUPT 64 9.82% 0.00% 35.35us ( +- 14.21% ) PENDING_INTERRUPT 24 3.68% 0.00% 9.29us ( +- 31.39% ) CR_ACCESS 7 1.07% 0.00% 8.12us ( +- 5.76% ) IO_INSTRUCTION 3 0.46% 0.00% 18.00us ( +- 11.79% ) EXCEPTION_NMI 1 0.15% 0.00% 5.83us ( +- -nan% ) Total Samples:652, Total events handled time:77396109.80us. See the mmio events: Analyze events for all VCPUs: MMIO Access Samples Samples% Time% Avg time 0xfee00380:W 387 84.31% 79.28% 8.29us ( +- 3.32% ) 0xfee00300:W 24 5.23% 9.96% 16.79us ( +- 1.97% ) 0xfee00300:R 24 5.23% 7.83% 13.20us ( +- 3.00% ) 0xfee00310:W 24 5.23% 2.93% 4.94us ( +- 3.84% ) Total Samples:459, Total events handled time:4044.59us. See the ioport event: Analyze events for all VCPUs: IO Port Access Samples Samples% Time% Avg time 0xc050:POUT 3 100.00% 100.00% 13.75us ( +- 10.83% ) Total Samples:3, Total events handled time:41.26us. And, --vcpu is used to track the specified vcpu and --key is used to sort the result: Analyze events for VCPU 0: VM-EXIT Samples Samples% Time% Avg time HLT 27 13.85% 99.97% 405790.24us ( +- 12.70% ) EXTERNAL_INTERRUPT 13 6.67% 0.00% 27.94us ( +- 22.26% ) APIC_ACCESS 146 74.87% 0.03% 21.69us ( +- 2.91% ) IO_INSTRUCTION 2 1.03% 0.00% 17.77us ( +- 20.56% ) CR_ACCESS 2 1.03% 0.00% 8.55us ( +- 6.47% ) PENDING_INTERRUPT 5 2.56% 0.00% 6.27us ( +- 3.94% ) Total Samples:195, Total events handled time:10959950.90us. Signed-off-by: Dong Hao <haodong@linux.vnet.ibm.com> Signed-off-by: Runzhen Wang <runzhen@linux.vnet.ibm.com> [ Dong Hao <haodong@linux.vnet.ibm.com> Runzhen Wang <runzhen@linux.vnet.ibm.com>: - rebase it on current acme's tree - fix the compiling-error on i386 ] Signed-off-by: Xiao Guangrong <xiaoguangrong@linux.vnet.ibm.com> Acked-by: David Ahern <dsahern@gmail.com> Cc: Avi Kivity <avi@redhat.com> Cc: David Ahern <dsahern@gmail.com> Cc: Ingo Molnar <mingo@kernel.org> Cc: Marcelo Tosatti <mtosatti@redhat.com> Cc: kvm@vger.kernel.org Cc: Runzhen Wang <runzhen@linux.vnet.ibm.com> Link: http://lkml.kernel.org/r/1347870675-31495-4-git-send-email-haodong@linux.vnet.ibm.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Showing 6 changed files with 929 additions and 6 deletions Side-by-side Diff
tools/perf/Documentation/perf-kvm.txt
... | ... | @@ -12,7 +12,7 @@ |
12 | 12 | [--guestkallsyms=<path> --guestmodules=<path> | --guestvmlinux=<path>]] |
13 | 13 | {top|record|report|diff|buildid-list} |
14 | 14 | 'perf kvm' [--host] [--guest] [--guestkallsyms=<path> --guestmodules=<path> |
15 | - | --guestvmlinux=<path>] {top|record|report|diff|buildid-list} | |
15 | + | --guestvmlinux=<path>] {top|record|report|diff|buildid-list|stat} | |
16 | 16 | |
17 | 17 | DESCRIPTION |
18 | 18 | ----------- |
... | ... | @@ -38,6 +38,18 @@ |
38 | 38 | so that other tools can be used to fetch packages with matching symbol tables |
39 | 39 | for use by perf report. |
40 | 40 | |
41 | + 'perf kvm stat <command>' to run a command and gather performance counter | |
42 | + statistics. | |
43 | + Especially, perf 'kvm stat record/report' generates a statistical analysis | |
44 | + of KVM events. Currently, vmexit, mmio and ioport events are supported. | |
45 | + 'perf kvm stat record <command>' records kvm events and the events between | |
46 | + start and end <command>. | |
47 | + And this command produces a file which contains tracing results of kvm | |
48 | + events. | |
49 | + | |
50 | + 'perf kvm stat report' reports statistical data which includes events | |
51 | + handled time, samples, and so on. | |
52 | + | |
41 | 53 | OPTIONS |
42 | 54 | ------- |
43 | 55 | -i:: |
44 | 56 | |
... | ... | @@ -68,8 +80,22 @@ |
68 | 80 | --guestvmlinux=<path>:: |
69 | 81 | Guest os kernel vmlinux. |
70 | 82 | |
83 | +STAT REPORT OPTIONS | |
84 | +------------------- | |
85 | +--vcpu=<value>:: | |
86 | + analyze events which occures on this vcpu. (default: all vcpus) | |
87 | + | |
88 | +--events=<value>:: | |
89 | + events to be analyzed. Possible values: vmexit, mmio, ioport. | |
90 | + (default: vmexit) | |
91 | +-k:: | |
92 | +--key=<value>:: | |
93 | + Sorting key. Possible values: sample (default, sort by samples | |
94 | + number), time (sort by average time). | |
95 | + | |
71 | 96 | SEE ALSO |
72 | 97 | -------- |
73 | 98 | linkperf:perf-top[1], linkperf:perf-record[1], linkperf:perf-report[1], |
74 | -linkperf:perf-diff[1], linkperf:perf-buildid-list[1] | |
99 | +linkperf:perf-diff[1], linkperf:perf-buildid-list[1], | |
100 | +linkperf:perf-stat[1] |
tools/perf/MANIFEST
tools/perf/builtin-kvm.c
1 | 1 | #include "builtin.h" |
2 | 2 | #include "perf.h" |
3 | 3 | |
4 | +#include "util/evsel.h" | |
4 | 5 | #include "util/util.h" |
5 | 6 | #include "util/cache.h" |
6 | 7 | #include "util/symbol.h" |
7 | 8 | |
... | ... | @@ -10,8 +11,10 @@ |
10 | 11 | |
11 | 12 | #include "util/parse-options.h" |
12 | 13 | #include "util/trace-event.h" |
13 | - | |
14 | 14 | #include "util/debug.h" |
15 | +#include "util/debugfs.h" | |
16 | +#include "util/tool.h" | |
17 | +#include "util/stat.h" | |
15 | 18 | |
16 | 19 | #include <sys/prctl.h> |
17 | 20 | |
18 | 21 | |
... | ... | @@ -19,11 +22,840 @@ |
19 | 22 | #include <pthread.h> |
20 | 23 | #include <math.h> |
21 | 24 | |
22 | -static const char *file_name; | |
25 | +#include "../../arch/x86/include/asm/svm.h" | |
26 | +#include "../../arch/x86/include/asm/vmx.h" | |
27 | +#include "../../arch/x86/include/asm/kvm.h" | |
28 | + | |
29 | +struct event_key { | |
30 | + #define INVALID_KEY (~0ULL) | |
31 | + u64 key; | |
32 | + int info; | |
33 | +}; | |
34 | + | |
35 | +struct kvm_events_ops { | |
36 | + bool (*is_begin_event)(struct event_format *event, void *data, | |
37 | + struct event_key *key); | |
38 | + bool (*is_end_event)(struct event_format *event, void *data, | |
39 | + struct event_key *key); | |
40 | + void (*decode_key)(struct event_key *key, char decode[20]); | |
41 | + const char *name; | |
42 | +}; | |
43 | + | |
44 | +static void exit_event_get_key(struct event_format *event, void *data, | |
45 | + struct event_key *key) | |
46 | +{ | |
47 | + key->info = 0; | |
48 | + key->key = raw_field_value(event, "exit_reason", data); | |
49 | +} | |
50 | + | |
51 | +static bool kvm_exit_event(struct event_format *event) | |
52 | +{ | |
53 | + return !strcmp(event->name, "kvm_exit"); | |
54 | +} | |
55 | + | |
56 | +static bool exit_event_begin(struct event_format *event, void *data, | |
57 | + struct event_key *key) | |
58 | +{ | |
59 | + if (kvm_exit_event(event)) { | |
60 | + exit_event_get_key(event, data, key); | |
61 | + return true; | |
62 | + } | |
63 | + | |
64 | + return false; | |
65 | +} | |
66 | + | |
67 | +static bool kvm_entry_event(struct event_format *event) | |
68 | +{ | |
69 | + return !strcmp(event->name, "kvm_entry"); | |
70 | +} | |
71 | + | |
72 | +static bool exit_event_end(struct event_format *event, void *data __maybe_unused, | |
73 | + struct event_key *key __maybe_unused) | |
74 | +{ | |
75 | + return kvm_entry_event(event); | |
76 | +} | |
77 | + | |
78 | +struct exit_reasons_table { | |
79 | + unsigned long exit_code; | |
80 | + const char *reason; | |
81 | +}; | |
82 | + | |
83 | +struct exit_reasons_table vmx_exit_reasons[] = { | |
84 | + VMX_EXIT_REASONS | |
85 | +}; | |
86 | + | |
87 | +struct exit_reasons_table svm_exit_reasons[] = { | |
88 | + SVM_EXIT_REASONS | |
89 | +}; | |
90 | + | |
91 | +static int cpu_isa; | |
92 | + | |
93 | +static const char *get_exit_reason(u64 exit_code) | |
94 | +{ | |
95 | + int table_size = ARRAY_SIZE(svm_exit_reasons); | |
96 | + struct exit_reasons_table *table = svm_exit_reasons; | |
97 | + | |
98 | + if (cpu_isa == 1) { | |
99 | + table = vmx_exit_reasons; | |
100 | + table_size = ARRAY_SIZE(vmx_exit_reasons); | |
101 | + } | |
102 | + | |
103 | + while (table_size--) { | |
104 | + if (table->exit_code == exit_code) | |
105 | + return table->reason; | |
106 | + table++; | |
107 | + } | |
108 | + | |
109 | + pr_err("unknown kvm exit code:%lld on %s\n", | |
110 | + (unsigned long long)exit_code, cpu_isa ? "VMX" : "SVM"); | |
111 | + return "UNKNOWN"; | |
112 | +} | |
113 | + | |
114 | +static void exit_event_decode_key(struct event_key *key, char decode[20]) | |
115 | +{ | |
116 | + const char *exit_reason = get_exit_reason(key->key); | |
117 | + | |
118 | + scnprintf(decode, 20, "%s", exit_reason); | |
119 | +} | |
120 | + | |
121 | +static struct kvm_events_ops exit_events = { | |
122 | + .is_begin_event = exit_event_begin, | |
123 | + .is_end_event = exit_event_end, | |
124 | + .decode_key = exit_event_decode_key, | |
125 | + .name = "VM-EXIT" | |
126 | +}; | |
127 | + | |
128 | + /* | |
129 | + * For the mmio events, we treat: | |
130 | + * the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry | |
131 | + * the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...). | |
132 | + */ | |
133 | +static void mmio_event_get_key(struct event_format *event, void *data, | |
134 | + struct event_key *key) | |
135 | +{ | |
136 | + key->key = raw_field_value(event, "gpa", data); | |
137 | + key->info = raw_field_value(event, "type", data); | |
138 | +} | |
139 | + | |
140 | +#define KVM_TRACE_MMIO_READ_UNSATISFIED 0 | |
141 | +#define KVM_TRACE_MMIO_READ 1 | |
142 | +#define KVM_TRACE_MMIO_WRITE 2 | |
143 | + | |
144 | +static bool mmio_event_begin(struct event_format *event, void *data, | |
145 | + struct event_key *key) | |
146 | +{ | |
147 | + /* MMIO read begin event in kernel. */ | |
148 | + if (kvm_exit_event(event)) | |
149 | + return true; | |
150 | + | |
151 | + /* MMIO write begin event in kernel. */ | |
152 | + if (!strcmp(event->name, "kvm_mmio") && | |
153 | + raw_field_value(event, "type", data) == KVM_TRACE_MMIO_WRITE) { | |
154 | + mmio_event_get_key(event, data, key); | |
155 | + return true; | |
156 | + } | |
157 | + | |
158 | + return false; | |
159 | +} | |
160 | + | |
161 | +static bool mmio_event_end(struct event_format *event, void *data, | |
162 | + struct event_key *key) | |
163 | +{ | |
164 | + /* MMIO write end event in kernel. */ | |
165 | + if (kvm_entry_event(event)) | |
166 | + return true; | |
167 | + | |
168 | + /* MMIO read end event in kernel.*/ | |
169 | + if (!strcmp(event->name, "kvm_mmio") && | |
170 | + raw_field_value(event, "type", data) == KVM_TRACE_MMIO_READ) { | |
171 | + mmio_event_get_key(event, data, key); | |
172 | + return true; | |
173 | + } | |
174 | + | |
175 | + return false; | |
176 | +} | |
177 | + | |
178 | +static void mmio_event_decode_key(struct event_key *key, char decode[20]) | |
179 | +{ | |
180 | + scnprintf(decode, 20, "%#lx:%s", (unsigned long)key->key, | |
181 | + key->info == KVM_TRACE_MMIO_WRITE ? "W" : "R"); | |
182 | +} | |
183 | + | |
184 | +static struct kvm_events_ops mmio_events = { | |
185 | + .is_begin_event = mmio_event_begin, | |
186 | + .is_end_event = mmio_event_end, | |
187 | + .decode_key = mmio_event_decode_key, | |
188 | + .name = "MMIO Access" | |
189 | +}; | |
190 | + | |
191 | + /* The time of emulation pio access is from kvm_pio to kvm_entry. */ | |
192 | +static void ioport_event_get_key(struct event_format *event, void *data, | |
193 | + struct event_key *key) | |
194 | +{ | |
195 | + key->key = raw_field_value(event, "port", data); | |
196 | + key->info = raw_field_value(event, "rw", data); | |
197 | +} | |
198 | + | |
199 | +static bool ioport_event_begin(struct event_format *event, void *data, | |
200 | + struct event_key *key) | |
201 | +{ | |
202 | + if (!strcmp(event->name, "kvm_pio")) { | |
203 | + ioport_event_get_key(event, data, key); | |
204 | + return true; | |
205 | + } | |
206 | + | |
207 | + return false; | |
208 | +} | |
209 | + | |
210 | +static bool ioport_event_end(struct event_format *event, void *data __maybe_unused, | |
211 | + struct event_key *key __maybe_unused) | |
212 | +{ | |
213 | + if (kvm_entry_event(event)) | |
214 | + return true; | |
215 | + | |
216 | + return false; | |
217 | +} | |
218 | + | |
219 | +static void ioport_event_decode_key(struct event_key *key, char decode[20]) | |
220 | +{ | |
221 | + scnprintf(decode, 20, "%#llx:%s", (unsigned long long)key->key, | |
222 | + key->info ? "POUT" : "PIN"); | |
223 | +} | |
224 | + | |
225 | +static struct kvm_events_ops ioport_events = { | |
226 | + .is_begin_event = ioport_event_begin, | |
227 | + .is_end_event = ioport_event_end, | |
228 | + .decode_key = ioport_event_decode_key, | |
229 | + .name = "IO Port Access" | |
230 | +}; | |
231 | + | |
232 | +static const char *report_event = "vmexit"; | |
233 | +struct kvm_events_ops *events_ops; | |
234 | + | |
235 | +static bool register_kvm_events_ops(void) | |
236 | +{ | |
237 | + bool ret = true; | |
238 | + | |
239 | + if (!strcmp(report_event, "vmexit")) | |
240 | + events_ops = &exit_events; | |
241 | + else if (!strcmp(report_event, "mmio")) | |
242 | + events_ops = &mmio_events; | |
243 | + else if (!strcmp(report_event, "ioport")) | |
244 | + events_ops = &ioport_events; | |
245 | + else { | |
246 | + pr_err("Unknown report event:%s\n", report_event); | |
247 | + ret = false; | |
248 | + } | |
249 | + | |
250 | + return ret; | |
251 | +} | |
252 | + | |
253 | +struct kvm_event_stats { | |
254 | + u64 time; | |
255 | + struct stats stats; | |
256 | +}; | |
257 | + | |
258 | +struct kvm_event { | |
259 | + struct list_head hash_entry; | |
260 | + struct rb_node rb; | |
261 | + | |
262 | + struct event_key key; | |
263 | + | |
264 | + struct kvm_event_stats total; | |
265 | + | |
266 | + #define DEFAULT_VCPU_NUM 8 | |
267 | + int max_vcpu; | |
268 | + struct kvm_event_stats *vcpu; | |
269 | +}; | |
270 | + | |
271 | +struct vcpu_event_record { | |
272 | + int vcpu_id; | |
273 | + u64 start_time; | |
274 | + struct kvm_event *last_event; | |
275 | +}; | |
276 | + | |
277 | +#define EVENTS_BITS 12 | |
278 | +#define EVENTS_CACHE_SIZE (1UL << EVENTS_BITS) | |
279 | + | |
280 | +static u64 total_time; | |
281 | +static u64 total_count; | |
282 | +static struct list_head kvm_events_cache[EVENTS_CACHE_SIZE]; | |
283 | + | |
284 | +static void init_kvm_event_record(void) | |
285 | +{ | |
286 | + int i; | |
287 | + | |
288 | + for (i = 0; i < (int)EVENTS_CACHE_SIZE; i++) | |
289 | + INIT_LIST_HEAD(&kvm_events_cache[i]); | |
290 | +} | |
291 | + | |
292 | +static int kvm_events_hash_fn(u64 key) | |
293 | +{ | |
294 | + return key & (EVENTS_CACHE_SIZE - 1); | |
295 | +} | |
296 | + | |
297 | +static bool kvm_event_expand(struct kvm_event *event, int vcpu_id) | |
298 | +{ | |
299 | + int old_max_vcpu = event->max_vcpu; | |
300 | + | |
301 | + if (vcpu_id < event->max_vcpu) | |
302 | + return true; | |
303 | + | |
304 | + while (event->max_vcpu <= vcpu_id) | |
305 | + event->max_vcpu += DEFAULT_VCPU_NUM; | |
306 | + | |
307 | + event->vcpu = realloc(event->vcpu, | |
308 | + event->max_vcpu * sizeof(*event->vcpu)); | |
309 | + if (!event->vcpu) { | |
310 | + pr_err("Not enough memory\n"); | |
311 | + return false; | |
312 | + } | |
313 | + | |
314 | + memset(event->vcpu + old_max_vcpu, 0, | |
315 | + (event->max_vcpu - old_max_vcpu) * sizeof(*event->vcpu)); | |
316 | + return true; | |
317 | +} | |
318 | + | |
319 | +static struct kvm_event *kvm_alloc_init_event(struct event_key *key) | |
320 | +{ | |
321 | + struct kvm_event *event; | |
322 | + | |
323 | + event = zalloc(sizeof(*event)); | |
324 | + if (!event) { | |
325 | + pr_err("Not enough memory\n"); | |
326 | + return NULL; | |
327 | + } | |
328 | + | |
329 | + event->key = *key; | |
330 | + return event; | |
331 | +} | |
332 | + | |
333 | +static struct kvm_event *find_create_kvm_event(struct event_key *key) | |
334 | +{ | |
335 | + struct kvm_event *event; | |
336 | + struct list_head *head; | |
337 | + | |
338 | + BUG_ON(key->key == INVALID_KEY); | |
339 | + | |
340 | + head = &kvm_events_cache[kvm_events_hash_fn(key->key)]; | |
341 | + list_for_each_entry(event, head, hash_entry) | |
342 | + if (event->key.key == key->key && event->key.info == key->info) | |
343 | + return event; | |
344 | + | |
345 | + event = kvm_alloc_init_event(key); | |
346 | + if (!event) | |
347 | + return NULL; | |
348 | + | |
349 | + list_add(&event->hash_entry, head); | |
350 | + return event; | |
351 | +} | |
352 | + | |
353 | +static bool handle_begin_event(struct vcpu_event_record *vcpu_record, | |
354 | + struct event_key *key, u64 timestamp) | |
355 | +{ | |
356 | + struct kvm_event *event = NULL; | |
357 | + | |
358 | + if (key->key != INVALID_KEY) | |
359 | + event = find_create_kvm_event(key); | |
360 | + | |
361 | + vcpu_record->last_event = event; | |
362 | + vcpu_record->start_time = timestamp; | |
363 | + return true; | |
364 | +} | |
365 | + | |
366 | +static void | |
367 | +kvm_update_event_stats(struct kvm_event_stats *kvm_stats, u64 time_diff) | |
368 | +{ | |
369 | + kvm_stats->time += time_diff; | |
370 | + update_stats(&kvm_stats->stats, time_diff); | |
371 | +} | |
372 | + | |
373 | +static double kvm_event_rel_stddev(int vcpu_id, struct kvm_event *event) | |
374 | +{ | |
375 | + struct kvm_event_stats *kvm_stats = &event->total; | |
376 | + | |
377 | + if (vcpu_id != -1) | |
378 | + kvm_stats = &event->vcpu[vcpu_id]; | |
379 | + | |
380 | + return rel_stddev_stats(stddev_stats(&kvm_stats->stats), | |
381 | + avg_stats(&kvm_stats->stats)); | |
382 | +} | |
383 | + | |
384 | +static bool update_kvm_event(struct kvm_event *event, int vcpu_id, | |
385 | + u64 time_diff) | |
386 | +{ | |
387 | + kvm_update_event_stats(&event->total, time_diff); | |
388 | + | |
389 | + if (!kvm_event_expand(event, vcpu_id)) | |
390 | + return false; | |
391 | + | |
392 | + kvm_update_event_stats(&event->vcpu[vcpu_id], time_diff); | |
393 | + return true; | |
394 | +} | |
395 | + | |
396 | +static bool handle_end_event(struct vcpu_event_record *vcpu_record, | |
397 | + struct event_key *key, u64 timestamp) | |
398 | +{ | |
399 | + struct kvm_event *event; | |
400 | + u64 time_begin, time_diff; | |
401 | + | |
402 | + event = vcpu_record->last_event; | |
403 | + time_begin = vcpu_record->start_time; | |
404 | + | |
405 | + /* The begin event is not caught. */ | |
406 | + if (!time_begin) | |
407 | + return true; | |
408 | + | |
409 | + /* | |
410 | + * In some case, the 'begin event' only records the start timestamp, | |
411 | + * the actual event is recognized in the 'end event' (e.g. mmio-event). | |
412 | + */ | |
413 | + | |
414 | + /* Both begin and end events did not get the key. */ | |
415 | + if (!event && key->key == INVALID_KEY) | |
416 | + return true; | |
417 | + | |
418 | + if (!event) | |
419 | + event = find_create_kvm_event(key); | |
420 | + | |
421 | + if (!event) | |
422 | + return false; | |
423 | + | |
424 | + vcpu_record->last_event = NULL; | |
425 | + vcpu_record->start_time = 0; | |
426 | + | |
427 | + BUG_ON(timestamp < time_begin); | |
428 | + | |
429 | + time_diff = timestamp - time_begin; | |
430 | + return update_kvm_event(event, vcpu_record->vcpu_id, time_diff); | |
431 | +} | |
432 | + | |
433 | +static struct vcpu_event_record | |
434 | +*per_vcpu_record(struct thread *thread, struct event_format *event, void *data) | |
435 | +{ | |
436 | + /* Only kvm_entry records vcpu id. */ | |
437 | + if (!thread->priv && kvm_entry_event(event)) { | |
438 | + struct vcpu_event_record *vcpu_record; | |
439 | + | |
440 | + vcpu_record = zalloc(sizeof(struct vcpu_event_record)); | |
441 | + if (!vcpu_record) { | |
442 | + pr_err("Not enough memory\n"); | |
443 | + return NULL; | |
444 | + } | |
445 | + | |
446 | + vcpu_record->vcpu_id = raw_field_value(event, "vcpu_id", data); | |
447 | + thread->priv = vcpu_record; | |
448 | + } | |
449 | + | |
450 | + return (struct vcpu_event_record *)thread->priv; | |
451 | +} | |
452 | + | |
453 | +static bool handle_kvm_event(struct thread *thread, struct event_format *event, | |
454 | + void *data, u64 timestamp) | |
455 | +{ | |
456 | + struct vcpu_event_record *vcpu_record; | |
457 | + struct event_key key = {.key = INVALID_KEY}; | |
458 | + | |
459 | + vcpu_record = per_vcpu_record(thread, event, data); | |
460 | + if (!vcpu_record) | |
461 | + return true; | |
462 | + | |
463 | + if (events_ops->is_begin_event(event, data, &key)) | |
464 | + return handle_begin_event(vcpu_record, &key, timestamp); | |
465 | + | |
466 | + if (events_ops->is_end_event(event, data, &key)) | |
467 | + return handle_end_event(vcpu_record, &key, timestamp); | |
468 | + | |
469 | + return true; | |
470 | +} | |
471 | + | |
472 | +typedef int (*key_cmp_fun)(struct kvm_event*, struct kvm_event*, int); | |
473 | +struct kvm_event_key { | |
474 | + const char *name; | |
475 | + key_cmp_fun key; | |
476 | +}; | |
477 | + | |
478 | +static int trace_vcpu = -1; | |
479 | +#define GET_EVENT_KEY(func, field) \ | |
480 | +static u64 get_event_ ##func(struct kvm_event *event, int vcpu) \ | |
481 | +{ \ | |
482 | + if (vcpu == -1) \ | |
483 | + return event->total.field; \ | |
484 | + \ | |
485 | + if (vcpu >= event->max_vcpu) \ | |
486 | + return 0; \ | |
487 | + \ | |
488 | + return event->vcpu[vcpu].field; \ | |
489 | +} | |
490 | + | |
491 | +#define COMPARE_EVENT_KEY(func, field) \ | |
492 | +GET_EVENT_KEY(func, field) \ | |
493 | +static int compare_kvm_event_ ## func(struct kvm_event *one, \ | |
494 | + struct kvm_event *two, int vcpu)\ | |
495 | +{ \ | |
496 | + return get_event_ ##func(one, vcpu) > \ | |
497 | + get_event_ ##func(two, vcpu); \ | |
498 | +} | |
499 | + | |
500 | +GET_EVENT_KEY(time, time); | |
501 | +COMPARE_EVENT_KEY(count, stats.n); | |
502 | +COMPARE_EVENT_KEY(mean, stats.mean); | |
503 | + | |
504 | +#define DEF_SORT_NAME_KEY(name, compare_key) \ | |
505 | + { #name, compare_kvm_event_ ## compare_key } | |
506 | + | |
507 | +static struct kvm_event_key keys[] = { | |
508 | + DEF_SORT_NAME_KEY(sample, count), | |
509 | + DEF_SORT_NAME_KEY(time, mean), | |
510 | + { NULL, NULL } | |
511 | +}; | |
512 | + | |
513 | +static const char *sort_key = "sample"; | |
514 | +static key_cmp_fun compare; | |
515 | + | |
516 | +static bool select_key(void) | |
517 | +{ | |
518 | + int i; | |
519 | + | |
520 | + for (i = 0; keys[i].name; i++) { | |
521 | + if (!strcmp(keys[i].name, sort_key)) { | |
522 | + compare = keys[i].key; | |
523 | + return true; | |
524 | + } | |
525 | + } | |
526 | + | |
527 | + pr_err("Unknown compare key:%s\n", sort_key); | |
528 | + return false; | |
529 | +} | |
530 | + | |
531 | +static struct rb_root result; | |
532 | +static void insert_to_result(struct kvm_event *event, key_cmp_fun bigger, | |
533 | + int vcpu) | |
534 | +{ | |
535 | + struct rb_node **rb = &result.rb_node; | |
536 | + struct rb_node *parent = NULL; | |
537 | + struct kvm_event *p; | |
538 | + | |
539 | + while (*rb) { | |
540 | + p = container_of(*rb, struct kvm_event, rb); | |
541 | + parent = *rb; | |
542 | + | |
543 | + if (bigger(event, p, vcpu)) | |
544 | + rb = &(*rb)->rb_left; | |
545 | + else | |
546 | + rb = &(*rb)->rb_right; | |
547 | + } | |
548 | + | |
549 | + rb_link_node(&event->rb, parent, rb); | |
550 | + rb_insert_color(&event->rb, &result); | |
551 | +} | |
552 | + | |
553 | +static void update_total_count(struct kvm_event *event, int vcpu) | |
554 | +{ | |
555 | + total_count += get_event_count(event, vcpu); | |
556 | + total_time += get_event_time(event, vcpu); | |
557 | +} | |
558 | + | |
559 | +static bool event_is_valid(struct kvm_event *event, int vcpu) | |
560 | +{ | |
561 | + return !!get_event_count(event, vcpu); | |
562 | +} | |
563 | + | |
564 | +static void sort_result(int vcpu) | |
565 | +{ | |
566 | + unsigned int i; | |
567 | + struct kvm_event *event; | |
568 | + | |
569 | + for (i = 0; i < EVENTS_CACHE_SIZE; i++) | |
570 | + list_for_each_entry(event, &kvm_events_cache[i], hash_entry) | |
571 | + if (event_is_valid(event, vcpu)) { | |
572 | + update_total_count(event, vcpu); | |
573 | + insert_to_result(event, compare, vcpu); | |
574 | + } | |
575 | +} | |
576 | + | |
577 | +/* returns left most element of result, and erase it */ | |
578 | +static struct kvm_event *pop_from_result(void) | |
579 | +{ | |
580 | + struct rb_node *node = rb_first(&result); | |
581 | + | |
582 | + if (!node) | |
583 | + return NULL; | |
584 | + | |
585 | + rb_erase(node, &result); | |
586 | + return container_of(node, struct kvm_event, rb); | |
587 | +} | |
588 | + | |
589 | +static void print_vcpu_info(int vcpu) | |
590 | +{ | |
591 | + pr_info("Analyze events for "); | |
592 | + | |
593 | + if (vcpu == -1) | |
594 | + pr_info("all VCPUs:\n\n"); | |
595 | + else | |
596 | + pr_info("VCPU %d:\n\n", vcpu); | |
597 | +} | |
598 | + | |
599 | +static void print_result(int vcpu) | |
600 | +{ | |
601 | + char decode[20]; | |
602 | + struct kvm_event *event; | |
603 | + | |
604 | + pr_info("\n\n"); | |
605 | + print_vcpu_info(vcpu); | |
606 | + pr_info("%20s ", events_ops->name); | |
607 | + pr_info("%10s ", "Samples"); | |
608 | + pr_info("%9s ", "Samples%"); | |
609 | + | |
610 | + pr_info("%9s ", "Time%"); | |
611 | + pr_info("%16s ", "Avg time"); | |
612 | + pr_info("\n\n"); | |
613 | + | |
614 | + while ((event = pop_from_result())) { | |
615 | + u64 ecount, etime; | |
616 | + | |
617 | + ecount = get_event_count(event, vcpu); | |
618 | + etime = get_event_time(event, vcpu); | |
619 | + | |
620 | + events_ops->decode_key(&event->key, decode); | |
621 | + pr_info("%20s ", decode); | |
622 | + pr_info("%10llu ", (unsigned long long)ecount); | |
623 | + pr_info("%8.2f%% ", (double)ecount / total_count * 100); | |
624 | + pr_info("%8.2f%% ", (double)etime / total_time * 100); | |
625 | + pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3, | |
626 | + kvm_event_rel_stddev(vcpu, event)); | |
627 | + pr_info("\n"); | |
628 | + } | |
629 | + | |
630 | + pr_info("\nTotal Samples:%lld, Total events handled time:%.2fus.\n\n", | |
631 | + (unsigned long long)total_count, total_time / 1e3); | |
632 | +} | |
633 | + | |
634 | +static int process_sample_event(struct perf_tool *tool __maybe_unused, | |
635 | + union perf_event *event, | |
636 | + struct perf_sample *sample, | |
637 | + struct perf_evsel *evsel, | |
638 | + struct machine *machine) | |
639 | +{ | |
640 | + struct thread *thread = machine__findnew_thread(machine, sample->tid); | |
641 | + | |
642 | + if (thread == NULL) { | |
643 | + pr_debug("problem processing %d event, skipping it.\n", | |
644 | + event->header.type); | |
645 | + return -1; | |
646 | + } | |
647 | + | |
648 | + if (!handle_kvm_event(thread, evsel->tp_format, sample->raw_data, | |
649 | + sample->time)) | |
650 | + return -1; | |
651 | + | |
652 | + return 0; | |
653 | +} | |
654 | + | |
655 | +static struct perf_tool eops = { | |
656 | + .sample = process_sample_event, | |
657 | + .comm = perf_event__process_comm, | |
658 | + .ordered_samples = true, | |
659 | +}; | |
660 | + | |
661 | +static int get_cpu_isa(struct perf_session *session) | |
662 | +{ | |
663 | + char *cpuid; | |
664 | + int isa; | |
665 | + | |
666 | + cpuid = perf_header__read_feature(session, HEADER_CPUID); | |
667 | + | |
668 | + if (!cpuid) { | |
669 | + pr_err("read HEADER_CPUID failed.\n"); | |
670 | + return -ENOTSUP; | |
671 | + } | |
672 | + | |
673 | + if (strstr(cpuid, "Intel")) | |
674 | + isa = 1; | |
675 | + else if (strstr(cpuid, "AMD")) | |
676 | + isa = 0; | |
677 | + else { | |
678 | + pr_err("CPU %s is not supported.\n", cpuid); | |
679 | + isa = -ENOTSUP; | |
680 | + } | |
681 | + | |
682 | + free(cpuid); | |
683 | + return isa; | |
684 | +} | |
685 | + | |
686 | +static const char *file_name; | |
687 | + | |
688 | +static int read_events(void) | |
689 | +{ | |
690 | + struct perf_session *kvm_session; | |
691 | + int ret; | |
692 | + | |
693 | + kvm_session = perf_session__new(file_name, O_RDONLY, 0, false, &eops); | |
694 | + if (!kvm_session) { | |
695 | + pr_err("Initializing perf session failed\n"); | |
696 | + return -EINVAL; | |
697 | + } | |
698 | + | |
699 | + if (!perf_session__has_traces(kvm_session, "kvm record")) | |
700 | + return -EINVAL; | |
701 | + | |
702 | + /* | |
703 | + * Do not use 'isa' recorded in kvm_exit tracepoint since it is not | |
704 | + * traced in the old kernel. | |
705 | + */ | |
706 | + ret = get_cpu_isa(kvm_session); | |
707 | + | |
708 | + if (ret < 0) | |
709 | + return ret; | |
710 | + | |
711 | + cpu_isa = ret; | |
712 | + | |
713 | + return perf_session__process_events(kvm_session, &eops); | |
714 | +} | |
715 | + | |
716 | +static bool verify_vcpu(int vcpu) | |
717 | +{ | |
718 | + if (vcpu != -1 && vcpu < 0) { | |
719 | + pr_err("Invalid vcpu:%d.\n", vcpu); | |
720 | + return false; | |
721 | + } | |
722 | + | |
723 | + return true; | |
724 | +} | |
725 | + | |
726 | +static int kvm_events_report_vcpu(int vcpu) | |
727 | +{ | |
728 | + int ret = -EINVAL; | |
729 | + | |
730 | + if (!verify_vcpu(vcpu)) | |
731 | + goto exit; | |
732 | + | |
733 | + if (!select_key()) | |
734 | + goto exit; | |
735 | + | |
736 | + if (!register_kvm_events_ops()) | |
737 | + goto exit; | |
738 | + | |
739 | + init_kvm_event_record(); | |
740 | + setup_pager(); | |
741 | + | |
742 | + ret = read_events(); | |
743 | + if (ret) | |
744 | + goto exit; | |
745 | + | |
746 | + sort_result(vcpu); | |
747 | + print_result(vcpu); | |
748 | +exit: | |
749 | + return ret; | |
750 | +} | |
751 | + | |
752 | +static const char * const record_args[] = { | |
753 | + "record", | |
754 | + "-R", | |
755 | + "-f", | |
756 | + "-m", "1024", | |
757 | + "-c", "1", | |
758 | + "-e", "kvm:kvm_entry", | |
759 | + "-e", "kvm:kvm_exit", | |
760 | + "-e", "kvm:kvm_mmio", | |
761 | + "-e", "kvm:kvm_pio", | |
762 | +}; | |
763 | + | |
764 | +#define STRDUP_FAIL_EXIT(s) \ | |
765 | + ({ char *_p; \ | |
766 | + _p = strdup(s); \ | |
767 | + if (!_p) \ | |
768 | + return -ENOMEM; \ | |
769 | + _p; \ | |
770 | + }) | |
771 | + | |
772 | +static int kvm_events_record(int argc, const char **argv) | |
773 | +{ | |
774 | + unsigned int rec_argc, i, j; | |
775 | + const char **rec_argv; | |
776 | + | |
777 | + rec_argc = ARRAY_SIZE(record_args) + argc + 2; | |
778 | + rec_argv = calloc(rec_argc + 1, sizeof(char *)); | |
779 | + | |
780 | + if (rec_argv == NULL) | |
781 | + return -ENOMEM; | |
782 | + | |
783 | + for (i = 0; i < ARRAY_SIZE(record_args); i++) | |
784 | + rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]); | |
785 | + | |
786 | + rec_argv[i++] = STRDUP_FAIL_EXIT("-o"); | |
787 | + rec_argv[i++] = STRDUP_FAIL_EXIT(file_name); | |
788 | + | |
789 | + for (j = 1; j < (unsigned int)argc; j++, i++) | |
790 | + rec_argv[i] = argv[j]; | |
791 | + | |
792 | + return cmd_record(i, rec_argv, NULL); | |
793 | +} | |
794 | + | |
795 | +static const char * const kvm_events_report_usage[] = { | |
796 | + "perf kvm stat report [<options>]", | |
797 | + NULL | |
798 | +}; | |
799 | + | |
800 | +static const struct option kvm_events_report_options[] = { | |
801 | + OPT_STRING(0, "event", &report_event, "report event", | |
802 | + "event for reporting: vmexit, mmio, ioport"), | |
803 | + OPT_INTEGER(0, "vcpu", &trace_vcpu, | |
804 | + "vcpu id to report"), | |
805 | + OPT_STRING('k', "key", &sort_key, "sort-key", | |
806 | + "key for sorting: sample(sort by samples number)" | |
807 | + " time (sort by avg time)"), | |
808 | + OPT_END() | |
809 | +}; | |
810 | + | |
811 | +static int kvm_events_report(int argc, const char **argv) | |
812 | +{ | |
813 | + symbol__init(); | |
814 | + | |
815 | + if (argc) { | |
816 | + argc = parse_options(argc, argv, | |
817 | + kvm_events_report_options, | |
818 | + kvm_events_report_usage, 0); | |
819 | + if (argc) | |
820 | + usage_with_options(kvm_events_report_usage, | |
821 | + kvm_events_report_options); | |
822 | + } | |
823 | + | |
824 | + return kvm_events_report_vcpu(trace_vcpu); | |
825 | +} | |
826 | + | |
827 | +static void print_kvm_stat_usage(void) | |
828 | +{ | |
829 | + printf("Usage: perf kvm stat <command>\n\n"); | |
830 | + | |
831 | + printf("# Available commands:\n"); | |
832 | + printf("\trecord: record kvm events\n"); | |
833 | + printf("\treport: report statistical data of kvm events\n"); | |
834 | + | |
835 | + printf("\nOtherwise, it is the alias of 'perf stat':\n"); | |
836 | +} | |
837 | + | |
838 | +static int kvm_cmd_stat(int argc, const char **argv) | |
839 | +{ | |
840 | + if (argc == 1) { | |
841 | + print_kvm_stat_usage(); | |
842 | + goto perf_stat; | |
843 | + } | |
844 | + | |
845 | + if (!strncmp(argv[1], "rec", 3)) | |
846 | + return kvm_events_record(argc - 1, argv + 1); | |
847 | + | |
848 | + if (!strncmp(argv[1], "rep", 3)) | |
849 | + return kvm_events_report(argc - 1 , argv + 1); | |
850 | + | |
851 | +perf_stat: | |
852 | + return cmd_stat(argc, argv, NULL); | |
853 | +} | |
854 | + | |
23 | 855 | static char name_buffer[256]; |
24 | 856 | |
25 | 857 | static const char * const kvm_usage[] = { |
26 | - "perf kvm [<options>] {top|record|report|diff|buildid-list}", | |
858 | + "perf kvm [<options>] {top|record|report|diff|buildid-list|stat}", | |
27 | 859 | NULL |
28 | 860 | }; |
29 | 861 | |
... | ... | @@ -135,6 +967,8 @@ |
135 | 967 | return cmd_top(argc, argv, NULL); |
136 | 968 | else if (!strncmp(argv[0], "buildid-list", 12)) |
137 | 969 | return __cmd_buildid_list(argc, argv); |
970 | + else if (!strncmp(argv[0], "stat", 4)) | |
971 | + return kvm_cmd_stat(argc, argv); | |
138 | 972 | else |
139 | 973 | usage_with_options(kvm_usage, kvm_options); |
140 | 974 |
tools/perf/util/header.c
... | ... | @@ -1673,6 +1673,11 @@ |
1673 | 1673 | return 0; |
1674 | 1674 | } |
1675 | 1675 | |
1676 | +static char *read_cpuid(struct perf_header *ph, int fd) | |
1677 | +{ | |
1678 | + return do_read_string(fd, ph); | |
1679 | +} | |
1680 | + | |
1676 | 1681 | static struct perf_evsel * |
1677 | 1682 | perf_evlist__find_by_index(struct perf_evlist *evlist, int idx) |
1678 | 1683 | { |
... | ... | @@ -1726,6 +1731,7 @@ |
1726 | 1731 | struct feature_ops { |
1727 | 1732 | int (*write)(int fd, struct perf_header *h, struct perf_evlist *evlist); |
1728 | 1733 | void (*print)(struct perf_header *h, int fd, FILE *fp); |
1734 | + char *(*read)(struct perf_header *h, int fd); | |
1729 | 1735 | int (*process)(struct perf_file_section *section, |
1730 | 1736 | struct perf_header *h, int feat, int fd, void *data); |
1731 | 1737 | const char *name; |
... | ... | @@ -1740,6 +1746,9 @@ |
1740 | 1746 | #define FEAT_OPF(n, func) \ |
1741 | 1747 | [n] = { .name = #n, .write = write_##func, .print = print_##func, \ |
1742 | 1748 | .full_only = true } |
1749 | +#define FEAT_OPA_R(n, func) \ | |
1750 | + [n] = { .name = #n, .write = write_##func, .print = print_##func, \ | |
1751 | + .read = read_##func } | |
1743 | 1752 | |
1744 | 1753 | /* feature_ops not implemented: */ |
1745 | 1754 | #define print_tracing_data NULL |
... | ... | @@ -1754,7 +1763,7 @@ |
1754 | 1763 | FEAT_OPA(HEADER_ARCH, arch), |
1755 | 1764 | FEAT_OPA(HEADER_NRCPUS, nrcpus), |
1756 | 1765 | FEAT_OPA(HEADER_CPUDESC, cpudesc), |
1757 | - FEAT_OPA(HEADER_CPUID, cpuid), | |
1766 | + FEAT_OPA_R(HEADER_CPUID, cpuid), | |
1758 | 1767 | FEAT_OPA(HEADER_TOTAL_MEM, total_mem), |
1759 | 1768 | FEAT_OPP(HEADER_EVENT_DESC, event_desc), |
1760 | 1769 | FEAT_OPA(HEADER_CMDLINE, cmdline), |
... | ... | @@ -1807,6 +1816,54 @@ |
1807 | 1816 | perf_header__process_sections(header, fd, &hd, |
1808 | 1817 | perf_file_section__fprintf_info); |
1809 | 1818 | return 0; |
1819 | +} | |
1820 | + | |
1821 | +struct header_read_data { | |
1822 | + int feat; | |
1823 | + char *result; | |
1824 | +}; | |
1825 | + | |
1826 | +static int perf_file_section__read_feature(struct perf_file_section *section, | |
1827 | + struct perf_header *ph, | |
1828 | + int feat, int fd, void *data) | |
1829 | +{ | |
1830 | + struct header_read_data *hd = data; | |
1831 | + | |
1832 | + if (feat != hd->feat) | |
1833 | + return 0; | |
1834 | + | |
1835 | + if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) { | |
1836 | + pr_debug("Failed to lseek to %" PRIu64 " offset for feature " | |
1837 | + "%d, continuing...\n", section->offset, feat); | |
1838 | + return 0; | |
1839 | + } | |
1840 | + | |
1841 | + if (feat >= HEADER_LAST_FEATURE) { | |
1842 | + pr_warning("unknown feature %d\n", feat); | |
1843 | + return 0; | |
1844 | + } | |
1845 | + | |
1846 | + if (!feat_ops[feat].read) { | |
1847 | + pr_warning("read is not supported for feature %d\n", feat); | |
1848 | + return 0; | |
1849 | + } | |
1850 | + | |
1851 | + hd->result = feat_ops[feat].read(ph, fd); | |
1852 | + return 0; | |
1853 | +} | |
1854 | + | |
1855 | +char *perf_header__read_feature(struct perf_session *session, int feat) | |
1856 | +{ | |
1857 | + struct perf_header *header = &session->header; | |
1858 | + struct header_read_data hd; | |
1859 | + int fd = session->fd; | |
1860 | + | |
1861 | + hd.feat = feat; | |
1862 | + hd.result = NULL; | |
1863 | + | |
1864 | + perf_header__process_sections(header, fd, &hd, | |
1865 | + perf_file_section__read_feature); | |
1866 | + return hd.result; | |
1810 | 1867 | } |
1811 | 1868 | |
1812 | 1869 | static int do_write_feat(int fd, struct perf_header *h, int type, |
tools/perf/util/header.h
... | ... | @@ -94,6 +94,7 @@ |
94 | 94 | int feat, int fd, void *data)); |
95 | 95 | |
96 | 96 | int perf_header__fprintf_info(struct perf_session *s, FILE *fp, bool full); |
97 | +char *perf_header__read_feature(struct perf_session *session, int feat); | |
97 | 98 | |
98 | 99 | int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, |
99 | 100 | const char *name, bool is_kallsyms, bool is_vdso); |