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(*args, **kw): 107 if len(args) >= 2: 108 self, func, *args = args 109 elif not args: 110 raise TypeError("descriptor 'runcall' of 'Profile' object " 111 "needs an argument") 112 elif 'func' in kw: 113 func = kw.pop('func') 114 self, *args = args 115 import warnings 116 warnings.warn("Passing 'func' as keyword argument is deprecated", 117 DeprecationWarning, stacklevel=2) 118 else: 119 raise TypeError('runcall expected at least 1 positional argument, ' 120 'got %d' % (len(args)-1)) 121 122 self.enable() 123 try: 124 return func(*args, **kw) 125 finally: 126 self.disable() 127 runcall.__text_signature__ = '($self, func, /, *args, **kw)' 128 129 def __enter__(self): 130 self.enable() 131 return self 132 133 def __exit__(self, *exc_info): 134 self.disable() 135 136# ____________________________________________________________ 137 138def label(code): 139 if isinstance(code, str): 140 return ('~', 0, code) # built-in functions ('~' sorts at the end) 141 else: 142 return (code.co_filename, code.co_firstlineno, code.co_name) 143 144# ____________________________________________________________ 145 146def main(): 147 import os 148 import sys 149 import runpy 150 import pstats 151 from optparse import OptionParser 152 usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..." 153 parser = OptionParser(usage=usage) 154 parser.allow_interspersed_args = False 155 parser.add_option('-o', '--outfile', dest="outfile", 156 help="Save stats to <outfile>", default=None) 157 parser.add_option('-s', '--sort', dest="sort", 158 help="Sort order when printing to stdout, based on pstats.Stats class", 159 default=-1, 160 choices=sorted(pstats.Stats.sort_arg_dict_default)) 161 parser.add_option('-m', dest="module", action="store_true", 162 help="Profile a library module", default=False) 163 164 if not sys.argv[1:]: 165 parser.print_usage() 166 sys.exit(2) 167 168 (options, args) = parser.parse_args() 169 sys.argv[:] = args 170 171 if len(args) > 0: 172 if options.module: 173 code = "run_module(modname, run_name='__main__')" 174 globs = { 175 'run_module': runpy.run_module, 176 'modname': args[0] 177 } 178 else: 179 progname = args[0] 180 sys.path.insert(0, os.path.dirname(progname)) 181 with open(progname, 'rb') as fp: 182 code = compile(fp.read(), progname, 'exec') 183 globs = { 184 '__file__': progname, 185 '__name__': '__main__', 186 '__package__': None, 187 '__cached__': None, 188 } 189 runctx(code, globs, None, options.outfile, options.sort) 190 else: 191 parser.print_usage() 192 return parser 193 194# When invoked as main program, invoke the profiler on a script 195if __name__ == '__main__': 196 main() 197