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