Armin Rigo | a871ef2 | 2006-02-08 12:53:56 +0000 | [diff] [blame] | 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 | |
| 39 | def runctx(statement, globals, locals, filename=None): |
| 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: |
| 56 | result = prof.print_stats() |
| 57 | return result |
| 58 | |
| 59 | # Backwards compatibility. |
| 60 | def help(): |
Guido van Rossum | be19ed7 | 2007-02-09 05:37:30 +0000 | [diff] [blame] | 61 | print("Documentation for the profile/cProfile modules can be found ") |
| 62 | print("in the Python Library Reference, section 'The Python Profiler'.") |
Armin Rigo | a871ef2 | 2006-02-08 12:53:56 +0000 | [diff] [blame] | 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: |
Georg Brandl | 7cae87c | 2006-09-06 06:51:57 +0000 | [diff] [blame] | 140 | exec(cmd, globals, locals) |
Armin Rigo | a871ef2 | 2006-02-08 12:53:56 +0000 | [diff] [blame] | 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): |
Guido van Rossum | 3172c5d | 2007-10-16 18:12:55 +0000 | [diff] [blame] | 156 | if isinstance(code, str): |
Armin Rigo | a871ef2 | 2006-02-08 12:53:56 +0000 | [diff] [blame] | 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", |
| 172 | help="Sort order when printing to stdout, based on pstats.Stats class", default=-1) |
| 173 | |
| 174 | if not sys.argv[1:]: |
| 175 | parser.print_usage() |
| 176 | sys.exit(2) |
| 177 | |
| 178 | (options, args) = parser.parse_args() |
| 179 | sys.argv[:] = args |
| 180 | |
| 181 | if (len(sys.argv) > 0): |
| 182 | sys.path.insert(0, os.path.dirname(sys.argv[0])) |
Neal Norwitz | 0168802 | 2007-08-12 00:43:29 +0000 | [diff] [blame] | 183 | fp = open(sys.argv[0]) |
| 184 | try: |
| 185 | script = fp.read() |
| 186 | finally: |
| 187 | fp.close() |
| 188 | run('exec(%r)' % script, options.outfile, options.sort) |
Armin Rigo | a871ef2 | 2006-02-08 12:53:56 +0000 | [diff] [blame] | 189 | else: |
| 190 | parser.print_usage() |
| 191 | return parser |
| 192 | |
| 193 | # When invoked as main program, invoke the profiler on a script |
| 194 | if __name__ == '__main__': |
| 195 | main() |