1
# this is copied from the lsprof distro because somehow
2
# it is not installed by distutils
3
# I made one modification to profile so that it returns a pair
4
# instead of just the Stats object
11
from _lsprof import Profiler, profiler_entry
14
__all__ = ['profile', 'Stats']
19
def _thread_profile(f, *args, **kwds):
20
# we lose the first profile point for a new thread in order to trampoline
21
# a new Profile object into place
23
thr = thread.get_ident()
24
_g_threadmap[thr] = p = Profiler()
25
# this overrides our sys.setprofile hook:
26
p.enable(subcalls=True, builtins=True)
29
def profile(f, *args, **kwds):
30
"""Run a function profile.
32
Exceptions are not caught: If you need stats even when exceptions are to be
33
raised, passing in a closure that will catch the exceptions and transform
34
them appropriately for your driver function.
36
:return: The functions return value and a stats object.
40
p.enable(subcalls=True)
41
threading.setprofile(_thread_profile)
43
ret = f(*args, **kwds)
46
for pp in _g_threadmap.values():
48
threading.setprofile(None)
51
for tid, pp in _g_threadmap.items():
52
threads[tid] = Stats(pp.getstats(), {})
54
return ret, Stats(p.getstats(), threads)
60
def __init__(self, data, threads):
62
self.threads = threads
64
def sort(self, crit="inlinetime"):
66
if crit not in profiler_entry.__dict__:
67
raise ValueError, "Can't sort by %s" % crit
68
self.data.sort(lambda b, a: cmp(getattr(a, crit),
72
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
75
def pprint(self, top=None, file=None):
82
cols = "% 12s %12s %11.4f %11.4f %s\n"
83
hcols = "% 12s %12s %12s %12s %s\n"
84
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
85
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
86
"Inline(ms)", "module:lineno(function)"))
88
file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
89
e.inlinetime, label(e.code)))
92
file.write(cols % ("+%s" % se.callcount, se.reccallcount,
93
se.totaltime, se.inlinetime,
94
"+%s" % label(se.code)))
97
"""Replace all references to code objects with string
98
descriptions; this makes it possible to pickle the instance."""
100
# this code is probably rather ickier than it needs to be!
101
for i in range(len(self.data)):
103
if not isinstance(e.code, str):
104
self.data[i] = type(e)((label(e.code),) + e[1:])
106
for j in range(len(e.calls)):
108
if not isinstance(se.code, str):
109
e.calls[j] = type(se)((label(se.code),) + se[1:])
110
for s in self.threads.values():
113
def calltree(self, file):
114
"""Output profiling data in calltree format (for KCacheGrind)."""
115
_CallTreeFilter(self.data).output(file)
117
def save(self, filename, format=None):
118
"""Save profiling data to a file.
120
:param filename: the name of the output file
121
:param format: 'txt' for a text representation;
122
'callgrind' for calltree format;
123
otherwise a pickled Python object. A format of None indicates
124
that the format to use is to be found from the filename. If
125
the name starts with callgrind.out, callgrind format is used
126
otherwise the format is given by the filename extension.
129
basename = os.path.basename(filename)
130
if basename.startswith('callgrind.out'):
133
ext = os.path.splitext(filename)[1]
136
outfile = open(filename, 'wb')
138
if format == "callgrind":
139
self.calltree(outfile)
140
elif format == "txt":
141
self.pprint(file=outfile)
144
cPickle.dump(self, outfile, 2)
149
class _CallTreeFilter(object):
150
"""Converter of a Stats object to input suitable for KCacheGrind.
152
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
153
with the changes made by J.P. Calderone and Itamar applied. Note that
154
isinstance(code, str) needs to be used at times to determine if the code
155
object is actually an external code object (with a filename, etc.) or
159
def __init__(self, data):
163
def output(self, out_file):
164
self.out_file = out_file
165
out_file.write('events: Ticks\n')
166
self._print_summary()
167
for entry in self.data:
170
def _print_summary(self):
172
for entry in self.data:
173
totaltime = int(entry.totaltime * 1000)
174
max_cost = max(max_cost, totaltime)
175
self.out_file.write('summary: %d\n' % (max_cost,))
177
def _entry(self, entry):
178
out_file = self.out_file
180
inlinetime = int(entry.inlinetime * 1000)
181
#out_file.write('ob=%s\n' % (code.co_filename,))
182
if isinstance(code, str):
183
out_file.write('fi=~\n')
185
out_file.write('fi=%s\n' % (code.co_filename,))
186
out_file.write('fn=%s\n' % (label(code, True),))
187
if isinstance(code, str):
188
out_file.write('0 %s\n' % (inlinetime,))
190
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
191
# recursive calls are counted in entry.calls
196
if isinstance(code, str):
199
lineno = code.co_firstlineno
200
for subentry in calls:
201
self._subentry(lineno, subentry)
204
def _subentry(self, lineno, subentry):
205
out_file = self.out_file
207
totaltime = int(subentry.totaltime * 1000)
208
#out_file.write('cob=%s\n' % (code.co_filename,))
209
out_file.write('cfn=%s\n' % (label(code, True),))
210
if isinstance(code, str):
211
out_file.write('cfi=~\n')
212
out_file.write('calls=%d 0\n' % (subentry.callcount,))
214
out_file.write('cfi=%s\n' % (code.co_filename,))
215
out_file.write('calls=%d %d\n' % (
216
subentry.callcount, code.co_firstlineno))
217
out_file.write('%d %d\n' % (lineno, totaltime))
221
def label(code, calltree=False):
222
if isinstance(code, str):
225
mname = _fn2mod[code.co_filename]
227
for k, v in sys.modules.items():
230
if getattr(v, '__file__', None) is None:
232
if not isinstance(v.__file__, str):
234
if v.__file__.startswith(code.co_filename):
235
mname = _fn2mod[code.co_filename] = k
238
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
240
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
242
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
245
if __name__ == '__main__':
247
sys.argv = sys.argv[1:]
249
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
251
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
252
stats = profile(execfile, sys.argv[0], globals(), locals())