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