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
13
from bzrlib import errors
25
15
__all__ = ['profile', 'Stats']
28
17
def profile(f, *args, **kwds):
29
18
"""Run a function profile.
49
38
class BzrProfiler(object):
50
39
"""Bzr utility wrapper around Profiler.
52
41
For most uses the module level 'profile()' function will be suitable.
53
42
However profiling when a simple wrapped function isn't available may
54
43
be easier to accomplish using this class.
60
49
Note that profiling involves a threading.Lock around the actual profiling.
61
50
This is needed because profiling involves global manipulation of the python
62
51
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
52
Trying to do so will lock out the second profiler unless the global
53
bzrlib.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
65
54
cause profiling to fail rather than blocking.
116
105
def _thread_profile(self, f, *args, **kwds):
117
106
# we lose the first profile point for a new thread in order to
118
107
# trampoline a new Profile object into place
119
thr = _thread.get_ident()
108
thr = thread.get_ident()
120
109
self._g_threadmap[thr] = p = Profiler()
121
110
# this overrides our sys.setprofile hook:
122
111
p.enable(subcalls=True, builtins=True)
125
114
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
117
def __init__(self, data, threads):
135
119
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)
121
def sort(self, crit="inlinetime"):
123
if crit not in profiler_entry.__dict__:
124
raise ValueError, "Can't sort by %s" % crit
125
self.data.sort(lambda b, a: cmp(getattr(a, crit),
147
127
for e in self.data:
149
e.calls.sort(key=key_func, reverse=reverse)
129
e.calls.sort(lambda b, a: cmp(getattr(a, crit),
151
132
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
135
file = sys.stdout
163
139
cols = "% 12s %12s %11.4f %11.4f %s\n"
164
140
hcols = "% 12s %12s %12s %12s %s\n"
141
cols2 = "+%12s %12s %11.4f %11.4f + %s\n"
165
142
file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
166
143
"Inline(ms)", "module:lineno(function)"))
213
190
ext = os.path.splitext(filename)[1]
216
with open(filename, 'wb') as outfile:
193
outfile = open(filename, 'wb')
217
195
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))
196
self.calltree(outfile)
222
197
elif format == "txt":
223
self.pprint(file=codecs.getwriter('utf-8')(outfile))
198
self.pprint(file=outfile)
226
pickle.dump(self, outfile, 2)
201
cPickle.dump(self, outfile, 2)
229
206
class _CallTreeFilter(object):
284
262
out_file = self.out_file
285
263
code = subentry.code
286
264
totaltime = int(subentry.totaltime * 1000)
265
#out_file.write('cob=%s\n' % (code.co_filename,))
266
out_file.write('cfn=%s\n' % (label(code, True),))
287
267
if isinstance(code, str):
288
268
out_file.write('cfi=~\n')
289
out_file.write('cfn=%s\n' % (label(code, True),))
290
269
out_file.write('calls=%d 0\n' % (subentry.callcount,))
292
271
out_file.write('cfi=%s\n' % (code.co_filename,))
293
out_file.write('cfn=%s\n' % (label(code, True),))
294
272
out_file.write('calls=%d %d\n' % (
295
273
subentry.callcount, code.co_firstlineno))
296
274
out_file.write('%d %d\n' % (lineno, totaltime))
302
278
def label(code, calltree=False):
303
279
if isinstance(code, str):
316
292
mname = _fn2mod[code.co_filename] = k
319
mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
295
mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
321
297
return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
323
299
return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
302
if __name__ == '__main__':
327
304
sys.argv = sys.argv[1:]
329
306
sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
332
result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
308
sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
309
stats = profile(execfile, sys.argv[0], globals(), locals())
337
if __name__ == '__main__':