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