xref: /qemu/scripts/qemu-trace-stap (revision ce315328f8e9bf5201db4217f3ffe0784110aa4b)
1#!/usr/bin/env python3
2# -*- python -*-
3#
4# Copyright (C) 2019 Red Hat, Inc
5#
6# QEMU SystemTap Trace Tool
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, see <http://www.gnu.org/licenses/>.
20
21import argparse
22import copy
23import os.path
24import re
25import subprocess
26import sys
27
28
29def probe_prefix(binary):
30    dirname, filename = os.path.split(binary)
31    return re.sub("-", ".", filename) + ".log"
32
33
34def which(binary):
35    for path in os.environ["PATH"].split(os.pathsep):
36        if os.path.exists(os.path.join(path, binary)):
37                return os.path.join(path, binary)
38
39    print("Unable to find '%s' in $PATH" % binary)
40    sys.exit(1)
41
42
43def tapset_dir(binary):
44    dirname, filename = os.path.split(binary)
45    if dirname == '':
46        thisfile = which(binary)
47    else:
48        thisfile = os.path.realpath(binary)
49        if not os.path.exists(thisfile):
50            print("Unable to find '%s'" % thisfile)
51            sys.exit(1)
52
53    basedir = os.path.split(thisfile)[0]
54    tapset = os.path.join(basedir, "..", "share", "systemtap", "tapset")
55    return os.path.realpath(tapset)
56
57
58def cmd_run(args):
59    stap = which("stap")
60    prefix = probe_prefix(args.binary)
61    tapsets = tapset_dir(args.binary)
62
63    if args.verbose:
64        print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary))
65
66    probes = []
67    for probe in args.probes:
68        probes.append("probe %s.%s {}" % (prefix, probe))
69    if len(probes) == 0:
70        print("At least one probe pattern must be specified")
71        sys.exit(1)
72
73    script = " ".join(probes)
74    if args.verbose:
75        print("Compiling script '%s'" % script)
76        script = """probe begin { print("Running script, <Ctrl>-c to quit\\n") } """ + script
77
78    # We request an 8MB buffer, since the stap default 1MB buffer
79    # can be easily overflowed by frequently firing QEMU traces
80    stapargs = [stap, "-s", "8", "-I", tapsets ]
81    if args.pid is not None:
82        stapargs.extend(["-x", args.pid])
83    stapargs.extend(["-e", script])
84    subprocess.call(stapargs)
85
86
87def cmd_list(args):
88    stap = which("stap")
89    tapsets = tapset_dir(args.binary)
90
91    if args.verbose:
92        print("Using tapset dir '%s' for binary '%s'" % (tapsets, args.binary))
93
94    def print_probes(verbose, name):
95        prefix = probe_prefix(args.binary)
96        offset = len(prefix) + 1
97        script = prefix + "." + name
98
99        if verbose:
100            print("Listing probes with name '%s'" % script)
101        proc = subprocess.Popen([stap, "-I", tapsets, "-l", script],
102                                stdout=subprocess.PIPE,
103                                universal_newlines=True)
104        out, err = proc.communicate()
105        if proc.returncode != 0:
106            print("No probes found, are the tapsets installed in %s" % tapset_dir(args.binary))
107            sys.exit(1)
108
109        for line in out.splitlines():
110            if line.startswith(prefix):
111                print("%s" % line[offset:])
112
113    if len(args.probes) == 0:
114        print_probes(args.verbose, "*")
115    else:
116        for probe in args.probes:
117            print_probes(args.verbose, probe)
118
119
120def main():
121    parser = argparse.ArgumentParser(description="QEMU SystemTap trace tool")
122    parser.add_argument("-v", "--verbose", help="Print verbose progress info",
123                        action='store_true')
124
125    subparser = parser.add_subparsers(help="commands")
126    subparser.required = True
127    subparser.dest = "command"
128
129    runparser = subparser.add_parser("run", help="Run a trace session",
130                                     formatter_class=argparse.RawDescriptionHelpFormatter,
131                                     epilog="""
132
133To watch all trace points on the qemu-system-x86_64 binary:
134
135   %(argv0)s run qemu-system-x86_64
136
137To only watch the trace points matching the qio* and qcrypto* patterns
138
139   %(argv0)s run qemu-system-x86_64 'qio*' 'qcrypto*'
140""" % {"argv0": sys.argv[0]})
141    runparser.set_defaults(func=cmd_run)
142    runparser.add_argument("--pid", "-p", dest="pid",
143                           help="Restrict tracing to a specific process ID")
144    runparser.add_argument("binary", help="QEMU system or user emulator binary")
145    runparser.add_argument("probes", help="Probe names or wildcards",
146                           nargs=argparse.REMAINDER)
147
148    listparser = subparser.add_parser("list", help="List probe points",
149                                      formatter_class=argparse.RawDescriptionHelpFormatter,
150                                      epilog="""
151
152To list all trace points on the qemu-system-x86_64 binary:
153
154   %(argv0)s list qemu-system-x86_64
155
156To only list the trace points matching the qio* and qcrypto* patterns
157
158   %(argv0)s list qemu-system-x86_64 'qio*' 'qcrypto*'
159""" % {"argv0": sys.argv[0]})
160    listparser.set_defaults(func=cmd_list)
161    listparser.add_argument("binary", help="QEMU system or user emulator binary")
162    listparser.add_argument("probes", help="Probe names or wildcards",
163                            nargs=argparse.REMAINDER)
164
165    args = parser.parse_args()
166
167    args.func(args)
168    sys.exit(0)
169
170if __name__ == '__main__':
171    main()
172