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
:return: The functions return value and a stats object.
36
p.enable(subcalls=True)
37
threading.setprofile(_thread_profile)
38
# Note: The except clause is needed below so that profiling data still
39
# gets dumped even when exceptions are encountered. The except clause code
40
# is taken straight from run_bzr_catch_errrors() in commands.py and ought
41
# to be kept in sync with it.
44
ret = f(*args, **kwds)
45
except (KeyboardInterrupt, Exception), e:
47
bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
51
for pp in _g_threadmap.values():
53
threading.setprofile(None)
56
for tid, pp in _g_threadmap.items():
57
threads[tid] = Stats(pp.getstats(), {})
59
return ret, Stats(p.getstats(), threads)
65
def __init__(self, data, threads):
67
self.threads = threads
69
def sort(self, crit="inlinetime"):
71
if crit not in profiler_entry.__dict__:
72
raise ValueError, "Can't sort by %s" % crit
73
self.data.sort(lambda b, a: cmp(getattr(a, crit),
77
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
80
def pprint(self, top=None, file=None):
87
cols = "% 12s %12s %11.4f %11.4f %s\n"
88
hcols = "% 12s %12s %12s %12s %s\n"
89
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
90
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
91
"Inline(ms)", "module:lineno(function)"))
93
file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
94
e.inlinetime, label(e.code)))
97
file.write(cols % ("+%s" % se.callcount, se.reccallcount,
98
se.totaltime, se.inlinetime,
99
"+%s" % label(se.code)))
102
"""Replace all references to code objects with string
103
descriptions; this makes it possible to pickle the instance."""
105
# this code is probably rather ickier than it needs to be!
106
for i in range(len(self.data)):
108
if not isinstance(e.code, str):
109
self.data[i] = type(e)((label(e.code),) + e[1:])
111
for j in range(len(e.calls)):
113
if not isinstance(se.code, str):
114
e.calls[j] = type(se)((label(se.code),) + se[1:])
115
for s in self.threads.values():
118
def calltree(self, file):
119
"""Output profiling data in calltree format (for KCacheGrind)."""
120
_CallTreeFilter(self.data).output(file)
122
def save(self, filename, format=None):
123
"""Save profiling data to a file.
125
:param filename: the name of the output file
126
:param format: 'txt' for a text representation;
127
'callgrind' for calltree format;
128
otherwise a pickled Python object. A format of None indicates
129
that the format to use is to be found from the filename. If
130
the name starts with callgrind.out, callgrind format is used
131
otherwise the format is given by the filename extension.
134
basename = os.path.basename(filename)
135
if basename.startswith('callgrind.out'):
138
ext = os.path.splitext(filename)[1]
141
outfile = open(filename, 'wb')
143
if format == "callgrind":
144
self.calltree(outfile)
145
elif format == "txt":
146
self.pprint(file=outfile)
149
cPickle.dump(self, outfile, 2)
154
class _CallTreeFilter(object):
155
"""Converter of a Stats object to input suitable for KCacheGrind.
157
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
158
with the changes made by J.P. Calderone and Itamar applied. Note that
159
isinstance(code, str) needs to be used at times to determine if the code
160
object is actually an external code object (with a filename, etc.) or
164
def __init__(self, data):
168
def output(self, out_file):
169
self.out_file = out_file
170
out_file.write('events: Ticks\n')
171
self._print_summary()
172
for entry in self.data:
175
def _print_summary(self):
177
for entry in self.data:
178
totaltime = int(entry.totaltime * 1000)
179
max_cost = max(max_cost, totaltime)
180
self.out_file.write('summary: %d\n' % (max_cost,))
182
def _entry(self, entry):
183
out_file = self.out_file
185
inlinetime = int(entry.inlinetime * 1000)
186
#out_file.write('ob=%s\n' % (code.co_filename,))
187
if isinstance(code, str):
188
out_file.write('fi=~\n')
190
out_file.write('fi=%s\n' % (code.co_filename,))
191
out_file.write('fn=%s\n' % (label(code, True),))
192
if isinstance(code, str):
193
out_file.write('0 %s\n' % (inlinetime,))
195
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
196
# recursive calls are counted in entry.calls
201
if isinstance(code, str):
204
lineno = code.co_firstlineno
205
for subentry in calls:
206
self._subentry(lineno, subentry)
209
def _subentry(self, lineno, subentry):
210
out_file = self.out_file
212
totaltime = int(subentry.totaltime * 1000)
213
#out_file.write('cob=%s\n' % (code.co_filename,))
214
out_file.write('cfn=%s\n' % (label(code, True),))
215
if isinstance(code, str):
216
out_file.write('cfi=~\n')
217
out_file.write('calls=%d 0\n' % (subentry.callcount,))
219
out_file.write('cfi=%s\n' % (code.co_filename,))
220
out_file.write('calls=%d %d\n' % (
221
subentry.callcount, code.co_firstlineno))
222
out_file.write('%d %d\n' % (lineno, totaltime))
226
def label(code, calltree=False):
227
if isinstance(code, str):
230
mname = _fn2mod[code.co_filename]
232
for k, v in sys.modules.items():
235
if getattr(v, '__file__', None) is None:
237
if not isinstance(v.__file__, str):
239
if v.__file__.startswith(code.co_filename):
240
mname = _fn2mod[code.co_filename] = k
243
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
245
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
247
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
250
if __name__ == '__main__':
252
sys.argv = sys.argv[1:]
254
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
256
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
257
stats = profile(execfile, sys.argv[0], globals(), locals())