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