def profile(fct, sort='cumulative', rootrem=None, as_df=False, pyinst_format=None, **kwargs): """ Profiles the execution of a function. @param fct function to profile @param sort see `sort_stats <https://docs.python.org/3/library/ profile.html#pstats.Stats.sort_stats>`_ @param rootrem root to remove in filenames @param as_df return the results as a dataframe and not text @param pyinst_format format for :epkg:`pyinstrument`, if not empty, the function uses this module or raises an exception if not installed, the options are *text*, *textu* (text with colors), *json*, *html* @param kwargs additional parameters used to create the profiler @return raw results, statistics text dump (or dataframe is *as_df* is True) .. plot:: import matplotlib.pyplot as plt from pyquickhelper.pycode.profiling import profile from pyquickhelper.texthelper import compare_module_version def fctm(): return compare_module_version('0.20.4', '0.22.dev0') pr, df = profile(lambda: [fctm() for i in range(0, 1000)], as_df=True) ax = df[['namefct', 'cum_tall']].head(n=15).set_index( 'namefct').plot(kind='bar', figsize=(8, 3), rot=30) ax.set_title("example of a graph") for la in ax.get_xticklabels(): la.set_horizontalalignment('right'); plt.show() """ if pyinst_format is None: pr = cProfile.Profile(**kwargs) pr.enable() fct() pr.disable() s = StringIO() ps = pstats.Stats(pr, stream=s).sort_stats(sort) ps.print_stats() res = s.getvalue() try: pack = site.getsitepackages() except AttributeError: # pragma: no cover import numpy pack = os.path.normpath( os.path.abspath( os.path.join(os.path.dirname(numpy.__file__), ".."))) pack = [pack] pack_ = os.path.normpath(os.path.join(pack[-1], '..')) def clean_text(res): res = res.replace(pack[-1], "site-packages") res = res.replace(pack_, "lib") if rootrem is not None: if isinstance(rootrem, str): res = res.replace(rootrem, '') else: for sub in rootrem: if isinstance(sub, str): res = res.replace(sub, '') elif isinstance(sub, tuple) and len(sub) == 2: res = res.replace(sub[0], sub[1]) else: raise TypeError( "rootrem must contains strings or tuple not {0}" .format(rootrem)) return res if as_df: def better_name(row): if len(row['fct']) > 15: return "{}-{}".format(row['file'].split(':')[-1], row['fct']) name = row['file'].replace("\\", "/") return "{}-{}".format(name.split('/')[-1], row['fct']) rows = _process_pstats(ps, clean_text) import pandas df = pandas.DataFrame(rows) df = df[[ 'fct', 'file', 'ncalls1', 'ncalls2', 'tin', 'cum_tin', 'tall', 'cum_tall' ]] df['namefct'] = df.apply(lambda row: better_name(row), axis=1) df = df.groupby(['namefct', 'file'], as_index=False).sum().sort_values( 'cum_tall', ascending=False).reset_index(drop=True) return ps, df else: res = clean_text(res) return ps, res elif as_df: raise ValueError( # pragma: no cover "as_df is not a compatible option with pyinst_format") else: try: from pyinstrument import Profiler except ImportError as e: # pragma: no cover raise ImportError("pyinstrument is not installed.") from e profiler = Profiler(**kwargs) profiler.start() fct() profiler.stop() if pyinst_format == "text": return profiler, profiler.output_text(unicode=False, color=False) elif pyinst_format == "textu": return profiler, profiler.output_text(unicode=True, color=True) elif pyinst_format == "json": from pyinstrument.renderers import JSONRenderer return profiler, profiler.output(JSONRenderer()) elif pyinst_format == "html": return profiler, profiler.output_html() else: raise ValueError("Unknown format '{}'.".format(pyinst_format))
def main(): usage = ("usage: pyinstrument [options] scriptfile [arg] ...") parser = OptionParser(usage=usage) parser.allow_interspersed_args = False parser.add_option('', '--setprofile', dest='setprofile', action='store_true', help='run in setprofile mode, instead of signal mode', default=False) parser.add_option('', '--html', dest="output_html", action='store_true', help="output HTML instead of text", default=False) parser.add_option('', '--flame', dest='output_flame', action='store_true', help='output an HTML flame chart', default=False) parser.add_option('-r', '--renderer', dest='output_renderer', action='store', type='string', help='python import path to a renderer class', default=None) parser.add_option('-o', '--outfile', dest="outfile", action='store', help="save report to <outfile>", default=None) parser.add_option('', '--unicode', dest='unicode', action='store_true', help='force unicode text output') parser.add_option('', '--no-unicode', dest='unicode', action='store_false', help='force ascii text output') parser.add_option('', '--color', dest='color', action='store_true', help='force ansi color text output') parser.add_option('', '--no-color', dest='color', action='store_false', help='force no color text output') parser.add_option('-m', '', dest='module_name', action='store', help='searches sys.path for the named module and runs the corresponding .py file as a script.') if not sys.argv[1:]: parser.print_help() sys.exit(2) options, args = parser.parse_args() if args == [] and options.module_name is None: parser.print_help() sys.exit(2) if options.module_name is not None: sys.argv[:] = [options.module_name] + args code = "run_module(modname, run_name='__main__')" globs = { 'run_module': runpy.run_module, 'modname': options.module_name } else: sys.argv[:] = args progname = args[0] sys.path.insert(0, os.path.dirname(progname)) with open(progname, 'rb') as fp: code = compile(fp.read(), progname, 'exec') globs = { '__file__': progname, '__name__': '__main__', '__package__': None, } if options.output_renderer: renderer = options.output_renderer elif options.output_html: renderer = 'html' else: renderer = 'text' recorder = get_renderer_class(renderer).preferred_recorder profiler = Profiler(recorder=recorder) profiler.start() try: exec_(code, globs, None) except (SystemExit, KeyboardInterrupt): pass profiler.stop() if options.outfile: f = codecs.open(options.outfile, 'w', 'utf-8') else: f = sys.stdout renderer_kwargs = {} if renderer == 'text': unicode_override = options.unicode != None color_override = options.color != None unicode = options.unicode if unicode_override else file_supports_unicode(f) color = options.color if color_override else file_supports_color(f) renderer_kwargs = {'unicode': unicode, 'color': color} f.write(profiler.output(renderer=renderer, **renderer_kwargs)) f.close()
def main(): usage = ("usage: pyinstrument [options] scriptfile [arg] ...") parser = OptionParser(usage=usage) parser.allow_interspersed_args = False parser.add_option( '-m', '', dest='module_name', action='store', help="run library module as a script, like 'python -m module'") parser.add_option( '-r', '--renderer', dest='renderer', action='store', type='string', help= "how the report should be rendered. One of: 'text', 'html', 'json', or python import path to a renderer class", default='text') parser.add_option('', '--html', dest="output_html", action='store_true', help="Shortcut for '--renderer=html'", default=False) parser.add_option('-o', '--outfile', dest="outfile", action='store', help="save report to <outfile>", default=None) parser.add_option('', '--unicode', dest='unicode', action='store_true', help='(text renderer only) force unicode text output') parser.add_option('', '--no-unicode', dest='unicode', action='store_false', help='(text renderer only) force ascii text output') parser.add_option('', '--color', dest='color', action='store_true', help='(text renderer only) force ansi color text output') parser.add_option('', '--no-color', dest='color', action='store_false', help='(text renderer only) force no color text output') if not sys.argv[1:]: parser.print_help() sys.exit(2) options, args = parser.parse_args() if args == [] and options.module_name is None: parser.print_help() sys.exit(2) if options.module_name is not None: sys.argv[:] = [options.module_name] + args code = "run_module(modname, run_name='__main__')" globs = { 'run_module': runpy.run_module, 'modname': options.module_name } else: sys.argv[:] = args progname = args[0] sys.path.insert(0, os.path.dirname(progname)) with open(progname, 'rb') as fp: code = compile(fp.read(), progname, 'exec') globs = { '__file__': progname, '__name__': '__main__', '__package__': None, } profiler = Profiler() profiler.start() try: exec_(code, globs, None) except (SystemExit, KeyboardInterrupt): pass profiler.stop() if options.output_html: options.renderer = 'html' output_to_temp_file = (options.renderer == 'html' and not options.outfile and file_is_a_tty(sys.stdout)) if options.outfile: f = codecs.open(options.outfile, 'w', 'utf-8') elif output_to_temp_file: output_file = tempfile.NamedTemporaryFile(suffix='.html', delete=False) f = codecs.getwriter('utf-8')(output_file) output_filename = output_file.name else: f = sys.stdout renderer_kwargs = {} if options.renderer == 'text': unicode_override = options.unicode != None color_override = options.color != None unicode = options.unicode if unicode_override else file_supports_unicode( f) color = options.color if color_override else file_supports_color(f) renderer_kwargs = {'unicode': unicode, 'color': color} f.write(profiler.output(renderer=options.renderer, **renderer_kwargs)) f.close() if output_to_temp_file: print('stdout is a terminal, so saved profile output to %s' % output_filename) import webbrowser, urllib.parse url = urllib.parse.urlunparse( ('file', '', output_filename, '', '', '')) webbrowser.open(url)