scripts/kvm/kvm_stat: Cleanup of platform detection
[qemu.git] / scripts / kvm / kvm_stat
blob5b6742a6b736a54d0d67a145554504c4c154172f
1 #!/usr/bin/python
3 # top-like utility for displaying kvm statistics
5 # Copyright 2006-2008 Qumranet Technologies
6 # Copyright 2008-2011 Red Hat, Inc.
8 # Authors:
9 # Avi Kivity <avi@redhat.com>
11 # This work is licensed under the terms of the GNU GPL, version 2. See
12 # the COPYING file in the top-level directory.
14 import curses
15 import sys
16 import os
17 import time
18 import optparse
19 import ctypes
20 import fcntl
21 import resource
22 import struct
23 import re
24 from collections import defaultdict
26 VMX_EXIT_REASONS = {
27 'EXCEPTION_NMI': 0,
28 'EXTERNAL_INTERRUPT': 1,
29 'TRIPLE_FAULT': 2,
30 'PENDING_INTERRUPT': 7,
31 'NMI_WINDOW': 8,
32 'TASK_SWITCH': 9,
33 'CPUID': 10,
34 'HLT': 12,
35 'INVLPG': 14,
36 'RDPMC': 15,
37 'RDTSC': 16,
38 'VMCALL': 18,
39 'VMCLEAR': 19,
40 'VMLAUNCH': 20,
41 'VMPTRLD': 21,
42 'VMPTRST': 22,
43 'VMREAD': 23,
44 'VMRESUME': 24,
45 'VMWRITE': 25,
46 'VMOFF': 26,
47 'VMON': 27,
48 'CR_ACCESS': 28,
49 'DR_ACCESS': 29,
50 'IO_INSTRUCTION': 30,
51 'MSR_READ': 31,
52 'MSR_WRITE': 32,
53 'INVALID_STATE': 33,
54 'MWAIT_INSTRUCTION': 36,
55 'MONITOR_INSTRUCTION': 39,
56 'PAUSE_INSTRUCTION': 40,
57 'MCE_DURING_VMENTRY': 41,
58 'TPR_BELOW_THRESHOLD': 43,
59 'APIC_ACCESS': 44,
60 'EPT_VIOLATION': 48,
61 'EPT_MISCONFIG': 49,
62 'WBINVD': 54,
63 'XSETBV': 55,
64 'APIC_WRITE': 56,
65 'INVPCID': 58,
68 SVM_EXIT_REASONS = {
69 'READ_CR0': 0x000,
70 'READ_CR3': 0x003,
71 'READ_CR4': 0x004,
72 'READ_CR8': 0x008,
73 'WRITE_CR0': 0x010,
74 'WRITE_CR3': 0x013,
75 'WRITE_CR4': 0x014,
76 'WRITE_CR8': 0x018,
77 'READ_DR0': 0x020,
78 'READ_DR1': 0x021,
79 'READ_DR2': 0x022,
80 'READ_DR3': 0x023,
81 'READ_DR4': 0x024,
82 'READ_DR5': 0x025,
83 'READ_DR6': 0x026,
84 'READ_DR7': 0x027,
85 'WRITE_DR0': 0x030,
86 'WRITE_DR1': 0x031,
87 'WRITE_DR2': 0x032,
88 'WRITE_DR3': 0x033,
89 'WRITE_DR4': 0x034,
90 'WRITE_DR5': 0x035,
91 'WRITE_DR6': 0x036,
92 'WRITE_DR7': 0x037,
93 'EXCP_BASE': 0x040,
94 'INTR': 0x060,
95 'NMI': 0x061,
96 'SMI': 0x062,
97 'INIT': 0x063,
98 'VINTR': 0x064,
99 'CR0_SEL_WRITE': 0x065,
100 'IDTR_READ': 0x066,
101 'GDTR_READ': 0x067,
102 'LDTR_READ': 0x068,
103 'TR_READ': 0x069,
104 'IDTR_WRITE': 0x06a,
105 'GDTR_WRITE': 0x06b,
106 'LDTR_WRITE': 0x06c,
107 'TR_WRITE': 0x06d,
108 'RDTSC': 0x06e,
109 'RDPMC': 0x06f,
110 'PUSHF': 0x070,
111 'POPF': 0x071,
112 'CPUID': 0x072,
113 'RSM': 0x073,
114 'IRET': 0x074,
115 'SWINT': 0x075,
116 'INVD': 0x076,
117 'PAUSE': 0x077,
118 'HLT': 0x078,
119 'INVLPG': 0x079,
120 'INVLPGA': 0x07a,
121 'IOIO': 0x07b,
122 'MSR': 0x07c,
123 'TASK_SWITCH': 0x07d,
124 'FERR_FREEZE': 0x07e,
125 'SHUTDOWN': 0x07f,
126 'VMRUN': 0x080,
127 'VMMCALL': 0x081,
128 'VMLOAD': 0x082,
129 'VMSAVE': 0x083,
130 'STGI': 0x084,
131 'CLGI': 0x085,
132 'SKINIT': 0x086,
133 'RDTSCP': 0x087,
134 'ICEBP': 0x088,
135 'WBINVD': 0x089,
136 'MONITOR': 0x08a,
137 'MWAIT': 0x08b,
138 'MWAIT_COND': 0x08c,
139 'XSETBV': 0x08d,
140 'NPF': 0x400,
143 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
144 AARCH64_EXIT_REASONS = {
145 'UNKNOWN': 0x00,
146 'WFI': 0x01,
147 'CP15_32': 0x03,
148 'CP15_64': 0x04,
149 'CP14_MR': 0x05,
150 'CP14_LS': 0x06,
151 'FP_ASIMD': 0x07,
152 'CP10_ID': 0x08,
153 'CP14_64': 0x0C,
154 'ILL_ISS': 0x0E,
155 'SVC32': 0x11,
156 'HVC32': 0x12,
157 'SMC32': 0x13,
158 'SVC64': 0x15,
159 'HVC64': 0x16,
160 'SMC64': 0x17,
161 'SYS64': 0x18,
162 'IABT': 0x20,
163 'IABT_HYP': 0x21,
164 'PC_ALIGN': 0x22,
165 'DABT': 0x24,
166 'DABT_HYP': 0x25,
167 'SP_ALIGN': 0x26,
168 'FP_EXC32': 0x28,
169 'FP_EXC64': 0x2C,
170 'SERROR': 0x2F,
171 'BREAKPT': 0x30,
172 'BREAKPT_HYP': 0x31,
173 'SOFTSTP': 0x32,
174 'SOFTSTP_HYP': 0x33,
175 'WATCHPT': 0x34,
176 'WATCHPT_HYP': 0x35,
177 'BKPT32': 0x38,
178 'VECTOR32': 0x3A,
179 'BRK64': 0x3C,
182 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
183 USERSPACE_EXIT_REASONS = {
184 'UNKNOWN': 0,
185 'EXCEPTION': 1,
186 'IO': 2,
187 'HYPERCALL': 3,
188 'DEBUG': 4,
189 'HLT': 5,
190 'MMIO': 6,
191 'IRQ_WINDOW_OPEN': 7,
192 'SHUTDOWN': 8,
193 'FAIL_ENTRY': 9,
194 'INTR': 10,
195 'SET_TPR': 11,
196 'TPR_ACCESS': 12,
197 'S390_SIEIC': 13,
198 'S390_RESET': 14,
199 'DCR': 15,
200 'NMI': 16,
201 'INTERNAL_ERROR': 17,
202 'OSI': 18,
203 'PAPR_HCALL': 19,
204 'S390_UCONTROL': 20,
205 'WATCHDOG': 21,
206 'S390_TSCH': 22,
207 'EPR': 23,
208 'SYSTEM_EVENT': 24,
211 X86_EXIT_REASONS = {
212 'vmx': VMX_EXIT_REASONS,
213 'svm': SVM_EXIT_REASONS,
216 SC_PERF_EVT_OPEN = None
217 EXIT_REASONS = None
219 IOCTL_NUMBERS = {
220 'SET_FILTER' : 0x40082406,
221 'ENABLE' : 0x00002400,
222 'DISABLE' : 0x00002401,
223 'RESET' : 0x00002403,
226 def x86_init(flag):
227 global SC_PERF_EVT_OPEN
228 global EXIT_REASONS
230 SC_PERF_EVT_OPEN = 298
231 EXIT_REASONS = X86_EXIT_REASONS[flag]
233 def s390_init():
234 global SC_PERF_EVT_OPEN
236 SC_PERF_EVT_OPEN = 331
238 def ppc_init():
239 global SC_PERF_EVT_OPEN
240 global IOCTL_NUMBERS
242 SC_PERF_EVT_OPEN = 319
244 IOCTL_NUMBERS['ENABLE'] = 0x20002400
245 IOCTL_NUMBERS['DISABLE'] = 0x20002401
246 IOCTL_NUMBERS['SET_FILTER'] = 0x80002406 | (ctypes.sizeof(ctypes.c_char_p)
247 << 16)
249 def aarch64_init():
250 global SC_PERF_EVT_OPEN
251 global EXIT_REASONS
253 SC_PERF_EVT_OPEN = 241
254 EXIT_REASONS = AARCH64_EXIT_REASONS
256 def detect_platform():
257 machine = os.uname()[4]
259 if machine.startswith('ppc'):
260 ppc_init()
261 elif machine.startswith('aarch64'):
262 aarch64_init()
263 elif machine.startswith('s390'):
264 s390_init()
265 else:
266 for line in file('/proc/cpuinfo').readlines():
267 if line.startswith('flags'):
268 for flag in line.split():
269 if flag in X86_EXIT_REASONS:
270 x86_init(flag)
271 return
274 def walkdir(path):
275 """Returns os.walk() data for specified directory.
277 As it is only a wrapper it returns the same 3-tuple of (dirpath,
278 dirnames, filenames).
280 return next(os.walk(path))
282 filters = {}
283 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
284 if EXIT_REASONS:
285 filters['kvm_exit'] = ('exit_reason', EXIT_REASONS)
287 libc = ctypes.CDLL('libc.so.6', use_errno=True)
288 syscall = libc.syscall
290 class perf_event_attr(ctypes.Structure):
291 _fields_ = [('type', ctypes.c_uint32),
292 ('size', ctypes.c_uint32),
293 ('config', ctypes.c_uint64),
294 ('sample_freq', ctypes.c_uint64),
295 ('sample_type', ctypes.c_uint64),
296 ('read_format', ctypes.c_uint64),
297 ('flags', ctypes.c_uint64),
298 ('wakeup_events', ctypes.c_uint32),
299 ('bp_type', ctypes.c_uint32),
300 ('bp_addr', ctypes.c_uint64),
301 ('bp_len', ctypes.c_uint64),
303 def _perf_event_open(attr, pid, cpu, group_fd, flags):
304 return syscall(SC_PERF_EVT_OPEN, ctypes.pointer(attr), ctypes.c_int(pid),
305 ctypes.c_int(cpu), ctypes.c_int(group_fd),
306 ctypes.c_long(flags))
308 PERF_TYPE_TRACEPOINT = 2
309 PERF_FORMAT_GROUP = 1 << 3
311 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
312 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
314 class Group(object):
315 def __init__(self, cpu):
316 self.events = []
317 self.group_leader = None
318 self.cpu = cpu
319 def add_event(self, name, event_set, tracepoint, tracefilter=None):
320 self.events.append(Event(group=self,
321 name=name, event_set=event_set,
322 tracepoint=tracepoint,
323 tracefilter=tracefilter))
324 if len(self.events) == 1:
325 self.file = os.fdopen(self.events[0].fd)
326 def read(self):
327 length = 8 * (1 + len(self.events))
328 fmt = 'xxxxxxxx' + 'q' * len(self.events)
329 return dict(zip([event.name for event in self.events],
330 struct.unpack(fmt, self.file.read(length))))
332 class Event(object):
333 def __init__(self, group, name, event_set, tracepoint, tracefilter=None):
334 self.name = name
335 attr = perf_event_attr()
336 attr.type = PERF_TYPE_TRACEPOINT
337 attr.size = ctypes.sizeof(attr)
338 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', event_set,
339 tracepoint, 'id')
340 id = int(file(id_path).read())
341 attr.config = id
342 attr.sample_period = 1
343 attr.read_format = PERF_FORMAT_GROUP
344 group_leader = -1
345 if group.events:
346 group_leader = group.events[0].fd
347 fd = _perf_event_open(attr, -1, group.cpu, group_leader, 0)
348 if fd == -1:
349 err = ctypes.get_errno()
350 raise OSError(err, os.strerror(err),
351 'while calling sys_perf_event_open().')
352 if tracefilter:
353 fcntl.ioctl(fd, IOCTL_NUMBERS['SET_FILTER'], tracefilter)
354 self.fd = fd
355 def enable(self):
356 fcntl.ioctl(self.fd, IOCTL_NUMBERS['ENABLE'], 0)
357 def disable(self):
358 fcntl.ioctl(self.fd, IOCTL_NUMBERS['DISABLE'], 0)
359 def reset(self):
360 fcntl.ioctl(self.fd, IOCTL_NUMBERS['RESET'], 0)
362 class TracepointProvider(object):
363 def __init__(self):
364 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
365 fields = walkdir(path)[1]
366 extra = []
367 for f in fields:
368 if f in filters:
369 subfield, values = filters[f]
370 for name, number in values.iteritems():
371 extra.append(f + '(' + name + ')')
372 fields += extra
373 self._setup(fields)
374 self.select(fields)
375 def fields(self):
376 return self._fields
378 def _online_cpus(self):
379 l = []
380 pattern = r'cpu([0-9]+)'
381 basedir = '/sys/devices/system/cpu'
382 for entry in os.listdir(basedir):
383 match = re.match(pattern, entry)
384 if not match:
385 continue
386 path = os.path.join(basedir, entry, 'online')
387 if os.path.exists(path) and open(path).read().strip() != '1':
388 continue
389 l.append(int(match.group(1)))
390 return l
392 def _setup(self, _fields):
393 self._fields = _fields
394 cpus = self._online_cpus()
396 # The constant is needed as a buffer for python libs, std
397 # streams and other files that the script opens.
398 rlimit = len(cpus) * len(_fields) + 50
399 try:
400 resource.setrlimit(resource.RLIMIT_NOFILE, (rlimit, rlimit))
401 except ValueError:
402 sys.exit("NOFILE rlimit could not be raised to {0}".format(rlimit))
404 events = []
405 self.group_leaders = []
406 for cpu in cpus:
407 group = Group(cpu)
408 for name in _fields:
409 tracepoint = name
410 tracefilter = None
411 m = re.match(r'(.*)\((.*)\)', name)
412 if m:
413 tracepoint, sub = m.groups()
414 tracefilter = '%s==%d\0' % (filters[tracepoint][0],
415 filters[tracepoint][1][sub])
416 event = group.add_event(name, event_set='kvm',
417 tracepoint=tracepoint,
418 tracefilter=tracefilter)
419 self.group_leaders.append(group)
420 def select(self, fields):
421 for group in self.group_leaders:
422 for event in group.events:
423 if event.name in fields:
424 event.reset()
425 event.enable()
426 else:
427 event.disable()
428 def read(self):
429 ret = defaultdict(int)
430 for group in self.group_leaders:
431 for name, val in group.read().iteritems():
432 ret[name] += val
433 return ret
435 class DebugfsProvider(object):
436 def __init__(self):
437 self._fields = walkdir(PATH_DEBUGFS_KVM)[2]
438 def fields(self):
439 return self._fields
440 def select(self, fields):
441 self._fields = fields
442 def read(self):
443 def val(key):
444 return int(file(PATH_DEBUGFS_KVM + '/' + key).read())
445 return dict([(key, val(key)) for key in self._fields])
447 class Stats:
448 def __init__(self, providers, fields=None):
449 self.providers = providers
450 self.fields_filter = fields
451 self._update()
452 def _update(self):
453 def wanted(key):
454 if not self.fields_filter:
455 return True
456 return re.match(self.fields_filter, key) is not None
457 self.values = dict()
458 for d in self.providers:
459 provider_fields = [key for key in d.fields() if wanted(key)]
460 for key in provider_fields:
461 self.values[key] = None
462 d.select(provider_fields)
463 def set_fields_filter(self, fields_filter):
464 self.fields_filter = fields_filter
465 self._update()
466 def get(self):
467 for d in self.providers:
468 new = d.read()
469 for key in d.fields():
470 oldval = self.values.get(key, (0, 0))
471 newval = new[key]
472 newdelta = None
473 if oldval is not None:
474 newdelta = newval - oldval[0]
475 self.values[key] = (newval, newdelta)
476 return self.values
478 LABEL_WIDTH = 40
479 NUMBER_WIDTH = 10
481 def tui(screen, stats):
482 curses.use_default_colors()
483 curses.noecho()
484 drilldown = False
485 fields_filter = stats.fields_filter
486 def update_drilldown():
487 if not fields_filter:
488 if drilldown:
489 stats.set_fields_filter(None)
490 else:
491 stats.set_fields_filter(r'^[^\(]*$')
492 update_drilldown()
493 def refresh(sleeptime):
494 screen.erase()
495 screen.addstr(0, 0, 'kvm statistics')
496 screen.addstr(2, 1, 'Event')
497 screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH - len('Total'), 'Total')
498 screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 - len('Current'), 'Current')
499 row = 3
500 s = stats.get()
501 def sortkey(x):
502 if s[x][1]:
503 return (-s[x][1], -s[x][0])
504 else:
505 return (0, -s[x][0])
506 for key in sorted(s.keys(), key=sortkey):
507 if row >= screen.getmaxyx()[0]:
508 break
509 values = s[key]
510 if not values[0] and not values[1]:
511 break
512 col = 1
513 screen.addstr(row, col, key)
514 col += LABEL_WIDTH
515 screen.addstr(row, col, '%10d' % (values[0],))
516 col += NUMBER_WIDTH
517 if values[1] is not None:
518 screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
519 row += 1
520 screen.refresh()
522 sleeptime = 0.25
523 while True:
524 refresh(sleeptime)
525 curses.halfdelay(int(sleeptime * 10))
526 sleeptime = 3
527 try:
528 c = screen.getkey()
529 if c == 'x':
530 drilldown = not drilldown
531 update_drilldown()
532 if c == 'q':
533 break
534 except KeyboardInterrupt:
535 break
536 except curses.error:
537 continue
539 def batch(stats):
540 s = stats.get()
541 time.sleep(1)
542 s = stats.get()
543 for key in sorted(s.keys()):
544 values = s[key]
545 print '%-22s%10d%10d' % (key, values[0], values[1])
547 def log(stats):
548 keys = sorted(stats.get().iterkeys())
549 def banner():
550 for k in keys:
551 print '%10s' % k[0:9],
552 print
553 def statline():
554 s = stats.get()
555 for k in keys:
556 print ' %9d' % s[k][1],
557 print
558 line = 0
559 banner_repeat = 20
560 while True:
561 time.sleep(1)
562 if line % banner_repeat == 0:
563 banner()
564 statline()
565 line += 1
567 def get_options():
568 optparser = optparse.OptionParser()
569 optparser.add_option('-1', '--once', '--batch',
570 action='store_true',
571 default=False,
572 dest='once',
573 help='run in batch mode for one second',
575 optparser.add_option('-l', '--log',
576 action='store_true',
577 default=False,
578 dest='log',
579 help='run in logging mode (like vmstat)',
581 optparser.add_option('-t', '--tracepoints',
582 action='store_true',
583 default=False,
584 dest='tracepoints',
585 help='retrieve statistics from tracepoints',
587 optparser.add_option('-d', '--debugfs',
588 action='store_true',
589 default=False,
590 dest='debugfs',
591 help='retrieve statistics from debugfs',
593 optparser.add_option('-f', '--fields',
594 action='store',
595 default=None,
596 dest='fields',
597 help='fields to display (regex)',
599 (options, _) = optparser.parse_args(sys.argv)
600 return options
602 def get_providers(options):
603 providers = []
605 if options.tracepoints:
606 providers.append(TracepointProvider())
607 if options.debugfs:
608 providers.append(DebugfsProvider())
609 if len(providers) == 0:
610 providers.append(TracepointProvider())
612 return providers
614 def check_access():
615 if not os.path.exists('/sys/kernel/debug'):
616 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
617 sys.exit(1)
619 if not os.path.exists(PATH_DEBUGFS_KVM):
620 sys.stderr.write("Please make sure, that debugfs is mounted and "
621 "readable by the current user:\n"
622 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
623 "Also ensure, that the kvm modules are loaded.\n")
624 sys.exit(1)
626 if not os.path.exists(PATH_DEBUGFS_TRACING):
627 sys.stderr.write("Please make {0} readable by the current user.\n"
628 .format(PATH_DEBUGFS_TRACING))
629 sys.exit(1)
631 def main():
632 check_access()
633 detect_platform()
634 options = get_options()
635 providers = get_providers(options)
636 stats = Stats(providers, fields=options.fields)
638 if options.log:
639 log(stats)
640 elif not options.once:
641 curses.wrapper(tui, stats)
642 else:
643 batch(stats)
645 if __name__ == "__main__":
646 main()