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
260 # PPC comes in 32 and 64 bit and some generated ioctl
261 # numbers depend on the wordsize.
262 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
263 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
267 self.sc_perf_evt_open = 241
268 self.ioctl_numbers = IOCTL_NUMBERS
269 self.exit_reasons = AARCH64_EXIT_REASONS
271 class ArchS390(Arch):
273 self.sc_perf_evt_open = 331
274 self.ioctl_numbers = IOCTL_NUMBERS
275 self.exit_reasons = None
277 ARCH = Arch.get_arch()
281 """Returns os.walk() data for specified directory.
283 As it is only a wrapper it returns the same 3-tuple of (dirpath,
284 dirnames, filenames).
286 return next(os.walk(path))
289 def parse_int_list(list_string):
290 """Returns an int list from a string of comma separated integers and
293 members = list_string.split(',')
295 for member in members:
296 if '-' not in member:
297 integers.append(int(member))
299 int_range = member.split('-')
300 integers.extend(range(int(int_range[0]),
301 int(int_range[1]) + 1))
306 def get_online_cpus():
307 with open('/sys/devices/system/cpu/online') as cpu_list:
308 cpu_string = cpu_list.readline()
309 return parse_int_list(cpu_string)
314 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
315 if ARCH.exit_reasons:
316 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
319 libc = ctypes.CDLL('libc.so.6', use_errno=True)
320 syscall = libc.syscall
322 class perf_event_attr(ctypes.Structure):
323 _fields_ = [('type', ctypes.c_uint32),
324 ('size', ctypes.c_uint32),
325 ('config', ctypes.c_uint64),
326 ('sample_freq', ctypes.c_uint64),
327 ('sample_type', ctypes.c_uint64),
328 ('read_format', ctypes.c_uint64),
329 ('flags', ctypes.c_uint64),
330 ('wakeup_events', ctypes.c_uint32),
331 ('bp_type', ctypes.c_uint32),
332 ('bp_addr', ctypes.c_uint64),
333 ('bp_len', ctypes.c_uint64),
337 super(self.__class__, self).__init__()
338 self.type = PERF_TYPE_TRACEPOINT
339 self.size = ctypes.sizeof(self)
340 self.read_format = PERF_FORMAT_GROUP
342 def perf_event_open(attr, pid, cpu, group_fd, flags):
343 return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
344 ctypes.c_int(pid), ctypes.c_int(cpu),
345 ctypes.c_int(group_fd), ctypes.c_long(flags))
347 PERF_TYPE_TRACEPOINT = 2
348 PERF_FORMAT_GROUP = 1 << 3
350 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
351 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
357 def add_event(self, event):
358 self.events.append(event)
361 length = 8 * (1 + len(self.events))
362 read_format = 'xxxxxxxx' + 'Q' * len(self.events)
363 return dict(zip([event.name for event in self.events],
364 struct.unpack(read_format,
365 os.read(self.events[0].fd, length))))
368 def __init__(self, name, group, trace_cpu, trace_point, trace_filter,
372 self.setup_event(group, trace_cpu, trace_point, trace_filter,
375 def setup_event_attribute(self, trace_set, trace_point):
376 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
379 event_attr = perf_event_attr()
380 event_attr.config = int(open(id_path).read())
383 def setup_event(self, group, trace_cpu, trace_point, trace_filter,
385 event_attr = self.setup_event_attribute(trace_set, trace_point)
389 group_leader = group.events[0].fd
391 fd = perf_event_open(event_attr, -1, trace_cpu,
394 err = ctypes.get_errno()
395 raise OSError(err, os.strerror(err),
396 'while calling sys_perf_event_open().')
399 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
405 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
408 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
411 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
413 class TracepointProvider(object):
415 self.group_leaders = []
416 self.filters = get_filters()
417 self._fields = self.get_available_fields()
419 self.fields = self._fields
421 def get_available_fields(self):
422 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
423 fields = walkdir(path)[1]
426 if field in self.filters:
427 filter_name_, filter_dicts = self.filters[field]
428 for name in filter_dicts:
429 extra.append(field + '(' + name + ')')
433 def setup_traces(self):
434 cpus = get_online_cpus()
436 # The constant is needed as a buffer for python libs, std
437 # streams and other files that the script opens.
438 newlim = len(cpus) * len(self._fields) + 50
440 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
443 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
444 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
446 # Raising the soft limit is sufficient.
447 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
450 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
454 for name in self._fields:
457 match = re.match(r'(.*)\((.*)\)', name)
459 tracepoint, sub = match.groups()
460 tracefilter = ('%s==%d\0' %
461 (self.filters[tracepoint][0],
462 self.filters[tracepoint][1][sub]))
464 group.add_event(Event(name=name,
467 trace_point=tracepoint,
468 trace_filter=tracefilter))
469 self.group_leaders.append(group)
471 def available_fields(self):
472 return self.get_available_fields()
479 def fields(self, fields):
480 self._fields = fields
481 for group in self.group_leaders:
482 for index, event in enumerate(group.events):
483 if event.name in fields:
487 # Do not disable the group leader.
488 # It would disable all of its events.
493 ret = defaultdict(int)
494 for group in self.group_leaders:
495 for name, val in group.read().iteritems():
496 if name in self._fields:
500 class DebugfsProvider(object):
502 self._fields = self.get_available_fields()
504 def get_available_fields(self):
505 return walkdir(PATH_DEBUGFS_KVM)[2]
512 def fields(self, fields):
513 self._fields = fields
517 return int(file(PATH_DEBUGFS_KVM + '/' + key).read())
518 return dict([(key, val(key)) for key in self._fields])
521 def __init__(self, providers, fields=None):
522 self.providers = providers
523 self._fields_filter = fields
525 self.update_provider_filters()
527 def update_provider_filters(self):
529 if not self._fields_filter:
531 return re.match(self._fields_filter, key) is not None
533 # As we reset the counters when updating the fields we can
534 # also clear the cache of old values.
536 for provider in self.providers:
537 provider_fields = [key for key in provider.get_available_fields()
539 provider.fields = provider_fields
542 def fields_filter(self):
543 return self._fields_filter
545 @fields_filter.setter
546 def fields_filter(self, fields_filter):
547 self._fields_filter = fields_filter
548 self.update_provider_filters()
551 for provider in self.providers:
552 new = provider.read()
553 for key in provider.fields:
554 oldval = self.values.get(key, (0, 0))
555 newval = new.get(key, 0)
557 if oldval is not None:
558 newdelta = newval - oldval[0]
559 self.values[key] = (newval, newdelta)
566 def __init__(self, stats):
569 self.drilldown = False
570 self.update_drilldown()
573 """Initialises curses for later use. Based on curses.wrapper
574 implementation from the Python standard library."""
575 self.screen = curses.initscr()
579 # The try/catch works around a minor bit of
580 # over-conscientiousness in the curses module, the error
581 # return from C start_color() is ignorable.
587 curses.use_default_colors()
590 def __exit__(self, *exception):
591 """Resets the terminal to its normal state. Based on curses.wrappre
592 implementation from the Python standard library."""
594 self.screen.keypad(0)
599 def update_drilldown(self):
600 if not self.stats.fields_filter:
601 self.stats.fields_filter = r'^[^\(]*$'
603 elif self.stats.fields_filter == r'^[^\(]*$':
604 self.stats.fields_filter = None
606 def refresh(self, sleeptime):
608 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
609 self.screen.addstr(2, 1, 'Event')
610 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH -
611 len('Total'), 'Total')
612 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 -
613 len('Current'), 'Current')
615 stats = self.stats.get()
618 return (-stats[x][1], -stats[x][0])
620 return (0, -stats[x][0])
621 for key in sorted(stats.keys(), key=sortkey):
623 if row >= self.screen.getmaxyx()[0]:
626 if not values[0] and not values[1]:
629 self.screen.addstr(row, col, key)
631 self.screen.addstr(row, col, '%10d' % (values[0],))
633 if values[1] is not None:
634 self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
636 self.screen.refresh()
638 def show_filter_selection(self):
641 self.screen.addstr(0, 0,
642 "Show statistics for events matching a regex.",
644 self.screen.addstr(2, 0,
646 .format(self.stats.fields_filter))
647 self.screen.addstr(3, 0, "New regex: ")
649 regex = self.screen.getstr()
655 self.stats.fields_filter = regex
660 def show_stats(self):
663 self.refresh(sleeptime)
664 curses.halfdelay(int(sleeptime * 10))
667 char = self.screen.getkey()
669 self.drilldown = not self.drilldown
670 self.update_drilldown()
674 self.show_filter_selection()
675 except KeyboardInterrupt:
684 for key in sorted(s.keys()):
686 print '%-42s%10d%10d' % (key, values[0], values[1])
689 keys = sorted(stats.get().iterkeys())
697 print ' %9d' % s[k][1],
703 if line % banner_repeat == 0:
709 description_text = """
710 This script displays various statistics about VMs running under KVM.
711 The statistics are gathered from the KVM debugfs entries and / or the
712 currently available perf traces.
714 The monitoring takes additional cpu cycles and might affect the VM's
719 /sys/kernel/debug/kvm
720 /sys/kernel/debug/trace/events/*
722 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
723 CAP_SYS_ADMIN and perf events are used.
724 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
725 the large number of files that are possibly opened.
728 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
729 def format_description(self, description):
731 return description + "\n"
735 optparser = optparse.OptionParser(description=description_text,
736 formatter=PlainHelpFormatter())
737 optparser.add_option('-1', '--once', '--batch',
741 help='run in batch mode for one second',
743 optparser.add_option('-l', '--log',
747 help='run in logging mode (like vmstat)',
749 optparser.add_option('-t', '--tracepoints',
753 help='retrieve statistics from tracepoints',
755 optparser.add_option('-d', '--debugfs',
759 help='retrieve statistics from debugfs',
761 optparser.add_option('-f', '--fields',
765 help='fields to display (regex)',
767 (options, _) = optparser.parse_args(sys.argv)
770 def get_providers(options):
773 if options.tracepoints:
774 providers.append(TracepointProvider())
776 providers.append(DebugfsProvider())
777 if len(providers) == 0:
778 providers.append(TracepointProvider())
782 def check_access(options):
783 if not os.path.exists('/sys/kernel/debug'):
784 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
787 if not os.path.exists(PATH_DEBUGFS_KVM):
788 sys.stderr.write("Please make sure, that debugfs is mounted and "
789 "readable by the current user:\n"
790 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
791 "Also ensure, that the kvm modules are loaded.\n")
794 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints
795 or not options.debugfs):
796 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
797 "when using the option -t (default).\n"
798 "If it is enabled, make {0} readable by the "
800 .format(PATH_DEBUGFS_TRACING))
801 if options.tracepoints:
804 sys.stderr.write("Falling back to debugfs statistics!\n")
805 options.debugfs = True
811 options = get_options()
812 options = check_access(options)
813 providers = get_providers(options)
814 stats = Stats(providers, fields=options.fields)
818 elif not options.once:
819 with Tui(stats) as tui:
824 if __name__ == "__main__":