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 extension of
120
ext = os.path.splitext(filename)[1]
123
outfile = open(filename, 'wb')
125
if format == "callgrind":
126
self.calltree(outfile)
127
elif format == "txt":
128
self.pprint(file=outfile)
131
cPickle.dump(self, outfile, 2)
136
class _CallTreeFilter(object):
137
"""Converter of a Stats object to input suitable for KCacheGrind.
139
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
140
with the changes made by J.P. Calderone and Itamar applied. Note that
141
isinstance(code, str) needs to be used at times to determine if the code
142
object is actually an external code object (with a filename, etc.) or
146
def __init__(self, data):
150
def output(self, out_file):
151
self.out_file = out_file
152
print >> out_file, 'events: Ticks'
153
self._print_summary()
154
for entry in self.data:
157
def _print_summary(self):
159
for entry in self.data:
160
totaltime = int(entry.totaltime * 1000)
161
max_cost = max(max_cost, totaltime)
162
print >> self.out_file, 'summary: %d' % (max_cost,)
164
def _entry(self, entry):
165
out_file = self.out_file
167
inlinetime = int(entry.inlinetime * 1000)
168
#print >> out_file, 'ob=%s' % (code.co_filename,)
169
if isinstance(code, str):
170
print >> out_file, 'fi=~'
172
print >> out_file, 'fi=%s' % (code.co_filename,)
173
print >> out_file, 'fn=%s' % (label(code, True),)
174
if isinstance(code, str):
175
print >> out_file, '0 ', inlinetime
177
print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
178
# recursive calls are counted in entry.calls
183
if isinstance(code, str):
186
lineno = code.co_firstlineno
187
for subentry in calls:
188
self._subentry(lineno, subentry)
191
def _subentry(self, lineno, subentry):
192
out_file = self.out_file
194
totaltime = int(subentry.totaltime * 1000)
195
#print >> out_file, 'cob=%s' % (code.co_filename,)
196
print >> out_file, 'cfn=%s' % (label(code, True),)
197
if isinstance(code, str):
198
print >> out_file, 'cfi=~'
199
print >> out_file, 'calls=%d 0' % (subentry.callcount,)
201
print >> out_file, 'cfi=%s' % (code.co_filename,)
202
print >> out_file, 'calls=%d %d' % (
203
subentry.callcount, code.co_firstlineno)
204
print >> out_file, '%d %d' % (lineno, totaltime)
208
def label(code, calltree=False):
209
if isinstance(code, str):
212
mname = _fn2mod[code.co_filename]
214
for k, v in sys.modules.items():
217
if getattr(v, '__file__', None) is None:
219
if not isinstance(v.__file__, str):
221
if v.__file__.startswith(code.co_filename):
222
mname = _fn2mod[code.co_filename] = k
225
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
227
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
229
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
232
if __name__ == '__main__':
234
sys.argv = sys.argv[1:]
236
print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
238
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
239
stats = profile(execfile, sys.argv[0], globals(), locals())