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
9
from _lsprof import Profiler, profiler_entry
25
11
__all__ = ['profile', 'Stats']
16
def _thread_profile(f, *args, **kwds):
17
# we lose the first profile point for a new thread in order to trampoline
18
# a new Profile object into place
20
thr = thread.get_ident()
21
_g_threadmap[thr] = p = Profiler()
22
# this overrides our sys.setprofile hook:
23
p.enable(subcalls=True, builtins=True)
27
26
def profile(f, *args, **kwds):
28
"""Run a function profile.
30
Exceptions are not caught: If you need stats even when exceptions are to be
31
raised, pass in a closure that will catch the exceptions and transform them
32
appropriately for your driver function.
34
Important caveat: only one profile can execute at a time. See BzrProfiler
37
:return: The functions return value and a stats object.
39
profiler = BzrProfiler()
30
p.enable(subcalls=True)
31
threading.setprofile(_thread_profile)
42
33
ret = f(*args, **kwds)
44
stats = profiler.stop()
48
class BzrProfiler(object):
49
"""Bzr utility wrapper around Profiler.
36
for pp in _g_threadmap.values():
38
threading.setprofile(None)
51
For most uses the module level 'profile()' function will be suitable.
52
However profiling when a simple wrapped function isn't available may
53
be easier to accomplish using this class.
55
To use it, create a BzrProfiler and call start() on it. Some arbitrary
56
time later call stop() to stop profiling and retrieve the statistics
57
from the code executed in the interim.
59
Note that profiling involves a threading.Lock around the actual profiling.
60
This is needed because profiling involves global manipulation of the python
61
interpreter state. As such you cannot perform multiple profiles at once.
62
Trying to do so will lock out the second profiler unless the global
63
breezy.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
64
cause profiling to fail rather than blocking.
68
"""Serialise rather than failing to profile concurrent profile requests."""
70
profiler_lock = threading.Lock()
71
"""Global lock used to serialise profiles."""
76
This hooks into threading and will record all calls made until
79
self._g_threadmap = {}
81
permitted = self.__class__.profiler_lock.acquire(
82
self.__class__.profiler_block)
84
raise errors.InternalBzrError(msg="Already profiling something")
86
self.p.enable(subcalls=True)
87
threading.setprofile(self._thread_profile)
89
self.__class__.profiler_lock.release()
95
This unhooks from threading and cleans up the profiler, returning
96
the gathered Stats object.
98
:return: A breezy.lsprof.Stats object.
102
for pp in self._g_threadmap.values():
104
threading.setprofile(None)
108
for tid, pp in self._g_threadmap.items():
109
threads[tid] = Stats(pp.getstats(), {})
110
self._g_threadmap = None
111
return Stats(p.getstats(), threads)
113
self.__class__.profiler_lock.release()
115
def _thread_profile(self, f, *args, **kwds):
116
# we lose the first profile point for a new thread in order to
117
# trampoline a new Profile object into place
118
thr = _thread.get_ident()
119
self._g_threadmap[thr] = p = Profiler()
120
# this overrides our sys.setprofile hook:
121
p.enable(subcalls=True, builtins=True)
41
for tid, pp in _g_threadmap.items():
42
threads[tid] = Stats(pp.getstats(), {})
44
return ret, Stats(p.getstats(), threads)
124
47
class Stats(object):
125
"""Wrapper around the collected data.
127
A Stats instance is created when the profiler finishes. Normal
128
usage is to use save() to write out the data to a file, or pprint()
129
to write human-readable information to the command line.
132
50
def __init__(self, data, threads):
134
52
self.threads = threads
136
def sort(self, crit="inlinetime", reverse=True):
137
"""Sort the data by the supplied critera.
139
:param crit: the data attribute used as the sort key."""
140
if crit not in profiler_entry.__dict__ or crit == 'code':
141
raise ValueError("Can't sort by %s" % crit)
143
key_func = operator.attrgetter(crit)
144
self.data.sort(key=key_func, reverse=reverse)
54
def sort(self, crit="inlinetime"):
56
if crit not in profiler_entry.__dict__:
57
raise ValueError, "Can't sort by %s" % crit
58
self.data.sort(lambda b, a: cmp(getattr(a, crit),
146
60
for e in self.data:
148
e.calls.sort(key=key_func, reverse=reverse)
62
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
150
65
def pprint(self, top=None, file=None):
151
"""Pretty-print the data as plain text for human consumption.
153
:param top: only output the top n entries.
154
The default value of None means output all data.
155
:param file: the output file; if None, output will
156
default to stdout."""
194
104
"""Output profiling data in calltree format (for KCacheGrind)."""
195
105
_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
108
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
110
def __init__(self, data):
241
112
self.out_file = None
243
114
def output(self, out_file):
244
self.out_file = out_file
245
out_file.write('events: Ticks\n')
115
self.out_file = out_file
116
print >> out_file, 'events: Ticks'
246
117
self._print_summary()
247
118
for entry in self.data:
248
119
self._entry(entry)
252
123
for entry in self.data:
253
124
totaltime = int(entry.totaltime * 1000)
254
125
max_cost = max(max_cost, totaltime)
255
self.out_file.write('summary: %d\n' % (max_cost,))
126
print >> self.out_file, 'summary: %d' % (max_cost,)
257
128
def _entry(self, entry):
258
129
out_file = self.out_file
259
130
code = entry.code
260
131
inlinetime = int(entry.inlinetime * 1000)
261
#out_file.write('ob=%s\n' % (code.co_filename,))
262
if isinstance(code, str):
263
out_file.write('fi=~\n')
265
out_file.write('fi=%s\n' % (code.co_filename,))
266
out_file.write('fn=%s\n' % (label(code, True),))
267
if isinstance(code, str):
268
out_file.write('0 %s\n' % (inlinetime,))
270
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
132
#print >> out_file, 'ob=%s' % (code.co_filename,)
133
print >> out_file, 'fi=%s' % (code.co_filename,)
134
print >> out_file, 'fn=%s' % (label(code, True),)
135
print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
271
136
# recursive calls are counted in entry.calls
273
138
calls = entry.calls
276
if isinstance(code, str):
279
lineno = code.co_firstlineno
280
141
for subentry in calls:
281
self._subentry(lineno, subentry)
142
self._subentry(code.co_firstlineno, subentry)
284
145
def _subentry(self, lineno, subentry):
285
146
out_file = self.out_file
286
147
code = subentry.code
287
148
totaltime = int(subentry.totaltime * 1000)
288
#out_file.write('cob=%s\n' % (code.co_filename,))
289
if isinstance(code, str):
290
out_file.write('cfi=~\n')
291
out_file.write('cfn=%s\n' % (label(code, True),))
292
out_file.write('calls=%d 0\n' % (subentry.callcount,))
294
out_file.write('cfi=%s\n' % (code.co_filename,))
295
out_file.write('cfn=%s\n' % (label(code, True),))
296
out_file.write('calls=%d %d\n' % (
297
subentry.callcount, code.co_firstlineno))
298
out_file.write('%d %d\n' % (lineno, totaltime))
149
#print >> out_file, 'cob=%s' % (code.co_filename,)
150
print >> out_file, 'cfn=%s' % (label(code, True),)
151
print >> out_file, 'cfi=%s' % (code.co_filename,)
152
print >> out_file, 'calls=%d %d' % (
153
subentry.callcount, code.co_firstlineno)
154
print >> out_file, '%d %d' % (lineno, totaltime)