3 # top-like utility for displaying kvm statistics
5 # Copyright 2006-2008 Qumranet Technologies
6 # Copyright 2008-2011 Red Hat, Inc.
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.
24 from collections import defaultdict
25 from time import sleep
29 'EXTERNAL_INTERRUPT': 1,
31 'PENDING_INTERRUPT': 7,
55 'MWAIT_INSTRUCTION': 36,
56 'MONITOR_INSTRUCTION': 39,
57 'PAUSE_INSTRUCTION': 40,
58 'MCE_DURING_VMENTRY': 41,
59 'TPR_BELOW_THRESHOLD': 43,
100 'CR0_SEL_WRITE': 0x065,
124 'TASK_SWITCH': 0x07d,
125 'FERR_FREEZE': 0x07e,
144 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
145 AARCH64_EXIT_REASONS = {
183 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
184 USERSPACE_EXIT_REASONS = {
192 'IRQ_WINDOW_OPEN': 7,
202 'INTERNAL_ERROR': 17,
213 'SET_FILTER': 0x40082406,
214 'ENABLE': 0x00002400,
215 'DISABLE': 0x00002401,
220 """Class that encapsulates global architecture specific data like
221 syscall and ioctl numbers.
226 machine = os.uname()[4]
228 if machine.startswith('ppc'):
230 elif machine.startswith('aarch64'):
232 elif machine.startswith('s390'):
236 for line in open('/proc/cpuinfo'):
237 if not line.startswith('flags'):
242 return ArchX86(VMX_EXIT_REASONS)
244 return ArchX86(SVM_EXIT_REASONS)
248 def __init__(self, exit_reasons):
249 self.sc_perf_evt_open = 298
250 self.ioctl_numbers = IOCTL_NUMBERS
251 self.exit_reasons = exit_reasons
255 self.sc_perf_evt_open = 319
256 self.ioctl_numbers = IOCTL_NUMBERS
257 self.ioctl_numbers['ENABLE'] = 0x20002400
258 self.ioctl_numbers['DISABLE'] = 0x20002401
259 self.ioctl_numbers['RESET'] = 0x20002403
261 # PPC comes in 32 and 64 bit and some generated ioctl
262 # numbers depend on the wordsize.
263 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
264 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
265 self.exit_reasons = {}
269 self.sc_perf_evt_open = 241
270 self.ioctl_numbers = IOCTL_NUMBERS
271 self.exit_reasons = AARCH64_EXIT_REASONS
273 class ArchS390(Arch):
275 self.sc_perf_evt_open = 331
276 self.ioctl_numbers = IOCTL_NUMBERS
277 self.exit_reasons = None
279 ARCH = Arch.get_arch()
283 """Returns os.walk() data for specified directory.
285 As it is only a wrapper it returns the same 3-tuple of (dirpath,
286 dirnames, filenames).
288 return next(os.walk(path))
291 def parse_int_list(list_string):
292 """Returns an int list from a string of comma separated integers and
295 members = list_string.split(',')
297 for member in members:
298 if '-' not in member:
299 integers.append(int(member))
301 int_range = member.split('-')
302 integers.extend(range(int(int_range[0]),
303 int(int_range[1]) + 1))
308 def get_online_cpus():
309 with open('/sys/devices/system/cpu/online') as cpu_list:
310 cpu_string = cpu_list.readline()
311 return parse_int_list(cpu_string)
316 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
317 if ARCH.exit_reasons:
318 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
321 libc = ctypes.CDLL('libc.so.6', use_errno=True)
322 syscall = libc.syscall
324 class perf_event_attr(ctypes.Structure):
325 _fields_ = [('type', ctypes.c_uint32),
326 ('size', ctypes.c_uint32),
327 ('config', ctypes.c_uint64),
328 ('sample_freq', ctypes.c_uint64),
329 ('sample_type', ctypes.c_uint64),
330 ('read_format', ctypes.c_uint64),
331 ('flags', ctypes.c_uint64),
332 ('wakeup_events', ctypes.c_uint32),
333 ('bp_type', ctypes.c_uint32),
334 ('bp_addr', ctypes.c_uint64),
335 ('bp_len', ctypes.c_uint64),
339 super(self.__class__, self).__init__()
340 self.type = PERF_TYPE_TRACEPOINT
341 self.size = ctypes.sizeof(self)
342 self.read_format = PERF_FORMAT_GROUP
344 def perf_event_open(attr, pid, cpu, group_fd, flags):
345 return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
346 ctypes.c_int(pid), ctypes.c_int(cpu),
347 ctypes.c_int(group_fd), ctypes.c_long(flags))
349 PERF_TYPE_TRACEPOINT = 2
350 PERF_FORMAT_GROUP = 1 << 3
352 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
353 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
359 def add_event(self, event):
360 self.events.append(event)
363 length = 8 * (1 + len(self.events))
364 read_format = 'xxxxxxxx' + 'Q' * len(self.events)
365 return dict(zip([event.name for event in self.events],
366 struct.unpack(read_format,
367 os.read(self.events[0].fd, length))))
370 def __init__(self, name, group, trace_cpu, trace_point, trace_filter,
374 self.setup_event(group, trace_cpu, trace_point, trace_filter,
377 def setup_event_attribute(self, trace_set, trace_point):
378 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
381 event_attr = perf_event_attr()
382 event_attr.config = int(open(id_path).read())
385 def setup_event(self, group, trace_cpu, trace_point, trace_filter,
387 event_attr = self.setup_event_attribute(trace_set, trace_point)
391 group_leader = group.events[0].fd
393 fd = perf_event_open(event_attr, -1, trace_cpu,
396 err = ctypes.get_errno()
397 raise OSError(err, os.strerror(err),
398 'while calling sys_perf_event_open().')
401 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
407 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
410 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
413 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
415 class TracepointProvider(object):
417 self.group_leaders = []
418 self.filters = get_filters()
419 self._fields = self.get_available_fields()
421 self.fields = self._fields
423 def get_available_fields(self):
424 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
425 fields = walkdir(path)[1]
428 if field in self.filters:
429 filter_name_, filter_dicts = self.filters[field]
430 for name in filter_dicts:
431 extra.append(field + '(' + name + ')')
435 def setup_traces(self):
436 cpus = get_online_cpus()
438 # The constant is needed as a buffer for python libs, std
439 # streams and other files that the script opens.
440 newlim = len(cpus) * len(self._fields) + 50
442 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
445 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
446 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
448 # Raising the soft limit is sufficient.
449 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
452 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
456 for name in self._fields:
459 match = re.match(r'(.*)\((.*)\)', name)
461 tracepoint, sub = match.groups()
462 tracefilter = ('%s==%d\0' %
463 (self.filters[tracepoint][0],
464 self.filters[tracepoint][1][sub]))
466 group.add_event(Event(name=name,
469 trace_point=tracepoint,
470 trace_filter=tracefilter))
471 self.group_leaders.append(group)
473 def available_fields(self):
474 return self.get_available_fields()
481 def fields(self, fields):
482 self._fields = fields
483 for group in self.group_leaders:
484 for index, event in enumerate(group.events):
485 if event.name in fields:
489 # Do not disable the group leader.
490 # It would disable all of its events.
495 ret = defaultdict(int)
496 for group in self.group_leaders:
497 for name, val in group.read().iteritems():
498 if name in self._fields:
502 class DebugfsProvider(object):
504 self._fields = self.get_available_fields()
506 def get_available_fields(self):
507 return walkdir(PATH_DEBUGFS_KVM)[2]
514 def fields(self, fields):
515 self._fields = fields
519 return int(file(PATH_DEBUGFS_KVM + '/' + key).read())
520 return dict([(key, val(key)) for key in self._fields])
523 def __init__(self, providers, fields=None):
524 self.providers = providers
525 self._fields_filter = fields
527 self.update_provider_filters()
529 def update_provider_filters(self):
531 if not self._fields_filter:
533 return re.match(self._fields_filter, key) is not None
535 # As we reset the counters when updating the fields we can
536 # also clear the cache of old values.
538 for provider in self.providers:
539 provider_fields = [key for key in provider.get_available_fields()
541 provider.fields = provider_fields
544 def fields_filter(self):
545 return self._fields_filter
547 @fields_filter.setter
548 def fields_filter(self, fields_filter):
549 self._fields_filter = fields_filter
550 self.update_provider_filters()
553 for provider in self.providers:
554 new = provider.read()
555 for key in provider.fields:
556 oldval = self.values.get(key, (0, 0))
557 newval = new.get(key, 0)
559 if oldval is not None:
560 newdelta = newval - oldval[0]
561 self.values[key] = (newval, newdelta)
568 def __init__(self, stats):
571 self.drilldown = False
572 self.update_drilldown()
575 """Initialises curses for later use. Based on curses.wrapper
576 implementation from the Python standard library."""
577 self.screen = curses.initscr()
581 # The try/catch works around a minor bit of
582 # over-conscientiousness in the curses module, the error
583 # return from C start_color() is ignorable.
589 curses.use_default_colors()
592 def __exit__(self, *exception):
593 """Resets the terminal to its normal state. Based on curses.wrappre
594 implementation from the Python standard library."""
596 self.screen.keypad(0)
601 def update_drilldown(self):
602 if not self.stats.fields_filter:
603 self.stats.fields_filter = r'^[^\(]*$'
605 elif self.stats.fields_filter == r'^[^\(]*$':
606 self.stats.fields_filter = None
608 def refresh(self, sleeptime):
610 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
611 self.screen.addstr(2, 1, 'Event')
612 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH -
613 len('Total'), 'Total')
614 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 -
615 len('Current'), 'Current')
617 stats = self.stats.get()
620 return (-stats[x][1], -stats[x][0])
622 return (0, -stats[x][0])
623 for key in sorted(stats.keys(), key=sortkey):
625 if row >= self.screen.getmaxyx()[0]:
628 if not values[0] and not values[1]:
631 self.screen.addstr(row, col, key)
633 self.screen.addstr(row, col, '%10d' % (values[0],))
635 if values[1] is not None:
636 self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
638 self.screen.refresh()
640 def show_filter_selection(self):
643 self.screen.addstr(0, 0,
644 "Show statistics for events matching a regex.",
646 self.screen.addstr(2, 0,
648 .format(self.stats.fields_filter))
649 self.screen.addstr(3, 0, "New regex: ")
651 regex = self.screen.getstr()
657 self.stats.fields_filter = regex
662 def show_stats(self):
665 self.refresh(sleeptime)
666 curses.halfdelay(int(sleeptime * 10))
669 char = self.screen.getkey()
671 self.drilldown = not self.drilldown
672 self.update_drilldown()
676 self.show_filter_selection()
677 except KeyboardInterrupt:
686 for key in sorted(s.keys()):
688 print '%-42s%10d%10d' % (key, values[0], values[1])
691 keys = sorted(stats.get().iterkeys())
699 print ' %9d' % s[k][1],
705 if line % banner_repeat == 0:
711 description_text = """
712 This script displays various statistics about VMs running under KVM.
713 The statistics are gathered from the KVM debugfs entries and / or the
714 currently available perf traces.
716 The monitoring takes additional cpu cycles and might affect the VM's
721 /sys/kernel/debug/kvm
722 /sys/kernel/debug/trace/events/*
724 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
725 CAP_SYS_ADMIN and perf events are used.
726 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
727 the large number of files that are possibly opened.
730 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
731 def format_description(self, description):
733 return description + "\n"
737 optparser = optparse.OptionParser(description=description_text,
738 formatter=PlainHelpFormatter())
739 optparser.add_option('-1', '--once', '--batch',
743 help='run in batch mode for one second',
745 optparser.add_option('-l', '--log',
749 help='run in logging mode (like vmstat)',
751 optparser.add_option('-t', '--tracepoints',
755 help='retrieve statistics from tracepoints',
757 optparser.add_option('-d', '--debugfs',
761 help='retrieve statistics from debugfs',
763 optparser.add_option('-f', '--fields',
767 help='fields to display (regex)',
769 (options, _) = optparser.parse_args(sys.argv)
772 def get_providers(options):
775 if options.tracepoints:
776 providers.append(TracepointProvider())
778 providers.append(DebugfsProvider())
779 if len(providers) == 0:
780 providers.append(TracepointProvider())
784 def check_access(options):
785 if not os.path.exists('/sys/kernel/debug'):
786 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
789 if not os.path.exists(PATH_DEBUGFS_KVM):
790 sys.stderr.write("Please make sure, that debugfs is mounted and "
791 "readable by the current user:\n"
792 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
793 "Also ensure, that the kvm modules are loaded.\n")
796 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints
797 or not options.debugfs):
798 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
799 "when using the option -t (default).\n"
800 "If it is enabled, make {0} readable by the "
802 .format(PATH_DEBUGFS_TRACING))
803 if options.tracepoints:
806 sys.stderr.write("Falling back to debugfs statistics!\n")
807 options.debugfs = True
813 options = get_options()
814 options = check_access(options)
815 providers = get_providers(options)
816 stats = Stats(providers, fields=options.fields)
820 elif not options.once:
821 with Tui(stats) as tui:
826 if __name__ == "__main__":