Created
June 15, 2016 14:34
-
-
Save nickvandewiele/4b170e710e8829a60e025de268625301 to your computer and use it in GitHub Desktop.
A standalone script to convert a cProfile CPU-profiling *.prof file into a graph.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import subprocess | |
import logging | |
import pstats | |
try: | |
from gprof2dot import PstatsParser, DotWriter, SAMPLES, themes | |
except ImportError: | |
logging.warning( | |
'Trouble importing from package gprof2dot. Unable to create a graph of the profile statistics.') | |
logging.warning( | |
'Try getting the latest version with something like `pip install --upgrade gprof2dot`.') | |
class Tee: | |
"""A simple tee to create a stream which prints to many streams. | |
This is used to report the profiling statistics to both the log file | |
and the standard output. | |
""" | |
def __init__(self, *fileobjects): | |
self.fileobjects=fileobjects | |
def write(self, string): | |
for fileobject in self.fileobjects: | |
fileobject.write(string) | |
def main(stats_file): | |
processProfileStats(stats_file) | |
makeProfileGraph(stats_file) | |
def processProfileStats(stats_file): | |
out_stream = Tee(sys.stdout) | |
stats = pstats.Stats(stats_file, stream=out_stream) | |
stats.strip_dirs() | |
print >>out_stream, "Sorted by internal time" | |
stats.sort_stats('time') | |
stats.print_stats(25) | |
stats.print_callers(25) | |
print >>out_stream, "Sorted by cumulative time" | |
stats.sort_stats('cumulative') | |
stats.print_stats(25) | |
stats.print_callers(25) | |
stats.print_callees(25) | |
def makeProfileGraph(stats_file): | |
""" | |
Uses gprof2dot to create a graphviz dot file of the profiling information. | |
This requires the gprof2dot package available via `pip install gprof2dot`. | |
Render the result using the program 'dot' via a command like | |
`dot -Tpdf input.dot -o output.pdf`. | |
""" | |
# create an Options class to mimic optparser output as much as possible: | |
class Options: | |
pass | |
options = Options() | |
options.node_thres = 0.8 | |
options.edge_thres = 0.1 | |
options.strip = False | |
options.show_samples = False | |
options.root = "" | |
options.leaf = "" | |
options.wrap = True | |
theme = themes['color'] # bw color gray pink | |
# default "Arial" leads to PostScript warnings in dot (on Mac OS) | |
theme.fontname = "ArialMT" | |
parser = PstatsParser(stats_file) | |
profile = parser.parse() | |
dot_file = stats_file + '.dot' | |
output = open(dot_file, 'wt') | |
dot = DotWriter(output) | |
dot.strip = options.strip | |
dot.wrap = options.wrap | |
if options.show_samples: | |
dot.show_function_events.append(SAMPLES) | |
profile = profile | |
profile.prune(options.node_thres / 100.0, options.edge_thres / 100.0) | |
if options.root: | |
rootId = profile.getFunctionId(options.root) | |
if not rootId: | |
sys.stderr.write( | |
'root node ' + options.root + ' not found (might already be pruned : try -e0 -n0 flags)\n') | |
sys.exit(1) | |
profile.prune_root(rootId) | |
if options.leaf: | |
leafId = profile.getFunctionId(options.leaf) | |
if not leafId: | |
sys.stderr.write( | |
'leaf node ' + options.leaf + ' not found (maybe already pruned : try -e0 -n0 flags)\n') | |
sys.exit(1) | |
profile.prune_leaf(leafId) | |
dot.graph(profile, theme) | |
output.close() | |
try: | |
subprocess.check_call( | |
['dot', '-Tpdf', dot_file, '-o', '{0}.pdf'.format(dot_file)]) | |
except subprocess.CalledProcessError: | |
logging.error( | |
"Error returned by 'dot' when generating graph of the profile statistics.") | |
logging.info( | |
"To try it yourself:\n dot -Tpdf {0} -o {0}.pdf".format(dot_file)) | |
except OSError: | |
logging.error( | |
"Couldn't run 'dot' to create graph of profile statistics. Check graphviz is installed properly and on your path.") | |
logging.info( | |
"Once you've got it, try:\n dot -Tpdf {0} -o {0}.pdf".format(dot_file)) | |
else: | |
logging.info( | |
"Graph of profile statistics saved to: \n {0}.pdf".format(dot_file)) | |
if __name__ == "__main__": | |
stats_file = sys.argv[1] | |
main(stats_file) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment