|
|
4a2fec |
From 1dbc0659acce676594060b331eb4d854d26eed0c Mon Sep 17 00:00:00 2001
|
|
|
4a2fec |
From: David Hildenbrand <david@redhat.com>
|
|
|
4a2fec |
Date: Tue, 17 Oct 2017 19:15:52 +0200
|
|
|
4a2fec |
Subject: [PATCH 47/69] tools/kvm_stat: move functions to corresponding classes
|
|
|
4a2fec |
|
|
|
4a2fec |
RH-Author: David Hildenbrand <david@redhat.com>
|
|
|
4a2fec |
Message-id: <20171017191605.2378-27-david@redhat.com>
|
|
|
4a2fec |
Patchwork-id: 77334
|
|
|
4a2fec |
O-Subject: [RHEL-7.5 qemu-kvm-rhev PATCH 26/39] tools/kvm_stat: move functions to corresponding classes
|
|
|
4a2fec |
Bugzilla: 1497137
|
|
|
4a2fec |
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
4a2fec |
RH-Acked-by: Cornelia Huck <cohuck@redhat.com>
|
|
|
4a2fec |
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
4a2fec |
RH-Acked-by: Thomas Huth <thuth@redhat.com>
|
|
|
4a2fec |
|
|
|
4a2fec |
Upstream-status: linux.git 099a2dfc674e3333bd4ff5e5b106ccd788aa46d7
|
|
|
4a2fec |
|
|
|
4a2fec |
commit 099a2dfc674e3333bd4ff5e5b106ccd788aa46d7
|
|
|
4a2fec |
Author: Stefan Raspl <raspl@linux.vnet.ibm.com>
|
|
|
4a2fec |
Date: Wed Jun 7 21:08:33 2017 +0200
|
|
|
4a2fec |
|
|
|
4a2fec |
tools/kvm_stat: move functions to corresponding classes
|
|
|
4a2fec |
|
|
|
4a2fec |
Quite a few of the functions are used only in a single class. Moving
|
|
|
4a2fec |
functions accordingly to improve the overall structure.
|
|
|
4a2fec |
Furthermore, introduce a base class for the providers, which might also
|
|
|
4a2fec |
come handy for future extensions.
|
|
|
4a2fec |
|
|
|
4a2fec |
Signed-off-by: Stefan Raspl <raspl@linux.vnet.ibm.com>
|
|
|
4a2fec |
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
4a2fec |
|
|
|
4a2fec |
Signed-off-by: David Hildenbrand <david@redhat.com>
|
|
|
4a2fec |
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
4a2fec |
---
|
|
|
4a2fec |
scripts/kvm/kvm_stat | 327 ++++++++++++++++++++++++++-------------------------
|
|
|
4a2fec |
1 file changed, 165 insertions(+), 162 deletions(-)
|
|
|
4a2fec |
|
|
|
4a2fec |
diff --git a/scripts/kvm/kvm_stat b/scripts/kvm/kvm_stat
|
|
|
4a2fec |
index b8522d2..f81ed20 100755
|
|
|
4a2fec |
--- a/scripts/kvm/kvm_stat
|
|
|
4a2fec |
+++ b/scripts/kvm/kvm_stat
|
|
|
4a2fec |
@@ -295,121 +295,6 @@ class ArchS390(Arch):
|
|
|
4a2fec |
ARCH = Arch.get_arch()
|
|
|
4a2fec |
|
|
|
4a2fec |
|
|
|
4a2fec |
-def is_field_wanted(fields_filter, field):
|
|
|
4a2fec |
- """Indicate whether field is valid according to fields_filter."""
|
|
|
4a2fec |
- if not fields_filter:
|
|
|
4a2fec |
- return True
|
|
|
4a2fec |
- return re.match(fields_filter, field) is not None
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-def walkdir(path):
|
|
|
4a2fec |
- """Returns os.walk() data for specified directory.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- As it is only a wrapper it returns the same 3-tuple of (dirpath,
|
|
|
4a2fec |
- dirnames, filenames).
|
|
|
4a2fec |
- """
|
|
|
4a2fec |
- return next(os.walk(path))
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-def parse_int_list(list_string):
|
|
|
4a2fec |
- """Returns an int list from a string of comma separated integers and
|
|
|
4a2fec |
- integer ranges."""
|
|
|
4a2fec |
- integers = []
|
|
|
4a2fec |
- members = list_string.split(',')
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- for member in members:
|
|
|
4a2fec |
- if '-' not in member:
|
|
|
4a2fec |
- integers.append(int(member))
|
|
|
4a2fec |
- else:
|
|
|
4a2fec |
- int_range = member.split('-')
|
|
|
4a2fec |
- integers.extend(range(int(int_range[0]),
|
|
|
4a2fec |
- int(int_range[1]) + 1))
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- return integers
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-def get_pid_from_gname(gname):
|
|
|
4a2fec |
- """Fuzzy function to convert guest name to QEMU process pid.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- Returns a list of potential pids, can be empty if no match found.
|
|
|
4a2fec |
- Throws an exception on processing errors.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- """
|
|
|
4a2fec |
- pids = []
|
|
|
4a2fec |
- try:
|
|
|
4a2fec |
- child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
|
|
|
4a2fec |
- stdout=subprocess.PIPE)
|
|
|
4a2fec |
- except:
|
|
|
4a2fec |
- raise Exception
|
|
|
4a2fec |
- for line in child.stdout:
|
|
|
4a2fec |
- line = line.lstrip().split(' ', 1)
|
|
|
4a2fec |
- # perform a sanity check before calling the more expensive
|
|
|
4a2fec |
- # function to possibly extract the guest name
|
|
|
4a2fec |
- if ' -name ' in line[1] and gname == get_gname_from_pid(line[0]):
|
|
|
4a2fec |
- pids.append(int(line[0]))
|
|
|
4a2fec |
- child.stdout.close()
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- return pids
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-def get_gname_from_pid(pid):
|
|
|
4a2fec |
- """Returns the guest name for a QEMU process pid.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- Extracts the guest name from the QEMU comma line by processing the '-name'
|
|
|
4a2fec |
- option. Will also handle names specified out of sequence.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- """
|
|
|
4a2fec |
- name = ''
|
|
|
4a2fec |
- try:
|
|
|
4a2fec |
- line = open('/proc/{}/cmdline'.format(pid), 'rb').read().split('\0')
|
|
|
4a2fec |
- parms = line[line.index('-name') + 1].split(',')
|
|
|
4a2fec |
- while '' in parms:
|
|
|
4a2fec |
- # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results in
|
|
|
4a2fec |
- # ['foo', '', 'bar'], which we revert here
|
|
|
4a2fec |
- idx = parms.index('')
|
|
|
4a2fec |
- parms[idx - 1] += ',' + parms[idx + 1]
|
|
|
4a2fec |
- del parms[idx:idx+2]
|
|
|
4a2fec |
- # the '-name' switch allows for two ways to specify the guest name,
|
|
|
4a2fec |
- # where the plain name overrides the name specified via 'guest='
|
|
|
4a2fec |
- for arg in parms:
|
|
|
4a2fec |
- if '=' not in arg:
|
|
|
4a2fec |
- name = arg
|
|
|
4a2fec |
- break
|
|
|
4a2fec |
- if arg[:6] == 'guest=':
|
|
|
4a2fec |
- name = arg[6:]
|
|
|
4a2fec |
- except (ValueError, IOError, IndexError):
|
|
|
4a2fec |
- pass
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- return name
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-def get_online_cpus():
|
|
|
4a2fec |
- """Returns a list of cpu id integers."""
|
|
|
4a2fec |
- with open('/sys/devices/system/cpu/online') as cpu_list:
|
|
|
4a2fec |
- cpu_string = cpu_list.readline()
|
|
|
4a2fec |
- return parse_int_list(cpu_string)
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-def get_filters():
|
|
|
4a2fec |
- """Returns a dict of trace events, their filter ids and
|
|
|
4a2fec |
- the values that can be filtered.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- Trace events can be filtered for special values by setting a
|
|
|
4a2fec |
- filter string via an ioctl. The string normally has the format
|
|
|
4a2fec |
- identifier==value. For each filter a new event will be created, to
|
|
|
4a2fec |
- be able to distinguish the events.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- """
|
|
|
4a2fec |
- filters = {}
|
|
|
4a2fec |
- filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
|
|
|
4a2fec |
- if ARCH.exit_reasons:
|
|
|
4a2fec |
- filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
|
|
|
4a2fec |
- return filters
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
|
|
4a2fec |
-syscall = libc.syscall
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
class perf_event_attr(ctypes.Structure):
|
|
|
4a2fec |
"""Struct that holds the necessary data to set up a trace event.
|
|
|
4a2fec |
|
|
|
4a2fec |
@@ -439,25 +324,6 @@ class perf_event_attr(ctypes.Structure):
|
|
|
4a2fec |
self.read_format = PERF_FORMAT_GROUP
|
|
|
4a2fec |
|
|
|
4a2fec |
|
|
|
4a2fec |
-def perf_event_open(attr, pid, cpu, group_fd, flags):
|
|
|
4a2fec |
- """Wrapper for the sys_perf_evt_open() syscall.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- Used to set up performance events, returns a file descriptor or -1
|
|
|
4a2fec |
- on error.
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- Attributes are:
|
|
|
4a2fec |
- - syscall number
|
|
|
4a2fec |
- - struct perf_event_attr *
|
|
|
4a2fec |
- - pid or -1 to monitor all pids
|
|
|
4a2fec |
- - cpu number or -1 to monitor all cpus
|
|
|
4a2fec |
- - The file descriptor of the group leader or -1 to create a group.
|
|
|
4a2fec |
- - flags
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- """
|
|
|
4a2fec |
- return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
|
|
|
4a2fec |
- ctypes.c_int(pid), ctypes.c_int(cpu),
|
|
|
4a2fec |
- ctypes.c_int(group_fd), ctypes.c_long(flags))
|
|
|
4a2fec |
-
|
|
|
4a2fec |
PERF_TYPE_TRACEPOINT = 2
|
|
|
4a2fec |
PERF_FORMAT_GROUP = 1 << 3
|
|
|
4a2fec |
|
|
|
4a2fec |
@@ -502,6 +368,8 @@ class Event(object):
|
|
|
4a2fec |
"""Represents a performance event and manages its life cycle."""
|
|
|
4a2fec |
def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
|
|
|
4a2fec |
trace_filter, trace_set='kvm'):
|
|
|
4a2fec |
+ self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
|
|
4a2fec |
+ self.syscall = self.libc.syscall
|
|
|
4a2fec |
self.name = name
|
|
|
4a2fec |
self.fd = None
|
|
|
4a2fec |
self.setup_event(group, trace_cpu, trace_pid, trace_point,
|
|
|
4a2fec |
@@ -518,6 +386,25 @@ class Event(object):
|
|
|
4a2fec |
if self.fd:
|
|
|
4a2fec |
os.close(self.fd)
|
|
|
4a2fec |
|
|
|
4a2fec |
+ def perf_event_open(self, attr, pid, cpu, group_fd, flags):
|
|
|
4a2fec |
+ """Wrapper for the sys_perf_evt_open() syscall.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ Used to set up performance events, returns a file descriptor or -1
|
|
|
4a2fec |
+ on error.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ Attributes are:
|
|
|
4a2fec |
+ - syscall number
|
|
|
4a2fec |
+ - struct perf_event_attr *
|
|
|
4a2fec |
+ - pid or -1 to monitor all pids
|
|
|
4a2fec |
+ - cpu number or -1 to monitor all cpus
|
|
|
4a2fec |
+ - The file descriptor of the group leader or -1 to create a group.
|
|
|
4a2fec |
+ - flags
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ """
|
|
|
4a2fec |
+ return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
|
|
|
4a2fec |
+ ctypes.c_int(pid), ctypes.c_int(cpu),
|
|
|
4a2fec |
+ ctypes.c_int(group_fd), ctypes.c_long(flags))
|
|
|
4a2fec |
+
|
|
|
4a2fec |
def setup_event_attribute(self, trace_set, trace_point):
|
|
|
4a2fec |
"""Returns an initialized ctype perf_event_attr struct."""
|
|
|
4a2fec |
|
|
|
4a2fec |
@@ -546,8 +433,8 @@ class Event(object):
|
|
|
4a2fec |
if group.events:
|
|
|
4a2fec |
group_leader = group.events[0].fd
|
|
|
4a2fec |
|
|
|
4a2fec |
- fd = perf_event_open(event_attr, trace_pid,
|
|
|
4a2fec |
- trace_cpu, group_leader, 0)
|
|
|
4a2fec |
+ fd = self.perf_event_open(event_attr, trace_pid,
|
|
|
4a2fec |
+ trace_cpu, group_leader, 0)
|
|
|
4a2fec |
if fd == -1:
|
|
|
4a2fec |
err = ctypes.get_errno()
|
|
|
4a2fec |
raise OSError(err, os.strerror(err),
|
|
|
4a2fec |
@@ -582,7 +469,26 @@ class Event(object):
|
|
|
4a2fec |
fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
|
|
|
4a2fec |
|
|
|
4a2fec |
|
|
|
4a2fec |
-class TracepointProvider(object):
|
|
|
4a2fec |
+class Provider(object):
|
|
|
4a2fec |
+ """Encapsulates functionalities used by all providers."""
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def is_field_wanted(fields_filter, field):
|
|
|
4a2fec |
+ """Indicate whether field is valid according to fields_filter."""
|
|
|
4a2fec |
+ if not fields_filter:
|
|
|
4a2fec |
+ return True
|
|
|
4a2fec |
+ return re.match(fields_filter, field) is not None
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def walkdir(path):
|
|
|
4a2fec |
+ """Returns os.walk() data for specified directory.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ As it is only a wrapper it returns the same 3-tuple of (dirpath,
|
|
|
4a2fec |
+ dirnames, filenames).
|
|
|
4a2fec |
+ """
|
|
|
4a2fec |
+ return next(os.walk(path))
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+class TracepointProvider(Provider):
|
|
|
4a2fec |
"""Data provider for the stats class.
|
|
|
4a2fec |
|
|
|
4a2fec |
Manages the events/groups from which it acquires its data.
|
|
|
4a2fec |
@@ -590,10 +496,27 @@ class TracepointProvider(object):
|
|
|
4a2fec |
"""
|
|
|
4a2fec |
def __init__(self, pid, fields_filter):
|
|
|
4a2fec |
self.group_leaders = []
|
|
|
4a2fec |
- self.filters = get_filters()
|
|
|
4a2fec |
+ self.filters = self.get_filters()
|
|
|
4a2fec |
self.update_fields(fields_filter)
|
|
|
4a2fec |
self.pid = pid
|
|
|
4a2fec |
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def get_filters():
|
|
|
4a2fec |
+ """Returns a dict of trace events, their filter ids and
|
|
|
4a2fec |
+ the values that can be filtered.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ Trace events can be filtered for special values by setting a
|
|
|
4a2fec |
+ filter string via an ioctl. The string normally has the format
|
|
|
4a2fec |
+ identifier==value. For each filter a new event will be created, to
|
|
|
4a2fec |
+ be able to distinguish the events.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ """
|
|
|
4a2fec |
+ filters = {}
|
|
|
4a2fec |
+ filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
|
|
|
4a2fec |
+ if ARCH.exit_reasons:
|
|
|
4a2fec |
+ filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
|
|
|
4a2fec |
+ return filters
|
|
|
4a2fec |
+
|
|
|
4a2fec |
def get_available_fields(self):
|
|
|
4a2fec |
"""Returns a list of available event's of format 'event name(filter
|
|
|
4a2fec |
name)'.
|
|
|
4a2fec |
@@ -610,7 +533,7 @@ class TracepointProvider(object):
|
|
|
4a2fec |
|
|
|
4a2fec |
"""
|
|
|
4a2fec |
path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
|
|
|
4a2fec |
- fields = walkdir(path)[1]
|
|
|
4a2fec |
+ fields = self.walkdir(path)[1]
|
|
|
4a2fec |
extra = []
|
|
|
4a2fec |
for field in fields:
|
|
|
4a2fec |
if field in self.filters:
|
|
|
4a2fec |
@@ -623,7 +546,30 @@ class TracepointProvider(object):
|
|
|
4a2fec |
def update_fields(self, fields_filter):
|
|
|
4a2fec |
"""Refresh fields, applying fields_filter"""
|
|
|
4a2fec |
self._fields = [field for field in self.get_available_fields()
|
|
|
4a2fec |
- if is_field_wanted(fields_filter, field)]
|
|
|
4a2fec |
+ if self.is_field_wanted(fields_filter, field)]
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def get_online_cpus():
|
|
|
4a2fec |
+ """Returns a list of cpu id integers."""
|
|
|
4a2fec |
+ def parse_int_list(list_string):
|
|
|
4a2fec |
+ """Returns an int list from a string of comma separated integers and
|
|
|
4a2fec |
+ integer ranges."""
|
|
|
4a2fec |
+ integers = []
|
|
|
4a2fec |
+ members = list_string.split(',')
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ for member in members:
|
|
|
4a2fec |
+ if '-' not in member:
|
|
|
4a2fec |
+ integers.append(int(member))
|
|
|
4a2fec |
+ else:
|
|
|
4a2fec |
+ int_range = member.split('-')
|
|
|
4a2fec |
+ integers.extend(range(int(int_range[0]),
|
|
|
4a2fec |
+ int(int_range[1]) + 1))
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ return integers
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ with open('/sys/devices/system/cpu/online') as cpu_list:
|
|
|
4a2fec |
+ cpu_string = cpu_list.readline()
|
|
|
4a2fec |
+ return parse_int_list(cpu_string)
|
|
|
4a2fec |
|
|
|
4a2fec |
def setup_traces(self):
|
|
|
4a2fec |
"""Creates all event and group objects needed to be able to retrieve
|
|
|
4a2fec |
@@ -633,9 +579,9 @@ class TracepointProvider(object):
|
|
|
4a2fec |
# Fetch list of all threads of the monitored pid, as qemu
|
|
|
4a2fec |
# starts a thread for each vcpu.
|
|
|
4a2fec |
path = os.path.join('/proc', str(self._pid), 'task')
|
|
|
4a2fec |
- groupids = walkdir(path)[1]
|
|
|
4a2fec |
+ groupids = self.walkdir(path)[1]
|
|
|
4a2fec |
else:
|
|
|
4a2fec |
- groupids = get_online_cpus()
|
|
|
4a2fec |
+ groupids = self.get_online_cpus()
|
|
|
4a2fec |
|
|
|
4a2fec |
# The constant is needed as a buffer for python libs, std
|
|
|
4a2fec |
# streams and other files that the script opens.
|
|
|
4a2fec |
@@ -732,7 +678,7 @@ class TracepointProvider(object):
|
|
|
4a2fec |
event.reset()
|
|
|
4a2fec |
|
|
|
4a2fec |
|
|
|
4a2fec |
-class DebugfsProvider(object):
|
|
|
4a2fec |
+class DebugfsProvider(Provider):
|
|
|
4a2fec |
"""Provides data from the files that KVM creates in the kvm debugfs
|
|
|
4a2fec |
folder."""
|
|
|
4a2fec |
def __init__(self, pid, fields_filter):
|
|
|
4a2fec |
@@ -748,12 +694,12 @@ class DebugfsProvider(object):
|
|
|
4a2fec |
The fields are all available KVM debugfs files
|
|
|
4a2fec |
|
|
|
4a2fec |
"""
|
|
|
4a2fec |
- return walkdir(PATH_DEBUGFS_KVM)[2]
|
|
|
4a2fec |
+ return self.walkdir(PATH_DEBUGFS_KVM)[2]
|
|
|
4a2fec |
|
|
|
4a2fec |
def update_fields(self, fields_filter):
|
|
|
4a2fec |
"""Refresh fields, applying fields_filter"""
|
|
|
4a2fec |
self._fields = [field for field in self.get_available_fields()
|
|
|
4a2fec |
- if is_field_wanted(fields_filter, field)]
|
|
|
4a2fec |
+ if self.is_field_wanted(fields_filter, field)]
|
|
|
4a2fec |
|
|
|
4a2fec |
@property
|
|
|
4a2fec |
def fields(self):
|
|
|
4a2fec |
@@ -772,7 +718,7 @@ class DebugfsProvider(object):
|
|
|
4a2fec |
def pid(self, pid):
|
|
|
4a2fec |
self._pid = pid
|
|
|
4a2fec |
if pid != 0:
|
|
|
4a2fec |
- vms = walkdir(PATH_DEBUGFS_KVM)[1]
|
|
|
4a2fec |
+ vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
|
|
|
4a2fec |
if len(vms) == 0:
|
|
|
4a2fec |
self.do_read = False
|
|
|
4a2fec |
|
|
|
4a2fec |
@@ -834,11 +780,23 @@ class Stats(object):
|
|
|
4a2fec |
|
|
|
4a2fec |
"""
|
|
|
4a2fec |
def __init__(self, options):
|
|
|
4a2fec |
- self.providers = get_providers(options)
|
|
|
4a2fec |
+ self.providers = self.get_providers(options)
|
|
|
4a2fec |
self._pid_filter = options.pid
|
|
|
4a2fec |
self._fields_filter = options.fields
|
|
|
4a2fec |
self.values = {}
|
|
|
4a2fec |
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def get_providers(options):
|
|
|
4a2fec |
+ """Returns a list of data providers depending on the passed options."""
|
|
|
4a2fec |
+ providers = []
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ if options.debugfs:
|
|
|
4a2fec |
+ providers.append(DebugfsProvider(options.pid, options.fields))
|
|
|
4a2fec |
+ if options.tracepoints or not providers:
|
|
|
4a2fec |
+ providers.append(TracepointProvider(options.pid, options.fields))
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ return providers
|
|
|
4a2fec |
+
|
|
|
4a2fec |
def update_provider_filters(self):
|
|
|
4a2fec |
"""Propagates fields filters to providers."""
|
|
|
4a2fec |
# As we reset the counters when updating the fields we can
|
|
|
4a2fec |
@@ -933,6 +891,63 @@ class Tui(object):
|
|
|
4a2fec |
curses.nocbreak()
|
|
|
4a2fec |
curses.endwin()
|
|
|
4a2fec |
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def get_pid_from_gname(gname):
|
|
|
4a2fec |
+ """Fuzzy function to convert guest name to QEMU process pid.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ Returns a list of potential pids, can be empty if no match found.
|
|
|
4a2fec |
+ Throws an exception on processing errors.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ """
|
|
|
4a2fec |
+ pids = []
|
|
|
4a2fec |
+ try:
|
|
|
4a2fec |
+ child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
|
|
|
4a2fec |
+ stdout=subprocess.PIPE)
|
|
|
4a2fec |
+ except:
|
|
|
4a2fec |
+ raise Exception
|
|
|
4a2fec |
+ for line in child.stdout:
|
|
|
4a2fec |
+ line = line.lstrip().split(' ', 1)
|
|
|
4a2fec |
+ # perform a sanity check before calling the more expensive
|
|
|
4a2fec |
+ # function to possibly extract the guest name
|
|
|
4a2fec |
+ if (' -name ' in line[1] and
|
|
|
4a2fec |
+ gname == self.get_gname_from_pid(line[0])):
|
|
|
4a2fec |
+ pids.append(int(line[0]))
|
|
|
4a2fec |
+ child.stdout.close()
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ return pids
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ @staticmethod
|
|
|
4a2fec |
+ def get_gname_from_pid(pid):
|
|
|
4a2fec |
+ """Returns the guest name for a QEMU process pid.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ Extracts the guest name from the QEMU comma line by processing the
|
|
|
4a2fec |
+ '-name' option. Will also handle names specified out of sequence.
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ """
|
|
|
4a2fec |
+ name = ''
|
|
|
4a2fec |
+ try:
|
|
|
4a2fec |
+ line = open('/proc/{}/cmdline'
|
|
|
4a2fec |
+ .format(pid), 'rb').read().split('\0')
|
|
|
4a2fec |
+ parms = line[line.index('-name') + 1].split(',')
|
|
|
4a2fec |
+ while '' in parms:
|
|
|
4a2fec |
+ # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
|
|
|
4a2fec |
+ # in # ['foo', '', 'bar'], which we revert here
|
|
|
4a2fec |
+ idx = parms.index('')
|
|
|
4a2fec |
+ parms[idx - 1] += ',' + parms[idx + 1]
|
|
|
4a2fec |
+ del parms[idx:idx+2]
|
|
|
4a2fec |
+ # the '-name' switch allows for two ways to specify the guest name,
|
|
|
4a2fec |
+ # where the plain name overrides the name specified via 'guest='
|
|
|
4a2fec |
+ for arg in parms:
|
|
|
4a2fec |
+ if '=' not in arg:
|
|
|
4a2fec |
+ name = arg
|
|
|
4a2fec |
+ break
|
|
|
4a2fec |
+ if arg[:6] == 'guest=':
|
|
|
4a2fec |
+ name = arg[6:]
|
|
|
4a2fec |
+ except (ValueError, IOError, IndexError):
|
|
|
4a2fec |
+ pass
|
|
|
4a2fec |
+
|
|
|
4a2fec |
+ return name
|
|
|
4a2fec |
+
|
|
|
4a2fec |
def update_drilldown(self):
|
|
|
4a2fec |
"""Sets or removes a filter that only allows fields without braces."""
|
|
|
4a2fec |
if not self.stats.fields_filter:
|
|
|
4a2fec |
@@ -950,7 +965,7 @@ class Tui(object):
|
|
|
4a2fec |
if pid is None:
|
|
|
4a2fec |
pid = self.stats.pid_filter
|
|
|
4a2fec |
self.screen.erase()
|
|
|
4a2fec |
- gname = get_gname_from_pid(pid)
|
|
|
4a2fec |
+ gname = self.get_gname_from_pid(pid)
|
|
|
4a2fec |
if gname:
|
|
|
4a2fec |
gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
|
|
|
4a2fec |
if len(gname) > MAX_GUEST_NAME_LEN
|
|
|
4a2fec |
@@ -1096,7 +1111,7 @@ class Tui(object):
|
|
|
4a2fec |
else:
|
|
|
4a2fec |
pids = []
|
|
|
4a2fec |
try:
|
|
|
4a2fec |
- pids = get_pid_from_gname(gname)
|
|
|
4a2fec |
+ pids = self.get_pid_from_gname(gname)
|
|
|
4a2fec |
except:
|
|
|
4a2fec |
msg = '"' + gname + '": Internal error while searching, ' \
|
|
|
4a2fec |
'use pid filter instead'
|
|
|
4a2fec |
@@ -1229,7 +1244,7 @@ Press any other key to refresh statistics immediately.
|
|
|
4a2fec |
|
|
|
4a2fec |
def cb_guest_to_pid(option, opt, val, parser):
|
|
|
4a2fec |
try:
|
|
|
4a2fec |
- pids = get_pid_from_gname(val)
|
|
|
4a2fec |
+ pids = Tui.get_pid_from_gname(val)
|
|
|
4a2fec |
except:
|
|
|
4a2fec |
raise optparse.OptionValueError('Error while searching for guest '
|
|
|
4a2fec |
'"{}", use "-p" to specify a pid '
|
|
|
4a2fec |
@@ -1294,18 +1309,6 @@ Press any other key to refresh statistics immediately.
|
|
|
4a2fec |
return options
|
|
|
4a2fec |
|
|
|
4a2fec |
|
|
|
4a2fec |
-def get_providers(options):
|
|
|
4a2fec |
- """Returns a list of data providers depending on the passed options."""
|
|
|
4a2fec |
- providers = []
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- if options.debugfs:
|
|
|
4a2fec |
- providers.append(DebugfsProvider(options.pid, options.fields))
|
|
|
4a2fec |
- if options.tracepoints or not providers:
|
|
|
4a2fec |
- providers.append(TracepointProvider(options.pid, options.fields))
|
|
|
4a2fec |
-
|
|
|
4a2fec |
- return providers
|
|
|
4a2fec |
-
|
|
|
4a2fec |
-
|
|
|
4a2fec |
def check_access(options):
|
|
|
4a2fec |
"""Exits if the current user can't access all needed directories."""
|
|
|
4a2fec |
if not os.path.exists('/sys/kernel/debug'):
|
|
|
4a2fec |
--
|
|
|
4a2fec |
1.8.3.1
|
|
|
4a2fec |
|