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():
|
---|
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",
|
---|
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]))
|
---|
183 | run('execfile(%r)' % (sys.argv[0],), options.outfile, options.sort)
|
---|
184 | else:
|
---|
185 | parser.print_usage()
|
---|
186 | return parser
|
---|
187 |
|
---|
188 | # When invoked as main program, invoke the profiler on a script
|
---|
189 | if __name__ == '__main__':
|
---|
190 | main()
|
---|