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']
16
def profile(f, *args, **kwds):
17
"""Run a function profile.
19
Exceptions are not caught: If you need stats even when exceptions are to be
20
raised, pass in a closure that will catch the exceptions and transform them
21
appropriately for your driver function.
23
:return: The functions return value and a stats object.
25
profiler = BzrProfiler()
28
ret = f(*args, **kwds)
30
stats = profiler.stop()
34
class BzrProfiler(object):
35
"""Bzr utility wrapper around Profiler.
37
For most uses the module level 'profile()' function will be suitable.
38
However profiling when a simple wrapped function isn't available may
39
be easier to accomplish using this class.
41
To use it, create a BzrProfiler and call start() on it. Some arbitrary
42
time later call stop() to stop profiling and retrieve the statistics
43
from the code executed in the interim.
49
This hooks into threading and will record all calls made until
52
self._g_threadmap = {}
54
self.p.enable(subcalls=True)
55
threading.setprofile(self._thread_profile)
60
This unhooks from threading and cleans up the profiler, returning
61
the gathered Stats object.
63
:return: A bzrlib.lsprof.Stats object.
66
for pp in self._g_threadmap.values():
68
threading.setprofile(None)
72
for tid, pp in self._g_threadmap.items():
73
threads[tid] = Stats(pp.getstats(), {})
74
self._g_threadmap = None
75
return Stats(p.getstats(), threads)
77
def _thread_profile(self, f, *args, **kwds):
78
# we lose the first profile point for a new thread in order to
79
# trampoline a new Profile object into place
80
thr = thread.get_ident()
81
self._g_threadmap[thr] = p = Profiler()
82
# this overrides our sys.setprofile hook:
83
p.enable(subcalls=True, builtins=True)
89
def __init__(self, data, threads):
91
self.threads = threads
93
def sort(self, crit="inlinetime"):
95
if crit not in profiler_entry.__dict__:
96
raise ValueError, "Can't sort by %s" % crit
97
self.data.sort(lambda b, a: cmp(getattr(a, crit),
101
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
104
def pprint(self, top=None, file=None):
111
cols = "% 12s %12s %11.4f %11.4f %s\n"
112
hcols = "% 12s %12s %12s %12s %s\n"
113
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
114
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
115
"Inline(ms)", "module:lineno(function)"))
117
file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
118
e.inlinetime, label(e.code)))
121
file.write(cols % ("+%s" % se.callcount, se.reccallcount,
122
se.totaltime, se.inlinetime,
123
"+%s" % label(se.code)))
126
"""Replace all references to code objects with string
127
descriptions; this makes it possible to pickle the instance."""
129
# this code is probably rather ickier than it needs to be!
130
for i in range(len(self.data)):
132
if not isinstance(e.code, str):
133
self.data[i] = type(e)((label(e.code),) + e[1:])
135
for j in range(len(e.calls)):
137
if not isinstance(se.code, str):
138
e.calls[j] = type(se)((label(se.code),) + se[1:])
139
for s in self.threads.values():
142
def calltree(self, file):
143
"""Output profiling data in calltree format (for KCacheGrind)."""
144
_CallTreeFilter(self.data).output(file)
146
def save(self, filename, format=None):
147
"""Save profiling data to a file.
149
:param filename: the name of the output file
150
:param format: 'txt' for a text representation;
151
'callgrind' for calltree format;
152
otherwise a pickled Python object. A format of None indicates
153
that the format to use is to be found from the filename. If
154
the name starts with callgrind.out, callgrind format is used
155
otherwise the format is given by the filename extension.
158
basename = os.path.basename(filename)
159
if basename.startswith('callgrind.out'):
162
ext = os.path.splitext(filename)[1]
165
outfile = open(filename, 'wb')
167
if format == "callgrind":
168
self.calltree(outfile)
169
elif format == "txt":
170
self.pprint(file=outfile)
173
cPickle.dump(self, outfile, 2)
178
class _CallTreeFilter(object):
179
"""Converter of a Stats object to input suitable for KCacheGrind.
181
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
182
with the changes made by J.P. Calderone and Itamar applied. Note that
183
isinstance(code, str) needs to be used at times to determine if the code
184
object is actually an external code object (with a filename, etc.) or
188
def __init__(self, data):
192
def output(self, out_file):
193
self.out_file = out_file
194
out_file.write('events: Ticks\n')
195
self._print_summary()
196
for entry in self.data:
199
def _print_summary(self):
201
for entry in self.data:
202
totaltime = int(entry.totaltime * 1000)
203
max_cost = max(max_cost, totaltime)
204
self.out_file.write('summary: %d\n' % (max_cost,))
206
def _entry(self, entry):
207
out_file = self.out_file
209
inlinetime = int(entry.inlinetime * 1000)
210
#out_file.write('ob=%s\n' % (code.co_filename,))
211
if isinstance(code, str):
212
out_file.write('fi=~\n')
214
out_file.write('fi=%s\n' % (code.co_filename,))
215
out_file.write('fn=%s\n' % (label(code, True),))
216
if isinstance(code, str):
217
out_file.write('0 %s\n' % (inlinetime,))
219
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
220
# recursive calls are counted in entry.calls
225
if isinstance(code, str):
228
lineno = code.co_firstlineno
229
for subentry in calls:
230
self._subentry(lineno, subentry)
233
def _subentry(self, lineno, subentry):
234
out_file = self.out_file
236
totaltime = int(subentry.totaltime * 1000)
237
#out_file.write('cob=%s\n' % (code.co_filename,))
238
out_file.write('cfn=%s\n' % (label(code, True),))
239
if isinstance(code, str):
240
out_file.write('cfi=~\n')
241
out_file.write('calls=%d 0\n' % (subentry.callcount,))
243
out_file.write('cfi=%s\n' % (code.co_filename,))
244
out_file.write('calls=%d %d\n' % (
245
subentry.callcount, code.co_firstlineno))
246
out_file.write('%d %d\n' % (lineno, totaltime))
250
def label(code, calltree=False):
251
if isinstance(code, str):
254
mname = _fn2mod[code.co_filename]
256
for k, v in sys.modules.items():
259
if getattr(v, '__file__', None) is None:
261
if not isinstance(v.__file__, str):
263
if v.__file__.startswith(code.co_filename):
264
mname = _fn2mod[code.co_filename] = k
267
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
269
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
271
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
274
if __name__ == '__main__':
276
sys.argv = sys.argv[1:]
278
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
280
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
281
stats = profile(execfile, sys.argv[0], globals(), locals())