-
Notifications
You must be signed in to change notification settings - Fork 0
/
kernprof.py
235 lines (201 loc) · 7.16 KB
/
kernprof.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
""" Script to conveniently run profilers on code in a variety of circumstances.
"""
import functools
import optparse
import os
import sys
PY3 = sys.version_info[0] == 3
# Guard the import of cProfile such that 3.x people
# without lsprof can still use this script.
try:
from cProfile import Profile
except ImportError:
try:
from lsprof import Profile
except ImportError:
from profile import Profile
# Python 3.x compatibility utils: execfile
# ========================================
try:
execfile
except NameError:
# Python 3.x doesn't have 'execfile' builtin
import builtins
exec_ = getattr(builtins, "exec")
def execfile(filename, globals=None, locals=None):
with open(filename, 'rb') as f:
exec_(compile(f.read(), filename, 'exec'), globals, locals)
# =====================================
CO_GENERATOR = 0x0020
def is_generator(f):
""" Return True if a function is a generator.
"""
isgen = (f.__code__.co_flags & CO_GENERATOR) != 0
return isgen
class ContextualProfile(Profile):
""" A subclass of Profile that adds a context manager for Python
2.5 with: statements and a decorator.
"""
def __init__(self, *args, **kwds):
super(ContextualProfile, self).__init__(*args, **kwds)
self.enable_count = 0
def enable_by_count(self, subcalls=True, builtins=True):
""" Enable the profiler if it hasn't been enabled before.
"""
if self.enable_count == 0:
self.enable(subcalls=subcalls, builtins=builtins)
self.enable_count += 1
def disable_by_count(self):
""" Disable the profiler if the number of disable requests matches the
number of enable requests.
"""
if self.enable_count > 0:
self.enable_count -= 1
if self.enable_count == 0:
self.disable()
def __call__(self, func):
""" Decorate a function to start the profiler on function entry and stop
it on function exit.
"""
# FIXME: refactor this into a utility function so that both it and
# line_profiler can use it.
if is_generator(func):
wrapper = self.wrap_generator(func)
else:
wrapper = self.wrap_function(func)
return wrapper
# FIXME: refactor this stuff so that both LineProfiler and
# ContextualProfile can use the same implementation.
def wrap_generator(self, func):
""" Wrap a generator to profile it.
"""
@functools.wraps(func)
def wrapper(*args, **kwds):
g = func(*args, **kwds)
# The first iterate will not be a .send()
self.enable_by_count()
try:
item = next(g)
finally:
self.disable_by_count()
input = (yield item)
# But any following one might be.
while True:
self.enable_by_count()
try:
item = g.send(input)
finally:
self.disable_by_count()
input = (yield item)
return wrapper
def wrap_function(self, func):
""" Wrap a function to profile it.
"""
@functools.wraps(func)
def wrapper(*args, **kwds):
self.enable_by_count()
try:
result = func(*args, **kwds)
finally:
self.disable_by_count()
return result
return wrapper
def __enter__(self):
self.enable_by_count()
def __exit__(self, exc_type, exc_val, exc_tb):
self.disable_by_count()
def find_script(script_name):
""" Find the script.
If the input is not a file, then $PATH will be searched.
"""
if os.path.isfile(script_name):
return script_name
path = os.getenv('PATH', os.defpath).split(os.pathsep)
for dir in path:
if dir == '':
continue
fn = os.path.join(dir, script_name)
if os.path.isfile(fn):
return fn
sys.stderr.write('Could not find script %s\n' % script_name)
raise SystemExit(1)
def main(args=None):
if args is None:
args = sys.argv
usage = "%prog [-s setupfile] [-o output_file_path] scriptfile [arg] ..."
parser = optparse.OptionParser(usage=usage, version="%prog 1.0b2")
parser.allow_interspersed_args = False
parser.add_option('-l', '--line-by-line', action='store_true',
help="Use the line-by-line profiler from the line_profiler module "
"instead of Profile. Implies --builtin.")
parser.add_option('-b', '--builtin', action='store_true',
help="Put 'profile' in the builtins. Use 'profile.enable()' and "
"'profile.disable()' in your code to turn it on and off, or "
"'@profile' to decorate a single function, or 'with profile:' "
"to profile a single section of code.")
parser.add_option('-o', '--outfile', default=None,
help="Save stats to <outfile>")
parser.add_option('-s', '--setup', default=None,
help="Code to execute before the code to profile")
parser.add_option('-v', '--view', action='store_true',
help="View the results of the profile in addition to saving it.")
if not sys.argv[1:]:
parser.print_usage()
sys.exit(2)
options, args = parser.parse_args()
if not options.outfile:
if options.line_by_line:
extension = 'lprof'
else:
extension = 'prof'
options.outfile = '%s.%s' % (os.path.basename(args[0]), extension)
sys.argv[:] = args
if options.setup is not None:
# Run some setup code outside of the profiler. This is good for large
# imports.
setup_file = find_script(options.setup)
__file__ = setup_file
__name__ = '__main__'
# Make sure the script's directory is on sys.path instead of just
# kernprof.py's.
sys.path.insert(0, os.path.dirname(setup_file))
ns = locals()
execfile(setup_file, ns, ns)
if options.line_by_line:
import line_profiler
prof = line_profiler.LineProfiler()
options.builtin = True
else:
prof = ContextualProfile()
if options.builtin:
if PY3:
import builtins
else:
import __builtin__ as builtins
builtins.__dict__['profile'] = prof
script_file = find_script(sys.argv[0])
__file__ = script_file
__name__ = '__main__'
# Make sure the script's directory is on sys.path instead of just
# kernprof.py's.
sys.path.insert(0, os.path.dirname(script_file))
try:
try:
execfile_ = execfile
ns = locals()
if options.builtin:
execfile(script_file, ns, ns)
else:
prof.runctx('execfile_(%r, globals())' % (script_file,),
ns, ns)
except (KeyboardInterrupt, SystemExit):
pass
finally:
prof.dump_stats(options.outfile)
print('Wrote profile results to %s' % options.outfile)
if options.view:
prof.print_stats()
if __name__ == '__main__':
sys.exit(main(sys.argv))