tools/kvm_stat: add option '--guest'

Add a new option '-g'/'--guest' to select a particular process by providing
the QEMU guest name.
Notes:
- The logic to figure out the pid corresponding to the guest name might look
  scary, but works pretty reliably in practice; in the unlikely event that it
  returns add'l flukes, it will bail out and hint at using '-p' instead, no
  harm done.
- Mixing '-g' and '-p' is possible, and the final instance specified on the
  command line is the significant one. This is consistent with current
  behavior for '-p' which, if specified multiple times, also regards the final
  instance as the significant one.

Signed-off-by: Stefan Raspl <raspl@linux.vnet.ibm.com>
Reviewed-by: Janosch Frank <frankja@linux.vnet.ibm.com>
Signed-off-by: Radim Krčmář <rkrcmar@redhat.com>
This commit is contained in:
Stefan Raspl 2017-03-10 13:40:13 +01:00 committed by Paolo Bonzini
parent 645c1728a9
commit f9ff108735
2 changed files with 105 additions and 2 deletions

View File

@ -30,6 +30,7 @@ import fcntl
import resource
import struct
import re
import subprocess
from collections import defaultdict
VMX_EXIT_REASONS = {
@ -320,6 +321,30 @@ def parse_int_list(list_string):
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.
@ -977,7 +1002,7 @@ class Tui(object):
except re.error:
continue
def show_vm_selection(self):
def show_vm_selection_by_pid(self):
"""Draws PID selection mask.
Asks for a pid until a valid pid or 0 has been entered.
@ -1016,6 +1041,50 @@ class Tui(object):
msg = '"' + str(pid) + '": Not a valid pid'
continue
def show_vm_selection_by_guest_name(self):
"""Draws guest selection mask.
Asks for a guest name until a valid guest name or '' is entered.
"""
msg = ''
while True:
self.screen.erase()
self.screen.addstr(0, 0,
'Show statistics for specific guest.',
curses.A_BOLD)
self.screen.addstr(1, 0,
'This might limit the shown data to the trace '
'statistics.')
self.screen.addstr(5, 0, msg)
curses.echo()
self.screen.addstr(3, 0, "Guest [ENTER or guest]: ")
gname = self.screen.getstr()
curses.noecho()
if not gname:
self.refresh_header(0)
self.update_pid(0)
break
else:
pids = []
try:
pids = get_pid_from_gname(gname)
except:
msg = '"' + gname + '": Internal error while searching, ' \
'use pid filter instead'
continue
if len(pids) == 0:
msg = '"' + gname + '": Not an active guest'
continue
if len(pids) > 1:
msg = '"' + gname + '": Multiple matches found, use pid ' \
'filter instead'
continue
self.refresh_header(pids[0])
self.update_pid(pids[0])
break
def show_stats(self):
"""Refreshes the screen and processes user input."""
sleeptime = DELAY_INITIAL
@ -1035,8 +1104,11 @@ class Tui(object):
if char == 'f':
self.show_filter_selection()
sleeptime = DELAY_INITIAL
if char == 'g':
self.show_vm_selection_by_guest_name()
sleeptime = DELAY_INITIAL
if char == 'p':
self.show_vm_selection()
self.show_vm_selection_by_pid()
sleeptime = DELAY_INITIAL
except KeyboardInterrupt:
break
@ -1106,6 +1178,7 @@ Requirements:
Interactive Commands:
f filter by regular expression
g filter by guest name
p filter by PID
q quit
x toggle reporting of stats for individual child trace events
@ -1119,6 +1192,22 @@ Press any other key to refresh statistics immediately.
else:
return ""
def cb_guest_to_pid(option, opt, val, parser):
try:
pids = get_pid_from_gname(val)
except:
raise optparse.OptionValueError('Error while searching for guest '
'"{}", use "-p" to specify a pid '
'instead'.format(val))
if len(pids) == 0:
raise optparse.OptionValueError('No guest by the name "{}" '
'found'.format(val))
if len(pids) > 1:
raise optparse.OptionValueError('Multiple processes found (pids: '
'{}) - use "-p" to specify a pid '
'instead'.format(" ".join(pids)))
parser.values.pid = pids[0]
optparser = optparse.OptionParser(description=description_text,
formatter=PlainHelpFormatter())
optparser.add_option('-1', '--once', '--batch',
@ -1158,6 +1247,14 @@ Press any other key to refresh statistics immediately.
dest='pid',
help='restrict statistics to pid',
)
optparser.add_option('-g', '--guest',
action='callback',
type='string',
dest='pid',
metavar='GUEST',
help='restrict statistics to guest by name',
callback=cb_guest_to_pid,
)
(options, _) = optparser.parse_args(sys.argv)
return options

View File

@ -31,6 +31,8 @@ INTERACTIVE COMMANDS
[horizontal]
*f*:: filter by regular expression
*g*:: filter by guest name
*p*:: filter by PID
*q*:: quit
@ -62,6 +64,10 @@ OPTIONS
--pid=<pid>::
limit statistics to one virtual machine (pid)
-g<guest>::
--guest=<guest_name>::
limit statistics to one virtual machine (guest name)
-f<fields>::
--fields=<fields>::
fields to display (regex)