diff -Nru pprofile-1.10.1/debian/changelog pprofile-1.11.0/debian/changelog --- pprofile-1.10.1/debian/changelog 2017-06-19 23:47:22.000000000 +0000 +++ pprofile-1.11.0/debian/changelog 2017-08-12 15:55:11.000000000 +0000 @@ -1,3 +1,12 @@ +pprofile (1.11.0-1) unstable; urgency=medium + + * New upstream release 1.11.0 + * debian/control: Bumps Standards-Version to 4.0.1, no changes needed. + * debian/control: Adds Suggests field with python{3}-ipython to + pprofile{3} package + + -- Josue Ortega Sat, 12 Aug 2017 11:55:11 -0400 + pprofile (1.10.1-2) unstable; urgency=medium * Upload to unstable. diff -Nru pprofile-1.10.1/debian/control pprofile-1.11.0/debian/control --- pprofile-1.10.1/debian/control 2017-06-19 23:47:22.000000000 +0000 +++ pprofile-1.11.0/debian/control 2017-08-12 15:55:11.000000000 +0000 @@ -8,7 +8,7 @@ python-setuptools, python3-all, python3-setuptools -Standards-Version: 3.9.8 +Standards-Version: 4.0.1 Homepage: https://github.com/vpelletier/pprofile X-Python-Version: >= 2.7 X-Python3-Version: >= 3.5 @@ -19,6 +19,7 @@ Architecture: all Depends: ${python:Depends}, ${misc:Depends}, python-pkg-resources +Suggests: python-ipython Description: Line-granularity, deterministic and statistic Python profiler Line granularity allows locating precisely where time is spent in code. Thread awareness automatically propagates profiling to all threads (all @@ -41,6 +42,7 @@ Architecture: all Depends: ${python3:Depends}, ${misc:Depends}, python3-pkg-resources +Suggests: python3-ipython Description: Line-granularity, deterministic and statistic Python 3 profiler Line granularity allows locating precisely where time is spent in code. Thread awareness automatically propagates profiling to all threads (all diff -Nru pprofile-1.10.1/pprofile.py pprofile-1.11.0/pprofile.py --- pprofile-1.10.1/pprofile.py 2017-05-01 23:31:49.000000000 +0000 +++ pprofile-1.11.0/pprofile.py 2017-07-24 00:11:19.000000000 +0000 @@ -54,14 +54,20 @@ from time import time from warnings import warn import argparse +import cStringIO import inspect import linecache import os import re import runpy +import shlex import sys import threading import zipfile +try: + from IPython.core.magic import register_line_cell_magic +except ImportError: + register_line_cell_magic = lambda x: x __all__ = ( 'ProfileBase', 'ProfileRunnerBase', 'Profile', 'ThreadProfile', @@ -297,7 +303,7 @@ def _getFileTiming(self, frame): try: - return self.global_dict[id(frame.f_globals)] + return self.global_dict[(id(frame.f_globals), frame.f_code.co_filename)] except KeyError: f_globals = frame.f_globals name = self._getFilename(frame.f_code.co_filename, f_globals) @@ -588,18 +594,18 @@ return func(*args, **kw) def runfile(self, fd, argv, fd_name='', compile_flags=0, - dont_inherit=1): + dont_inherit=1, globals={}): with fd: code = compile(fd.read(), fd_name, 'exec', flags=compile_flags, dont_inherit=dont_inherit) original_sys_argv = list(sys.argv) + ctx_globals = globals.copy() + ctx_globals['__file__'] = fd_name + ctx_globals['__name__'] = '__main__' + ctx_globals['__package__'] = None try: sys.argv[:] = argv - return self.runctx(code, { - '__file__': fd_name, - '__name__': '__main__', - '__package__': None, - }, None) + return self.runctx(code, ctx_globals, None) finally: sys.argv[:] = original_sys_argv @@ -1005,13 +1011,13 @@ """ return os.path.normpath(os.path.splitdrive(name)[1]).lstrip(_allsep) -def main(): +def _main(argv, stdin=None): format_dict = { 'text': 'annotate', 'callgrind': 'callgrind', } - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(argv[0]) parser.add_argument('script', help='Python script to execute (optionaly ' 'followed by its arguments)', nargs='?') parser.add_argument('argv', nargs=argparse.REMAINDER) @@ -1032,7 +1038,7 @@ 'profiling when 0.') parser.add_argument('-m', dest='module', help='Searches sys.path for the named module and runs the ' - 'corresponding .py file as a script. When given, positional arguments' + 'corresponding .py file as a script. When given, positional arguments ' 'become sys.argv[1:]') group = parser.add_argument_group( @@ -1053,7 +1059,7 @@ help='Include files whose name would have otherwise excluded. ' 'If no exclusion was specified, all paths are excluded first.') - options = parser.parse_args() + options = parser.parse_args(argv[1:]) if options.exclude_syspath: options.exclude.extend('^' + re.escape(x) for x in sys.path) if options.include and not options.exclude: @@ -1072,14 +1078,34 @@ if options.script is None: parser.error('too few arguments') args = [options.script] + options.argv - runner_method_args = (args[0], args) + runner_method_kw = { + 'path': args[0], + 'argv': args, + } runner_method_id = 'runpath' + elif stdin is not None and options.module == '-': + # Undocumented way of using -m, used internaly by %%pprofile + args = [''] + if options.script is not None: + args.append(options.script) + args.extend(options.argv) + import __main__ + runner_method_kw = { + 'fd': stdin, + 'argv': args, + 'fd_name': '', + 'globals': __main__.__dict__, + } + runner_method_id = 'runfile' else: args = [options.module] if options.script is not None: args.append(options.script) args.extend(options.argv) - runner_method_args = (options.module, args) + runner_method_kw = { + 'module': options.module, + 'argv': args, + } runner_method_id = 'runmodule' if options.format is None: if _isCallgrindName(options.out): @@ -1101,7 +1127,7 @@ klass = Profile prof = runner = klass(verbose=options.verbose) try: - getattr(runner, runner_method_id)(*runner_method_args) + getattr(runner, runner_method_id)(**runner_method_kw) finally: if options.out == '-': out = _reopen(sys.stdout, errors='replace') @@ -1153,5 +1179,24 @@ # do not change exit status. sys.exit(1) +def pprofile(line, cell=None): + """ + Profile line execution. + """ + if cell is None: + # TODO: detect and use arguments (statistical profiling, ...) ? + return run(line) + else: + return _main(['%%pprofile', '-m', '-'] + shlex.split(line), cStringIO.StringIO(cell)) +try: + register_line_cell_magic(pprofile) +except Exception: + # ipython can be imported, but may not be currently running. + pass +del pprofile + +def main(): + _main(sys.argv) + if __name__ == '__main__': main() diff -Nru pprofile-1.10.1/README.rst pprofile-1.11.0/README.rst --- pprofile-1.10.1/README.rst 2017-05-01 23:31:49.000000000 +0000 +++ pprofile-1.11.0/README.rst 2017-07-24 00:11:19.000000000 +0000 @@ -49,6 +49,43 @@ For advanced usage, see :code:`pprofile --help` and :code:`pydoc pprofile`. +Profiling overhead +------------------ + +pprofile default mode (`Deterministic profiling`_) has a large overhead. +Part of the reason being that it is written to be as portable as possible +(so no C extension). This large overhead can be an issue, which can be +avoided by using `Statistic profiling`_ at the cost of some result +readability decrease. + +Rule of thumb: + ++-----------------------------+----------------------------+------------------------+ +| Code to profile runs for... | `Deterministic profiling`_ | `Statistic profiling`_ | ++=============================+============================+========================+ +| a few seconds | Yes | No [#]_ | ++-----------------------------+----------------------------+------------------------+ +| a few minutes | Maybe | Yes | ++-----------------------------+----------------------------+------------------------+ +| more (ex: daemon) | No | Yes [#]_ | ++-----------------------------+----------------------------+------------------------+ + +Once you identified the hot spot and you decide you need finer-grained +profiling to understand what needs fixing, you should try to make to-profile +code run for shorter time so you can reasonably use deterministic profiling: +use a smaller data set triggering the same code path, modify the code to only +enable profiling on small pieces of code... + +.. [#] Statistic profiling will not have time to collect + enough samples to produce usable output. + +.. [#] You may want to consider triggering pprofile from + a signal handler or other IPC mechanism to profile + a shorter subset. See `zpprofile.py` for how it can + be used to profile code inside a running (zope) + service (in which case the IPC mechanism is just + Zope normal URL handling). + Output ====== diff -Nru pprofile-1.10.1/setup.py pprofile-1.11.0/setup.py --- pprofile-1.10.1/setup.py 2017-05-01 23:31:49.000000000 +0000 +++ pprofile-1.11.0/setup.py 2017-07-24 00:11:19.000000000 +0000 @@ -9,7 +9,7 @@ setup( name='pprofile', - version='1.10.1', + version='1.11.0', author='Vincent Pelletier', author_email='plr.vincent@gmail.com', description=next(x for x in description.splitlines() if x.strip()), diff -Nru pprofile-1.10.1/.travis.yml pprofile-1.11.0/.travis.yml --- pprofile-1.10.1/.travis.yml 2017-05-01 23:31:49.000000000 +0000 +++ pprofile-1.11.0/.travis.yml 2017-07-24 00:11:19.000000000 +0000 @@ -1,8 +1,10 @@ +sudo: false language: python python: - "2.7" - - "3.4" + - "3.6" - "pypy" + - "pypy3" install: pip install . script: