per4m-synapse/per4m/giltracer.py
2021-01-08 11:34:48 +01:00

120 lines
4.4 KiB
Python

import argparse
import os
import sys
import viztracer
from viztracer.report_builder import ReportBuilder
import runpy
from .record import PerfRecord
usage = """
Run VizTracer and perf simultaneously, then inject the GIL information in the viztracer output
Usage:
$ giltracer -m per4m.example1
"""
class PerfRecordSched(PerfRecord):
def __init__(self, output='perf-sched.data', trace_output='schedtracer.json', verbose=1):
super().__init__(output=output, verbose=verbose, args=["-e 'sched:*'"])
self.trace_output = trace_output
self.verbose = verbose
def post_process(self, *args):
verbose = '-q ' + '-v ' * self.verbose
cmd = f"perf script -i {self.output} --no-inline --ns | per4m perf2trace sched -o {self.trace_output} {verbose}"
if self.verbose >= 1:
print(cmd)
if os.system(cmd) != 0:
raise OSError(f'Failed to run perf or per4m perf2trace, command:\n$ {cmd}')
class PerfRecordGIL(PerfRecord):
def __init__(self, output='perf-gil.data', trace_output='giltracer.json', viztracer_input="viztracer.json", verbose=1):
# TODO: check output of perf probe --list="python:*gil*" to see if probes are set
super().__init__(output=output, verbose=verbose, args=["-e 'python:*gil*'", "-e pytrace:function_entry", "-e pytrace:function_return"], stacktrace=False)
# this is used to filter the giltracer data
self.viztracer_input = viztracer_input
self.trace_output = trace_output
self.verbose = verbose
def post_process(self, *args):
verbose = '-q ' + '-v ' * self.verbose
# -i {self.viztracer_input} # we don't use this ftm
cmd = f"perf script -i {self.output} --no-inline --ns | per4m perf2trace gil -o {self.trace_output} {verbose}"
if self.verbose >= 1:
print(cmd)
if os.system(cmd) != 0:
raise OSError(f'Failed to run perf or per4m perf2trace, command:\n$ {cmd}')
def main(argv=sys.argv):
parser = argparse.ArgumentParser(argv[0],
formatter_class=argparse.RawDescriptionHelpFormatter,
usage=usage)
parser.add_argument('--module', '-m')
parser.add_argument('--import', dest="import_", help="Comma seperated list of modules to import before tracing (cleans up tracing output)")
parser.add_argument('--verbose', '-v', action='count', default=1)
parser.add_argument('--quiet', '-q', action='count', default=0)
parser.add_argument('--output', '-o', dest="output", default='giltracer.html', help="Output filename (default %(default)s)")
parser.add_argument('--state-detect', help="Use perf sched events to detect if a process is sleeping due to the GIL (default: %(default)s)", default=False, action='store_true')
parser.add_argument('--no-state-detect', dest="state_detect", action='store_false')
parser.add_argument('--gil-detect', help="Use uprobes to detect who has the GIL (read README.md) (default: %(default)s)", default=True, action='store_true')
parser.add_argument('--no-gil-detect', dest="gil_detect", action='store_false')
parser.add_argument('args', nargs=argparse.REMAINDER)
args = parser.parse_args(argv[1:])
verbose = args.verbose - args.quiet
if args.import_:
for module in args.import_.split(','):
if verbose >= 2:
print(f'importing {module}')
__import__(module)
perf1 = PerfRecordSched(verbose=verbose) if args.state_detect else None
perf2 = PerfRecordGIL(verbose=verbose) if args.gil_detect else None
vt = viztracer.VizTracer(output_file="viztracer.json", verbose=verbose)
# pass on the rest of the arguments
sys.argv = args.args
if args.module:
module = runpy.run_module(args.module)
else:
module = runpy.run_path(sys.argv[0])
if perf1:
perf1.start()
if perf2:
perf2.start()
try:
vt.start()
module['main'](args.args)
finally:
vt.stop()
if perf1:
perf1.stop()
if perf2:
perf2.stop()
vt.save('viztracer.json')
if perf1:
perf1.post_process()
if perf2:
perf2.post_process()
files = ['viztracer.json']
if perf1:
files.append('schedtracer.json')
if perf2:
files.append('giltracer.json')
builder = ReportBuilder(files, verbose=verbose)
builder.save(output_file=args.output)
if __name__ == '__main__':
main()