41
51
To use it, create a BzrProfiler and call start() on it. Some arbitrary
42
52
time later call stop() to stop profiling and retrieve the statistics
43
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."""
47
70
"""Start profiling.
49
72
This hooks into threading and will record all calls made until
52
75
self._g_threadmap = {}
53
76
self.p = Profiler()
54
self.p.enable(subcalls=True)
55
threading.setprofile(self._thread_profile)
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()
60
91
This unhooks from threading and cleans up the profiler, returning
61
92
the gathered Stats object.
63
:return: A bzrlib.lsprof.Stats object.
94
:return: A breezy.lsprof.Stats object.
66
for pp in self._g_threadmap.values():
68
threading.setprofile(None)
72
for tid, pp in self._g_threadmap.items():
73
threads[tid] = Stats(pp.getstats(), {})
74
self._g_threadmap = None
75
return Stats(p.getstats(), threads)
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()
77
111
def _thread_profile(self, f, *args, **kwds):
78
112
# we lose the first profile point for a new thread in order to
79
113
# trampoline a new Profile object into place
80
thr = thread.get_ident()
114
thr = _thread.get_ident()
81
115
self._g_threadmap[thr] = p = Profiler()
82
116
# this overrides our sys.setprofile hook:
83
117
p.enable(subcalls=True, builtins=True)
86
120
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.
89
128
def __init__(self, data, threads):
91
130
self.threads = threads
93
def sort(self, crit="inlinetime"):
95
if crit not in profiler_entry.__dict__:
96
raise ValueError, "Can't sort by %s" % crit
97
self.data.sort(lambda b, a: cmp(getattr(a, crit),
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)
99
142
for e in self.data:
101
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
144
e.calls.sort(key=key_func, reverse=reverse)
104
146
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."""
107
154
file = sys.stdout
162
208
ext = os.path.splitext(filename)[1]
165
outfile = open(filename, 'wb')
211
with open(filename, 'wb') as outfile:
167
212
if format == "callgrind":
168
self.calltree(outfile)
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))
169
217
elif format == "txt":
170
self.pprint(file=outfile)
218
self.pprint(file=codecs.getwriter('utf-8')(outfile))
173
cPickle.dump(self, outfile, 2)
221
pickle.dump(self, outfile, 2)
178
224
class _CallTreeFilter(object):
234
279
out_file = self.out_file
235
280
code = subentry.code
236
281
totaltime = int(subentry.totaltime * 1000)
237
#out_file.write('cob=%s\n' % (code.co_filename,))
238
out_file.write('cfn=%s\n' % (label(code, True),))
239
282
if isinstance(code, str):
240
283
out_file.write('cfi=~\n')
284
out_file.write('cfn=%s\n' % (label(code, True),))
241
285
out_file.write('calls=%d 0\n' % (subentry.callcount,))
243
287
out_file.write('cfi=%s\n' % (code.co_filename,))
288
out_file.write('cfn=%s\n' % (label(code, True),))
244
289
out_file.write('calls=%d %d\n' % (
245
290
subentry.callcount, code.co_firstlineno))
246
291
out_file.write('%d %d\n' % (lineno, totaltime))
250
297
def label(code, calltree=False):
251
298
if isinstance(code, str):
264
311
mname = _fn2mod[code.co_filename] = k
267
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
314
mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
269
316
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
271
318
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
274
if __name__ == '__main__':
276
322
sys.argv = sys.argv[1:]
278
324
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
280
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
281
stats = profile(execfile, sys.argv[0], globals(), locals())
327
result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
332
if __name__ == '__main__':