pyprof2calltree-1.4.5/0000755000175000017500000000000013647025044016001 5ustar pwallerpwaller00000000000000pyprof2calltree-1.4.5/pyprof2calltree.egg-info/0000755000175000017500000000000013647025044022610 5ustar pwallerpwaller00000000000000pyprof2calltree-1.4.5/pyprof2calltree.egg-info/PKG-INFO0000644000175000017500000001373013647025044023711 0ustar pwallerpwaller00000000000000Metadata-Version: 1.2 Name: pyprof2calltree Version: 1.4.5 Summary: Help visualize profiling data from cProfile with kcachegrind and qcachegrind Home-page: https://github.com/pwaller/pyprof2calltree/ Author: Olivier Grisel Author-email: olivier.grisel@ensta.org Maintainer: Peter Waller Maintainer-email: p@pwaller.net License: MIT Description: Overview ======== Script to help visualize profiling data collected with the cProfile Python module with the kcachegrind_ (screenshots_) graphical calltree analyser. This is a rebranding of the venerable http://www.gnome.org/~johan/lsprofcalltree.py script by David Allouche et Al. It aims at making it easier to distribute (e.g. through PyPI) and behave more like the scripts of the debian kcachegrind-converters_ package. The final goal is to make it part of the official upstream kdesdk_ package. .. _kcachegrind: http://kcachegrind.sourceforge.net .. _kcachegrind-converters: https://packages.debian.org/en/stable/kcachegrind-converters .. _kdesdk: http://websvn.kde.org/trunk/KDE/kdesdk/kcachegrind/converters/ .. _screenshots: http://images.google.fr/images?q=kcachegrind Command line usage ================== Upon installation you should have a `pyprof2calltree` script in your path:: $ pyprof2calltree --help usage: pyprof2calltree [-h] [-o output_file_path] [-i input_file_path] [-k] [-r scriptfile [args ...]] optional arguments: -h, --help show this help message and exit -o output_file_path, --outfile output_file_path Save calltree stats to -i input_file_path, --infile input_file_path Read Python stats from -k, --kcachegrind Run the kcachegrind tool on the converted data -r scriptfile [args ...], --run-script scriptfile [args ...] Name of the Python script to run to collect profiling data Python shell usage ================== `pyprof2calltree` is also best used from an interactive Python shell such as the default shell. For instance let us profile XML parsing:: >>> from xml.etree import ElementTree >>> from cProfile import Profile >>> xml_content = '\n' + '\ttext\n' * 100 + '' >>> profiler = Profile() >>> profiler.runctx( ... "ElementTree.fromstring(xml_content)", ... locals(), globals()) >>> from pyprof2calltree import convert, visualize >>> visualize(profiler.getstats()) # run kcachegrind >>> convert(profiler.getstats(), 'profiling_results.kgrind') # save for later or with the ipython_:: In [1]: %doctest_mode Exception reporting mode: Plain Doctest mode is: ON >>> from xml.etree import ElementTree >>> xml_content = '\n' + '\ttext\n' * 100 + '' >>> %prun -D out.stats ElementTree.fromstring(xml_content) *** Profile stats marshalled to file 'out.stats' >>> from pyprof2calltree import convert, visualize >>> visualize('out.stats') >>> convert('out.stats', 'out.kgrind') >>> results = %prun -r ElementTree.fromstring(xml_content) >>> visualize(results) .. _ipython: https://ipython.org/ Change log ========== - 1.4.5 - 2020-04-19: Nothing user facing - changes to testing and remove deprecated eggecutable - 1.4.4 - 2018-10-19: Numerous small improvements, drop support for EOL python versions - 1.4.3 - 2017-07-28: Windows support (fixed is_installed check - #21) - 1.4.2 - 2017-07-19: No feature or bug fixes, just license clarification (#20) - 1.4.1 - 2017-05-20: No feature or bug fixes, just test distribution (#17) - 1.4.0 - 2016-09-03: Support multiple functions with the same name, tick unit from millis to nanos, tests added (#15) - 1.3.2 - 2014-07-05: Bugfix: correct source file paths (#12) - 1.3.1 - 2013-11-27: Bugfix for broken output writing on Python 3 (#8) - 1.3.0 - 2013-11-19: qcachegrind support - 1.2.0 - 2013-11-09: Python 3 support - 1.1.1 - 2013-09-25: Miscellaneous bugfixes - 1.1.0 - 2008-12-21: integrate fix in conversion by David Glick - 1.0.3 - 2008-10-16: fix typos in 1.0 release - 1.0 - 2008-10-16: initial release under the pyprof2calltree name Keywords: profiler visualization programming tool kde kcachegrind qcachegrind Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: X11 Applications :: KDE Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Desktop Environment :: K Desktop Environment (KDE) Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: System :: System Shells Classifier: Topic :: Utilities Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* pyprof2calltree-1.4.5/pyprof2calltree.egg-info/SOURCES.txt0000644000175000017500000000057413647025044024502 0ustar pwallerpwaller00000000000000CONTRIBUTORS.txt LICENSE MANIFEST.in README.rst pyprof2calltree.py setup.cfg setup.py pyprof2calltree.egg-info/PKG-INFO pyprof2calltree.egg-info/SOURCES.txt pyprof2calltree.egg-info/dependency_links.txt pyprof2calltree.egg-info/entry_points.txt pyprof2calltree.egg-info/top_level.txt pyprof2calltree.egg-info/zip-safe test/__init__.py test/profile_code.py test/test_integration.pypyprof2calltree-1.4.5/pyprof2calltree.egg-info/dependency_links.txt0000644000175000017500000000000113647025044026656 0ustar pwallerpwaller00000000000000 pyprof2calltree-1.4.5/pyprof2calltree.egg-info/entry_points.txt0000644000175000017500000000007213647025044026105 0ustar pwallerpwaller00000000000000[console_scripts] pyprof2calltree = pyprof2calltree:main pyprof2calltree-1.4.5/pyprof2calltree.egg-info/top_level.txt0000644000175000017500000000002013647025044025332 0ustar pwallerpwaller00000000000000pyprof2calltree pyprof2calltree-1.4.5/pyprof2calltree.egg-info/zip-safe0000644000175000017500000000000113647025041024235 0ustar pwallerpwaller00000000000000 pyprof2calltree-1.4.5/test/0000755000175000017500000000000013647025044016760 5ustar pwallerpwaller00000000000000pyprof2calltree-1.4.5/test/__init__.py0000644000175000017500000000000013432102626021051 0ustar pwallerpwaller00000000000000pyprof2calltree-1.4.5/test/profile_code.py0000644000175000017500000000446613432102626021770 0ustar pwallerpwaller00000000000000# We're going to use a custom timer, so we don't actually have to do anything # in these functions. def top(): mid1() mid2() mid3(5) C1.samename() C2.samename() def mid1(): bot() for i in range(5): mid2() bot() def mid2(): bot() def bot(): pass def mid3(x): if x > 0: mid4(x) def mid4(x): mid3(x - 1) class C1(object): @staticmethod def samename(): pass class C2(object): @staticmethod def samename(): pass expected_output_py2 = """event: ns : Nanoseconds events: ns summary: 59000 fl= fn=top 5 6000 cfl= cfn=mid1 calls=1 13 5 27000 cfl= cfn=mid2 calls=1 20 5 3000 cfl= cfn=mid3 calls=1 28 5 21000 cfl= cfn=samename:38 calls=1 38 5 1000 cfl= cfn=samename:44 calls=1 44 5 1000 fl= fn=mid1 13 9000 cfl= cfn=mid2 calls=5 20 13 15000 cfl= cfn=bot calls=2 24 13 2000 cfl=~ cfn= calls=1 0 13 1000 fl= fn=mid2 20 12000 cfl= cfn=bot calls=6 24 20 6000 fl= fn=bot 24 8000 fl= fn=mid3 28 11000 cfl= cfn=mid4 calls=5 33 28 19000 fl= fn=mid4 33 10000 cfl= cfn=mid3 calls=5 28 33 17000 fl= fn=samename:38 38 1000 fl= fn=samename:44 44 1000 fl=~ fn= 0 1000 fl=~ fn= 0 1000 """.replace('', top.__code__.co_filename) expected_output_py3 = """event: ns : Nanoseconds events: ns summary: 57000 fl= fn=top 5 6000 cfl= cfn=mid1 calls=1 13 5 25000 cfl= cfn=mid2 calls=1 20 5 3000 cfl= cfn=mid3 calls=1 28 5 21000 cfl= cfn=samename:38 calls=1 38 5 1000 cfl= cfn=samename:44 calls=1 44 5 1000 fl= fn=mid1 13 8000 cfl= cfn=mid2 calls=5 20 13 15000 cfl= cfn=bot calls=2 24 13 2000 fl= fn=mid2 20 12000 cfl= cfn=bot calls=6 24 20 6000 fl= fn=bot 24 8000 fl= fn=mid3 28 11000 cfl= cfn=mid4 calls=5 33 28 19000 fl= fn=mid4 33 10000 cfl= cfn=mid3 calls=5 28 33 17000 fl= fn=samename:38 38 1000 fl= fn=samename:44 44 1000 fl=~ fn= 0 1000 """.replace('', __file__) pyprof2calltree-1.4.5/test/test_integration.py0000644000175000017500000000247013432102626022711 0ustar pwallerpwaller00000000000000import cProfile import pstats import sys import unittest from pyprof2calltree import CalltreeConverter from .profile_code import expected_output_py2, expected_output_py3, top try: from cStringIO import StringIO except ImportError: from io import StringIO if sys.version_info < (3, 0): expected_output = expected_output_py2 else: expected_output = expected_output_py3 class MockTimeProfile(cProfile.Profile): def __init__(self): self._mock_time = 0 super(MockTimeProfile, self).__init__(self._timer, 1e-9) def _timer(self): now = self._mock_time self._mock_time += 1000 return now class TestIntegration(unittest.TestCase): def setUp(self): self.profile = MockTimeProfile() self.profile.enable() top() self.profile.disable() def test_direct_entries(self): entries = self.profile.getstats() converter = CalltreeConverter(entries) out_file = StringIO() converter.output(out_file) self.assertEqual(out_file.getvalue(), expected_output) def test_pstats_data(self): stats = pstats.Stats(self.profile) converter = CalltreeConverter(stats) out_file = StringIO() converter.output(out_file) self.assertEqual(out_file.getvalue(), expected_output) pyprof2calltree-1.4.5/CONTRIBUTORS.txt0000644000175000017500000000225713432102626020477 0ustar pwallerpwaller00000000000000# Contributions to the pyprof2calltree project ## Creators * David Allouche * Jp Calderone * Itamar Shtull-Trauring * Johan Dahlin ## Maintainer * Peter Waller ## Contributors In chronological order: * Olivier Grisel * Repackaging and pstats support * David Glick * Fix in conversion algorithm * Peter Waller * Taking over PyPI maintainance * Steven Maude * Breaking things, documentation * Lukas Graf * Python 3.x compatibility * Jamie Wong * qcachegrind support * Yury V. Zaytsev * Bugfixes * Michael Droettboom * Source code display support * Zev Benjamin * Support for multiple functions with the same name * Tests * Jon Dufresne * A huge number of small fixes and consistency improvements across code, docs and setup.py alike. * [Your name or handle] <[email or website]> * [Brief summary of your changes] ## Thanks * Jon Dufresne for prompting a new release and many tidy up fixes! * Uwe L. Korn for pointing out mismatch in licensing pyprof2calltree-1.4.5/LICENSE0000644000175000017500000000221713432102626017002 0ustar pwallerpwaller00000000000000Copyright (c) 2006-2017 David Allouche, Jp Calderone, Itamar Shtull-Trauring, Johan Dahlin, Peter Waller and people listed in CONTRIBUTORS.txt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyprof2calltree-1.4.5/MANIFEST.in0000644000175000017500000000010513432102626017525 0ustar pwallerpwaller00000000000000include LICENSE include CONTRIBUTORS.txt recursive-include test *.py pyprof2calltree-1.4.5/README.rst0000644000175000017500000000743313647025005017474 0ustar pwallerpwaller00000000000000Overview ======== Script to help visualize profiling data collected with the cProfile Python module with the kcachegrind_ (screenshots_) graphical calltree analyser. This is a rebranding of the venerable http://www.gnome.org/~johan/lsprofcalltree.py script by David Allouche et Al. It aims at making it easier to distribute (e.g. through PyPI) and behave more like the scripts of the debian kcachegrind-converters_ package. The final goal is to make it part of the official upstream kdesdk_ package. .. _kcachegrind: http://kcachegrind.sourceforge.net .. _kcachegrind-converters: https://packages.debian.org/en/stable/kcachegrind-converters .. _kdesdk: http://websvn.kde.org/trunk/KDE/kdesdk/kcachegrind/converters/ .. _screenshots: http://images.google.fr/images?q=kcachegrind Command line usage ================== Upon installation you should have a `pyprof2calltree` script in your path:: $ pyprof2calltree --help usage: pyprof2calltree [-h] [-o output_file_path] [-i input_file_path] [-k] [-r scriptfile [args ...]] optional arguments: -h, --help show this help message and exit -o output_file_path, --outfile output_file_path Save calltree stats to -i input_file_path, --infile input_file_path Read Python stats from -k, --kcachegrind Run the kcachegrind tool on the converted data -r scriptfile [args ...], --run-script scriptfile [args ...] Name of the Python script to run to collect profiling data Python shell usage ================== `pyprof2calltree` is also best used from an interactive Python shell such as the default shell. For instance let us profile XML parsing:: >>> from xml.etree import ElementTree >>> from cProfile import Profile >>> xml_content = '\n' + '\ttext\n' * 100 + '' >>> profiler = Profile() >>> profiler.runctx( ... "ElementTree.fromstring(xml_content)", ... locals(), globals()) >>> from pyprof2calltree import convert, visualize >>> visualize(profiler.getstats()) # run kcachegrind >>> convert(profiler.getstats(), 'profiling_results.kgrind') # save for later or with the ipython_:: In [1]: %doctest_mode Exception reporting mode: Plain Doctest mode is: ON >>> from xml.etree import ElementTree >>> xml_content = '\n' + '\ttext\n' * 100 + '' >>> %prun -D out.stats ElementTree.fromstring(xml_content) *** Profile stats marshalled to file 'out.stats' >>> from pyprof2calltree import convert, visualize >>> visualize('out.stats') >>> convert('out.stats', 'out.kgrind') >>> results = %prun -r ElementTree.fromstring(xml_content) >>> visualize(results) .. _ipython: https://ipython.org/ Change log ========== - 1.4.5 - 2020-04-19: Nothing user facing - changes to testing and remove deprecated eggecutable - 1.4.4 - 2018-10-19: Numerous small improvements, drop support for EOL python versions - 1.4.3 - 2017-07-28: Windows support (fixed is_installed check - #21) - 1.4.2 - 2017-07-19: No feature or bug fixes, just license clarification (#20) - 1.4.1 - 2017-05-20: No feature or bug fixes, just test distribution (#17) - 1.4.0 - 2016-09-03: Support multiple functions with the same name, tick unit from millis to nanos, tests added (#15) - 1.3.2 - 2014-07-05: Bugfix: correct source file paths (#12) - 1.3.1 - 2013-11-27: Bugfix for broken output writing on Python 3 (#8) - 1.3.0 - 2013-11-19: qcachegrind support - 1.2.0 - 2013-11-09: Python 3 support - 1.1.1 - 2013-09-25: Miscellaneous bugfixes - 1.1.0 - 2008-12-21: integrate fix in conversion by David Glick - 1.0.3 - 2008-10-16: fix typos in 1.0 release - 1.0 - 2008-10-16: initial release under the pyprof2calltree name pyprof2calltree-1.4.5/pyprof2calltree.py0000755000175000017500000003255013432102626021472 0ustar pwallerpwaller00000000000000#!/usr/bin/env python # Copyright (c) 2006-2008, David Allouche, Jp Calderone, Itamar Shtull-Trauring, # Johan Dahlin, Olivier Grisel # # Send maintenance requests needing new PyPI packages to: # Peter Waller # https://github.com/pwaller/pyprof2calltree # # See CONTRIBUTORS.txt. # # All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """pyprof2calltree: profiling output which is readable by kcachegrind This script can either take raw cProfile.Profile.getstats() log entries or take a previously recorded instance of the pstats.Stats class. """ from __future__ import unicode_literals import argparse import cProfile import errno import io import os import pstats import subprocess import sys import tempfile from collections import defaultdict __all__ = ['convert', 'visualize', 'CalltreeConverter'] SCALE = 1e9 class Code(object): def __init__(self, filename, firstlineno, name): self.co_filename = filename self.co_firstlineno = firstlineno self.co_name = name def __repr__(self): return '' % (self.co_filename, self.co_firstlineno, self.co_name) class Entry(object): def __init__(self, code, callcount, reccallcount, inlinetime, totaltime, calls): self.code = code self.callcount = callcount self.reccallcount = reccallcount self.inlinetime = inlinetime self.totaltime = totaltime self.calls = calls def __repr__(self): return '' % ( self.code, self.callcount, self.reccallcount, self.inlinetime, self.totaltime, self.calls ) class Subentry(object): def __init__(self, code, callcount, reccallcount, inlinetime, totaltime): self.code = code self.callcount = callcount self.reccallcount = reccallcount self.inlinetime = inlinetime self.totaltime = totaltime def __repr__(self): return '' % ( self.code, self.callcount, self.reccallcount, self.inlinetime, self.totaltime ) def is_basestring(s): try: unicode # Python 2.x return isinstance(s, basestring) except NameError: # Python 3.x return isinstance(s, (str, bytes)) def pstats2entries(data): """Helper to convert serialized pstats back to a list of raw entries. Converse operation of cProfile.Profile.snapshot_stats() """ # Each entry's key is a tuple of (filename, line number, function name) entries = {} allcallers = {} # first pass over stats to build the list of entry instances for code_info, call_info in data.stats.items(): # build a fake code object code = Code(*code_info) # build a fake entry object. entry.calls will be filled during the # second pass over stats cc, nc, tt, ct, callers = call_info entry = Entry(code, callcount=cc, reccallcount=nc - cc, inlinetime=tt, totaltime=ct, calls=[]) # collect the new entry entries[code_info] = entry allcallers[code_info] = list(callers.items()) # second pass of stats to plug callees into callers for entry in entries.values(): entry_label = cProfile.label(entry.code) entry_callers = allcallers.get(entry_label, []) for entry_caller, call_info in entry_callers: cc, nc, tt, ct = call_info subentry = Subentry(entry.code, callcount=cc, reccallcount=nc - cc, inlinetime=tt, totaltime=ct) # entry_caller has the same form as code_info entries[entry_caller].calls.append(subentry) return list(entries.values()) def is_installed(prog): """Return whether or not a given executable is installed on the machine.""" with open(os.devnull, 'w') as devnull: try: if os.name == 'nt': retcode = subprocess.call(['where', prog], stdout=devnull) else: retcode = subprocess.call(['which', prog], stdout=devnull) except OSError as e: # If where or which doesn't exist, a "ENOENT" error will occur (The # FileNotFoundError subclass on Python 3). if e.errno != errno.ENOENT: raise retcode = 1 return retcode == 0 def _entry_sort_key(entry): return cProfile.label(entry.code) KCACHEGRIND_EXECUTABLES = ["kcachegrind", "qcachegrind"] class CalltreeConverter(object): """Convert raw cProfile or pstats data to the calltree format""" def __init__(self, profiling_data): if is_basestring(profiling_data): # treat profiling_data as a filename of pstats serialized data self.entries = pstats2entries(pstats.Stats(profiling_data)) elif isinstance(profiling_data, pstats.Stats): # convert pstats data to cProfile list of entries self.entries = pstats2entries(profiling_data) else: # assume this are direct cProfile entries self.entries = profiling_data self.out_file = None self._code_by_position = defaultdict(set) self._populate_code_by_position() def _populate_code_by_position(self): for entry in self.entries: self._add_code_by_position(entry.code) if not entry.calls: continue for subentry in entry.calls: self._add_code_by_position(subentry.code) def _add_code_by_position(self, code): co_filename, _, co_name = cProfile.label(code) self._code_by_position[(co_filename, co_name)].add(code) def munged_function_name(self, code): co_filename, co_firstlineno, co_name = cProfile.label(code) if len(self._code_by_position[(co_filename, co_name)]) == 1: return co_name return "%s:%d" % (co_name, co_firstlineno) def output(self, out_file): """Write the converted entries to out_file""" self.out_file = out_file out_file.write('event: ns : Nanoseconds\n') out_file.write('events: ns\n') self._output_summary() for entry in sorted(self.entries, key=_entry_sort_key): self._output_entry(entry) def visualize(self): """Launch kcachegrind on the converted entries. One of the executables listed in KCACHEGRIND_EXECUTABLES must be present in the system path. """ available_cmd = None for cmd in KCACHEGRIND_EXECUTABLES: if is_installed(cmd): available_cmd = cmd break if available_cmd is None: sys.stderr.write("Could not find kcachegrind. Tried: %s\n" % ", ".join(KCACHEGRIND_EXECUTABLES)) return if self.out_file is None: fd, outfile = tempfile.mkstemp(".log", "pyprof2calltree") use_temp_file = True else: outfile = self.out_file.name use_temp_file = False try: if use_temp_file: with io.open(fd, "w") as f: self.output(f) subprocess.call([available_cmd, outfile]) finally: # clean the temporary file if use_temp_file: os.remove(outfile) self.out_file = None def _output_summary(self): max_cost = 0 for entry in self.entries: totaltime = int(entry.totaltime * SCALE) max_cost = max(max_cost, totaltime) # Version 0.7.4 of kcachegrind appears to ignore the summary line and # calculate the total cost by summing the exclusive cost of all # functions, but it doesn't hurt to output it anyway. self.out_file.write('summary: %d\n' % (max_cost,)) def _output_entry(self, entry): out_file = self.out_file code = entry.code co_filename, co_firstlineno, co_name = cProfile.label(code) munged_name = self.munged_function_name(code) out_file.write('fl=%s\nfn=%s\n' % (co_filename, munged_name)) inlinetime = int(entry.inlinetime * SCALE) out_file.write('%d %d\n' % (co_firstlineno, inlinetime)) # recursive calls are counted in entry.calls if entry.calls: for subentry in sorted(entry.calls, key=_entry_sort_key): self._output_subentry(co_firstlineno, subentry.code, subentry.callcount, int(subentry.totaltime * SCALE)) out_file.write('\n') def _output_subentry(self, lineno, code, callcount, totaltime): out_file = self.out_file co_filename, co_firstlineno, co_name = cProfile.label(code) munged_name = self.munged_function_name(code) out_file.write('cfl=%s\ncfn=%s\n' % (co_filename, munged_name)) out_file.write('calls=%d %d\n' % (callcount, co_firstlineno)) out_file.write('%d %d\n' % (lineno, totaltime)) def main(): """Execute the converter using parameters provided on the command line""" parser = argparse.ArgumentParser() parser.add_argument('-o', '--outfile', metavar='output_file_path', help="Save calltree stats to ") parser.add_argument('-i', '--infile', metavar='input_file_path', help="Read Python stats from ") parser.add_argument('-k', '--kcachegrind', help="Run the kcachegrind tool on the converted data", action="store_true") parser.add_argument('-r', '--run-script', nargs=argparse.REMAINDER, metavar=('scriptfile', 'args'), dest='script', help="Name of the Python script to run to collect" " profiling data") args = parser.parse_args() outfile = args.outfile if args.script is not None: # collect profiling data by running the given script if not args.outfile: outfile = '%s.log' % os.path.basename(args.script[0]) fd, tmp_path = tempfile.mkstemp(suffix='.prof', prefix='pyprof2calltree') os.close(fd) try: cmd = [ sys.executable, '-m', 'cProfile', '-o', tmp_path, ] cmd.extend(args.script) subprocess.check_call(cmd) kg = CalltreeConverter(tmp_path) finally: os.remove(tmp_path) elif args.infile is not None: # use the profiling data from some input file if not args.outfile: outfile = '%s.log' % os.path.basename(args.infile) if args.infile == outfile: # prevent name collisions by appending another extension outfile += ".log" kg = CalltreeConverter(pstats.Stats(args.infile)) else: # at least an input file or a script to run is required parser.print_usage() sys.exit(2) if args.outfile is not None or not args.kcachegrind: # user either explicitly required output file or requested by not # explicitly asking to launch kcachegrind sys.stderr.write("writing converted data to: %s\n" % outfile) with open(outfile, 'w') as f: kg.output(f) if args.kcachegrind: sys.stderr.write("launching kcachegrind\n") kg.visualize() def visualize(profiling_data): """launch the kcachegrind on `profiling_data` `profiling_data` can either be: - a pstats.Stats instance - the filename of a pstats.Stats dump - the result of a call to cProfile.Profile.getstats() """ converter = CalltreeConverter(profiling_data) converter.visualize() def convert(profiling_data, outputfile): """convert `profiling_data` to calltree format and dump it to `outputfile` `profiling_data` can either be: - a pstats.Stats instance - the filename of a pstats.Stats dump - the result of a call to cProfile.Profile.getstats() `outputfile` can either be: - a file() instance open in write mode - a filename """ converter = CalltreeConverter(profiling_data) if is_basestring(outputfile): with open(outputfile, "w") as f: converter.output(f) else: converter.output(outputfile) if __name__ == '__main__': sys.exit(main()) pyprof2calltree-1.4.5/setup.cfg0000644000175000017500000000044513647025044017625 0ustar pwallerpwaller00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE [flake8] max-line-length = 88 [isort] combine_as_imports = True force_grid_wrap = 0 include_trailing_comma = True line_length = 88 multi_line_output = 3 not_skip = __init__.py skip = .tox/ [egg_info] tag_build = tag_date = 0 pyprof2calltree-1.4.5/setup.py0000644000175000017500000000346013647024733017522 0ustar pwallerpwaller00000000000000from setuptools import setup version = '1.4.5' def readall(path): with open(path) as f: return f.read() setup( name='pyprof2calltree', version=version, description=( "Help visualize profiling data from cProfile with kcachegrind and qcachegrind" ), long_description=readall('README.rst'), keywords='profiler visualization programming tool kde kcachegrind qcachegrind', classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: X11 Applications :: KDE", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Topic :: Desktop Environment :: K Desktop Environment (KDE)", "Topic :: Software Development", "Topic :: Software Development :: Quality Assurance", "Topic :: System :: System Shells", "Topic :: Utilities", ], author='Olivier Grisel', author_email='olivier.grisel@ensta.org', maintainer='Peter Waller', maintainer_email='p@pwaller.net', url='https://github.com/pwaller/pyprof2calltree/', license='MIT', py_modules=['pyprof2calltree'], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", zip_safe=True, entry_points={ 'console_scripts': [ 'pyprof2calltree = pyprof2calltree:main', ], } ) pyprof2calltree-1.4.5/PKG-INFO0000644000175000017500000001373013647025044017102 0ustar pwallerpwaller00000000000000Metadata-Version: 1.2 Name: pyprof2calltree Version: 1.4.5 Summary: Help visualize profiling data from cProfile with kcachegrind and qcachegrind Home-page: https://github.com/pwaller/pyprof2calltree/ Author: Olivier Grisel Author-email: olivier.grisel@ensta.org Maintainer: Peter Waller Maintainer-email: p@pwaller.net License: MIT Description: Overview ======== Script to help visualize profiling data collected with the cProfile Python module with the kcachegrind_ (screenshots_) graphical calltree analyser. This is a rebranding of the venerable http://www.gnome.org/~johan/lsprofcalltree.py script by David Allouche et Al. It aims at making it easier to distribute (e.g. through PyPI) and behave more like the scripts of the debian kcachegrind-converters_ package. The final goal is to make it part of the official upstream kdesdk_ package. .. _kcachegrind: http://kcachegrind.sourceforge.net .. _kcachegrind-converters: https://packages.debian.org/en/stable/kcachegrind-converters .. _kdesdk: http://websvn.kde.org/trunk/KDE/kdesdk/kcachegrind/converters/ .. _screenshots: http://images.google.fr/images?q=kcachegrind Command line usage ================== Upon installation you should have a `pyprof2calltree` script in your path:: $ pyprof2calltree --help usage: pyprof2calltree [-h] [-o output_file_path] [-i input_file_path] [-k] [-r scriptfile [args ...]] optional arguments: -h, --help show this help message and exit -o output_file_path, --outfile output_file_path Save calltree stats to -i input_file_path, --infile input_file_path Read Python stats from -k, --kcachegrind Run the kcachegrind tool on the converted data -r scriptfile [args ...], --run-script scriptfile [args ...] Name of the Python script to run to collect profiling data Python shell usage ================== `pyprof2calltree` is also best used from an interactive Python shell such as the default shell. For instance let us profile XML parsing:: >>> from xml.etree import ElementTree >>> from cProfile import Profile >>> xml_content = '\n' + '\ttext\n' * 100 + '' >>> profiler = Profile() >>> profiler.runctx( ... "ElementTree.fromstring(xml_content)", ... locals(), globals()) >>> from pyprof2calltree import convert, visualize >>> visualize(profiler.getstats()) # run kcachegrind >>> convert(profiler.getstats(), 'profiling_results.kgrind') # save for later or with the ipython_:: In [1]: %doctest_mode Exception reporting mode: Plain Doctest mode is: ON >>> from xml.etree import ElementTree >>> xml_content = '\n' + '\ttext\n' * 100 + '' >>> %prun -D out.stats ElementTree.fromstring(xml_content) *** Profile stats marshalled to file 'out.stats' >>> from pyprof2calltree import convert, visualize >>> visualize('out.stats') >>> convert('out.stats', 'out.kgrind') >>> results = %prun -r ElementTree.fromstring(xml_content) >>> visualize(results) .. _ipython: https://ipython.org/ Change log ========== - 1.4.5 - 2020-04-19: Nothing user facing - changes to testing and remove deprecated eggecutable - 1.4.4 - 2018-10-19: Numerous small improvements, drop support for EOL python versions - 1.4.3 - 2017-07-28: Windows support (fixed is_installed check - #21) - 1.4.2 - 2017-07-19: No feature or bug fixes, just license clarification (#20) - 1.4.1 - 2017-05-20: No feature or bug fixes, just test distribution (#17) - 1.4.0 - 2016-09-03: Support multiple functions with the same name, tick unit from millis to nanos, tests added (#15) - 1.3.2 - 2014-07-05: Bugfix: correct source file paths (#12) - 1.3.1 - 2013-11-27: Bugfix for broken output writing on Python 3 (#8) - 1.3.0 - 2013-11-19: qcachegrind support - 1.2.0 - 2013-11-09: Python 3 support - 1.1.1 - 2013-09-25: Miscellaneous bugfixes - 1.1.0 - 2008-12-21: integrate fix in conversion by David Glick - 1.0.3 - 2008-10-16: fix typos in 1.0 release - 1.0 - 2008-10-16: initial release under the pyprof2calltree name Keywords: profiler visualization programming tool kde kcachegrind qcachegrind Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: X11 Applications :: KDE Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Topic :: Desktop Environment :: K Desktop Environment (KDE) Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: System :: System Shells Classifier: Topic :: Utilities Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*