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
13
__all__ = ['profile', 'Stats']
18
def _thread_profile(f, *args, **kwds):
19
# we lose the first profile point for a new thread in order to trampoline
20
# a new Profile object into place
22
thr = thread.get_ident()
23
_g_threadmap[thr] = p = Profiler()
24
# this overrides our sys.setprofile hook:
25
p.enable(subcalls=True, builtins=True)
28
def profile(f, *args, **kwds):
32
p.enable(subcalls=True)
33
threading.setprofile(_thread_profile)
35
ret = f(*args, **kwds)
38
for pp in _g_threadmap.values():
40
threading.setprofile(None)
43
for tid, pp in _g_threadmap.items():
44
threads[tid] = Stats(pp.getstats(), {})
46
return ret, Stats(p.getstats(), threads)
52
def __init__(self, data, threads):
54
self.threads = threads
56
def sort(self, crit="inlinetime"):
58
if crit not in profiler_entry.__dict__:
59
raise ValueError, "Can't sort by %s" % crit
60
self.data.sort(lambda b, a: cmp(getattr(a, crit),
64
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
67
def pprint(self, top=None, file=None):
74
cols = "% 12s %12s %11.4f %11.4f %s\n"
75
hcols = "% 12s %12s %12s %12s %s\n"
76
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
77
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
78
"Inline(ms)", "module:lineno(function)"))
80
file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
81
e.inlinetime, label(e.code)))
84
file.write(cols % ("+%s" % se.callcount, se.reccallcount,
85
se.totaltime, se.inlinetime,
86
"+%s" % label(se.code)))
89
"""Replace all references to code objects with string
90
descriptions; this makes it possible to pickle the instance."""
92
# this code is probably rather ickier than it needs to be!
93
for i in range(len(self.data)):
95
if not isinstance(e.code, str):
96
self.data[i] = type(e)((label(e.code),) + e[1:])
98
for j in range(len(e.calls)):
100
if not isinstance(se.code, str):
101
e.calls[j] = type(se)((label(se.code),) + se[1:])
102
for s in self.threads.values():
105
def calltree(self, file):
106
"""Output profiling data in calltree format (for KCacheGrind)."""
107
_CallTreeFilter(self.data).output(file)
109
def save(self, filename, format=None):
110
"""Save profiling data to a file.
112
:param filename: the name of the output file
113
:param format: 'txt' for a text representation;
114
'callgrind' for calltree format;
115
otherwise a pickled Python object. A format of None indicates
116
that the format to use is to be found from the filename. If
117
the name starts with callgrind.out, callgrind format is used
118
otherwise the format is given by the filename extension.
121
if filename.startswith('callgrind.out'):
124
ext = os.path.splitext(filename)[1]
127
outfile = open(filename, 'wb')
129
if format == "callgrind":
130
self.calltree(outfile)
131
elif format == "txt":
132
self.pprint(file=outfile)
135
cPickle.dump(self, outfile, 2)
140
class _CallTreeFilter(object):
141
"""Converter of a Stats object to input suitable for KCacheGrind.
143
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
144
with the changes made by J.P. Calderone and Itamar applied. Note that
145
isinstance(code, str) needs to be used at times to determine if the code
146
object is actually an external code object (with a filename, etc.) or
150
def __init__(self, data):
154
def output(self, out_file):
155
self.out_file = out_file
156
print >> out_file, 'events: Ticks'
157
self._print_summary()
158
for entry in self.data:
161
def _print_summary(self):
163
for entry in self.data:
164
totaltime = int(entry.totaltime * 1000)
165
max_cost = max(max_cost, totaltime)
166
print >> self.out_file, 'summary: %d' % (max_cost,)
168
def _entry(self, entry):
169
out_file = self.out_file
171
inlinetime = int(entry.inlinetime * 1000)
172
#print >> out_file, 'ob=%s' % (code.co_filename,)
173
if isinstance(code, str):
174
print >> out_file, 'fi=~'
176
print >> out_file, 'fi=%s' % (code.co_filename,)
177
print >> out_file, 'fn=%s' % (label(code, True),)
178
if isinstance(code, str):
179
print >> out_file, '0 ', inlinetime
181
print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
182
# recursive calls are counted in entry.calls
187
if isinstance(code, str):
190
lineno = code.co_firstlineno
191
for subentry in calls:
192
self._subentry(lineno, subentry)
195
def _subentry(self, lineno, subentry):
196
out_file = self.out_file
198
totaltime = int(subentry.totaltime * 1000)
199
#print >> out_file, 'cob=%s' % (code.co_filename,)
200
print >> out_file, 'cfn=%s' % (label(code, True),)
201
if isinstance(code, str):
202
print >> out_file, 'cfi=~'
203
print >> out_file, 'calls=%d 0' % (subentry.callcount,)
205
print >> out_file, 'cfi=%s' % (code.co_filename,)
206
print >> out_file, 'calls=%d %d' % (
207
subentry.callcount, code.co_firstlineno)
208
print >> out_file, '%d %d' % (lineno, totaltime)
212
def label(code, calltree=False):
213
if isinstance(code, str):
216
mname = _fn2mod[code.co_filename]
218
for k, v in sys.modules.items():
221
if getattr(v, '__file__', None) is None:
223
if not isinstance(v.__file__, str):
225
if v.__file__.startswith(code.co_filename):
226
mname = _fn2mod[code.co_filename] = k
229
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
231
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
233
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
236
if __name__ == '__main__':
238
sys.argv = sys.argv[1:]
240
print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
242
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
243
stats = profile(execfile, sys.argv[0], globals(), locals())