mirror of
https://github.com/torvalds/linux
synced 2024-09-06 18:01:05 +00:00
tools/kvm_stat: separate drilldown and fields filtering
Drilldown (i.e. toggle display of child trace events) was implemented by overriding the fields filter. This resulted in inconsistencies: E.g. when drilldown was not active, adding a filter that also matches child trace events would not only filter fields according to the filter, but also add in the child trace events matching the filter. E.g. on x86, setting 'kvm_userspace_exit' as the fields filter after startup would result in display of kvm_userspace_exit(DCR), although that wasn't previously present - not exactly what one would expect from a filter. This patch addresses the issue by keeping drilldown and fields filter separate. While at it, we also fix a PEP8 issue by adding a blank line at one place (since we're in the area...). We implement this by adding a framework that also allows to define a taxonomy among the debugfs events to identify child trace events. I.e. drilldown using 'x' can now also work with debugfs. A respective parent- child relationship is only known for S390 at the moment, but could be added adjusting other platforms' ARCH.dbg_is_child() methods accordingly. Signed-off-by: Stefan Raspl <raspl@linux.vnet.ibm.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
516f1190a1
commit
18e8f4100e
|
@ -228,6 +228,7 @@ IOCTL_NUMBERS = {
|
|||
}
|
||||
|
||||
ENCODING = locale.getpreferredencoding(False)
|
||||
TRACE_FILTER = re.compile(r'^[^\(]*$')
|
||||
|
||||
|
||||
class Arch(object):
|
||||
|
@ -260,6 +261,11 @@ class Arch(object):
|
|||
return ArchX86(SVM_EXIT_REASONS)
|
||||
return
|
||||
|
||||
def tracepoint_is_child(self, field):
|
||||
if (TRACE_FILTER.match(field)):
|
||||
return None
|
||||
return field.split('(', 1)[0]
|
||||
|
||||
|
||||
class ArchX86(Arch):
|
||||
def __init__(self, exit_reasons):
|
||||
|
@ -267,6 +273,10 @@ class ArchX86(Arch):
|
|||
self.ioctl_numbers = IOCTL_NUMBERS
|
||||
self.exit_reasons = exit_reasons
|
||||
|
||||
def debugfs_is_child(self, field):
|
||||
""" Returns name of parent if 'field' is a child, None otherwise """
|
||||
return None
|
||||
|
||||
|
||||
class ArchPPC(Arch):
|
||||
def __init__(self):
|
||||
|
@ -282,6 +292,10 @@ class ArchPPC(Arch):
|
|||
self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
|
||||
self.exit_reasons = {}
|
||||
|
||||
def debugfs_is_child(self, field):
|
||||
""" Returns name of parent if 'field' is a child, None otherwise """
|
||||
return None
|
||||
|
||||
|
||||
class ArchA64(Arch):
|
||||
def __init__(self):
|
||||
|
@ -289,6 +303,10 @@ class ArchA64(Arch):
|
|||
self.ioctl_numbers = IOCTL_NUMBERS
|
||||
self.exit_reasons = AARCH64_EXIT_REASONS
|
||||
|
||||
def debugfs_is_child(self, field):
|
||||
""" Returns name of parent if 'field' is a child, None otherwise """
|
||||
return None
|
||||
|
||||
|
||||
class ArchS390(Arch):
|
||||
def __init__(self):
|
||||
|
@ -296,6 +314,12 @@ class ArchS390(Arch):
|
|||
self.ioctl_numbers = IOCTL_NUMBERS
|
||||
self.exit_reasons = None
|
||||
|
||||
def debugfs_is_child(self, field):
|
||||
""" Returns name of parent if 'field' is a child, None otherwise """
|
||||
if field.startswith('instruction_'):
|
||||
return 'exit_instruction'
|
||||
|
||||
|
||||
ARCH = Arch.get_arch()
|
||||
|
||||
|
||||
|
@ -472,6 +496,10 @@ class Event(object):
|
|||
|
||||
class Provider(object):
|
||||
"""Encapsulates functionalities used by all providers."""
|
||||
def __init__(self, pid):
|
||||
self.child_events = False
|
||||
self.pid = pid
|
||||
|
||||
@staticmethod
|
||||
def is_field_wanted(fields_filter, field):
|
||||
"""Indicate whether field is valid according to fields_filter."""
|
||||
|
@ -499,7 +527,7 @@ class TracepointProvider(Provider):
|
|||
self.group_leaders = []
|
||||
self.filters = self._get_filters()
|
||||
self.update_fields(fields_filter)
|
||||
self.pid = pid
|
||||
super(TracepointProvider, self).__init__(pid)
|
||||
|
||||
@staticmethod
|
||||
def _get_filters():
|
||||
|
@ -519,7 +547,7 @@ class TracepointProvider(Provider):
|
|||
return filters
|
||||
|
||||
def _get_available_fields(self):
|
||||
"""Returns a list of available event's of format 'event name(filter
|
||||
"""Returns a list of available events of format 'event name(filter
|
||||
name)'.
|
||||
|
||||
All available events have directories under
|
||||
|
@ -547,7 +575,8 @@ class TracepointProvider(Provider):
|
|||
def update_fields(self, fields_filter):
|
||||
"""Refresh fields, applying fields_filter"""
|
||||
self.fields = [field for field in self._get_available_fields()
|
||||
if self.is_field_wanted(fields_filter, field)]
|
||||
if self.is_field_wanted(fields_filter, field) or
|
||||
ARCH.tracepoint_is_child(field)]
|
||||
|
||||
@staticmethod
|
||||
def _get_online_cpus():
|
||||
|
@ -668,8 +697,12 @@ class TracepointProvider(Provider):
|
|||
ret = defaultdict(int)
|
||||
for group in self.group_leaders:
|
||||
for name, val in group.read().items():
|
||||
if name in self._fields:
|
||||
ret[name] += val
|
||||
if name not in self._fields:
|
||||
continue
|
||||
parent = ARCH.tracepoint_is_child(name)
|
||||
if parent:
|
||||
name += ' ' + parent
|
||||
ret[name] += val
|
||||
return ret
|
||||
|
||||
def reset(self):
|
||||
|
@ -687,7 +720,7 @@ class DebugfsProvider(Provider):
|
|||
self._baseline = {}
|
||||
self.do_read = True
|
||||
self.paths = []
|
||||
self.pid = pid
|
||||
super(DebugfsProvider, self).__init__(pid)
|
||||
if include_past:
|
||||
self._restore()
|
||||
|
||||
|
@ -702,7 +735,8 @@ class DebugfsProvider(Provider):
|
|||
def update_fields(self, fields_filter):
|
||||
"""Refresh fields, applying fields_filter"""
|
||||
self._fields = [field for field in self._get_available_fields()
|
||||
if self.is_field_wanted(fields_filter, field)]
|
||||
if self.is_field_wanted(fields_filter, field) or
|
||||
ARCH.debugfs_is_child(field)]
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
|
@ -763,14 +797,15 @@ class DebugfsProvider(Provider):
|
|||
self._baseline[key] = 0
|
||||
if self._baseline.get(key, -1) == -1:
|
||||
self._baseline[key] = value
|
||||
increment = (results.get(field, 0) + value -
|
||||
self._baseline.get(key, 0))
|
||||
if by_guest:
|
||||
pid = key.split('-')[0]
|
||||
if pid in results:
|
||||
results[pid] += increment
|
||||
else:
|
||||
results[pid] = increment
|
||||
parent = ARCH.debugfs_is_child(field)
|
||||
if parent:
|
||||
field = field + ' ' + parent
|
||||
else:
|
||||
if by_guest:
|
||||
field = key.split('-')[0] # set 'field' to 'pid'
|
||||
increment = value - self._baseline.get(key, 0)
|
||||
if field in results:
|
||||
results[field] += increment
|
||||
else:
|
||||
results[field] = increment
|
||||
|
||||
|
@ -812,6 +847,7 @@ class Stats(object):
|
|||
self._pid_filter = options.pid
|
||||
self._fields_filter = options.fields
|
||||
self.values = {}
|
||||
self._child_events = False
|
||||
|
||||
def _get_providers(self, options):
|
||||
"""Returns a list of data providers depending on the passed options."""
|
||||
|
@ -860,12 +896,29 @@ class Stats(object):
|
|||
for provider in self.providers:
|
||||
provider.pid = self._pid_filter
|
||||
|
||||
@property
|
||||
def child_events(self):
|
||||
return self._child_events
|
||||
|
||||
@child_events.setter
|
||||
def child_events(self, val):
|
||||
self._child_events = val
|
||||
for provider in self.providers:
|
||||
provider.child_events = val
|
||||
|
||||
def get(self, by_guest=0):
|
||||
"""Returns a dict with field -> (value, delta to last value) of all
|
||||
provider data."""
|
||||
provider data.
|
||||
Key formats:
|
||||
* plain: 'key' is event name
|
||||
* child-parent: 'key' is in format '<child> <parent>'
|
||||
* pid: 'key' is the pid of the guest, and the record contains the
|
||||
aggregated event data
|
||||
These formats are generated by the providers, and handled in class TUI.
|
||||
"""
|
||||
for provider in self.providers:
|
||||
new = provider.read(by_guest=by_guest)
|
||||
for key in new if by_guest else provider.fields:
|
||||
for key in new:
|
||||
oldval = self.values.get(key, EventStat(0, 0)).value
|
||||
newval = new.get(key, 0)
|
||||
newdelta = newval - oldval
|
||||
|
@ -898,10 +951,10 @@ class Stats(object):
|
|||
self.get(to_pid)
|
||||
return 0
|
||||
|
||||
|
||||
DELAY_DEFAULT = 3.0
|
||||
MAX_GUEST_NAME_LEN = 48
|
||||
MAX_REGEX_LEN = 44
|
||||
DEFAULT_REGEX = r'^[^\(]*$'
|
||||
SORT_DEFAULT = 0
|
||||
|
||||
|
||||
|
@ -1031,14 +1084,6 @@ class Tui(object):
|
|||
|
||||
return name
|
||||
|
||||
def _update_drilldown(self):
|
||||
"""Sets or removes a filter that only allows fields without braces."""
|
||||
if not self.stats.fields_filter:
|
||||
self.stats.fields_filter = DEFAULT_REGEX
|
||||
|
||||
elif self.stats.fields_filter == DEFAULT_REGEX:
|
||||
self.stats.fields_filter = None
|
||||
|
||||
def _update_pid(self, pid):
|
||||
"""Propagates pid selection to stats object."""
|
||||
self.screen.addstr(4, 1, 'Updating pid filter...')
|
||||
|
@ -1060,8 +1105,7 @@ class Tui(object):
|
|||
.format(pid, gname), curses.A_BOLD)
|
||||
else:
|
||||
self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
|
||||
if self.stats.fields_filter and self.stats.fields_filter \
|
||||
!= DEFAULT_REGEX:
|
||||
if self.stats.fields_filter:
|
||||
regex = self.stats.fields_filter
|
||||
if len(regex) > MAX_REGEX_LEN:
|
||||
regex = regex[:MAX_REGEX_LEN] + '...'
|
||||
|
@ -1077,6 +1121,9 @@ class Tui(object):
|
|||
self.screen.refresh()
|
||||
|
||||
def _refresh_body(self, sleeptime):
|
||||
def is_child_field(field):
|
||||
return field.find('(') != -1
|
||||
|
||||
row = 3
|
||||
self.screen.move(row, 0)
|
||||
self.screen.clrtobot()
|
||||
|
@ -1084,7 +1131,11 @@ class Tui(object):
|
|||
total = 0.
|
||||
ctotal = 0.
|
||||
for key, values in stats.items():
|
||||
if key.find('(') == -1:
|
||||
if self._display_guests:
|
||||
if self.get_gname_from_pid(key):
|
||||
total += values.value
|
||||
continue
|
||||
if not key.find(' ') != -1:
|
||||
total += values.value
|
||||
else:
|
||||
ctotal += values.value
|
||||
|
@ -1101,19 +1152,26 @@ class Tui(object):
|
|||
# sort by overall value
|
||||
return v.value
|
||||
|
||||
sorted_items = sorted(stats.items(), key=sortkey, reverse=True)
|
||||
|
||||
# print events
|
||||
tavg = 0
|
||||
for key, values in sorted(stats.items(), key=sortkey, reverse=True):
|
||||
for key, values in sorted_items:
|
||||
if row >= self.screen.getmaxyx()[0] - 1:
|
||||
break
|
||||
if not values.value and not values.delta:
|
||||
break
|
||||
if values == (0, 0):
|
||||
continue
|
||||
if not self.stats.child_events and key.find(' ') != -1:
|
||||
continue
|
||||
if values.value is not None:
|
||||
cur = int(round(values.delta / sleeptime)) if values.delta else ''
|
||||
if self._display_guests:
|
||||
key = self.get_gname_from_pid(key)
|
||||
self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' %
|
||||
(key, values.value, values.value * 100 / total,
|
||||
cur))
|
||||
if not key:
|
||||
continue
|
||||
self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key
|
||||
.split(' ')[0], values.value,
|
||||
values.value * 100 / total, cur))
|
||||
if cur != '' and key.find('(') == -1:
|
||||
tavg += cur
|
||||
row += 1
|
||||
|
@ -1189,7 +1247,7 @@ class Tui(object):
|
|||
regex = self.screen.getstr().decode(ENCODING)
|
||||
curses.noecho()
|
||||
if len(regex) == 0:
|
||||
self.stats.fields_filter = DEFAULT_REGEX
|
||||
self.stats.fields_filter = ''
|
||||
self._refresh_header()
|
||||
return
|
||||
try:
|
||||
|
@ -1307,7 +1365,7 @@ class Tui(object):
|
|||
self._display_guests = not self._display_guests
|
||||
self._refresh_header()
|
||||
if char == 'c':
|
||||
self.stats.fields_filter = DEFAULT_REGEX
|
||||
self.stats.fields_filter = ''
|
||||
self._refresh_header(0)
|
||||
self._update_pid(0)
|
||||
if char == 'f':
|
||||
|
@ -1332,9 +1390,7 @@ class Tui(object):
|
|||
curses.curs_set(0)
|
||||
sleeptime = self._delay_initial
|
||||
if char == 'x':
|
||||
self._update_drilldown()
|
||||
# prevents display of current values on next refresh
|
||||
self.stats.get(self._display_guests)
|
||||
self.stats.child_events = not self.stats.child_events
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except curses.error:
|
||||
|
@ -1348,7 +1404,8 @@ def batch(stats):
|
|||
time.sleep(1)
|
||||
s = stats.get()
|
||||
for key, values in sorted(s.items()):
|
||||
print('%-42s%10d%10d' % (key, values.value, values.delta))
|
||||
print('%-42s%10d%10d' % (key.split(' ')[0], values.value,
|
||||
values.delta))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
@ -1359,7 +1416,7 @@ def log(stats):
|
|||
|
||||
def banner():
|
||||
for key in keys:
|
||||
print(key, end=' ')
|
||||
print(key.split(' ')[0], end=' ')
|
||||
print()
|
||||
|
||||
def statline():
|
||||
|
@ -1470,7 +1527,7 @@ Press any other key to refresh statistics immediately.
|
|||
)
|
||||
optparser.add_option('-f', '--fields',
|
||||
action='store',
|
||||
default=DEFAULT_REGEX,
|
||||
default='',
|
||||
dest='fields',
|
||||
help='''fields to display (regex)
|
||||
"-f help" for a list of available events''',
|
||||
|
|
Loading…
Reference in a new issue