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
11
21
from _lsprof import Profiler, profiler_entry
14
25
__all__ = ['profile', 'Stats']
16
28
def profile(f, *args, **kwds):
17
29
"""Run a function profile.
41
56
To use it, create a BzrProfiler and call start() on it. Some arbitrary
42
57
time later call stop() to stop profiling and retrieve the statistics
43
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."""
47
75
"""Start profiling.
49
77
This hooks into threading and will record all calls made until
52
80
self._g_threadmap = {}
53
81
self.p = Profiler()
54
self.p.enable(subcalls=True)
55
threading.setprofile(self._thread_profile)
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()
60
96
This unhooks from threading and cleans up the profiler, returning
61
97
the gathered Stats object.
63
:return: A bzrlib.lsprof.Stats object.
99
: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)
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()
77
116
def _thread_profile(self, f, *args, **kwds):
78
117
# we lose the first profile point for a new thread in order to
79
118
# trampoline a new Profile object into place
80
thr = thread.get_ident()
119
thr = _thread.get_ident()
81
120
self._g_threadmap[thr] = p = Profiler()
82
121
# this overrides our sys.setprofile hook:
83
122
p.enable(subcalls=True, builtins=True)
86
125
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.
89
133
def __init__(self, data, threads):
91
135
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),
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)
99
147
for e in self.data:
101
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
149
e.calls.sort(key=key_func, reverse=reverse)
104
151
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."""
107
159
file = sys.stdout
162
213
ext = os.path.splitext(filename)[1]
165
outfile = open(filename, 'wb')
216
with open(filename, 'wb') as outfile:
167
217
if format == "callgrind":
168
self.calltree(outfile)
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))
169
222
elif format == "txt":
170
self.pprint(file=outfile)
223
self.pprint(file=codecs.getwriter('utf-8')(outfile))
173
cPickle.dump(self, outfile, 2)
226
pickle.dump(self, outfile, 2)
178
229
class _CallTreeFilter(object):
234
284
out_file = self.out_file
235
285
code = subentry.code
236
286
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
287
if isinstance(code, str):
240
288
out_file.write('cfi=~\n')
289
out_file.write('cfn=%s\n' % (label(code, True),))
241
290
out_file.write('calls=%d 0\n' % (subentry.callcount,))
243
292
out_file.write('cfi=%s\n' % (code.co_filename,))
293
out_file.write('cfn=%s\n' % (label(code, True),))
244
294
out_file.write('calls=%d %d\n' % (
245
295
subentry.callcount, code.co_firstlineno))
246
296
out_file.write('%d %d\n' % (lineno, totaltime))
250
302
def label(code, calltree=False):
251
303
if isinstance(code, str):
264
316
mname = _fn2mod[code.co_filename] = k
267
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
319
mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
269
321
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
271
323
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
274
if __name__ == '__main__':
276
327
sys.argv = sys.argv[1:]
278
329
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())
332
result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
337
if __name__ == '__main__':