3
3
# I made one modification to profile so that it returns a pair
4
4
# instead of just the Stats object
8
import cPickle as pickle
16
9
from _lsprof import Profiler, profiler_entry
20
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)
23
26
def profile(f, *args, **kwds):
24
"""Run a function profile.
26
Exceptions are not caught: If you need stats even when exceptions are to be
27
raised, pass in a closure that will catch the exceptions and transform them
28
appropriately for your driver function.
30
Important caveat: only one profile can execute at a time. See BzrProfiler
33
:return: The functions return value and a stats object.
35
profiler = BzrProfiler()
30
p.enable(subcalls=True)
31
threading.setprofile(_thread_profile)
38
33
ret = f(*args, **kwds)
40
stats = profiler.stop()
44
class BzrProfiler(object):
45
"""Bzr utility wrapper around Profiler.
47
For most uses the module level 'profile()' function will be suitable.
48
However profiling when a simple wrapped function isn't available may
49
be easier to accomplish using this class.
51
To use it, create a BzrProfiler and call start() on it. Some arbitrary
52
time later call stop() to stop profiling and retrieve the statistics
53
from the code executed in the interim.
55
Note that profiling involves a threading.Lock around the actual profiling.
56
This is needed because profiling involves global manipulation of the python
57
interpreter state. As such you cannot perform multiple profiles at once.
58
Trying to do so will lock out the second profiler unless the global
59
breezy.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
60
cause profiling to fail rather than blocking.
64
"""Serialise rather than failing to profile concurrent profile requests."""
66
profiler_lock = threading.Lock()
67
"""Global lock used to serialise profiles."""
72
This hooks into threading and will record all calls made until
75
self._g_threadmap = {}
77
permitted = self.__class__.profiler_lock.acquire(
78
self.__class__.profiler_block)
80
raise errors.InternalBzrError(msg="Already profiling something")
82
self.p.enable(subcalls=True)
83
threading.setprofile(self._thread_profile)
85
self.__class__.profiler_lock.release()
91
This unhooks from threading and cleans up the profiler, returning
92
the gathered Stats object.
94
:return: A breezy.lsprof.Stats object.
98
for pp in self._g_threadmap.values():
100
threading.setprofile(None)
104
for tid, pp in self._g_threadmap.items():
105
threads[tid] = Stats(pp.getstats(), {})
106
self._g_threadmap = None
107
return Stats(p.getstats(), threads)
109
self.__class__.profiler_lock.release()
111
def _thread_profile(self, f, *args, **kwds):
112
# we lose the first profile point for a new thread in order to
113
# trampoline a new Profile object into place
114
thr = _thread.get_ident()
115
self._g_threadmap[thr] = p = Profiler()
116
# this overrides our sys.setprofile hook:
117
p.enable(subcalls=True, builtins=True)
36
for pp in _g_threadmap.values():
38
threading.setprofile(None)
41
for tid, pp in _g_threadmap.items():
42
threads[tid] = Stats(pp.getstats(), {})
44
return ret, Stats(p.getstats(), threads)
120
47
class Stats(object):
121
"""Wrapper around the collected data.
123
A Stats instance is created when the profiler finishes. Normal
124
usage is to use save() to write out the data to a file, or pprint()
125
to write human-readable information to the command line.
128
50
def __init__(self, data, threads):
130
52
self.threads = threads
132
def sort(self, crit="inlinetime", reverse=True):
133
"""Sort the data by the supplied critera.
135
:param crit: the data attribute used as the sort key."""
136
if crit not in profiler_entry.__dict__ or crit == 'code':
137
raise ValueError("Can't sort by %s" % crit)
139
key_func = operator.attrgetter(crit)
140
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),
142
60
for e in self.data:
144
e.calls.sort(key=key_func, reverse=reverse)
62
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
146
65
def pprint(self, top=None, file=None):
147
"""Pretty-print the data as plain text for human consumption.
149
:param top: only output the top n entries.
150
The default value of None means output all data.
151
:param file: the output file; if None, output will
152
default to stdout."""
189
104
"""Output profiling data in calltree format (for KCacheGrind)."""
190
105
_CallTreeFilter(self.data).output(file)
192
def save(self, filename, format=None):
193
"""Save profiling data to a file.
195
:param filename: the name of the output file
196
:param format: 'txt' for a text representation;
197
'callgrind' for calltree format;
198
otherwise a pickled Python object. A format of None indicates
199
that the format to use is to be found from the filename. If
200
the name starts with callgrind.out, callgrind format is used
201
otherwise the format is given by the filename extension.
204
basename = os.path.basename(filename)
205
if basename.startswith('callgrind.out'):
208
ext = os.path.splitext(filename)[1]
211
with open(filename, 'wb') as outfile:
212
if format == "callgrind":
213
# The callgrind format states it is 'ASCII based':
214
# <http://valgrind.org/docs/manual/cl-format.html>
215
# But includes filenames so lets ignore and use UTF-8.
216
self.calltree(codecs.getwriter('utf-8')(outfile))
217
elif format == "txt":
218
self.pprint(file=codecs.getwriter('utf-8')(outfile))
221
pickle.dump(self, outfile, 2)
224
108
class _CallTreeFilter(object):
225
"""Converter of a Stats object to input suitable for KCacheGrind.
227
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
228
with the changes made by J.P. Calderone and Itamar applied. Note that
229
isinstance(code, str) needs to be used at times to determine if the code
230
object is actually an external code object (with a filename, etc.) or
234
110
def __init__(self, data):
236
112
self.out_file = None
238
114
def output(self, out_file):
239
self.out_file = out_file
240
out_file.write('events: Ticks\n')
115
self.out_file = out_file
116
print >> out_file, 'events: Ticks'
241
117
self._print_summary()
242
118
for entry in self.data:
243
119
self._entry(entry)
247
123
for entry in self.data:
248
124
totaltime = int(entry.totaltime * 1000)
249
125
max_cost = max(max_cost, totaltime)
250
self.out_file.write('summary: %d\n' % (max_cost,))
126
print >> self.out_file, 'summary: %d' % (max_cost,)
252
128
def _entry(self, entry):
253
129
out_file = self.out_file
254
130
code = entry.code
255
131
inlinetime = int(entry.inlinetime * 1000)
256
if isinstance(code, str):
257
out_file.write('fi=~\n')
259
out_file.write('fi=%s\n' % (code.co_filename,))
260
out_file.write('fn=%s\n' % (label(code, True),))
261
if isinstance(code, str):
262
out_file.write('0 %s\n' % (inlinetime,))
264
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)
265
136
# recursive calls are counted in entry.calls
267
138
calls = entry.calls
270
if isinstance(code, str):
273
lineno = code.co_firstlineno
274
141
for subentry in calls:
275
self._subentry(lineno, subentry)
142
self._subentry(code.co_firstlineno, subentry)
278
145
def _subentry(self, lineno, subentry):
279
146
out_file = self.out_file
280
147
code = subentry.code
281
148
totaltime = int(subentry.totaltime * 1000)
282
if isinstance(code, str):
283
out_file.write('cfi=~\n')
284
out_file.write('cfn=%s\n' % (label(code, True),))
285
out_file.write('calls=%d 0\n' % (subentry.callcount,))
287
out_file.write('cfi=%s\n' % (code.co_filename,))
288
out_file.write('cfn=%s\n' % (label(code, True),))
289
out_file.write('calls=%d %d\n' % (
290
subentry.callcount, code.co_firstlineno))
291
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)
297
159
def label(code, calltree=False):
298
160
if isinstance(code, str):
311
173
mname = _fn2mod[code.co_filename] = k
314
mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
176
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
316
178
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
318
180
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
183
if __name__ == '__main__':
322
185
sys.argv = sys.argv[1:]
324
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
187
print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
327
result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
189
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
190
stats = profile(execfile, sys.argv[0], globals(), locals())
332
if __name__ == '__main__':