qemu-trace-stap does not support Python 3 yet:
  $ scripts/qemu-trace-stap list path/to/qemu-system-x86_64
  Traceback (most recent call last):
    File "scripts/qemu-trace-stap", line 175, in <module>
      main()
    File "scripts/qemu-trace-stap", line 171, in main
      args.func(args)
    File "scripts/qemu-trace-stap", line 118, in cmd_list
      print_probes(args.verbose, "*")
    File "scripts/qemu-trace-stap", line 114, in print_probes
      if line.startswith(prefix):
  TypeError: startswith first arg must be bytes or a tuple of bytes, not str
Now that QEMU requires Python 3.5 or later we can switch to pure Python
3.  Use Popen()'s universal_newlines=True argument to treat stdout as
text instead of binary.
Fixes: 62dd1048c0 ("trace: add ability to do simple printf logging via systemtap")
Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1787395
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Message-id: 20200107112438.383958-1-stefanha@redhat.com
Message-Id: <20200107112438.383958-1-stefanha@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
		
	
		
			
				
	
	
		
			176 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python3
 | 
						|
# -*- python -*-
 | 
						|
#
 | 
						|
# Copyright (C) 2019 Red Hat, Inc
 | 
						|
#
 | 
						|
# QEMU SystemTap Trace Tool
 | 
						|
#
 | 
						|
# This program is free software; you can redistribute it and/or modify
 | 
						|
# it under the terms of the GNU General Public License as published by
 | 
						|
# the Free Software Foundation; either version 2 of the License, or
 | 
						|
# (at your option) any later version.
 | 
						|
#
 | 
						|
# This program is distributed in the hope that it will be useful,
 | 
						|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
# GNU General Public License for more details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU General Public License
 | 
						|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
import argparse
 | 
						|
import copy
 | 
						|
import os.path
 | 
						|
import re
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
 | 
						|
 | 
						|
def probe_prefix(binary):
 | 
						|
    dirname, filename = os.path.split(binary)
 | 
						|
    return re.sub("-", ".", filename) + ".log"
 | 
						|
 | 
						|
 | 
						|
def which(binary):
 | 
						|
    for path in os.environ["PATH"].split(os.pathsep):
 | 
						|
        if os.path.exists(os.path.join(path, binary)):
 | 
						|
                return os.path.join(path, binary)
 | 
						|
 | 
						|
    print("Unable to find '%s' in $PATH" % binary)
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
 | 
						|
def tapset_dir(binary):
 | 
						|
    dirname, filename = os.path.split(binary)
 | 
						|
    if dirname == '':
 | 
						|
        thisfile = which(binary)
 | 
						|
    else:
 | 
						|
        thisfile = os.path.realpath(binary)
 | 
						|
        if not os.path.exists(thisfile):
 | 
						|
            print("Unable to find '%s'" % thisfile)
 | 
						|
            sys.exit(1)
 | 
						|
 | 
						|
    basedir = os.path.split(thisfile)[0]
 | 
						|
    tapset = os.path.join(basedir, "..", "share", "systemtap", "tapset")
 | 
						|
    return os.path.realpath(tapset)
 | 
						|
 | 
						|
 | 
						|
def tapset_env(tapset_dir):
 | 
						|
    tenv = copy.copy(os.environ)
 | 
						|
    tenv["SYSTEMTAP_TAPSET"] = tapset_dir
 | 
						|
    return tenv
 | 
						|
 | 
						|
def cmd_run(args):
 | 
						|
    prefix = probe_prefix(args.binary)
 | 
						|
    tapsets = tapset_dir(args.binary)
 | 
						|
 | 
						|
    if args.verbose:
 | 
						|
        print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary))
 | 
						|
 | 
						|
    probes = []
 | 
						|
    for probe in args.probes:
 | 
						|
        probes.append("probe %s.%s {}" % (prefix, probe))
 | 
						|
    if len(probes) == 0:
 | 
						|
        print("At least one probe pattern must be specified")
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    script = " ".join(probes)
 | 
						|
    if args.verbose:
 | 
						|
        print("Compiling script '%s'" % script)
 | 
						|
        script = """probe begin { print("Running script, <Ctrl>-c to quit\\n") } """ + script
 | 
						|
 | 
						|
    # We request an 8MB buffer, since the stap default 1MB buffer
 | 
						|
    # can be easily overflowed by frequently firing QEMU traces
 | 
						|
    stapargs = ["stap", "-s", "8"]
 | 
						|
    if args.pid is not None:
 | 
						|
        stapargs.extend(["-x", args.pid])
 | 
						|
    stapargs.extend(["-e", script])
 | 
						|
    subprocess.call(stapargs, env=tapset_env(tapsets))
 | 
						|
 | 
						|
 | 
						|
def cmd_list(args):
 | 
						|
    tapsets = tapset_dir(args.binary)
 | 
						|
 | 
						|
    if args.verbose:
 | 
						|
        print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary))
 | 
						|
 | 
						|
    def print_probes(verbose, name):
 | 
						|
        prefix = probe_prefix(args.binary)
 | 
						|
        offset = len(prefix) + 1
 | 
						|
        script = prefix + "." + name
 | 
						|
 | 
						|
        if verbose:
 | 
						|
            print("Listing probes with name '%s'" % script)
 | 
						|
        proc = subprocess.Popen(["stap", "-l", script],
 | 
						|
                                stdout=subprocess.PIPE,
 | 
						|
                                universal_newlines=True,
 | 
						|
                                env=tapset_env(tapsets))
 | 
						|
        out, err = proc.communicate()
 | 
						|
        if proc.returncode != 0:
 | 
						|
            print("No probes found, are the tapsets installed in %s" % tapset_dir(args.binary))
 | 
						|
            sys.exit(1)
 | 
						|
 | 
						|
        for line in out.splitlines():
 | 
						|
            if line.startswith(prefix):
 | 
						|
                print("%s" % line[offset:])
 | 
						|
 | 
						|
    if len(args.probes) == 0:
 | 
						|
        print_probes(args.verbose, "*")
 | 
						|
    else:
 | 
						|
        for probe in args.probes:
 | 
						|
            print_probes(args.verbose, probe)
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    parser = argparse.ArgumentParser(description="QEMU SystemTap trace tool")
 | 
						|
    parser.add_argument("-v", "--verbose", help="Print verbose progress info",
 | 
						|
                        action='store_true')
 | 
						|
 | 
						|
    subparser = parser.add_subparsers(help="commands")
 | 
						|
    subparser.required = True
 | 
						|
    subparser.dest = "command"
 | 
						|
 | 
						|
    runparser = subparser.add_parser("run", help="Run a trace session",
 | 
						|
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
 | 
						|
                                     epilog="""
 | 
						|
 | 
						|
To watch all trace points on the qemu-system-x86_64 binary:
 | 
						|
 | 
						|
   %(argv0)s run qemu-system-x86_64
 | 
						|
 | 
						|
To only watch the trace points matching the qio* and qcrypto* patterns
 | 
						|
 | 
						|
   %(argv0)s run qemu-system-x86_64 'qio*' 'qcrypto*'
 | 
						|
""" % {"argv0": sys.argv[0]})
 | 
						|
    runparser.set_defaults(func=cmd_run)
 | 
						|
    runparser.add_argument("--pid", "-p", dest="pid",
 | 
						|
                           help="Restrict tracing to a specific process ID")
 | 
						|
    runparser.add_argument("binary", help="QEMU system or user emulator binary")
 | 
						|
    runparser.add_argument("probes", help="Probe names or wildcards",
 | 
						|
                           nargs=argparse.REMAINDER)
 | 
						|
 | 
						|
    listparser = subparser.add_parser("list", help="List probe points",
 | 
						|
                                      formatter_class=argparse.RawDescriptionHelpFormatter,
 | 
						|
                                      epilog="""
 | 
						|
 | 
						|
To list all trace points on the qemu-system-x86_64 binary:
 | 
						|
 | 
						|
   %(argv0)s list qemu-system-x86_64
 | 
						|
 | 
						|
To only list the trace points matching the qio* and qcrypto* patterns
 | 
						|
 | 
						|
   %(argv0)s list qemu-system-x86_64 'qio*' 'qcrypto*'
 | 
						|
""" % {"argv0": sys.argv[0]})
 | 
						|
    listparser.set_defaults(func=cmd_list)
 | 
						|
    listparser.add_argument("binary", help="QEMU system or user emulator binary")
 | 
						|
    listparser.add_argument("probes", help="Probe names or wildcards",
 | 
						|
                            nargs=argparse.REMAINDER)
 | 
						|
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    args.func(args)
 | 
						|
    sys.exit(0)
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main()
 |