3
3
# I made one modification to profile so that it returns a pair
4
4
# instead of just the Stats object
7
from _lsprof import Profiler, profiler_entry, profiler_subentry
11
from _lsprof import Profiler, profiler_entry
9
14
__all__ = ['profile', 'Stats']
19
def _thread_profile(f, *args, **kwds):
20
# we lose the first profile point for a new thread in order to trampoline
21
# a new Profile object into place
23
thr = thread.get_ident()
24
_g_threadmap[thr] = p = Profiler()
25
# this overrides our sys.setprofile hook:
26
p.enable(subcalls=True, builtins=True)
11
29
def profile(f, *args, **kwds):
30
"""Run a function profile.
32
:return: The functions return value and a stats object.
14
36
p.enable(subcalls=True)
37
threading.setprofile(_thread_profile)
38
# Note: The except clause is needed below so that profiling data still
39
# gets dumped even when exceptions are encountered. The except clause code
40
# is taken straight from run_bzr_catch_errrors() in commands.py and ought
41
# to be kept in sync with it.
16
ret = f(*args, **kwds)
44
ret = f(*args, **kwds)
45
except (KeyboardInterrupt, Exception), e:
47
bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
19
return ret,Stats(p.getstats())
51
for pp in _g_threadmap.values():
53
threading.setprofile(None)
56
for tid, pp in _g_threadmap.items():
57
threads[tid] = Stats(pp.getstats(), {})
59
return ret, Stats(p.getstats(), threads)
22
62
class Stats(object):
23
63
"""XXX docstring"""
25
def __init__(self, data):
65
def __init__(self, data, threads):
67
self.threads = threads
28
69
def sort(self, crit="inlinetime"):
29
70
"""XXX docstring"""
67
108
if not isinstance(e.code, str):
68
109
self.data[i] = type(e)((label(e.code),) + e[1:])
70
for j in range(len(e.calls)):
72
if not isinstance(se.code, str):
73
e.calls[j] = type(se)((label(se.code),) + se[1:])
111
for j in range(len(e.calls)):
113
if not isinstance(se.code, str):
114
e.calls[j] = type(se)((label(se.code),) + se[1:])
115
for s in self.threads.values():
118
def calltree(self, file):
119
"""Output profiling data in calltree format (for KCacheGrind)."""
120
_CallTreeFilter(self.data).output(file)
122
def save(self, filename, format=None):
123
"""Save profiling data to a file.
125
:param filename: the name of the output file
126
:param format: 'txt' for a text representation;
127
'callgrind' for calltree format;
128
otherwise a pickled Python object. A format of None indicates
129
that the format to use is to be found from the filename. If
130
the name starts with callgrind.out, callgrind format is used
131
otherwise the format is given by the filename extension.
134
basename = os.path.basename(filename)
135
if basename.startswith('callgrind.out'):
138
ext = os.path.splitext(filename)[1]
141
outfile = open(filename, 'wb')
143
if format == "callgrind":
144
self.calltree(outfile)
145
elif format == "txt":
146
self.pprint(file=outfile)
149
cPickle.dump(self, outfile, 2)
154
class _CallTreeFilter(object):
155
"""Converter of a Stats object to input suitable for KCacheGrind.
157
This code is taken from http://ddaa.net/blog/python/lsprof-calltree
158
with the changes made by J.P. Calderone and Itamar applied. Note that
159
isinstance(code, str) needs to be used at times to determine if the code
160
object is actually an external code object (with a filename, etc.) or
164
def __init__(self, data):
168
def output(self, out_file):
169
self.out_file = out_file
170
out_file.write('events: Ticks\n')
171
self._print_summary()
172
for entry in self.data:
175
def _print_summary(self):
177
for entry in self.data:
178
totaltime = int(entry.totaltime * 1000)
179
max_cost = max(max_cost, totaltime)
180
self.out_file.write('summary: %d\n' % (max_cost,))
182
def _entry(self, entry):
183
out_file = self.out_file
185
inlinetime = int(entry.inlinetime * 1000)
186
#out_file.write('ob=%s\n' % (code.co_filename,))
187
if isinstance(code, str):
188
out_file.write('fi=~\n')
190
out_file.write('fi=%s\n' % (code.co_filename,))
191
out_file.write('fn=%s\n' % (label(code, True),))
192
if isinstance(code, str):
193
out_file.write('0 %s\n' % (inlinetime,))
195
out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
196
# recursive calls are counted in entry.calls
201
if isinstance(code, str):
204
lineno = code.co_firstlineno
205
for subentry in calls:
206
self._subentry(lineno, subentry)
209
def _subentry(self, lineno, subentry):
210
out_file = self.out_file
212
totaltime = int(subentry.totaltime * 1000)
213
#out_file.write('cob=%s\n' % (code.co_filename,))
214
out_file.write('cfn=%s\n' % (label(code, True),))
215
if isinstance(code, str):
216
out_file.write('cfi=~\n')
217
out_file.write('calls=%d 0\n' % (subentry.callcount,))
219
out_file.write('cfi=%s\n' % (code.co_filename,))
220
out_file.write('calls=%d %d\n' % (
221
subentry.callcount, code.co_firstlineno))
222
out_file.write('%d %d\n' % (lineno, totaltime))
226
def label(code, calltree=False):
78
227
if isinstance(code, str):
81
230
mname = _fn2mod[code.co_filename]
83
for k, v in sys.modules.iteritems():
232
for k, v in sys.modules.items():
86
if not hasattr(v, '__file__'):
235
if getattr(v, '__file__', None) is None:
88
237
if not isinstance(v.__file__, str):