blob: c24d45bab47ac3cbec59f369038060d04f9ab9b7 [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
Armin Rigoa871ef22006-02-08 12:53:56 +00002
3"""Python interface for the 'lsprof' profiler.
4 Compatible with the 'profile' module.
5"""
6
Georg Brandlb6b13242009-09-04 17:15:16 +00007__all__ = ["run", "runctx", "Profile"]
Armin Rigoa871ef22006-02-08 12:53:56 +00008
9import _lsprof
10
11# ____________________________________________________________
12# Simple interface
13
14def 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
Georg Brandl8e43fbf2010-08-02 12:20:23 +000039def runctx(statement, globals, locals, filename=None, sort=-1):
Armin Rigoa871ef22006-02-08 12:53:56 +000040 """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:
Georg Brandl8e43fbf2010-08-02 12:20:23 +000056 result = prof.print_stats(sort)
Armin Rigoa871ef22006-02-08 12:53:56 +000057 return result
58
Armin Rigoa871ef22006-02-08 12:53:56 +000059# ____________________________________________________________
60
61class Profile(_lsprof.Profiler):
62 """Profile(custom_timer=None, time_unit=None, subcalls=True, builtins=True)
63
64 Builds a profiler object using the specified timer function.
65 The default timer is a fast built-in one based on real time.
66 For custom timer functions returning integers, time_unit can
67 be a float specifying a scale (i.e. how long each integer unit
68 is, in seconds).
69 """
70
71 # Most of the functionality is in the base class.
72 # This subclass only adds convenient and backward-compatible methods.
73
74 def print_stats(self, sort=-1):
75 import pstats
76 pstats.Stats(self).strip_dirs().sort_stats(sort).print_stats()
77
78 def dump_stats(self, file):
79 import marshal
80 f = open(file, 'wb')
81 self.create_stats()
82 marshal.dump(self.stats, f)
83 f.close()
84
85 def create_stats(self):
86 self.disable()
87 self.snapshot_stats()
88
89 def snapshot_stats(self):
90 entries = self.getstats()
91 self.stats = {}
92 callersdicts = {}
93 # call information
94 for entry in entries:
95 func = label(entry.code)
96 nc = entry.callcount # ncalls column of pstats (before '/')
97 cc = nc - entry.reccallcount # ncalls column of pstats (after '/')
98 tt = entry.inlinetime # tottime column of pstats
99 ct = entry.totaltime # cumtime column of pstats
100 callers = {}
101 callersdicts[id(entry.code)] = callers
102 self.stats[func] = cc, nc, tt, ct, callers
103 # subcall information
104 for entry in entries:
105 if entry.calls:
106 func = label(entry.code)
107 for subentry in entry.calls:
108 try:
109 callers = callersdicts[id(subentry.code)]
110 except KeyError:
111 continue
112 nc = subentry.callcount
113 cc = nc - subentry.reccallcount
114 tt = subentry.inlinetime
115 ct = subentry.totaltime
116 if func in callers:
117 prev = callers[func]
118 nc += prev[0]
119 cc += prev[1]
120 tt += prev[2]
121 ct += prev[3]
122 callers[func] = nc, cc, tt, ct
123
124 # The following two methods can be called by clients to use
125 # a profiler to profile a statement, given as a string.
126
127 def run(self, cmd):
128 import __main__
129 dict = __main__.__dict__
130 return self.runctx(cmd, dict, dict)
131
132 def runctx(self, cmd, globals, locals):
133 self.enable()
134 try:
Georg Brandl7cae87c2006-09-06 06:51:57 +0000135 exec(cmd, globals, locals)
Armin Rigoa871ef22006-02-08 12:53:56 +0000136 finally:
137 self.disable()
138 return self
139
140 # This method is more useful to profile a single function call.
141 def runcall(self, func, *args, **kw):
142 self.enable()
143 try:
144 return func(*args, **kw)
145 finally:
146 self.disable()
147
148# ____________________________________________________________
149
150def label(code):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000151 if isinstance(code, str):
Armin Rigoa871ef22006-02-08 12:53:56 +0000152 return ('~', 0, code) # built-in functions ('~' sorts at the end)
153 else:
154 return (code.co_filename, code.co_firstlineno, code.co_name)
155
156# ____________________________________________________________
157
158def main():
159 import os, sys
160 from optparse import OptionParser
161 usage = "cProfile.py [-o output_file_path] [-s sort] scriptfile [arg] ..."
162 parser = OptionParser(usage=usage)
163 parser.allow_interspersed_args = False
164 parser.add_option('-o', '--outfile', dest="outfile",
165 help="Save stats to <outfile>", default=None)
166 parser.add_option('-s', '--sort', dest="sort",
Georg Brandl8e43fbf2010-08-02 12:20:23 +0000167 help="Sort order when printing to stdout, based on pstats.Stats class",
168 default=-1)
Armin Rigoa871ef22006-02-08 12:53:56 +0000169
170 if not sys.argv[1:]:
171 parser.print_usage()
172 sys.exit(2)
173
174 (options, args) = parser.parse_args()
175 sys.argv[:] = args
176
Georg Brandl8e43fbf2010-08-02 12:20:23 +0000177 if len(args) > 0:
178 progname = args[0]
179 sys.path.insert(0, os.path.dirname(progname))
180 with open(progname, 'rb') as fp:
181 code = compile(fp.read(), progname, 'exec')
182 globs = {
183 '__file__': progname,
184 '__name__': '__main__',
185 '__package__': None,
186 '__cached__': None,
187 }
188 runctx(code, globs, None, options.outfile, options.sort)
Armin Rigoa871ef22006-02-08 12:53:56 +0000189 else:
190 parser.print_usage()
191 return parser
192
193# When invoked as main program, invoke the profiler on a script
194if __name__ == '__main__':
195 main()