1#! /usr/bin/env python3 2 3"""Python interface for the 'lsprof' profiler. 4 Compatible with the 'profile' module. 5""" 6 7__all__ = ["run", "runctx", "Profile"] 8 9import _lsprof 10import profile as _pyprofile 11 12# ____________________________________________________________ 13# Simple interface 14 15def run(statement, filename=None, sort=-1): 16 return _pyprofile._Utils(Profile).run(statement, filename, sort) 17 18def runctx(statement, globals, locals, filename=None, sort=-1): 19 return _pyprofile._Utils(Profile).runctx(statement, globals, locals, 20 filename, sort) 21 22run.__doc__ = _pyprofile.run.__doc__ 23runctx.__doc__ = _pyprofile.runctx.__doc__ 24 25# ____________________________________________________________ 26 27class Profile(_lsprof.Profiler): 28 """Profile(timer=None, timeunit=None, subcalls=True, builtins=True) 29 30 Builds a profiler object using the specified timer function. 31 The default timer is a fast built-in one based on real time. 32 For custom timer functions returning integers, timeunit can 33 be a float specifying a scale (i.e. how long each integer unit 34 is, in seconds). 35 """ 36 37 # Most of the functionality is in the base class. 38 # This subclass only adds convenient and backward-compatible methods. 39 40 def print_stats(self, sort=-1): 41 import pstats 42 pstats.Stats(self).strip_dirs().sort_stats(sort).print_stats() 43 44 def dump_stats(self, file): 45 import marshal 46 with open(file, 'wb') as f: 47 self.create_stats() 48 marshal.dump(self.stats, f) 49 50 def create_stats(self): 51 self.disable() 52 self.snapshot_stats() 53 54 def snapshot_stats(self): 55 entries = self.getstats() 56 self.stats = {} 57 callersdicts = {} 58 # call information 59 for entry in entries: 60 func = label(entry.code) 61 nc = entry.callcount # ncalls column of pstats (before '/') 62 cc = nc - entry.reccallcount # ncalls column of pstats (after '/') 63 tt = entry.inlinetime # tottime column of pstats 64 ct = entry.totaltime # cumtime column of pstats 65 callers = {} 66 callersdicts[id(entry.code)] = callers 67 self.stats[func] = cc, nc, tt, ct, callers 68 # subcall information 69 for entry in entries: 70 if entry.calls: 71 func = label(entry.code) 72 for subentry in entry.calls: 73 try: 74 callers = callersdicts[id(subentry.code)] 75 except KeyError: 76 continue 77 nc = subentry.callcount 78 cc = nc - subentry.reccallcount 79 tt = subentry.inlinetime 80 ct = subentry.totaltime 81 if func in callers: 82 prev = callers[func] 83 nc += prev[0] 84 cc += prev[1] 85 tt += prev[2] 86 ct += prev[3] 87 callers[func] = nc, cc, tt, ct 88 89 # The following two methods can be called by clients to use 90 # a profiler to profile a statement, given as a string. 91 92 def run(self, cmd): 93 import __main__ 94 dict = __main__.__dict__ 95 return self.runctx(cmd, dict, dict) 96 97 def runctx(self, cmd, globals, locals): 98 self.enable() 99 try: 100 exec(cmd, globals, locals) 101 finally: 102 self.disable() 103 return self 104 105 # This method is more useful to profile a single function call. 106 def runcall(self, func, *args, **kw): 107 self.enable() 108 try: 109 return func(*args, **kw) 110 finally: 111 self.disable() 112 113# ____________________________________________________________ 114 115def label(code): 116 if isinstance(code, str): 117 return ('~', 0, code) # built-in functions ('~' sorts at the end) 118 else: 119 return (code.co_filename, code.co_firstlineno, code.co_name) 120 121# ____________________________________________________________ 122 123def main(): 124 import os 125 import sys 126 import runpy 127 import pstats 128 from optparse import OptionParser 129 usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..." 130 parser = OptionParser(usage=usage) 131 parser.allow_interspersed_args = False 132 parser.add_option('-o', '--outfile', dest="outfile", 133 help="Save stats to <outfile>", default=None) 134 parser.add_option('-s', '--sort', dest="sort", 135 help="Sort order when printing to stdout, based on pstats.Stats class", 136 default=-1, 137 choices=sorted(pstats.Stats.sort_arg_dict_default)) 138 parser.add_option('-m', dest="module", action="store_true", 139 help="Profile a library module", default=False) 140 141 if not sys.argv[1:]: 142 parser.print_usage() 143 sys.exit(2) 144 145 (options, args) = parser.parse_args() 146 sys.argv[:] = args 147 148 if len(args) > 0: 149 if options.module: 150 code = "run_module(modname, run_name='__main__')" 151 globs = { 152 'run_module': runpy.run_module, 153 'modname': args[0] 154 } 155 else: 156 progname = args[0] 157 sys.path.insert(0, os.path.dirname(progname)) 158 with open(progname, 'rb') as fp: 159 code = compile(fp.read(), progname, 'exec') 160 globs = { 161 '__file__': progname, 162 '__name__': '__main__', 163 '__package__': None, 164 '__cached__': None, 165 } 166 runctx(code, globs, None, options.outfile, options.sort) 167 else: 168 parser.print_usage() 169 return parser 170 171# When invoked as main program, invoke the profiler on a script 172if __name__ == '__main__': 173 main() 174