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)
117
def __init__(self, data, threads):
119
self.threads = threads
121
def sort(self, crit="inlinetime"):
123
if crit not in profiler_entry.__dict__:
124
raise ValueError, "Can't sort by %s" % crit
125
self.data.sort(lambda b, a: cmp(getattr(a, crit),
129
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
132
def pprint(self, top=None, file=None):
139
cols = "% 12s %12s %11.4f %11.4f %s\n"
140
hcols = "% 12s %12s %12s %12s %s\n"
141
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
142
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
143
"Inline(ms)", "module:lineno(function)"))
145
file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
146
e.inlinetime, label(e.code)))
149
file.write(cols % ("+%s" % se.callcount, se.reccallcount,
150
se.totaltime, se.inlinetime,
151
"+%s" % label(se.code)))
154
"""Replace all references to code objects with string
155
descriptions; this makes it possible to pickle the instance."""
157
# this code is probably rather ickier than it needs to be!
158
for i in range(len(self.data)):
160
if not isinstance(e.code, str):
161
self.data[i] = type(e)((label(e.code),) + e[1:])
163
for j in range(len(e.calls)):
165
if not isinstance(se.code, str):
166
e.calls[j] = type(se)((label(se.code),) + se[1:])
167
for s in self.threads.values():
170
def calltree(self, file):
171
"""Output profiling data in calltree format (for KCacheGrind)."""
172
_CallTreeFilter(self.data).output(file)
174
def save(self, filename, format=None):
175
"""Save profiling data to a file.
177
:param filename: the name of the output file
178
:param format: 'txt' for a text representation;
179
'callgrind' for calltree format;
180
otherwise a pickled Python object. A format of None indicates
181
that the format to use is to be found from the filename. If
182
the name starts with callgrind.out, callgrind format is used
183
otherwise the format is given by the filename extension.
186
basename = os.path.basename(filename)
187
if basename.startswith('callgrind.out'):
190
ext = os.path.splitext(filename)[1]
193
outfile = open(filename, 'wb')
195
if format == "callgrind":
196
self.calltree(outfile)
197
elif format == "txt":
198
self.pprint(file=outfile)
201
cPickle.dump(self, outfile, 2)
206
class _CallTreeFilter(object):
207
"""Converter of a Stats object to input suitable for KCacheGrind.
209
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
210
with the changes made by J.P. Calderone and Itamar applied. Note that
211
isinstance(code, str) needs to be used at times to determine if the code
212
object is actually an external code object (with a filename, etc.) or
216
def __init__(self, data):
220
def output(self, out_file):
221
self.out_file = out_file
222
out_file.write('events: Ticks\n')
223
self._print_summary()
224
for entry in self.data:
227
def _print_summary(self):
229
for entry in self.data:
230
totaltime = int(entry.totaltime * 1000)
231
max_cost = max(max_cost, totaltime)
232
self.out_file.write('summary: %d\n' % (max_cost,))
234
def _entry(self, entry):
235
out_file = self.out_file
237
inlinetime = int(entry.inlinetime * 1000)
238
#out_file.write('ob=%s\n' % (code.co_filename,))
239
if isinstance(code, str):
240
out_file.write('fi=~\n')
242
out_file.write('fi=%s\n' % (code.co_filename,))
243
out_file.write('fn=%s\n' % (label(code, True),))
244
if isinstance(code, str):
245
out_file.write('0 %s\n' % (inlinetime,))
247
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
248
# recursive calls are counted in entry.calls
253
if isinstance(code, str):
256
lineno = code.co_firstlineno
257
for subentry in calls:
258
self._subentry(lineno, subentry)
261
def _subentry(self, lineno, subentry):
262
out_file = self.out_file
264
totaltime = int(subentry.totaltime * 1000)
265
#out_file.write('cob=%s\n' % (code.co_filename,))
266
out_file.write('cfn=%s\n' % (label(code, True),))
267
if isinstance(code, str):
268
out_file.write('cfi=~\n')
269
out_file.write('calls=%d 0\n' % (subentry.callcount,))
271
out_file.write('cfi=%s\n' % (code.co_filename,))
272
out_file.write('calls=%d %d\n' % (
273
subentry.callcount, code.co_firstlineno))
274
out_file.write('%d %d\n' % (lineno, totaltime))
278
def label(code, calltree=False):
279
if isinstance(code, str):
282
mname = _fn2mod[code.co_filename]
284
for k, v in sys.modules.items():
287
if getattr(v, '__file__', None) is None:
289
if not isinstance(v.__file__, str):
291
if v.__file__.startswith(code.co_filename):
292
mname = _fn2mod[code.co_filename] = k
295
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
297
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
299
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
302
if __name__ == '__main__':
304
sys.argv = sys.argv[1:]
306
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
308
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
309
stats = profile(execfile, sys.argv[0], globals(), locals())