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
from bzrlib import errors
15
__all__ = ['profile', 'Stats']
17
def profile(f, *args, **kwds):
18
"""Run a function profile.
20
Exceptions are not caught: If you need stats even when exceptions are to be
21
raised, pass in a closure that will catch the exceptions and transform them
22
appropriately for your driver function.
24
Important caveat: only one profile can execute at a time. See BzrProfiler
27
:return: The functions return value and a stats object.
29
profiler = BzrProfiler()
32
ret = f(*args, **kwds)
34
stats = profiler.stop()
38
class BzrProfiler(object):
39
"""Bzr utility wrapper around Profiler.
41
For most uses the module level 'profile()' function will be suitable.
42
However profiling when a simple wrapped function isn't available may
43
be easier to accomplish using this class.
45
To use it, create a BzrProfiler and call start() on it. Some arbitrary
46
time later call stop() to stop profiling and retrieve the statistics
47
from the code executed in the interim.
49
Note that profiling involves a threading.Lock around the actual profiling.
50
This is needed because profiling involves global manipulation of the python
51
interpreter state. As such you cannot perform multiple profiles at once.
52
Trying to do so will lock out the second profiler unless the global
53
bzrlib.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
54
cause profiling to fail rather than blocking.
58
"""Serialise rather than failing to profile concurrent profile requests."""
60
profiler_lock = threading.Lock()
61
"""Global lock used to serialise profiles."""
66
This hooks into threading and will record all calls made until
69
self._g_threadmap = {}
71
permitted = self.__class__.profiler_lock.acquire(
72
self.__class__.profiler_block)
74
raise errors.InternalBzrError(msg="Already profiling something")
76
self.p.enable(subcalls=True)
77
threading.setprofile(self._thread_profile)
79
self.__class__.profiler_lock.release()
85
This unhooks from threading and cleans up the profiler, returning
86
the gathered Stats object.
88
:return: A bzrlib.lsprof.Stats object.
92
for pp in self._g_threadmap.values():
94
threading.setprofile(None)
98
for tid, pp in self._g_threadmap.items():
99
threads[tid] = Stats(pp.getstats(), {})
100
self._g_threadmap = None
101
return Stats(p.getstats(), threads)
103
self.__class__.profiler_lock.release()
105
def _thread_profile(self, f, *args, **kwds):
106
# we lose the first profile point for a new thread in order to
107
# trampoline a new Profile object into place
108
thr = thread.get_ident()
109
self._g_threadmap[thr] = p = Profiler()
110
# this overrides our sys.setprofile hook:
111
p.enable(subcalls=True, builtins=True)
115
"""Wrapper around the collected data.
117
A Stats instance is created when the profiler finishes. Normal
118
usage is to use save() to write out the data to a file, or pprint()
119
to write human-readable information to the command line.
122
def __init__(self, data, threads):
124
self.threads = threads
126
def sort(self, crit="inlinetime"):
127
"""Sort the data by the supplied critera.
129
:param crit: the data attribute used as the sort key."""
130
if crit not in profiler_entry.__dict__:
131
raise ValueError, "Can't sort by %s" % crit
132
self.data.sort(lambda b, a: cmp(getattr(a, crit),
136
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
139
def pprint(self, top=None, file=None):
140
"""Pretty-print the data as plain text for human consumption.
142
:param top: only output the top n entries.
143
The default value of None means output all data.
144
:param file: the output file; if None, output will
145
default to stdout."""
151
cols = "% 12s %12s %11.4f %11.4f %s\n"
152
hcols = "% 12s %12s %12s %12s %s\n"
153
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
154
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
155
"Inline(ms)", "module:lineno(function)"))
157
file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
158
e.inlinetime, label(e.code)))
161
file.write(cols % ("+%s" % se.callcount, se.reccallcount,
162
se.totaltime, se.inlinetime,
163
"+%s" % label(se.code)))
166
"""Replace all references to code objects with string
167
descriptions; this makes it possible to pickle the instance."""
169
# this code is probably rather ickier than it needs to be!
170
for i in range(len(self.data)):
172
if not isinstance(e.code, str):
173
self.data[i] = type(e)((label(e.code),) + e[1:])
175
for j in range(len(e.calls)):
177
if not isinstance(se.code, str):
178
e.calls[j] = type(se)((label(se.code),) + se[1:])
179
for s in self.threads.values():
182
def calltree(self, file):
183
"""Output profiling data in calltree format (for KCacheGrind)."""
184
_CallTreeFilter(self.data).output(file)
186
def save(self, filename, format=None):
187
"""Save profiling data to a file.
189
:param filename: the name of the output file
190
:param format: 'txt' for a text representation;
191
'callgrind' for calltree format;
192
otherwise a pickled Python object. A format of None indicates
193
that the format to use is to be found from the filename. If
194
the name starts with callgrind.out, callgrind format is used
195
otherwise the format is given by the filename extension.
198
basename = os.path.basename(filename)
199
if basename.startswith('callgrind.out'):
202
ext = os.path.splitext(filename)[1]
205
outfile = open(filename, 'wb')
207
if format == "callgrind":
208
self.calltree(outfile)
209
elif format == "txt":
210
self.pprint(file=outfile)
213
cPickle.dump(self, outfile, 2)
218
class _CallTreeFilter(object):
219
"""Converter of a Stats object to input suitable for KCacheGrind.
221
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
222
with the changes made by J.P. Calderone and Itamar applied. Note that
223
isinstance(code, str) needs to be used at times to determine if the code
224
object is actually an external code object (with a filename, etc.) or
228
def __init__(self, data):
232
def output(self, out_file):
233
self.out_file = out_file
234
out_file.write('events: Ticks\n')
235
self._print_summary()
236
for entry in self.data:
239
def _print_summary(self):
241
for entry in self.data:
242
totaltime = int(entry.totaltime * 1000)
243
max_cost = max(max_cost, totaltime)
244
self.out_file.write('summary: %d\n' % (max_cost,))
246
def _entry(self, entry):
247
out_file = self.out_file
249
inlinetime = int(entry.inlinetime * 1000)
250
#out_file.write('ob=%s\n' % (code.co_filename,))
251
if isinstance(code, str):
252
out_file.write('fi=~\n')
254
out_file.write('fi=%s\n' % (code.co_filename,))
255
out_file.write('fn=%s\n' % (label(code, True),))
256
if isinstance(code, str):
257
out_file.write('0 %s\n' % (inlinetime,))
259
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
260
# recursive calls are counted in entry.calls
265
if isinstance(code, str):
268
lineno = code.co_firstlineno
269
for subentry in calls:
270
self._subentry(lineno, subentry)
273
def _subentry(self, lineno, subentry):
274
out_file = self.out_file
276
totaltime = int(subentry.totaltime * 1000)
277
#out_file.write('cob=%s\n' % (code.co_filename,))
278
if isinstance(code, str):
279
out_file.write('cfi=~\n')
280
out_file.write('cfn=%s\n' % (label(code, True),))
281
out_file.write('calls=%d 0\n' % (subentry.callcount,))
283
out_file.write('cfi=%s\n' % (code.co_filename,))
284
out_file.write('cfn=%s\n' % (label(code, True),))
285
out_file.write('calls=%d %d\n' % (
286
subentry.callcount, code.co_firstlineno))
287
out_file.write('%d %d\n' % (lineno, totaltime))
291
def label(code, calltree=False):
292
if isinstance(code, str):
295
mname = _fn2mod[code.co_filename]
297
for k, v in sys.modules.items():
300
if getattr(v, '__file__', None) is None:
302
if not isinstance(v.__file__, str):
304
if v.__file__.startswith(code.co_filename):
305
mname = _fn2mod[code.co_filename] = k
308
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
310
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
312
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
315
if __name__ == '__main__':
317
sys.argv = sys.argv[1:]
319
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
321
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
322
stats = profile(execfile, sys.argv[0], globals(), locals())