3
3
# I made one modification to profile so that it returns a pair
4
4
# instead of just the Stats object
6
from __future__ import absolute_import
10
import cPickle as pickle
19
import thread as _thread
21
from _lsprof import Profiler, profiler_entry
7
from _lsprof import Profiler, profiler_entry, profiler_subentry
25
9
__all__ = ['profile', 'Stats']
28
11
def profile(f, *args, **kwds):
29
"""Run a function profile.
31
Exceptions are not caught: If you need stats even when exceptions are to be
32
raised, pass in a closure that will catch the exceptions and transform them
33
appropriately for your driver function.
35
Important caveat: only one profile can execute at a time. See BzrProfiler
38
:return: The functions return value and a stats object.
40
profiler = BzrProfiler()
14
p.enable(subcalls=True)
43
16
ret = f(*args, **kwds)
45
stats = profiler.stop()
49
class BzrProfiler(object):
50
"""Bzr utility wrapper around Profiler.
52
For most uses the module level 'profile()' function will be suitable.
53
However profiling when a simple wrapped function isn't available may
54
be easier to accomplish using this class.
56
To use it, create a BzrProfiler and call start() on it. Some arbitrary
57
time later call stop() to stop profiling and retrieve the statistics
58
from the code executed in the interim.
60
Note that profiling involves a threading.Lock around the actual profiling.
61
This is needed because profiling involves global manipulation of the python
62
interpreter state. As such you cannot perform multiple profiles at once.
63
Trying to do so will lock out the second profiler unless the global
64
breezy.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
65
cause profiling to fail rather than blocking.
69
"""Serialise rather than failing to profile concurrent profile requests."""
71
profiler_lock = threading.Lock()
72
"""Global lock used to serialise profiles."""
77
This hooks into threading and will record all calls made until
80
self._g_threadmap = {}
82
permitted = self.__class__.profiler_lock.acquire(
83
self.__class__.profiler_block)
85
raise errors.InternalBzrError(msg="Already profiling something")
87
self.p.enable(subcalls=True)
88
threading.setprofile(self._thread_profile)
90
self.__class__.profiler_lock.release()
96
This unhooks from threading and cleans up the profiler, returning
97
the gathered Stats object.
99
:return: A breezy.lsprof.Stats object.
103
for pp in self._g_threadmap.values():
105
threading.setprofile(None)
109
for tid, pp in self._g_threadmap.items():
110
threads[tid] = Stats(pp.getstats(), {})
111
self._g_threadmap = None
112
return Stats(p.getstats(), threads)
114
self.__class__.profiler_lock.release()
116
def _thread_profile(self, f, *args, **kwds):
117
# we lose the first profile point for a new thread in order to
118
# trampoline a new Profile object into place
119
thr = _thread.get_ident()
120
self._g_threadmap[thr] = p = Profiler()
121
# this overrides our sys.setprofile hook:
122
p.enable(subcalls=True, builtins=True)
19
return ret,Stats(p.getstats())
125
22
class Stats(object):
126
"""Wrapper around the collected data.
128
A Stats instance is created when the profiler finishes. Normal
129
usage is to use save() to write out the data to a file, or pprint()
130
to write human-readable information to the command line.
133
def __init__(self, data, threads):
25
def __init__(self, data):
135
self.threads = threads
137
def sort(self, crit="inlinetime", reverse=True):
138
"""Sort the data by the supplied critera.
140
:param crit: the data attribute used as the sort key."""
141
if crit not in profiler_entry.__dict__ or crit == 'code':
142
raise ValueError("Can't sort by %s" % crit)
144
key_func = operator.attrgetter(crit)
145
self.data.sort(key=key_func, reverse=reverse)
28
def sort(self, crit="inlinetime"):
30
if crit not in profiler_entry.__dict__:
31
raise ValueError, "Can't sort by %s" % crit
32
self.data.sort(lambda b, a: cmp(getattr(a, crit),
147
34
for e in self.data:
149
e.calls.sort(key=key_func, reverse=reverse)
36
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
151
39
def pprint(self, top=None, file=None):
152
"""Pretty-print the data as plain text for human consumption.
154
:param top: only output the top n entries.
155
The default value of None means output all data.
156
:param file: the output file; if None, output will
157
default to stdout."""
183
67
if not isinstance(e.code, str):
184
68
self.data[i] = type(e)((label(e.code),) + e[1:])
186
for j in range(len(e.calls)):
188
if not isinstance(se.code, str):
189
e.calls[j] = type(se)((label(se.code),) + se[1:])
190
for s in self.threads.values():
193
def calltree(self, file):
194
"""Output profiling data in calltree format (for KCacheGrind)."""
195
_CallTreeFilter(self.data).output(file)
197
def save(self, filename, format=None):
198
"""Save profiling data to a file.
200
:param filename: the name of the output file
201
:param format: 'txt' for a text representation;
202
'callgrind' for calltree format;
203
otherwise a pickled Python object. A format of None indicates
204
that the format to use is to be found from the filename. If
205
the name starts with callgrind.out, callgrind format is used
206
otherwise the format is given by the filename extension.
209
basename = os.path.basename(filename)
210
if basename.startswith('callgrind.out'):
213
ext = os.path.splitext(filename)[1]
216
with open(filename, 'wb') as outfile:
217
if format == "callgrind":
218
# The callgrind format states it is 'ASCII based':
219
# <http://valgrind.org/docs/manual/cl-format.html>
220
# But includes filenames so lets ignore and use UTF-8.
221
self.calltree(codecs.getwriter('utf-8')(outfile))
222
elif format == "txt":
223
self.pprint(file=codecs.getwriter('utf-8')(outfile))
226
pickle.dump(self, outfile, 2)
229
class _CallTreeFilter(object):
230
"""Converter of a Stats object to input suitable for KCacheGrind.
232
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
233
with the changes made by J.P. Calderone and Itamar applied. Note that
234
isinstance(code, str) needs to be used at times to determine if the code
235
object is actually an external code object (with a filename, etc.) or
239
def __init__(self, data):
243
def output(self, out_file):
244
self.out_file = out_file
245
out_file.write('events: Ticks\n')
246
self._print_summary()
247
for entry in self.data:
250
def _print_summary(self):
252
for entry in self.data:
253
totaltime = int(entry.totaltime * 1000)
254
max_cost = max(max_cost, totaltime)
255
self.out_file.write('summary: %d\n' % (max_cost,))
257
def _entry(self, entry):
258
out_file = self.out_file
260
inlinetime = int(entry.inlinetime * 1000)
261
if isinstance(code, str):
262
out_file.write('fi=~\n')
264
out_file.write('fi=%s\n' % (code.co_filename,))
265
out_file.write('fn=%s\n' % (label(code, True),))
266
if isinstance(code, str):
267
out_file.write('0 %s\n' % (inlinetime,))
269
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
270
# recursive calls are counted in entry.calls
275
if isinstance(code, str):
278
lineno = code.co_firstlineno
279
for subentry in calls:
280
self._subentry(lineno, subentry)
283
def _subentry(self, lineno, subentry):
284
out_file = self.out_file
286
totaltime = int(subentry.totaltime * 1000)
287
if isinstance(code, str):
288
out_file.write('cfi=~\n')
289
out_file.write('cfn=%s\n' % (label(code, True),))
290
out_file.write('calls=%d 0\n' % (subentry.callcount,))
292
out_file.write('cfi=%s\n' % (code.co_filename,))
293
out_file.write('cfn=%s\n' % (label(code, True),))
294
out_file.write('calls=%d %d\n' % (
295
subentry.callcount, code.co_firstlineno))
296
out_file.write('%d %d\n' % (lineno, totaltime))
70
for j in range(len(e.calls)):
72
if not isinstance(se.code, str):
73
e.calls[j] = type(se)((label(se.code),) + se[1:])
302
def label(code, calltree=False):
303
78
if isinstance(code, str):
306
81
mname = _fn2mod[code.co_filename]
308
for k, v in sys.modules.items():
83
for k, v in sys.modules.iteritems():
311
if getattr(v, '__file__', None) is None:
86
if not hasattr(v, '__file__'):
313
88
if not isinstance(v.__file__, str):
316
91
mname = _fn2mod[code.co_filename] = k
319
mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
321
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
323
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
94
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
96
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
99
if __name__ == '__main__':
327
101
sys.argv = sys.argv[1:]
329
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
103
print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
332
result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
105
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
106
stats = profile(execfile, sys.argv[0], globals(), locals())
337
if __name__ == '__main__':