From 773bffeeb2f1fca7739516d0a5a814dd14a5cc83 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:25 +0200 Subject: tools/kvm_stat: fix typo Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 8f74ed8e7237..904eb6214602 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -929,7 +929,7 @@ class Tui(object): return self def __exit__(self, *exception): - """Resets the terminal to its normal state. Based on curses.wrappre + """Resets the terminal to its normal state. Based on curses.wrapper implementation from the Python standard library.""" if self.screen: self.screen.keypad(0) -- cgit v1.2.3 From 124c2fc9fdf5fb1d9cea4707d7e5471e317ba3bf Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:26 +0200 Subject: tools/kvm_stat: fix event counts display for interrupted intervals When an update interval is interrupted via key press (e.g. space), the 'Current' column value is calculated using the full interval length instead of the elapsed time, which leads to lower than actual numbers. Furthermore, the value should be rounded, not truncated. This is fixed by using the actual elapsed time for the calculation. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 904eb6214602..b571584419ae 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1009,7 +1009,8 @@ class Tui(object): self.screen.addstr(row, col, '%7.1f' % (values[0] * 100 / total,)) col += 7 if values[1] is not None: - self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,)) + self.screen.addstr(row, col, '%8d' % + round(values[1] / sleeptime)) row += 1 self.screen.refresh() @@ -1130,9 +1131,11 @@ class Tui(object): """Refreshes the screen and processes user input.""" sleeptime = DELAY_INITIAL self.refresh_header() + start = 0.0 # result based on init value never appears on screen while True: - self.refresh_body(sleeptime) + self.refresh_body(time.time() - start) curses.halfdelay(int(sleeptime * 10)) + start = time.time() sleeptime = DELAY_REGULAR try: char = self.screen.getkey() -- cgit v1.2.3 From 81468d73b6eb0ed251e7c77f2cc44c0f4edb4d36 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:27 +0200 Subject: tools/kvm_stat: fix undue use of initial sleeptime We should not use the initial sleeptime for any key press that does not switch to a different screen, as that introduces an unaesthetic flicker due to two updates in quick succession. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 3 --- 1 file changed, 3 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index b571584419ae..6e29e5b072ab 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1142,14 +1142,12 @@ class Tui(object): if char == 'x': self.refresh_header() self.update_drilldown() - sleeptime = DELAY_INITIAL if char == 'q': break if char == 'c': self.stats.fields_filter = DEFAULT_REGEX self.refresh_header(0) self.update_pid(0) - sleeptime = DELAY_INITIAL if char == 'f': self.show_filter_selection() sleeptime = DELAY_INITIAL @@ -1162,7 +1160,6 @@ class Tui(object): if char == 'r': self.refresh_header() self.stats.reset() - sleeptime = DELAY_INITIAL except KeyboardInterrupt: break except curses.error: -- cgit v1.2.3 From 2da9d4aaa7348fc13374d7398c9c7027b0a9e2cb Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:28 +0200 Subject: tools/kvm_stat: remove unnecessary header redraws Certain interactive commands will not modify any information displayed in the header, hence we can skip them. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 2 -- 1 file changed, 2 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 6e29e5b072ab..d2526b698db4 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1140,7 +1140,6 @@ class Tui(object): try: char = self.screen.getkey() if char == 'x': - self.refresh_header() self.update_drilldown() if char == 'q': break @@ -1158,7 +1157,6 @@ class Tui(object): self.show_vm_selection_by_pid() sleeptime = DELAY_INITIAL if char == 'r': - self.refresh_header() self.stats.reset() except KeyboardInterrupt: break -- cgit v1.2.3 From 5a7d11f8dc59ddb36e89dca42a2526ea25914def Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:29 +0200 Subject: tools/kvm_stat: simplify line print logic Simplify line print logic for header and data lines in interactive mode as previously suggested by Radim. While at it, add a space between the first two columns to avoid the total bleeding into the event name. Furthermore, for column 'Current', differentiate between no events being reported (empty 'Current' column) vs the case where events were reported but the average was rounded down to zero ('0' in 'Current column), for the folks who appreciate the difference. Finally: Only skip events which were not reported at all yet, instead of events that don't have a value in the current interval. Considered using constants for the field widths in the format strings. However, that would make things a bit more complicated, and considering that there are only two places where output happens, I figured it isn't worth the trouble. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index d2526b698db4..a527b2fc6685 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -887,8 +887,6 @@ class Stats(object): self.values[key] = (newval, newdelta) return self.values -LABEL_WIDTH = 40 -NUMBER_WIDTH = 10 DELAY_INITIAL = 0.25 DELAY_REGULAR = 3.0 MAX_GUEST_NAME_LEN = 48 @@ -970,13 +968,8 @@ class Tui(object): if len(regex) > MAX_REGEX_LEN: regex = regex[:MAX_REGEX_LEN] + '...' self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex)) - self.screen.addstr(2, 1, 'Event') - self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH - - len('Total'), 'Total') - self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 7 - - len('%Total'), '%Total') - self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 7 + 8 - - len('Current'), 'Current') + self.screen.addstr(2, 1, '%-40s %10s%7s %7s' % + ('Event', 'Total', '%Total', 'Current')) self.screen.addstr(4, 1, 'Collecting data...') self.screen.refresh() @@ -1001,16 +994,11 @@ class Tui(object): values = stats[key] if not values[0] and not values[1]: break - col = 1 - self.screen.addstr(row, col, key) - col += LABEL_WIDTH - self.screen.addstr(row, col, '%10d' % (values[0],)) - col += NUMBER_WIDTH - self.screen.addstr(row, col, '%7.1f' % (values[0] * 100 / total,)) - col += 7 - if values[1] is not None: - self.screen.addstr(row, col, '%8d' % - round(values[1] / sleeptime)) + if values[0] is not None: + cur = int(round(values[1] / sleeptime)) if values[1] else '' + self.screen.addstr(row, 1, '%-40s %10d%7.1f %7s' % + (key, values[0], values[0] * 100 / total, + cur)) row += 1 self.screen.refresh() -- cgit v1.2.3 From 42a947b77b00da8fb1c9b1350eaa85fd3d53bacb Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:30 +0200 Subject: tools/kvm_stat: removed unused function Function available_fields() is not used in any place. Signed-off-by: Stefan Raspl Reviewed-by: Janosch Frank Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 3 --- 1 file changed, 3 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index a527b2fc6685..1b8626b42e22 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -671,9 +671,6 @@ class TracepointProvider(object): self.group_leaders.append(group) - def available_fields(self): - return self.get_available_fields() - @property def fields(self): return self._fields -- cgit v1.2.3 From 5e3823a49c50d70cc6b92808c262a43cf3505f3c Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:31 +0200 Subject: tools/kvm_stat: remove extra statement Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 1 - 1 file changed, 1 deletion(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 1b8626b42e22..e38791ddb37a 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1066,7 +1066,6 @@ class Tui(object): except ValueError: msg = '"' + str(pid) + '": Not a valid pid' - continue def show_vm_selection_by_guest_name(self): """Draws guest selection mask. -- cgit v1.2.3 From c469117df05955901d2950b6130770e526b1dbf4 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:32 +0200 Subject: tools/kvm_stat: simplify initializers Simplify a couple of initialization routines: * TracepointProvider and DebugfsProvider: Pass pid into __init__() instead of switching to the requested value in an extra call after initializing to the default first. * Pass a single options object into Stats.__init__(), delaying options evaluation accordingly, instead of evaluating options first and passing several parts of the options object to Stats.__init__() individually. * Eliminate Stats.update_provider_pid(), since this 2-line function is now used in a single place only. * Remove extra call to update_drilldown() in Tui.__init__() by getting the value of options.fields right initially when parsing options. * Simplify get_providers() logic. * Avoid duplicate fields initialization by handling it once in the providers' __init__() methods. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 74 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 38 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index e38791ddb37a..b8522d2ddb0a 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -295,6 +295,13 @@ class ArchS390(Arch): ARCH = Arch.get_arch() +def is_field_wanted(fields_filter, field): + """Indicate whether field is valid according to fields_filter.""" + if not fields_filter: + return True + return re.match(fields_filter, field) is not None + + def walkdir(path): """Returns os.walk() data for specified directory. @@ -581,11 +588,11 @@ class TracepointProvider(object): Manages the events/groups from which it acquires its data. """ - def __init__(self): + def __init__(self, pid, fields_filter): self.group_leaders = [] self.filters = get_filters() - self._fields = self.get_available_fields() - self._pid = 0 + self.update_fields(fields_filter) + self.pid = pid def get_available_fields(self): """Returns a list of available event's of format 'event name(filter @@ -613,6 +620,11 @@ class TracepointProvider(object): fields += extra return fields + def update_fields(self, fields_filter): + """Refresh fields, applying fields_filter""" + self._fields = [field for field in self.get_available_fields() + if is_field_wanted(fields_filter, field)] + def setup_traces(self): """Creates all event and group objects needed to be able to retrieve data.""" @@ -723,13 +735,12 @@ class TracepointProvider(object): class DebugfsProvider(object): """Provides data from the files that KVM creates in the kvm debugfs folder.""" - def __init__(self): - self._fields = self.get_available_fields() + def __init__(self, pid, fields_filter): + self.update_fields(fields_filter) self._baseline = {} - self._pid = 0 self.do_read = True self.paths = [] - self.reset() + self.pid = pid def get_available_fields(self): """"Returns a list of available fields. @@ -739,6 +750,11 @@ class DebugfsProvider(object): """ return walkdir(PATH_DEBUGFS_KVM)[2] + def update_fields(self, fields_filter): + """Refresh fields, applying fields_filter""" + self._fields = [field for field in self.get_available_fields() + if is_field_wanted(fields_filter, field)] + @property def fields(self): return self._fields @@ -754,9 +770,8 @@ class DebugfsProvider(object): @pid.setter def pid(self, pid): + self._pid = pid if pid != 0: - self._pid = pid - vms = walkdir(PATH_DEBUGFS_KVM)[1] if len(vms) == 0: self.do_read = False @@ -818,33 +833,19 @@ class Stats(object): provider data. """ - def __init__(self, providers, pid, fields=None): - self.providers = providers - self._pid_filter = pid - self._fields_filter = fields + def __init__(self, options): + self.providers = get_providers(options) + self._pid_filter = options.pid + self._fields_filter = options.fields self.values = {} - self.update_provider_pid() - self.update_provider_filters() def update_provider_filters(self): """Propagates fields filters to providers.""" - def wanted(key): - if not self._fields_filter: - return True - return re.match(self._fields_filter, key) is not None - # As we reset the counters when updating the fields we can # also clear the cache of old values. self.values = {} for provider in self.providers: - provider_fields = [key for key in provider.get_available_fields() - if wanted(key)] - provider.fields = provider_fields - - def update_provider_pid(self): - """Propagates pid filters to providers.""" - for provider in self.providers: - provider.pid = self._pid_filter + provider.update_fields(self._fields_filter) def reset(self): self.values = {} @@ -870,7 +871,8 @@ class Stats(object): if pid != self._pid_filter: self._pid_filter = pid self.values = {} - self.update_provider_pid() + for provider in self.providers: + provider.pid = self._pid_filter def get(self): """Returns a dict with field -> (value, delta to last value) of all @@ -896,7 +898,6 @@ class Tui(object): def __init__(self, stats): self.stats = stats self.screen = None - self.update_drilldown() def __enter__(self): """Initialises curses for later use. Based on curses.wrapper @@ -1270,7 +1271,7 @@ Press any other key to refresh statistics immediately. ) optparser.add_option('-f', '--fields', action='store', - default=None, + default=DEFAULT_REGEX, dest='fields', help='fields to display (regex)', ) @@ -1297,12 +1298,10 @@ def get_providers(options): """Returns a list of data providers depending on the passed options.""" providers = [] - if options.tracepoints: - providers.append(TracepointProvider()) if options.debugfs: - providers.append(DebugfsProvider()) - if len(providers) == 0: - providers.append(TracepointProvider()) + providers.append(DebugfsProvider(options.pid, options.fields)) + if options.tracepoints or not providers: + providers.append(TracepointProvider(options.pid, options.fields)) return providers @@ -1347,8 +1346,7 @@ def main(): sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n') sys.exit('Specified pid does not exist.') - providers = get_providers(options) - stats = Stats(providers, options.pid, fields=options.fields) + stats = Stats(options) if options.log: log(stats) -- cgit v1.2.3 From 099a2dfc674e3333bd4ff5e5b106ccd788aa46d7 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:33 +0200 Subject: tools/kvm_stat: move functions to corresponding classes Quite a few of the functions are used only in a single class. Moving functions accordingly to improve the overall structure. Furthermore, introduce a base class for the providers, which might also come handy for future extensions. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 327 ++++++++++++++++++++++---------------------- 1 file changed, 165 insertions(+), 162 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index b8522d2ddb0a..f81ed208307f 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -295,121 +295,6 @@ class ArchS390(Arch): ARCH = Arch.get_arch() -def is_field_wanted(fields_filter, field): - """Indicate whether field is valid according to fields_filter.""" - if not fields_filter: - return True - return re.match(fields_filter, field) is not None - - -def walkdir(path): - """Returns os.walk() data for specified directory. - - As it is only a wrapper it returns the same 3-tuple of (dirpath, - dirnames, filenames). - """ - return next(os.walk(path)) - - -def parse_int_list(list_string): - """Returns an int list from a string of comma separated integers and - integer ranges.""" - integers = [] - members = list_string.split(',') - - for member in members: - if '-' not in member: - integers.append(int(member)) - else: - int_range = member.split('-') - integers.extend(range(int(int_range[0]), - int(int_range[1]) + 1)) - - return integers - - -def get_pid_from_gname(gname): - """Fuzzy function to convert guest name to QEMU process pid. - - Returns a list of potential pids, can be empty if no match found. - Throws an exception on processing errors. - - """ - pids = [] - try: - child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'], - stdout=subprocess.PIPE) - except: - raise Exception - for line in child.stdout: - line = line.lstrip().split(' ', 1) - # perform a sanity check before calling the more expensive - # function to possibly extract the guest name - if ' -name ' in line[1] and gname == get_gname_from_pid(line[0]): - pids.append(int(line[0])) - child.stdout.close() - - return pids - - -def get_gname_from_pid(pid): - """Returns the guest name for a QEMU process pid. - - Extracts the guest name from the QEMU comma line by processing the '-name' - option. Will also handle names specified out of sequence. - - """ - name = '' - try: - line = open('/proc/{}/cmdline'.format(pid), 'rb').read().split('\0') - parms = line[line.index('-name') + 1].split(',') - while '' in parms: - # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results in - # ['foo', '', 'bar'], which we revert here - idx = parms.index('') - parms[idx - 1] += ',' + parms[idx + 1] - del parms[idx:idx+2] - # the '-name' switch allows for two ways to specify the guest name, - # where the plain name overrides the name specified via 'guest=' - for arg in parms: - if '=' not in arg: - name = arg - break - if arg[:6] == 'guest=': - name = arg[6:] - except (ValueError, IOError, IndexError): - pass - - return name - - -def get_online_cpus(): - """Returns a list of cpu id integers.""" - with open('/sys/devices/system/cpu/online') as cpu_list: - cpu_string = cpu_list.readline() - return parse_int_list(cpu_string) - - -def get_filters(): - """Returns a dict of trace events, their filter ids and - the values that can be filtered. - - Trace events can be filtered for special values by setting a - filter string via an ioctl. The string normally has the format - identifier==value. For each filter a new event will be created, to - be able to distinguish the events. - - """ - filters = {} - filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS) - if ARCH.exit_reasons: - filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons) - return filters - -libc = ctypes.CDLL('libc.so.6', use_errno=True) -syscall = libc.syscall - - class perf_event_attr(ctypes.Structure): """Struct that holds the necessary data to set up a trace event. @@ -439,25 +324,6 @@ class perf_event_attr(ctypes.Structure): self.read_format = PERF_FORMAT_GROUP -def perf_event_open(attr, pid, cpu, group_fd, flags): - """Wrapper for the sys_perf_evt_open() syscall. - - Used to set up performance events, returns a file descriptor or -1 - on error. - - Attributes are: - - syscall number - - struct perf_event_attr * - - pid or -1 to monitor all pids - - cpu number or -1 to monitor all cpus - - The file descriptor of the group leader or -1 to create a group. - - flags - - """ - return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr), - ctypes.c_int(pid), ctypes.c_int(cpu), - ctypes.c_int(group_fd), ctypes.c_long(flags)) - PERF_TYPE_TRACEPOINT = 2 PERF_FORMAT_GROUP = 1 << 3 @@ -502,6 +368,8 @@ class Event(object): """Represents a performance event and manages its life cycle.""" def __init__(self, name, group, trace_cpu, trace_pid, trace_point, trace_filter, trace_set='kvm'): + self.libc = ctypes.CDLL('libc.so.6', use_errno=True) + self.syscall = self.libc.syscall self.name = name self.fd = None self.setup_event(group, trace_cpu, trace_pid, trace_point, @@ -518,6 +386,25 @@ class Event(object): if self.fd: os.close(self.fd) + def perf_event_open(self, attr, pid, cpu, group_fd, flags): + """Wrapper for the sys_perf_evt_open() syscall. + + Used to set up performance events, returns a file descriptor or -1 + on error. + + Attributes are: + - syscall number + - struct perf_event_attr * + - pid or -1 to monitor all pids + - cpu number or -1 to monitor all cpus + - The file descriptor of the group leader or -1 to create a group. + - flags + + """ + return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr), + ctypes.c_int(pid), ctypes.c_int(cpu), + ctypes.c_int(group_fd), ctypes.c_long(flags)) + def setup_event_attribute(self, trace_set, trace_point): """Returns an initialized ctype perf_event_attr struct.""" @@ -546,8 +433,8 @@ class Event(object): if group.events: group_leader = group.events[0].fd - fd = perf_event_open(event_attr, trace_pid, - trace_cpu, group_leader, 0) + fd = self.perf_event_open(event_attr, trace_pid, + trace_cpu, group_leader, 0) if fd == -1: err = ctypes.get_errno() raise OSError(err, os.strerror(err), @@ -582,7 +469,26 @@ class Event(object): fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0) -class TracepointProvider(object): +class Provider(object): + """Encapsulates functionalities used by all providers.""" + @staticmethod + def is_field_wanted(fields_filter, field): + """Indicate whether field is valid according to fields_filter.""" + if not fields_filter: + return True + return re.match(fields_filter, field) is not None + + @staticmethod + def walkdir(path): + """Returns os.walk() data for specified directory. + + As it is only a wrapper it returns the same 3-tuple of (dirpath, + dirnames, filenames). + """ + return next(os.walk(path)) + + +class TracepointProvider(Provider): """Data provider for the stats class. Manages the events/groups from which it acquires its data. @@ -590,10 +496,27 @@ class TracepointProvider(object): """ def __init__(self, pid, fields_filter): self.group_leaders = [] - self.filters = get_filters() + self.filters = self.get_filters() self.update_fields(fields_filter) self.pid = pid + @staticmethod + def get_filters(): + """Returns a dict of trace events, their filter ids and + the values that can be filtered. + + Trace events can be filtered for special values by setting a + filter string via an ioctl. The string normally has the format + identifier==value. For each filter a new event will be created, to + be able to distinguish the events. + + """ + filters = {} + filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS) + if ARCH.exit_reasons: + filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons) + return filters + def get_available_fields(self): """Returns a list of available event's of format 'event name(filter name)'. @@ -610,7 +533,7 @@ class TracepointProvider(object): """ path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm') - fields = walkdir(path)[1] + fields = self.walkdir(path)[1] extra = [] for field in fields: if field in self.filters: @@ -623,7 +546,30 @@ class TracepointProvider(object): def update_fields(self, fields_filter): """Refresh fields, applying fields_filter""" self._fields = [field for field in self.get_available_fields() - if is_field_wanted(fields_filter, field)] + if self.is_field_wanted(fields_filter, field)] + + @staticmethod + def get_online_cpus(): + """Returns a list of cpu id integers.""" + def parse_int_list(list_string): + """Returns an int list from a string of comma separated integers and + integer ranges.""" + integers = [] + members = list_string.split(',') + + for member in members: + if '-' not in member: + integers.append(int(member)) + else: + int_range = member.split('-') + integers.extend(range(int(int_range[0]), + int(int_range[1]) + 1)) + + return integers + + with open('/sys/devices/system/cpu/online') as cpu_list: + cpu_string = cpu_list.readline() + return parse_int_list(cpu_string) def setup_traces(self): """Creates all event and group objects needed to be able to retrieve @@ -633,9 +579,9 @@ class TracepointProvider(object): # Fetch list of all threads of the monitored pid, as qemu # starts a thread for each vcpu. path = os.path.join('/proc', str(self._pid), 'task') - groupids = walkdir(path)[1] + groupids = self.walkdir(path)[1] else: - groupids = get_online_cpus() + groupids = self.get_online_cpus() # The constant is needed as a buffer for python libs, std # streams and other files that the script opens. @@ -732,7 +678,7 @@ class TracepointProvider(object): event.reset() -class DebugfsProvider(object): +class DebugfsProvider(Provider): """Provides data from the files that KVM creates in the kvm debugfs folder.""" def __init__(self, pid, fields_filter): @@ -748,12 +694,12 @@ class DebugfsProvider(object): The fields are all available KVM debugfs files """ - return walkdir(PATH_DEBUGFS_KVM)[2] + return self.walkdir(PATH_DEBUGFS_KVM)[2] def update_fields(self, fields_filter): """Refresh fields, applying fields_filter""" self._fields = [field for field in self.get_available_fields() - if is_field_wanted(fields_filter, field)] + if self.is_field_wanted(fields_filter, field)] @property def fields(self): @@ -772,7 +718,7 @@ class DebugfsProvider(object): def pid(self, pid): self._pid = pid if pid != 0: - vms = walkdir(PATH_DEBUGFS_KVM)[1] + vms = self.walkdir(PATH_DEBUGFS_KVM)[1] if len(vms) == 0: self.do_read = False @@ -834,11 +780,23 @@ class Stats(object): """ def __init__(self, options): - self.providers = get_providers(options) + self.providers = self.get_providers(options) self._pid_filter = options.pid self._fields_filter = options.fields self.values = {} + @staticmethod + def get_providers(options): + """Returns a list of data providers depending on the passed options.""" + providers = [] + + if options.debugfs: + providers.append(DebugfsProvider(options.pid, options.fields)) + if options.tracepoints or not providers: + providers.append(TracepointProvider(options.pid, options.fields)) + + return providers + def update_provider_filters(self): """Propagates fields filters to providers.""" # As we reset the counters when updating the fields we can @@ -933,6 +891,63 @@ class Tui(object): curses.nocbreak() curses.endwin() + @staticmethod + def get_pid_from_gname(gname): + """Fuzzy function to convert guest name to QEMU process pid. + + Returns a list of potential pids, can be empty if no match found. + Throws an exception on processing errors. + + """ + pids = [] + try: + child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'], + stdout=subprocess.PIPE) + except: + raise Exception + for line in child.stdout: + line = line.lstrip().split(' ', 1) + # perform a sanity check before calling the more expensive + # function to possibly extract the guest name + if (' -name ' in line[1] and + gname == self.get_gname_from_pid(line[0])): + pids.append(int(line[0])) + child.stdout.close() + + return pids + + @staticmethod + def get_gname_from_pid(pid): + """Returns the guest name for a QEMU process pid. + + Extracts the guest name from the QEMU comma line by processing the + '-name' option. Will also handle names specified out of sequence. + + """ + name = '' + try: + line = open('/proc/{}/cmdline' + .format(pid), 'rb').read().split('\0') + parms = line[line.index('-name') + 1].split(',') + while '' in parms: + # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results + # in # ['foo', '', 'bar'], which we revert here + idx = parms.index('') + parms[idx - 1] += ',' + parms[idx + 1] + del parms[idx:idx+2] + # the '-name' switch allows for two ways to specify the guest name, + # where the plain name overrides the name specified via 'guest=' + for arg in parms: + if '=' not in arg: + name = arg + break + if arg[:6] == 'guest=': + name = arg[6:] + except (ValueError, IOError, IndexError): + pass + + return name + def update_drilldown(self): """Sets or removes a filter that only allows fields without braces.""" if not self.stats.fields_filter: @@ -950,7 +965,7 @@ class Tui(object): if pid is None: pid = self.stats.pid_filter self.screen.erase() - gname = get_gname_from_pid(pid) + gname = self.get_gname_from_pid(pid) if gname: gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...' if len(gname) > MAX_GUEST_NAME_LEN @@ -1096,7 +1111,7 @@ class Tui(object): else: pids = [] try: - pids = get_pid_from_gname(gname) + pids = self.get_pid_from_gname(gname) except: msg = '"' + gname + '": Internal error while searching, ' \ 'use pid filter instead' @@ -1229,7 +1244,7 @@ Press any other key to refresh statistics immediately. def cb_guest_to_pid(option, opt, val, parser): try: - pids = get_pid_from_gname(val) + pids = Tui.get_pid_from_gname(val) except: raise optparse.OptionValueError('Error while searching for guest ' '"{}", use "-p" to specify a pid ' @@ -1294,18 +1309,6 @@ Press any other key to refresh statistics immediately. return options -def get_providers(options): - """Returns a list of data providers depending on the passed options.""" - providers = [] - - if options.debugfs: - providers.append(DebugfsProvider(options.pid, options.fields)) - if options.tracepoints or not providers: - providers.append(TracepointProvider(options.pid, options.fields)) - - return providers - - def check_access(options): """Exits if the current user can't access all needed directories.""" if not os.path.exists('/sys/kernel/debug'): -- cgit v1.2.3 From 62d1b6cc24d0dedde89e6a39aae1711270aee1c9 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:34 +0200 Subject: tools/kvm_stat: show cursor in selection screens Show the cursor in the interactive screens to specify pid, filter or guest name as an orientation for the user. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index f81ed208307f..53dcd406e9d6 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1148,13 +1148,19 @@ class Tui(object): self.refresh_header(0) self.update_pid(0) if char == 'f': + curses.curs_set(1) self.show_filter_selection() + curses.curs_set(0) sleeptime = DELAY_INITIAL if char == 'g': + curses.curs_set(1) self.show_vm_selection_by_guest_name() + curses.curs_set(0) sleeptime = DELAY_INITIAL if char == 'p': + curses.curs_set(1) self.show_vm_selection_by_pid() + curses.curs_set(0) sleeptime = DELAY_INITIAL if char == 'r': self.stats.reset() -- cgit v1.2.3 From 5725393764a342b6a5420fdd10184984ca08b5f6 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:35 +0200 Subject: tools/kvm_stat: display message indicating lack of events Give users some indication on the reason why no data is displayed on the screen yet. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 2 ++ 1 file changed, 2 insertions(+) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 53dcd406e9d6..790fbce95bd6 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1013,6 +1013,8 @@ class Tui(object): (key, values[0], values[0] * 100 / total, cur)) row += 1 + if row == 3: + self.screen.addstr(4, 1, 'No matching events reported yet') self.screen.refresh() def show_filter_selection(self): -- cgit v1.2.3 From f6d753102a2469ae4ce08ef3e34d170ec583fb50 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:36 +0200 Subject: tools/kvm_stat: make heading look a bit more like 'top' Print header in standout font just like the 'top' command does. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 790fbce95bd6..35147e4877ae 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -982,7 +982,8 @@ class Tui(object): regex = regex[:MAX_REGEX_LEN] + '...' self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex)) self.screen.addstr(2, 1, '%-40s %10s%7s %7s' % - ('Event', 'Total', '%Total', 'Current')) + ('Event', 'Total', '%Total', 'Current'), + curses.A_STANDOUT) self.screen.addstr(4, 1, 'Collecting data...') self.screen.refresh() -- cgit v1.2.3 From 38e89c37a1e05e6e16af582b980534abda29a4d9 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:37 +0200 Subject: tools/kvm_stat: rename 'Current' column to 'CurAvg/s' 'Current' can be misleading as it doesn't tell whether this is the amount of events in the last interval or the current average per second. Note that this necessitates widening the respective column by one more character. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 35147e4877ae..a9e7ea612e7f 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -981,8 +981,8 @@ class Tui(object): if len(regex) > MAX_REGEX_LEN: regex = regex[:MAX_REGEX_LEN] + '...' self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex)) - self.screen.addstr(2, 1, '%-40s %10s%7s %7s' % - ('Event', 'Total', '%Total', 'Current'), + self.screen.addstr(2, 1, '%-40s %10s%7s %8s' % + ('Event', 'Total', '%Total', 'CurAvg/s'), curses.A_STANDOUT) self.screen.addstr(4, 1, 'Collecting data...') self.screen.refresh() @@ -1010,7 +1010,7 @@ class Tui(object): break if values[0] is not None: cur = int(round(values[1] / sleeptime)) if values[1] else '' - self.screen.addstr(row, 1, '%-40s %10d%7.1f %7s' % + self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key, values[0], values[0] * 100 / total, cur)) row += 1 -- cgit v1.2.3 From 1fdea7b2893045e5258a13937c3d78c425fd7aa0 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:38 +0200 Subject: tools/kvm_stat: add new interactive command 'h' Display interactive commands reference on 'h'. While at it, sort interactive commands alphabetically in various places. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 37 ++++++++++++++++++++++++++++++++----- tools/kvm/kvm_stat/kvm_stat.txt | 2 ++ 2 files changed, 34 insertions(+), 5 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index a9e7ea612e7f..6838de38ecb5 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1018,6 +1018,30 @@ class Tui(object): self.screen.addstr(4, 1, 'No matching events reported yet') self.screen.refresh() + def show_help_interactive(self): + """Display help with list of interactive commands""" + msg = (' c clear filter', + ' f filter by regular expression', + ' g filter by guest name', + ' h display interactive commands reference', + ' p filter by PID', + ' q quit', + ' r reset stats', + ' x toggle reporting of stats for individual child trace' + ' events', + 'Any other key refreshes statistics immediately') + curses.cbreak() + self.screen.erase() + self.screen.addstr(0, 0, "Interactive commands reference", + curses.A_BOLD) + self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT) + row = 4 + for line in msg: + self.screen.addstr(row, 0, line) + row += 1 + self.screen.getkey() + self.refresh_header() + def show_filter_selection(self): """Draws filter selection mask. @@ -1142,10 +1166,6 @@ class Tui(object): sleeptime = DELAY_REGULAR try: char = self.screen.getkey() - if char == 'x': - self.update_drilldown() - if char == 'q': - break if char == 'c': self.stats.fields_filter = DEFAULT_REGEX self.refresh_header(0) @@ -1160,13 +1180,19 @@ class Tui(object): self.show_vm_selection_by_guest_name() curses.curs_set(0) sleeptime = DELAY_INITIAL + if char == 'h': + self.show_help_interactive() if char == 'p': curses.curs_set(1) self.show_vm_selection_by_pid() curses.curs_set(0) sleeptime = DELAY_INITIAL + if char == 'q': + break if char == 'r': self.stats.reset() + if char == 'x': + self.update_drilldown() except KeyboardInterrupt: break except curses.error: @@ -1237,10 +1263,11 @@ Interactive Commands: c clear filter f filter by regular expression g filter by guest name + h display interactive commands reference p filter by PID q quit - x toggle reporting of stats for individual child trace events r reset stats + x toggle reporting of stats for individual child trace events Press any other key to refresh statistics immediately. """ diff --git a/tools/kvm/kvm_stat/kvm_stat.txt b/tools/kvm/kvm_stat/kvm_stat.txt index 109431bdc63c..2bad6f22183b 100644 --- a/tools/kvm/kvm_stat/kvm_stat.txt +++ b/tools/kvm/kvm_stat/kvm_stat.txt @@ -35,6 +35,8 @@ INTERACTIVE COMMANDS *g*:: filter by guest name +*h*:: display interactive commands reference + *p*:: filter by PID *q*:: quit -- cgit v1.2.3 From 64eefad2cdbf2d7c76e24d0b67e19efdbe1c97a9 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:39 +0200 Subject: tools/kvm_stat: add new interactive command 's' Add new command 's' to modify the update interval. Limited to a maximum of 25.5 sec and a minimum of 0.1 sec, since curses cannot handle longer and shorter delays respectively. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 55 +++++++++++++++++++++++++++++++++++------ tools/kvm/kvm_stat/kvm_stat.txt | 2 ++ 2 files changed, 49 insertions(+), 8 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 6838de38ecb5..1276b88937c0 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -844,8 +844,7 @@ class Stats(object): self.values[key] = (newval, newdelta) return self.values -DELAY_INITIAL = 0.25 -DELAY_REGULAR = 3.0 +DELAY_DEFAULT = 3.0 MAX_GUEST_NAME_LEN = 48 MAX_REGEX_LEN = 44 DEFAULT_REGEX = r'^[^\(]*$' @@ -856,6 +855,8 @@ class Tui(object): def __init__(self, stats): self.stats = stats self.screen = None + self._delay_initial = 0.25 + self._delay_regular = DELAY_DEFAULT def __enter__(self): """Initialises curses for later use. Based on curses.wrapper @@ -1027,6 +1028,7 @@ class Tui(object): ' p filter by PID', ' q quit', ' r reset stats', + ' s set update interval', ' x toggle reporting of stats for individual child trace' ' events', 'Any other key refreshes statistics immediately') @@ -1106,10 +1108,41 @@ class Tui(object): self.refresh_header(pid) self.update_pid(pid) break - except ValueError: msg = '"' + str(pid) + '": Not a valid pid' + def show_set_update_interval(self): + """Draws update interval selection mask.""" + msg = '' + while True: + self.screen.erase() + self.screen.addstr(0, 0, 'Set update interval (defaults to %fs).' % + DELAY_DEFAULT, curses.A_BOLD) + self.screen.addstr(4, 0, msg) + self.screen.addstr(2, 0, 'Change delay from %.1fs to ' % + self._delay_regular) + curses.echo() + val = self.screen.getstr() + curses.noecho() + + try: + if len(val) > 0: + delay = float(val) + if delay < 0.1: + msg = '"' + str(val) + '": Value must be >=0.1' + continue + if delay > 25.5: + msg = '"' + str(val) + '": Value must be <=25.5' + continue + else: + delay = DELAY_DEFAULT + self._delay_regular = delay + break + + except ValueError: + msg = '"' + str(val) + '": Invalid value' + self.refresh_header() + def show_vm_selection_by_guest_name(self): """Draws guest selection mask. @@ -1156,14 +1189,14 @@ class Tui(object): def show_stats(self): """Refreshes the screen and processes user input.""" - sleeptime = DELAY_INITIAL + sleeptime = self._delay_initial self.refresh_header() start = 0.0 # result based on init value never appears on screen while True: self.refresh_body(time.time() - start) curses.halfdelay(int(sleeptime * 10)) start = time.time() - sleeptime = DELAY_REGULAR + sleeptime = self._delay_regular try: char = self.screen.getkey() if char == 'c': @@ -1174,23 +1207,28 @@ class Tui(object): curses.curs_set(1) self.show_filter_selection() curses.curs_set(0) - sleeptime = DELAY_INITIAL + sleeptime = self._delay_initial if char == 'g': curses.curs_set(1) self.show_vm_selection_by_guest_name() curses.curs_set(0) - sleeptime = DELAY_INITIAL + sleeptime = self._delay_initial if char == 'h': self.show_help_interactive() if char == 'p': curses.curs_set(1) self.show_vm_selection_by_pid() curses.curs_set(0) - sleeptime = DELAY_INITIAL + sleeptime = self._delay_initial if char == 'q': break if char == 'r': self.stats.reset() + if char == 's': + curses.curs_set(1) + self.show_set_update_interval() + curses.curs_set(0) + sleeptime = self._delay_initial if char == 'x': self.update_drilldown() except KeyboardInterrupt: @@ -1267,6 +1305,7 @@ Interactive Commands: p filter by PID q quit r reset stats + s set update interval x toggle reporting of stats for individual child trace events Press any other key to refresh statistics immediately. """ diff --git a/tools/kvm/kvm_stat/kvm_stat.txt b/tools/kvm/kvm_stat/kvm_stat.txt index 2bad6f22183b..cc019b09e0f5 100644 --- a/tools/kvm/kvm_stat/kvm_stat.txt +++ b/tools/kvm/kvm_stat/kvm_stat.txt @@ -43,6 +43,8 @@ INTERACTIVE COMMANDS *r*:: reset stats +*s*:: set update interval + *x*:: toggle reporting of stats for child trace events Press any other key to refresh statistics immediately. -- cgit v1.2.3 From 6667ae8f395099257afca0963838d2dc50a18da7 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:41 +0200 Subject: tools/kvm_stat: add new interactive command 'o' Add new interactive command 'o' to toggle sorting by 'CurAvg/s' (default) and 'Total' columns. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 17 ++++++++++++++++- tools/kvm/kvm_stat/kvm_stat.txt | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 1276b88937c0..cf7aa28ddf0c 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -848,6 +848,7 @@ DELAY_DEFAULT = 3.0 MAX_GUEST_NAME_LEN = 48 MAX_REGEX_LEN = 44 DEFAULT_REGEX = r'^[^\(]*$' +SORT_DEFAULT = 0 class Tui(object): @@ -857,6 +858,7 @@ class Tui(object): self.screen = None self._delay_initial = 0.25 self._delay_regular = DELAY_DEFAULT + self._sorting = SORT_DEFAULT def __enter__(self): """Initialises curses for later use. Based on curses.wrapper @@ -994,14 +996,23 @@ class Tui(object): self.screen.clrtobot() stats = self.stats.get() - def sortkey(x): + def sortCurAvg(x): + # sort by current events if available if stats[x][1]: return (-stats[x][1], -stats[x][0]) else: return (0, -stats[x][0]) + + def sortTotal(x): + # sort by totals + return (0, -stats[x][0]) total = 0. for val in stats.values(): total += val[0] + if self._sorting == SORT_DEFAULT: + sortkey = sortCurAvg + else: + sortkey = sortTotal for key in sorted(stats.keys(), key=sortkey): if row >= self.screen.getmaxyx()[0]: @@ -1025,6 +1036,7 @@ class Tui(object): ' f filter by regular expression', ' g filter by guest name', ' h display interactive commands reference', + ' o toggle sorting order (Total vs CurAvg/s)', ' p filter by PID', ' q quit', ' r reset stats', @@ -1215,6 +1227,8 @@ class Tui(object): sleeptime = self._delay_initial if char == 'h': self.show_help_interactive() + if char == 'o': + self._sorting = not self._sorting if char == 'p': curses.curs_set(1) self.show_vm_selection_by_pid() @@ -1302,6 +1316,7 @@ Interactive Commands: f filter by regular expression g filter by guest name h display interactive commands reference + o toggle sorting order (Total vs CurAvg/s) p filter by PID q quit r reset stats diff --git a/tools/kvm/kvm_stat/kvm_stat.txt b/tools/kvm/kvm_stat/kvm_stat.txt index cc019b09e0f5..e24ac464d341 100644 --- a/tools/kvm/kvm_stat/kvm_stat.txt +++ b/tools/kvm/kvm_stat/kvm_stat.txt @@ -37,6 +37,8 @@ INTERACTIVE COMMANDS *h*:: display interactive commands reference +*o*:: toggle sorting order (Total vs CurAvg/s) + *p*:: filter by PID *q*:: quit -- cgit v1.2.3 From 865279c53ca9d88718d974bb014b2c6ce259ac75 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Wed, 7 Jun 2017 21:08:43 +0200 Subject: tools/kvm_stat: display guest list in pid/guest selection screens Display a (possibly inaccurate) list of all running guests. Note that we leave a bit of extra room above the list for potential error messages. Furthermore, we deliberately do not reject pids or guest names that are not in our list, as we cannot rule out that our fuzzy approach might be in error somehow. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 49 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 12 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index cf7aa28ddf0c..2cf5176bbeee 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -894,15 +894,9 @@ class Tui(object): curses.nocbreak() curses.endwin() - @staticmethod - def get_pid_from_gname(gname): - """Fuzzy function to convert guest name to QEMU process pid. - - Returns a list of potential pids, can be empty if no match found. - Throws an exception on processing errors. - - """ - pids = [] + def get_all_gnames(self): + """Returns a list of (pid, gname) tuples of all running guests""" + res = [] try: child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'], stdout=subprocess.PIPE) @@ -912,11 +906,40 @@ class Tui(object): line = line.lstrip().split(' ', 1) # perform a sanity check before calling the more expensive # function to possibly extract the guest name - if (' -name ' in line[1] and - gname == self.get_gname_from_pid(line[0])): - pids.append(int(line[0])) + if ' -name ' in line[1]: + res.append((line[0], self.get_gname_from_pid(line[0]))) child.stdout.close() + return res + + def print_all_gnames(self, row): + """Print a list of all running guests along with their pids.""" + self.screen.addstr(row, 2, '%8s %-60s' % + ('Pid', 'Guest Name (fuzzy list, might be ' + 'inaccurate!)'), + curses.A_UNDERLINE) + row += 1 + try: + for line in self.get_all_gnames(): + self.screen.addstr(row, 2, '%8s %-60s' % (line[0], line[1])) + row += 1 + if row >= self.screen.getmaxyx()[0]: + break + except Exception: + self.screen.addstr(row + 1, 2, 'Not available') + + def get_pid_from_gname(self, gname): + """Fuzzy function to convert guest name to QEMU process pid. + + Returns a list of potential pids, can be empty if no match found. + Throws an exception on processing errors. + + """ + pids = [] + for line in self.get_all_gnames(): + if gname == line[1]: + pids.append(int(line[0])) + return pids @staticmethod @@ -1102,6 +1125,7 @@ class Tui(object): 'This might limit the shown data to the trace ' 'statistics.') self.screen.addstr(5, 0, msg) + self.print_all_gnames(7) curses.echo() self.screen.addstr(3, 0, "Pid [0 or pid]: ") @@ -1171,6 +1195,7 @@ class Tui(object): 'This might limit the shown data to the trace ' 'statistics.') self.screen.addstr(5, 0, msg) + self.print_all_gnames() curses.echo() self.screen.addstr(3, 0, "Guest [ENTER or guest]: ") gname = self.screen.getstr() -- cgit v1.2.3 From 61f381bb7e1a8e9250aa32b3963a7a5c4b92cbf5 Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Sun, 25 Jun 2017 21:34:14 +0200 Subject: tools/kvm_stat: fix error on interactive command 'g' Fix an instance where print_all_gnames() is called without the mandatory argument, resulting in a stack trace. To reproduce, simply press 'g' in interactive mode. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 2cf5176bbeee..39476e55f557 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -1195,7 +1195,7 @@ class Tui(object): 'This might limit the shown data to the trace ' 'statistics.') self.screen.addstr(5, 0, msg) - self.print_all_gnames() + self.print_all_gnames(7) curses.echo() self.screen.addstr(3, 0, "Guest [ENTER or guest]: ") gname = self.screen.getstr() -- cgit v1.2.3 From ab7ef193fab628fc5da6fd4f4672ffd0d1bb53df Mon Sep 17 00:00:00 2001 From: Stefan Raspl Date: Sun, 25 Jun 2017 21:34:15 +0200 Subject: tools/kvm_stat: add new command line switch '-i' It might be handy to display the full history of event stats to compare the current event distribution against any available historic data. Since we have that available for debugfs, we offer a respective command line option to display what's available. Signed-off-by: Stefan Raspl Signed-off-by: Paolo Bonzini --- tools/kvm/kvm_stat/kvm_stat | 34 ++++++++++++++++++++++++++++++---- tools/kvm/kvm_stat/kvm_stat.txt | 4 ++++ 2 files changed, 34 insertions(+), 4 deletions(-) (limited to 'tools') diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 39476e55f557..4065b2909085 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -681,12 +681,14 @@ class TracepointProvider(Provider): class DebugfsProvider(Provider): """Provides data from the files that KVM creates in the kvm debugfs folder.""" - def __init__(self, pid, fields_filter): + def __init__(self, pid, fields_filter, include_past): self.update_fields(fields_filter) self._baseline = {} self.do_read = True self.paths = [] self.pid = pid + if include_past: + self.restore() def get_available_fields(self): """"Returns a list of available fields. @@ -730,7 +732,14 @@ class DebugfsProvider(Provider): self.reset() def read(self, reset=0): - """Returns a dict with format:'file name / field -> current value'.""" + """Returns a dict with format:'file name / field -> current value'. + + Parameter 'reset': + 0 plain read + 1 reset field counts to 0 + 2 restore the original field counts + + """ results = {} # If no debugfs filtering support is available, then don't read. @@ -747,8 +756,10 @@ class DebugfsProvider(Provider): for field in self._fields: value = self.read_field(field, path) key = path + field - if reset: + if reset == 1: self._baseline[key] = value + if reset == 2: + self._baseline[key] = 0 if self._baseline.get(key, -1) == -1: self._baseline[key] = value results[field] = (results.get(field, 0) + value - @@ -771,6 +782,11 @@ class DebugfsProvider(Provider): self._baseline = {} self.read(1) + def restore(self): + """Reset field counters""" + self._baseline = {} + self.read(2) + class Stats(object): """Manages the data providers and the data they provide. @@ -791,7 +807,8 @@ class Stats(object): providers = [] if options.debugfs: - providers.append(DebugfsProvider(options.pid, options.fields)) + providers.append(DebugfsProvider(options.pid, options.fields, + options.dbgfs_include_past)) if options.tracepoints or not providers: providers.append(TracepointProvider(options.pid, options.fields)) @@ -1270,6 +1287,8 @@ class Tui(object): sleeptime = self._delay_initial if char == 'x': self.update_drilldown() + # prevents display of current values on next refresh + self.stats.get() except KeyboardInterrupt: break except curses.error: @@ -1381,6 +1400,13 @@ Press any other key to refresh statistics immediately. dest='once', hel