3 # top-like utility for displaying kvm statistics
5 # Copyright 2006-2008 Qumranet Technologies
6 # Copyright 2008-2011 Red Hat, Inc.
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
28 'EXTERNAL_INTERRUPT': 1,
30 'PENDING_INTERRUPT': 7,
54 'MWAIT_INSTRUCTION': 36,
55 'MONITOR_INSTRUCTION': 39,
56 'PAUSE_INSTRUCTION': 40,
57 'MCE_DURING_VMENTRY': 41,
58 'TPR_BELOW_THRESHOLD': 43,
99 'CR0_SEL_WRITE': 0x065,
123 'TASK_SWITCH': 0x07d,
124 'FERR_FREEZE': 0x07e,
143 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
144 AARCH64_EXIT_REASONS = {
182 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
183 USERSPACE_EXIT_REASONS = {
191 'IRQ_WINDOW_OPEN': 7,
201 'INTERNAL_ERROR': 17,
212 'SET_FILTER': 0x40082406,
213 'ENABLE': 0x00002400,
214 'DISABLE': 0x00002401,
219 """Class that encapsulates global architecture specific data like
220 syscall and ioctl numbers.
225 machine = os.uname()[4]
227 if machine.startswith('ppc'):
229 elif machine.startswith('aarch64'):
231 elif machine.startswith('s390'):
235 for line in open('/proc/cpuinfo'):
236 if not line.startswith('flags'):
241 return ArchX86(VMX_EXIT_REASONS)
243 return ArchX86(SVM_EXIT_REASONS)
247 def __init__(self, exit_reasons):
248 self.sc_perf_evt_open = 298
249 self.ioctl_numbers = IOCTL_NUMBERS
250 self.exit_reasons = exit_reasons
254 self.sc_perf_evt_open = 319
255 self.ioctl_numbers = IOCTL_NUMBERS
256 self.ioctl_numbers['ENABLE'] = 0x20002400
257 self.ioctl_numbers['DISABLE'] = 0x20002401
259 # PPC comes in 32 and 64 bit and some generated ioctl
260 # numbers depend on the wordsize.
261 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
262 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
266 self.sc_perf_evt_open = 241
267 self.ioctl_numbers = IOCTL_NUMBERS
268 self.exit_reasons = AARCH64_EXIT_REASONS
270 class ArchS390(Arch):
272 self.sc_perf_evt_open = 331
273 self.ioctl_numbers = IOCTL_NUMBERS
274 self.exit_reasons = None
276 ARCH = Arch.get_arch()
280 """Returns os.walk() data for specified directory.
282 As it is only a wrapper it returns the same 3-tuple of (dirpath,
283 dirnames, filenames).
285 return next(os.walk(path))
288 def parse_int_list(list_string):
289 """Returns an int list from a string of comma separated integers and
292 members = list_string.split(',')
294 for member in members:
295 if '-' not in member:
296 integers.append(int(member))
298 int_range = member.split('-')
299 integers.extend(range(int(int_range[0]),
300 int(int_range[1]) + 1))
305 def get_online_cpus():
306 with open('/sys/devices/system/cpu/online') as cpu_list:
307 cpu_string = cpu_list.readline()
308 return parse_int_list(cpu_string)
313 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
314 if ARCH.exit_reasons:
315 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
318 libc = ctypes.CDLL('libc.so.6', use_errno=True)
319 syscall = libc.syscall
321 class perf_event_attr(ctypes.Structure):
322 _fields_ = [('type', ctypes.c_uint32),
323 ('size', ctypes.c_uint32),
324 ('config', ctypes.c_uint64),
325 ('sample_freq', ctypes.c_uint64),
326 ('sample_type', ctypes.c_uint64),
327 ('read_format', ctypes.c_uint64),
328 ('flags', ctypes.c_uint64),
329 ('wakeup_events', ctypes.c_uint32),
330 ('bp_type', ctypes.c_uint32),
331 ('bp_addr', ctypes.c_uint64),
332 ('bp_len', ctypes.c_uint64),
336 super(self.__class__, self).__init__()
337 self.type = PERF_TYPE_TRACEPOINT
338 self.size = ctypes.sizeof(self)
339 self.read_format = PERF_FORMAT_GROUP
341 def perf_event_open(attr, pid, cpu, group_fd, flags):
342 return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
343 ctypes.c_int(pid), ctypes.c_int(cpu),
344 ctypes.c_int(group_fd), ctypes.c_long(flags))
346 PERF_TYPE_TRACEPOINT = 2
347 PERF_FORMAT_GROUP = 1 << 3
349 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
350 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
356 def add_event(self, event):
357 self.events.append(event)
360 length = 8 * (1 + len(self.events))
361 read_format = 'xxxxxxxx' + 'q' * len(self.events)
362 return dict(zip([event.name for event in self.events],
363 struct.unpack(read_format,
364 os.read(self.events[0].fd, length))))
367 def __init__(self, name, group, trace_cpu, trace_point, trace_filter,
371 self.setup_event(group, trace_cpu, trace_point, trace_filter,
374 def setup_event_attribute(self, trace_set, trace_point):
375 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
378 event_attr = perf_event_attr()
379 event_attr.config = int(open(id_path).read())
382 def setup_event(self, group, trace_cpu, trace_point, trace_filter,
384 event_attr = self.setup_event_attribute(trace_set, trace_point)
388 group_leader = group.events[0].fd
390 fd = perf_event_open(event_attr, -1, trace_cpu,
393 err = ctypes.get_errno()
394 raise OSError(err, os.strerror(err),
395 'while calling sys_perf_event_open().')
398 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
404 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
407 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
410 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
412 class TracepointProvider(object):
414 self.group_leaders = []
415 self.filters = get_filters()
416 self._fields = self.get_available_fields()
418 self.fields = self._fields
420 def get_available_fields(self):
421 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
422 fields = walkdir(path)[1]
425 if field in self.filters:
426 filter_name_, filter_dicts = self.filters[field]
427 for name in filter_dicts:
428 extra.append(field + '(' + name + ')')
432 def setup_traces(self):
433 cpus = get_online_cpus()
435 # The constant is needed as a buffer for python libs, std
436 # streams and other files that the script opens.
437 rlimit = len(cpus) * len(self._fields) + 50
439 resource.setrlimit(resource.RLIMIT_NOFILE, (rlimit, rlimit))
441 sys.exit("NOFILE rlimit could not be raised to {0}".format(rlimit))
445 for name in self._fields:
448 match = re.match(r'(.*)\((.*)\)', name)
450 tracepoint, sub = match.groups()
451 tracefilter = ('%s==%d\0' %
452 (self.filters[tracepoint][0],
453 self.filters[tracepoint][1][sub]))
455 group.add_event(Event(name=name,
458 trace_point=tracepoint,
459 trace_filter=tracefilter))
460 self.group_leaders.append(group)
467 def fields(self, fields):
468 self._fields = fields
469 for group in self.group_leaders:
470 for event in group.events:
471 if event.name in fields:
478 ret = defaultdict(int)
479 for group in self.group_leaders:
480 for name, val in group.read().iteritems():
484 class DebugfsProvider(object):
486 self._fields = walkdir(PATH_DEBUGFS_KVM)[2]
493 def fields(self, fields):
494 self._fields = fields
498 return int(file(PATH_DEBUGFS_KVM + '/' + key).read())
499 return dict([(key, val(key)) for key in self._fields])
502 def __init__(self, providers, fields=None):
503 self.providers = providers
504 self._fields_filter = fields
506 self.update_provider_filters()
508 def update_provider_filters(self):
510 if not self._fields_filter:
512 return re.match(self._fields_filter, key) is not None
514 # As we reset the counters when updating the fields we can
515 # also clear the cache of old values.
517 for provider in self.providers:
518 provider_fields = [key for key in provider.fields if wanted(key)]
519 provider.fields = provider_fields
522 def fields_filter(self):
523 return self._fields_filter
525 @fields_filter.setter
526 def fields_filter(self, fields_filter):
527 self._fields_filter = fields_filter
528 self.update_provider_filters()
531 for provider in self.providers:
532 new = provider.read()
533 for key in provider.fields:
534 oldval = self.values.get(key, (0, 0))
535 newval = new.get(key, 0)
537 if oldval is not None:
538 newdelta = newval - oldval[0]
539 self.values[key] = (newval, newdelta)
546 def __init__(self, stats):
549 self.drilldown = False
550 self.fields_filter = self.stats.fields_filter
551 self.update_drilldown()
554 """Initialises curses for later use. Based on curses.wrapper
555 implementation from the Python standard library."""
556 self.screen = curses.initscr()
560 # The try/catch works around a minor bit of
561 # over-conscientiousness in the curses module, the error
562 # return from C start_color() is ignorable.
568 curses.use_default_colors()
571 def __exit__(self, *exception):
572 """Resets the terminal to its normal state. Based on curses.wrappre
573 implementation from the Python standard library."""
575 self.screen.keypad(0)
580 def update_drilldown(self):
581 if not self.fields_filter:
583 self.stats.fields_filter = None
585 self.stats.fields_filter = r'^[^\(]*$'
587 def refresh(self, sleeptime):
589 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
590 self.screen.addstr(2, 1, 'Event')
591 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH -
592 len('Total'), 'Total')
593 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 -
594 len('Current'), 'Current')
596 stats = self.stats.get()
599 return (-stats[x][1], -stats[x][0])
601 return (0, -stats[x][0])
602 for key in sorted(stats.keys(), key=sortkey):
604 if row >= self.screen.getmaxyx()[0]:
607 if not values[0] and not values[1]:
610 self.screen.addstr(row, col, key)
612 self.screen.addstr(row, col, '%10d' % (values[0],))
614 if values[1] is not None:
615 self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
617 self.screen.refresh()
619 def show_stats(self):
622 self.refresh(sleeptime)
623 curses.halfdelay(int(sleeptime * 10))
626 char = self.screen.getkey()
628 self.drilldown = not self.drilldown
629 self.update_drilldown()
632 except KeyboardInterrupt:
641 for key in sorted(s.keys()):
643 print '%-42s%10d%10d' % (key, values[0], values[1])
646 keys = sorted(stats.get().iterkeys())
654 print ' %9d' % s[k][1],
660 if line % banner_repeat == 0:
666 optparser = optparse.OptionParser()
667 optparser.add_option('-1', '--once', '--batch',
671 help='run in batch mode for one second',
673 optparser.add_option('-l', '--log',
677 help='run in logging mode (like vmstat)',
679 optparser.add_option('-t', '--tracepoints',
683 help='retrieve statistics from tracepoints',
685 optparser.add_option('-d', '--debugfs',
689 help='retrieve statistics from debugfs',
691 optparser.add_option('-f', '--fields',
695 help='fields to display (regex)',
697 (options, _) = optparser.parse_args(sys.argv)
700 def get_providers(options):
703 if options.tracepoints:
704 providers.append(TracepointProvider())
706 providers.append(DebugfsProvider())
707 if len(providers) == 0:
708 providers.append(TracepointProvider())
713 if not os.path.exists('/sys/kernel/debug'):
714 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
717 if not os.path.exists(PATH_DEBUGFS_KVM):
718 sys.stderr.write("Please make sure, that debugfs is mounted and "
719 "readable by the current user:\n"
720 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
721 "Also ensure, that the kvm modules are loaded.\n")
724 if not os.path.exists(PATH_DEBUGFS_TRACING):
725 sys.stderr.write("Please make {0} readable by the current user.\n"
726 .format(PATH_DEBUGFS_TRACING))
731 options = get_options()
732 providers = get_providers(options)
733 stats = Stats(providers, fields=options.fields)
737 elif not options.once:
738 with Tui(stats) as tui:
743 if __name__ == "__main__":