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.
13 """The kvm_stat module outputs statistics about running KVM VMs
15 Three different ways of output formatting are available:
16 - as a top-like text ui
17 - in a key -> value format
18 - in an all keys, all values format
20 The data is sampled from the KVM's debugfs entries and its perf events.
22 from __future__ import print_function
36 from collections import defaultdict, namedtuple
40 'EXTERNAL_INTERRUPT': 1,
42 'PENDING_INTERRUPT': 7,
66 'MWAIT_INSTRUCTION': 36,
67 'MONITOR_INSTRUCTION': 39,
68 'PAUSE_INSTRUCTION': 40,
69 'MCE_DURING_VMENTRY': 41,
70 'TPR_BELOW_THRESHOLD': 43,
111 'CR0_SEL_WRITE': 0x065,
135 'TASK_SWITCH': 0x07d,
136 'FERR_FREEZE': 0x07e,
155 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
156 AARCH64_EXIT_REASONS = {
194 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
195 USERSPACE_EXIT_REASONS = {
203 'IRQ_WINDOW_OPEN': 7,
213 'INTERNAL_ERROR': 17,
224 'SET_FILTER': 0x40082406,
225 'ENABLE': 0x00002400,
226 'DISABLE': 0x00002401,
230 ENCODING = locale.getpreferredencoding(False)
231 TRACE_FILTER = re.compile(r'^[^\(]*$')
235 """Encapsulates global architecture specific data.
237 Contains the performance event open syscall and ioctl numbers, as
238 well as the VM exit reasons for the architecture it runs on.
243 machine = os.uname()[4]
245 if machine.startswith('ppc'):
247 elif machine.startswith('aarch64'):
249 elif machine.startswith('s390'):
253 for line in open('/proc/cpuinfo'):
254 if not line.startswith('flags'):
259 return ArchX86(VMX_EXIT_REASONS)
261 return ArchX86(SVM_EXIT_REASONS)
264 def tracepoint_is_child(self, field):
265 if (TRACE_FILTER.match(field)):
267 return field.split('(', 1)[0]
271 def __init__(self, exit_reasons):
272 self.sc_perf_evt_open = 298
273 self.ioctl_numbers = IOCTL_NUMBERS
274 self.exit_reason_field = 'exit_reason'
275 self.exit_reasons = exit_reasons
277 def debugfs_is_child(self, field):
278 """ Returns name of parent if 'field' is a child, None otherwise """
284 self.sc_perf_evt_open = 319
285 self.ioctl_numbers = IOCTL_NUMBERS
286 self.ioctl_numbers['ENABLE'] = 0x20002400
287 self.ioctl_numbers['DISABLE'] = 0x20002401
288 self.ioctl_numbers['RESET'] = 0x20002403
290 # PPC comes in 32 and 64 bit and some generated ioctl
291 # numbers depend on the wordsize.
292 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
293 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
294 self.exit_reason_field = 'exit_nr'
295 self.exit_reasons = {}
297 def debugfs_is_child(self, field):
298 """ Returns name of parent if 'field' is a child, None otherwise """
304 self.sc_perf_evt_open = 241
305 self.ioctl_numbers = IOCTL_NUMBERS
306 self.exit_reason_field = 'esr_ec'
307 self.exit_reasons = AARCH64_EXIT_REASONS
309 def debugfs_is_child(self, field):
310 """ Returns name of parent if 'field' is a child, None otherwise """
314 class ArchS390(Arch):
316 self.sc_perf_evt_open = 331
317 self.ioctl_numbers = IOCTL_NUMBERS
318 self.exit_reason_field = None
319 self.exit_reasons = None
321 def debugfs_is_child(self, field):
322 """ Returns name of parent if 'field' is a child, None otherwise """
323 if field.startswith('instruction_'):
324 return 'exit_instruction'
327 ARCH = Arch.get_arch()
330 class perf_event_attr(ctypes.Structure):
331 """Struct that holds the necessary data to set up a trace event.
333 For an extensive explanation see perf_event_open(2) and
334 include/uapi/linux/perf_event.h, struct perf_event_attr
336 All fields that are not initialized in the constructor are 0.
339 _fields_ = [('type', ctypes.c_uint32),
340 ('size', ctypes.c_uint32),
341 ('config', ctypes.c_uint64),
342 ('sample_freq', ctypes.c_uint64),
343 ('sample_type', ctypes.c_uint64),
344 ('read_format', ctypes.c_uint64),
345 ('flags', ctypes.c_uint64),
346 ('wakeup_events', ctypes.c_uint32),
347 ('bp_type', ctypes.c_uint32),
348 ('bp_addr', ctypes.c_uint64),
349 ('bp_len', ctypes.c_uint64),
353 super(self.__class__, self).__init__()
354 self.type = PERF_TYPE_TRACEPOINT
355 self.size = ctypes.sizeof(self)
356 self.read_format = PERF_FORMAT_GROUP
359 PERF_TYPE_TRACEPOINT = 2
360 PERF_FORMAT_GROUP = 1 << 3
364 """Represents a perf event group."""
369 def add_event(self, event):
370 self.events.append(event)
373 """Returns a dict with 'event name: value' for all events in the
376 Values are read by reading from the file descriptor of the
377 event that is the group leader. See perf_event_open(2) for
380 Read format for the used event configuration is:
382 u64 nr; /* The number of events */
384 u64 value; /* The value of the event */
389 length = 8 * (1 + len(self.events))
390 read_format = 'xxxxxxxx' + 'Q' * len(self.events)
391 return dict(zip([event.name for event in self.events],
392 struct.unpack(read_format,
393 os.read(self.events[0].fd, length))))
397 """Represents a performance event and manages its life cycle."""
398 def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
399 trace_filter, trace_set='kvm'):
400 self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
401 self.syscall = self.libc.syscall
404 self._setup_event(group, trace_cpu, trace_pid, trace_point,
405 trace_filter, trace_set)
408 """Closes the event's file descriptor.
410 As no python file object was created for the file descriptor,
411 python will not reference count the descriptor and will not
412 close it itself automatically, so we do it.
418 def _perf_event_open(self, attr, pid, cpu, group_fd, flags):
419 """Wrapper for the sys_perf_evt_open() syscall.
421 Used to set up performance events, returns a file descriptor or -1
426 - struct perf_event_attr *
427 - pid or -1 to monitor all pids
428 - cpu number or -1 to monitor all cpus
429 - The file descriptor of the group leader or -1 to create a group.
433 return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
434 ctypes.c_int(pid), ctypes.c_int(cpu),
435 ctypes.c_int(group_fd), ctypes.c_long(flags))
437 def _setup_event_attribute(self, trace_set, trace_point):
438 """Returns an initialized ctype perf_event_attr struct."""
440 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
443 event_attr = perf_event_attr()
444 event_attr.config = int(open(id_path).read())
447 def _setup_event(self, group, trace_cpu, trace_pid, trace_point,
448 trace_filter, trace_set):
449 """Sets up the perf event in Linux.
451 Issues the syscall to register the event in the kernel and
452 then sets the optional filter.
456 event_attr = self._setup_event_attribute(trace_set, trace_point)
458 # First event will be group leader.
461 # All others have to pass the leader's descriptor instead.
463 group_leader = group.events[0].fd
465 fd = self._perf_event_open(event_attr, trace_pid,
466 trace_cpu, group_leader, 0)
468 err = ctypes.get_errno()
469 raise OSError(err, os.strerror(err),
470 'while calling sys_perf_event_open().')
473 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
479 """Enables the trace event in the kernel.
481 Enabling the group leader makes reading counters from it and the
482 events under it possible.
485 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
488 """Disables the trace event in the kernel.
490 Disabling the group leader makes reading all counters under it
494 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
497 """Resets the count of the trace event in the kernel."""
498 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
501 class Provider(object):
502 """Encapsulates functionalities used by all providers."""
503 def __init__(self, pid):
504 self.child_events = False
508 def is_field_wanted(fields_filter, field):
509 """Indicate whether field is valid according to fields_filter."""
510 if not fields_filter:
512 return re.match(fields_filter, field) is not None
516 """Returns os.walk() data for specified directory.
518 As it is only a wrapper it returns the same 3-tuple of (dirpath,
519 dirnames, filenames).
521 return next(os.walk(path))
524 class TracepointProvider(Provider):
525 """Data provider for the stats class.
527 Manages the events/groups from which it acquires its data.
530 def __init__(self, pid, fields_filter):
531 self.group_leaders = []
532 self.filters = self._get_filters()
533 self.update_fields(fields_filter)
534 super(TracepointProvider, self).__init__(pid)
538 """Returns a dict of trace events, their filter ids and
539 the values that can be filtered.
541 Trace events can be filtered for special values by setting a
542 filter string via an ioctl. The string normally has the format
543 identifier==value. For each filter a new event will be created, to
544 be able to distinguish the events.
548 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
549 if ARCH.exit_reason_field and ARCH.exit_reasons:
550 filters['kvm_exit'] = (ARCH.exit_reason_field, ARCH.exit_reasons)
553 def _get_available_fields(self):
554 """Returns a list of available events of format 'event name(filter
557 All available events have directories under
558 /sys/kernel/debug/tracing/events/ which export information
559 about the specific event. Therefore, listing the dirs gives us
560 a list of all available events.
562 Some events like the vm exit reasons can be filtered for
563 specific values. To take account for that, the routine below
564 creates special fields with the following format:
565 event name(filter name)
568 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
569 fields = self.walkdir(path)[1]
572 if field in self.filters:
573 filter_name_, filter_dicts = self.filters[field]
574 for name in filter_dicts:
575 extra.append(field + '(' + name + ')')
579 def update_fields(self, fields_filter):
580 """Refresh fields, applying fields_filter"""
581 self.fields = [field for field in self._get_available_fields()
582 if self.is_field_wanted(fields_filter, field)]
583 # add parents for child fields - otherwise we won't see any output!
584 for field in self._fields:
585 parent = ARCH.tracepoint_is_child(field)
586 if (parent and parent not in self._fields):
587 self.fields.append(parent)
590 def _get_online_cpus():
591 """Returns a list of cpu id integers."""
592 def parse_int_list(list_string):
593 """Returns an int list from a string of comma separated integers and
596 members = list_string.split(',')
598 for member in members:
599 if '-' not in member:
600 integers.append(int(member))
602 int_range = member.split('-')
603 integers.extend(range(int(int_range[0]),
604 int(int_range[1]) + 1))
608 with open('/sys/devices/system/cpu/online') as cpu_list:
609 cpu_string = cpu_list.readline()
610 return parse_int_list(cpu_string)
612 def _setup_traces(self):
613 """Creates all event and group objects needed to be able to retrieve
615 fields = self._get_available_fields()
617 # Fetch list of all threads of the monitored pid, as qemu
618 # starts a thread for each vcpu.
619 path = os.path.join('/proc', str(self._pid), 'task')
620 groupids = self.walkdir(path)[1]
622 groupids = self._get_online_cpus()
624 # The constant is needed as a buffer for python libs, std
625 # streams and other files that the script opens.
626 newlim = len(groupids) * len(fields) + 50
628 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
631 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
632 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
634 # Raising the soft limit is sufficient.
635 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
638 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
640 for groupid in groupids:
645 match = re.match(r'(.*)\((.*)\)', name)
647 tracepoint, sub = match.groups()
648 tracefilter = ('%s==%d\0' %
649 (self.filters[tracepoint][0],
650 self.filters[tracepoint][1][sub]))
652 # From perf_event_open(2):
653 # pid > 0 and cpu == -1
654 # This measures the specified process/thread on any CPU.
656 # pid == -1 and cpu >= 0
657 # This measures all processes/threads on the specified CPU.
658 trace_cpu = groupid if self._pid == 0 else -1
659 trace_pid = int(groupid) if self._pid != 0 else -1
661 group.add_event(Event(name=name,
665 trace_point=tracepoint,
666 trace_filter=tracefilter))
668 self.group_leaders.append(group)
675 def fields(self, fields):
676 """Enables/disables the (un)wanted events"""
677 self._fields = fields
678 for group in self.group_leaders:
679 for index, event in enumerate(group.events):
680 if event.name in fields:
684 # Do not disable the group leader.
685 # It would disable all of its events.
695 """Changes the monitored pid by setting new traces."""
697 # The garbage collector will get rid of all Event/Group
698 # objects and open files after removing the references.
699 self.group_leaders = []
701 self.fields = self._fields
703 def read(self, by_guest=0):
704 """Returns 'event name: current value' for all enabled events."""
705 ret = defaultdict(int)
706 for group in self.group_leaders:
707 for name, val in group.read().items():
708 if name not in self._fields:
710 parent = ARCH.tracepoint_is_child(name)
717 """Reset all field counters"""
718 for group in self.group_leaders:
719 for event in group.events:
723 class DebugfsProvider(Provider):
724 """Provides data from the files that KVM creates in the kvm debugfs
726 def __init__(self, pid, fields_filter, include_past):
727 self.update_fields(fields_filter)
731 super(DebugfsProvider, self).__init__(pid)
735 def _get_available_fields(self):
736 """"Returns a list of available fields.
738 The fields are all available KVM debugfs files
741 return self.walkdir(PATH_DEBUGFS_KVM)[2]
743 def update_fields(self, fields_filter):
744 """Refresh fields, applying fields_filter"""
745 self._fields = [field for field in self._get_available_fields()
746 if self.is_field_wanted(fields_filter, field)]
747 # add parents for child fields - otherwise we won't see any output!
748 for field in self._fields:
749 parent = ARCH.debugfs_is_child(field)
750 if (parent and parent not in self._fields):
751 self.fields.append(parent)
758 def fields(self, fields):
759 self._fields = fields
770 vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
774 self.paths = list(filter(lambda x: "{}-".format(pid) in x, vms))
780 def _verify_paths(self):
781 """Remove invalid paths"""
782 for path in self.paths:
783 if not os.path.exists(os.path.join(PATH_DEBUGFS_KVM, path)):
784 self.paths.remove(path)
787 def read(self, reset=0, by_guest=0):
788 """Returns a dict with format:'file name / field -> current value'.
792 1 reset field counts to 0
793 2 restore the original field counts
798 # If no debugfs filtering support is available, then don't read.
806 for entry in os.walk(PATH_DEBUGFS_KVM):
810 for field in self._fields:
811 value = self._read_field(field, path)
814 self._baseline[key] = value
816 self._baseline[key] = 0
817 if self._baseline.get(key, -1) == -1:
818 self._baseline[key] = value
819 parent = ARCH.debugfs_is_child(field)
821 field = field + ' ' + parent
824 field = key.split('-')[0] # set 'field' to 'pid'
825 increment = value - self._baseline.get(key, 0)
827 results[field] += increment
829 results[field] = increment
833 def _read_field(self, field, path):
834 """Returns the value of a single field from a specific VM."""
836 return int(open(os.path.join(PATH_DEBUGFS_KVM,
844 """Reset field counters"""
849 """Reset field counters"""
854 EventStat = namedtuple('EventStat', ['value', 'delta'])
858 """Manages the data providers and the data they provide.
860 It is used to set filters on the provider's data and collect all
864 def __init__(self, options):
865 self.providers = self._get_providers(options)
866 self._pid_filter = options.pid
867 self._fields_filter = options.fields
869 self._child_events = False
871 def _get_providers(self, options):
872 """Returns a list of data providers depending on the passed options."""
876 providers.append(DebugfsProvider(options.pid, options.fields,
877 options.dbgfs_include_past))
878 if options.tracepoints or not providers:
879 providers.append(TracepointProvider(options.pid, options.fields))
883 def _update_provider_filters(self):
884 """Propagates fields filters to providers."""
885 # As we reset the counters when updating the fields we can
886 # also clear the cache of old values.
888 for provider in self.providers:
889 provider.update_fields(self._fields_filter)
893 for provider in self.providers:
897 def fields_filter(self):
898 return self._fields_filter
900 @fields_filter.setter
901 def fields_filter(self, fields_filter):
902 if fields_filter != self._fields_filter:
903 self._fields_filter = fields_filter
904 self._update_provider_filters()
907 def pid_filter(self):
908 return self._pid_filter
911 def pid_filter(self, pid):
912 if pid != self._pid_filter:
913 self._pid_filter = pid
915 for provider in self.providers:
916 provider.pid = self._pid_filter
919 def child_events(self):
920 return self._child_events
923 def child_events(self, val):
924 self._child_events = val
925 for provider in self.providers:
926 provider.child_events = val
928 def get(self, by_guest=0):
929 """Returns a dict with field -> (value, delta to last value) of all
932 * plain: 'key' is event name
933 * child-parent: 'key' is in format '<child> <parent>'
934 * pid: 'key' is the pid of the guest, and the record contains the
935 aggregated event data
936 These formats are generated by the providers, and handled in class TUI.
938 for provider in self.providers:
939 new = provider.read(by_guest=by_guest)
941 oldval = self.values.get(key, EventStat(0, 0)).value
942 newval = new.get(key, 0)
943 newdelta = newval - oldval
944 self.values[key] = EventStat(newval, newdelta)
947 def toggle_display_guests(self, to_pid):
948 """Toggle between collection of stats by individual event and by
951 Events reported by DebugfsProvider change when switching to/from
952 reading by guest values. Hence we have to remove the excess event
953 names from self.values.
956 if any(isinstance(ins, TracepointProvider) for ins in self.providers):
959 for provider in self.providers:
960 if isinstance(provider, DebugfsProvider):
961 for key in provider.fields:
962 if key in self.values.keys():
965 oldvals = self.values.copy()
969 # Update oldval (see get())
975 MAX_GUEST_NAME_LEN = 48
981 """Instruments curses to draw a nice text ui."""
982 def __init__(self, stats):
985 self._delay_initial = 0.25
986 self._delay_regular = DELAY_DEFAULT
987 self._sorting = SORT_DEFAULT
988 self._display_guests = 0
991 """Initialises curses for later use. Based on curses.wrapper
992 implementation from the Python standard library."""
993 self.screen = curses.initscr()
997 # The try/catch works around a minor bit of
998 # over-conscientiousness in the curses module, the error
999 # return from C start_color() is ignorable.
1001 curses.start_color()
1002 except curses.error:
1005 # Hide cursor in extra statement as some monochrome terminals
1006 # might support hiding but not colors.
1009 except curses.error:
1012 curses.use_default_colors()
1015 def __exit__(self, *exception):
1016 """Resets the terminal to its normal state. Based on curses.wrapper
1017 implementation from the Python standard library."""
1019 self.screen.keypad(0)
1025 def get_all_gnames():
1026 """Returns a list of (pid, gname) tuples of all running guests"""
1029 child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
1030 stdout=subprocess.PIPE)
1033 for line in child.stdout:
1034 line = line.decode(ENCODING).lstrip().split(' ', 1)
1035 # perform a sanity check before calling the more expensive
1036 # function to possibly extract the guest name
1037 if ' -name ' in line[1]:
1038 res.append((line[0], Tui.get_gname_from_pid(line[0])))
1039 child.stdout.close()
1043 def _print_all_gnames(self, row):
1044 """Print a list of all running guests along with their pids."""
1045 self.screen.addstr(row, 2, '%8s %-60s' %
1046 ('Pid', 'Guest Name (fuzzy list, might be '
1051 for line in self.get_all_gnames():
1052 self.screen.addstr(row, 2, '%8s %-60s' % (line[0], line[1]))
1054 if row >= self.screen.getmaxyx()[0]:
1057 self.screen.addstr(row + 1, 2, 'Not available')
1060 def get_pid_from_gname(gname):
1061 """Fuzzy function to convert guest name to QEMU process pid.
1063 Returns a list of potential pids, can be empty if no match found.
1064 Throws an exception on processing errors.
1068 for line in Tui.get_all_gnames():
1069 if gname == line[1]:
1070 pids.append(int(line[0]))
1075 def get_gname_from_pid(pid):
1076 """Returns the guest name for a QEMU process pid.
1078 Extracts the guest name from the QEMU comma line by processing the
1079 '-name' option. Will also handle names specified out of sequence.
1084 line = open('/proc/{}/cmdline'
1085 .format(pid), 'r').read().split('\0')
1086 parms = line[line.index('-name') + 1].split(',')
1088 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1089 # in # ['foo', '', 'bar'], which we revert here
1090 idx = parms.index('')
1091 parms[idx - 1] += ',' + parms[idx + 1]
1092 del parms[idx:idx+2]
1093 # the '-name' switch allows for two ways to specify the guest name,
1094 # where the plain name overrides the name specified via 'guest='
1099 if arg[:6] == 'guest=':
1101 except (ValueError, IOError, IndexError):
1106 def _update_pid(self, pid):
1107 """Propagates pid selection to stats object."""
1108 self.screen.addstr(4, 1, 'Updating pid filter...')
1109 self.screen.refresh()
1110 self.stats.pid_filter = pid
1112 def _refresh_header(self, pid=None):
1113 """Refreshes the header."""
1115 pid = self.stats.pid_filter
1117 gname = self.get_gname_from_pid(pid)
1120 gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
1121 if len(gname) > MAX_GUEST_NAME_LEN
1124 self._headline = 'kvm statistics - pid {0} {1}'.format(pid, gname)
1126 self._headline = 'kvm statistics - summary'
1127 self.screen.addstr(0, 0, self._headline, curses.A_BOLD)
1128 if self.stats.fields_filter:
1129 regex = self.stats.fields_filter
1130 if len(regex) > MAX_REGEX_LEN:
1131 regex = regex[:MAX_REGEX_LEN] + '...'
1132 self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
1133 if self._display_guests:
1134 col_name = 'Guest Name'
1137 self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
1138 (col_name, 'Total', '%Total', 'CurAvg/s'),
1140 self.screen.addstr(4, 1, 'Collecting data...')
1141 self.screen.refresh()
1143 def _refresh_body(self, sleeptime):
1144 def insert_child(sorted_items, child, values, parent):
1145 num = len(sorted_items)
1146 for i in range(0, num):
1147 # only add child if parent is present
1148 if parent.startswith(sorted_items[i][0]):
1149 sorted_items.insert(i + 1, (' ' + child, values))
1151 def get_sorted_events(self, stats):
1152 """ separate parent and child events """
1153 if self._sorting == SORT_DEFAULT:
1155 # sort by (delta value, overall value)
1157 return (v.delta, v.value)
1160 # sort by overall value
1166 # we can't rule out child events to appear prior to parents even
1167 # when sorted - separate out all children first, and add in later
1168 for key, values in sorted(stats.items(), key=sortkey,
1170 if values == (0, 0):
1172 if key.find(' ') != -1:
1173 if not self.stats.child_events:
1175 childs.insert(0, (key, values))
1177 sorted_items.append((key, values))
1178 if self.stats.child_events:
1179 for key, values in childs:
1180 (child, parent) = key.split(' ')
1181 insert_child(sorted_items, child, values, parent)
1185 if not self._is_running_guest(self.stats.pid_filter):
1187 try: # ...to identify the guest by name in case it's back
1188 pids = self.get_pid_from_gname(self._gname)
1190 self._refresh_header(pids[0])
1191 self._update_pid(pids[0])
1195 self._display_guest_dead()
1196 # leave final data on screen
1199 self.screen.move(row, 0)
1200 self.screen.clrtobot()
1201 stats = self.stats.get(self._display_guests)
1204 for key, values in stats.items():
1205 if self._display_guests:
1206 if self.get_gname_from_pid(key):
1207 total += values.value
1209 if not key.find(' ') != -1:
1210 total += values.value
1212 ctotal += values.value
1214 # we don't have any fields, or all non-child events are filtered
1220 guest_removed = False
1221 for key, values in get_sorted_events(self, stats):
1222 if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0):
1224 if self._display_guests:
1225 key = self.get_gname_from_pid(key)
1228 cur = int(round(values.delta / sleeptime)) if values.delta else 0
1230 guest_removed = True
1234 tcur += values.delta
1235 ptotal = values.value
1239 self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key,
1241 values.value * 100 / float(ltotal), cur))
1245 self.screen.addstr(4, 1, 'Guest removed, updating...')
1247 self.screen.addstr(4, 1, 'No matching events reported yet')
1249 tavg = int(round(tcur / sleeptime)) if tcur > 0 else ''
1250 self.screen.addstr(row, 1, '%-40s %10d %8s' %
1251 ('Total', total, tavg), curses.A_BOLD)
1252 self.screen.refresh()
1254 def _display_guest_dead(self):
1255 marker = ' Guest is DEAD '
1256 y = min(len(self._headline), 80 - len(marker))
1257 self.screen.addstr(0, y, marker, curses.A_BLINK | curses.A_STANDOUT)
1259 def _show_msg(self, text):
1260 """Display message centered text and exit on key press"""
1261 hint = 'Press any key to continue'
1264 (x, term_width) = self.screen.getmaxyx()
1267 start = (term_width - len(line)) // 2
1268 self.screen.addstr(row, start, line)
1270 self.screen.addstr(row + 1, (term_width - len(hint)) // 2, hint,
1272 self.screen.getkey()
1274 def _show_help_interactive(self):
1275 """Display help with list of interactive commands"""
1276 msg = (' b toggle events by guests (debugfs only, honors'
1279 ' f filter by regular expression',
1280 ' g filter by guest name/PID',
1281 ' h display interactive commands reference',
1282 ' o toggle sorting order (Total vs CurAvg/s)',
1283 ' p filter by guest name/PID',
1286 ' s set update interval',
1287 ' x toggle reporting of stats for individual child trace'
1289 'Any other key refreshes statistics immediately')
1292 self.screen.addstr(0, 0, "Interactive commands reference",
1294 self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
1297 self.screen.addstr(row, 0, line)
1299 self.screen.getkey()
1300 self._refresh_header()
1302 def _show_filter_selection(self):
1303 """Draws filter selection mask.
1305 Asks for a valid regex and sets the fields filter accordingly.
1311 self.screen.addstr(0, 0,
1312 "Show statistics for events matching a regex.",
1314 self.screen.addstr(2, 0,
1315 "Current regex: {0}"
1316 .format(self.stats.fields_filter))
1317 self.screen.addstr(5, 0, msg)
1318 self.screen.addstr(3, 0, "New regex: ")
1320 regex = self.screen.getstr().decode(ENCODING)
1323 self.stats.fields_filter = ''
1324 self._refresh_header()
1328 self.stats.fields_filter = regex
1329 self._refresh_header()
1332 msg = '"' + regex + '": Not a valid regular expression'
1335 def _show_set_update_interval(self):
1336 """Draws update interval selection mask."""
1340 self.screen.addstr(0, 0, 'Set update interval (defaults to %.1fs).' %
1341 DELAY_DEFAULT, curses.A_BOLD)
1342 self.screen.addstr(4, 0, msg)
1343 self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
1344 self._delay_regular)
1346 val = self.screen.getstr().decode(ENCODING)
1353 msg = '"' + str(val) + '": Value must be >=0.1'
1356 msg = '"' + str(val) + '": Value must be <=25.5'
1359 delay = DELAY_DEFAULT
1360 self._delay_regular = delay
1364 msg = '"' + str(val) + '": Invalid value'
1365 self._refresh_header()
1367 def _is_running_guest(self, pid):
1368 """Check if pid is still a running process."""
1371 return os.path.isdir(os.path.join('/proc/', str(pid)))
1373 def _show_vm_selection_by_guest(self):
1374 """Draws guest selection mask.
1376 Asks for a guest name or pid until a valid guest name or '' is entered.
1382 self.screen.addstr(0, 0,
1383 'Show statistics for specific guest or pid.',
1385 self.screen.addstr(1, 0,
1386 'This might limit the shown data to the trace '
1388 self.screen.addstr(5, 0, msg)
1389 self._print_all_gnames(7)
1392 self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1393 guest = self.screen.getstr().decode(ENCODING)
1397 if not guest or guest == '0':
1400 if not self._is_running_guest(guest):
1401 msg = '"' + guest + '": Not a running process'
1407 pids = self.get_pid_from_gname(guest)
1409 msg = '"' + guest + '": Internal error while searching, ' \
1410 'use pid filter instead'
1413 msg = '"' + guest + '": Not an active guest'
1416 msg = '"' + guest + '": Multiple matches found, use pid ' \
1422 self._refresh_header(pid)
1423 self._update_pid(pid)
1425 def show_stats(self):
1426 """Refreshes the screen and processes user input."""
1427 sleeptime = self._delay_initial
1428 self._refresh_header()
1429 start = 0.0 # result based on init value never appears on screen
1431 self._refresh_body(time.time() - start)
1432 curses.halfdelay(int(sleeptime * 10))
1434 sleeptime = self._delay_regular
1436 char = self.screen.getkey()
1438 self._display_guests = not self._display_guests
1439 if self.stats.toggle_display_guests(self._display_guests):
1440 self._show_msg(['Command not available with '
1441 'tracepoints enabled', 'Restart with '
1442 'debugfs only (see option \'-d\') and '
1444 self._display_guests = not self._display_guests
1445 self._refresh_header()
1447 self.stats.fields_filter = ''
1448 self._refresh_header(0)
1452 self._show_filter_selection()
1454 sleeptime = self._delay_initial
1455 if char == 'g' or char == 'p':
1456 self._show_vm_selection_by_guest()
1457 sleeptime = self._delay_initial
1459 self._show_help_interactive()
1461 self._sorting = not self._sorting
1468 self._show_set_update_interval()
1470 sleeptime = self._delay_initial
1472 self.stats.child_events = not self.stats.child_events
1473 except KeyboardInterrupt:
1475 except curses.error:
1480 """Prints statistics in a key, value format."""
1485 for key, values in sorted(s.items()):
1486 print('%-42s%10d%10d' % (key.split(' ')[0], values.value,
1488 except KeyboardInterrupt:
1493 """Prints statistics as reiterating key block, multiple value blocks."""
1494 keys = sorted(stats.get().keys())
1498 print(key.split(' ')[0], end=' ')
1504 print(' %9d' % s[key].delta, end=' ')
1511 if line % banner_repeat == 0:
1515 except KeyboardInterrupt:
1520 """Returns processed program arguments."""
1521 description_text = """
1522 This script displays various statistics about VMs running under KVM.
1523 The statistics are gathered from the KVM debugfs entries and / or the
1524 currently available perf traces.
1526 The monitoring takes additional cpu cycles and might affect the VM's
1534 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1535 CAP_SYS_ADMIN and perf events are used.
1536 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1537 the large number of files that are possibly opened.
1539 Interactive Commands:
1540 b toggle events by guests (debugfs only, honors filters)
1542 f filter by regular expression
1543 g filter by guest name
1544 h display interactive commands reference
1545 o toggle sorting order (Total vs CurAvg/s)
1549 s set update interval
1550 x toggle reporting of stats for individual child trace events
1551 Press any other key to refresh statistics immediately.
1552 """ % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
1554 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1555 def format_description(self, description):
1557 return description + "\n"
1561 def cb_guest_to_pid(option, opt, val, parser):
1563 pids = Tui.get_pid_from_gname(val)
1565 sys.exit('Error while searching for guest "{}". Use "-p" to '
1566 'specify a pid instead?'.format(val))
1568 sys.exit('Error: No guest by the name "{}" found'.format(val))
1570 sys.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1571 'to specify the desired pid'.format(" ".join(pids)))
1572 parser.values.pid = pids[0]
1574 optparser = optparse.OptionParser(description=description_text,
1575 formatter=PlainHelpFormatter())
1576 optparser.add_option('-1', '--once', '--batch',
1577 action='store_true',
1580 help='run in batch mode for one second',
1582 optparser.add_option('-i', '--debugfs-include-past',
1583 action='store_true',
1585 dest='dbgfs_include_past',
1586 help='include all available data on past events for '
1589 optparser.add_option('-l', '--log',
1590 action='store_true',
1593 help='run in logging mode (like vmstat)',
1595 optparser.add_option('-t', '--tracepoints',
1596 action='store_true',
1599 help='retrieve statistics from tracepoints',
1601 optparser.add_option('-d', '--debugfs',
1602 action='store_true',
1605 help='retrieve statistics from debugfs',
1607 optparser.add_option('-f', '--fields',
1611 help='''fields to display (regex)
1612 "-f help" for a list of available events''',
1614 optparser.add_option('-p', '--pid',
1619 help='restrict statistics to pid',
1621 optparser.add_option('-g', '--guest',
1626 help='restrict statistics to guest by name',
1627 callback=cb_guest_to_pid,
1629 options, unkn = optparser.parse_args(sys.argv)
1631 sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:]))
1633 # verify that we were passed a valid regex up front
1634 re.compile(options.fields)
1636 sys.exit('Error: "' + options.fields + '" is not a valid regular '
1642 def check_access(options):
1643 """Exits if the current user can't access all needed directories."""
1644 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1645 not options.debugfs):
1646 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1647 "when using the option -t (default).\n"
1648 "If it is enabled, make {0} readable by the "
1650 .format(PATH_DEBUGFS_TRACING))
1651 if options.tracepoints:
1654 sys.stderr.write("Falling back to debugfs statistics!\n")
1655 options.debugfs = True
1661 def assign_globals():
1662 global PATH_DEBUGFS_KVM
1663 global PATH_DEBUGFS_TRACING
1666 for line in open('/proc/mounts'):
1667 if line.split(' ')[0] == 'debugfs':
1668 debugfs = line.split(' ')[1]
1671 sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1672 "your kernel, mounted and\nreadable by the current "
1674 "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1677 PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm')
1678 PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing')
1680 if not os.path.exists(PATH_DEBUGFS_KVM):
1681 sys.stderr.write("Please make sure that CONFIG_KVM is enabled in "
1682 "your kernel and that the modules are loaded.\n")
1688 options = get_options()
1689 options = check_access(options)
1691 if (options.pid > 0 and
1692 not os.path.isdir(os.path.join('/proc/',
1693 str(options.pid)))):
1694 sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1695 sys.exit('Specified pid does not exist.')
1697 stats = Stats(options)
1699 if options.fields == 'help':
1700 stats.fields_filter = None
1702 for key in stats.get().keys():
1703 event_list.append(key.split('(', 1)[0])
1704 sys.stdout.write(' ' + '\n '.join(sorted(set(event_list))) + '\n')
1709 elif not options.once:
1710 with Tui(stats) as tui:
1715 if __name__ == "__main__":