~bzr-pqm/bzr/bzr.dev

1185.33.86 by Martin Pool
Add additional module for lsprof.
1
# this is copied from the lsprof distro because somehow
2
# it is not installed by distutils
3
# I made one modification to profile so that it returns a pair
4
# instead of just the Stats object
5
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
6
import cPickle
7
import os
1185.33.86 by Martin Pool
Add additional module for lsprof.
8
import sys
1553.7.2 by Robey Pointer
add threading support -- each thread created during an lsprof session gets its own profile object, and the Stats objects are attached to a 'threads' member of the final Stats object
9
import thread
10
import threading
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
11
from _lsprof import Profiler, profiler_entry
1185.33.86 by Martin Pool
Add additional module for lsprof.
12
2805.7.4 by Ian Clatworthy
incorporate feedback from lifeless
13
1185.33.86 by Martin Pool
Add additional module for lsprof.
14
__all__ = ['profile', 'Stats']
15
16
def profile(f, *args, **kwds):
3696.3.5 by Robert Collins
Streamline _walkdirs_utf8 for utf8 file systems, reducing time to traverse a mozilla tree from 1s to .6 seconds. (Robert Collins)
17
    """Run a function profile.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
18
4084.6.1 by Robert Collins
Refactor profiling exception handling to restore clear layers - command handling in commands.py, profiling in lsprof.py.
19
    Exceptions are not caught: If you need stats even when exceptions are to be
4641.3.2 by Robert Collins
lsprof support.
20
    raised, pass in a closure that will catch the exceptions and transform them
21
    appropriately for your driver function.
4084.6.1 by Robert Collins
Refactor profiling exception handling to restore clear layers - command handling in commands.py, profiling in lsprof.py.
22
3696.3.5 by Robert Collins
Streamline _walkdirs_utf8 for utf8 file systems, reducing time to traverse a mozilla tree from 1s to .6 seconds. (Robert Collins)
23
    :return: The functions return value and a stats object.
24
    """
4641.3.2 by Robert Collins
lsprof support.
25
    profiler = BzrProfiler()
26
    profiler.start()
1185.33.86 by Martin Pool
Add additional module for lsprof.
27
    try:
4084.6.1 by Robert Collins
Refactor profiling exception handling to restore clear layers - command handling in commands.py, profiling in lsprof.py.
28
        ret = f(*args, **kwds)
1185.33.86 by Martin Pool
Add additional module for lsprof.
29
    finally:
4641.3.2 by Robert Collins
lsprof support.
30
        stats = profiler.stop()
31
    return ret, stats
32
33
34
class BzrProfiler(object):
35
    """Bzr utility wrapper around Profiler.
36
    
37
    For most uses the module level 'profile()' function will be suitable.
38
    However profiling when a simple wrapped function isn't available may
39
    be easier to accomplish using this class.
40
41
    To use it, create a BzrProfiler and call start() on it. Some arbitrary
42
    time later call stop() to stop profiling and retrieve the statistics
43
    from the code executed in the interim.
44
    """
45
46
    def start(self):
47
        """Start profiling.
48
        
49
        This hooks into threading and will record all calls made until
50
        stop() is called.
51
        """
52
        self._g_threadmap = {}
53
        self.p = Profiler()
54
        self.p.enable(subcalls=True)
55
        threading.setprofile(self._thread_profile)
56
57
    def stop(self):
58
        """Stop profiling.
59
60
        This unhooks from threading and cleans up the profiler, returning
61
        the gathered Stats object.
62
63
        :return: A bzrlib.lsprof.Stats object.
64
        """
65
        self.p.disable()
66
        for pp in self._g_threadmap.values():
1553.7.2 by Robey Pointer
add threading support -- each thread created during an lsprof session gets its own profile object, and the Stats objects are attached to a 'threads' member of the final Stats object
67
            pp.disable()
68
        threading.setprofile(None)
4641.3.2 by Robert Collins
lsprof support.
69
        p = self.p
70
        self.p = None
71
        threads = {}
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)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
76
4641.3.2 by Robert Collins
lsprof support.
77
    def _thread_profile(self, f, *args, **kwds):
78
        # we lose the first profile point for a new thread in order to
79
        # trampoline a new Profile object into place
80
        thr = thread.get_ident()
81
        self._g_threadmap[thr] = p = Profiler()
82
        # this overrides our sys.setprofile hook:
83
        p.enable(subcalls=True, builtins=True)
1185.33.86 by Martin Pool
Add additional module for lsprof.
84
85
86
class Stats(object):
87
    """XXX docstring"""
88
1553.7.2 by Robey Pointer
add threading support -- each thread created during an lsprof session gets its own profile object, and the Stats objects are attached to a 'threads' member of the final Stats object
89
    def __init__(self, data, threads):
1185.33.86 by Martin Pool
Add additional module for lsprof.
90
        self.data = data
1553.7.2 by Robey Pointer
add threading support -- each thread created during an lsprof session gets its own profile object, and the Stats objects are attached to a 'threads' member of the final Stats object
91
        self.threads = threads
1185.33.86 by Martin Pool
Add additional module for lsprof.
92
93
    def sort(self, crit="inlinetime"):
94
        """XXX docstring"""
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),
98
                                        getattr(b, crit)))
99
        for e in self.data:
100
            if e.calls:
101
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
102
                                              getattr(b, crit)))
103
104
    def pprint(self, top=None, file=None):
105
        """XXX docstring"""
106
        if file is None:
107
            file = sys.stdout
108
        d = self.data
109
        if top is not None:
110
            d = d[:top]
111
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
112
        hcols = "% 12s %12s %12s %12s %s\n"
113
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
114
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
115
                            "Inline(ms)", "module:lineno(function)"))
116
        for e in d:
117
            file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
118
                               e.inlinetime, label(e.code)))
119
            if e.calls:
120
                for se in e.calls:
121
                    file.write(cols % ("+%s" % se.callcount, se.reccallcount,
122
                                       se.totaltime, se.inlinetime,
123
                                       "+%s" % label(se.code)))
124
125
    def freeze(self):
126
        """Replace all references to code objects with string
127
        descriptions; this makes it possible to pickle the instance."""
128
129
        # this code is probably rather ickier than it needs to be!
130
        for i in range(len(self.data)):
131
            e = self.data[i]
132
            if not isinstance(e.code, str):
133
                self.data[i] = type(e)((label(e.code),) + e[1:])
1706.2.7 by Robey Pointer
pull over some changes from the original lsprof
134
            if e.calls:
135
                for j in range(len(e.calls)):
136
                    se = e.calls[j]
137
                    if not isinstance(se.code, str):
138
                        e.calls[j] = type(se)((label(se.code),) + se[1:])
1553.7.2 by Robey Pointer
add threading support -- each thread created during an lsprof session gets its own profile object, and the Stats objects are attached to a 'threads' member of the final Stats object
139
        for s in self.threads.values():
140
            s.freeze()
141
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
142
    def calltree(self, file):
143
        """Output profiling data in calltree format (for KCacheGrind)."""
144
        _CallTreeFilter(self.data).output(file)
145
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
146
    def save(self, filename, format=None):
147
        """Save profiling data to a file.
148
149
        :param filename: the name of the output file
150
        :param format: 'txt' for a text representation;
151
            'callgrind' for calltree format;
152
            otherwise a pickled Python object. A format of None indicates
2654.2.2 by Ian Clatworthy
Put all format detection stuff in one spot as requested by John Arbash Meinel
153
            that the format to use is to be found from the filename. If
154
            the name starts with callgrind.out, callgrind format is used
155
            otherwise the format is given by the filename extension.
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
156
        """
157
        if format is None:
3535.5.1 by John Arbash Meinel
cleanup a few imports to be lazily loaded.
158
            basename = os.path.basename(filename)
2805.7.2 by Ian Clatworthy
use basename, not full path, when checking for callgrind.out file prefix
159
            if basename.startswith('callgrind.out'):
2654.2.2 by Ian Clatworthy
Put all format detection stuff in one spot as requested by John Arbash Meinel
160
                format = "callgrind"
161
            else:
3535.5.1 by John Arbash Meinel
cleanup a few imports to be lazily loaded.
162
                ext = os.path.splitext(filename)[1]
2654.2.2 by Ian Clatworthy
Put all format detection stuff in one spot as requested by John Arbash Meinel
163
                if len(ext) > 1:
164
                    format = ext[1:]
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
165
        outfile = open(filename, 'wb')
166
        try:
167
            if format == "callgrind":
168
                self.calltree(outfile)
169
            elif format == "txt":
170
                self.pprint(file=outfile)
171
            else:
172
                self.freeze()
173
                cPickle.dump(self, outfile, 2)
174
        finally:
175
            outfile.close()
176
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
177
178
class _CallTreeFilter(object):
2493.2.2 by Ian Clatworthy
Incorporate feedback from Robert Collins
179
    """Converter of a Stats object to input suitable for KCacheGrind.
180
181
    This code is taken from http://ddaa.net/blog/python/lsprof-calltree
182
    with the changes made by J.P. Calderone and Itamar applied. Note that
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
183
    isinstance(code, str) needs to be used at times to determine if the code
2493.2.2 by Ian Clatworthy
Incorporate feedback from Robert Collins
184
    object is actually an external code object (with a filename, etc.) or
185
    a Python built-in.
186
    """
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
187
188
    def __init__(self, data):
189
        self.data = data
190
        self.out_file = None
191
192
    def output(self, out_file):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
193
        self.out_file = out_file
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
194
        out_file.write('events: Ticks\n')
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
195
        self._print_summary()
196
        for entry in self.data:
197
            self._entry(entry)
198
199
    def _print_summary(self):
200
        max_cost = 0
201
        for entry in self.data:
202
            totaltime = int(entry.totaltime * 1000)
203
            max_cost = max(max_cost, totaltime)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
204
        self.out_file.write('summary: %d\n' % (max_cost,))
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
205
206
    def _entry(self, entry):
207
        out_file = self.out_file
208
        code = entry.code
209
        inlinetime = int(entry.inlinetime * 1000)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
210
        #out_file.write('ob=%s\n' % (code.co_filename,))
211
        if isinstance(code, str):
212
            out_file.write('fi=~\n')
213
        else:
214
            out_file.write('fi=%s\n' % (code.co_filename,))
215
        out_file.write('fn=%s\n' % (label(code, True),))
216
        if isinstance(code, str):
2911.6.4 by Blake Winton
Fix test failures
217
            out_file.write('0  %s\n' % (inlinetime,))
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
218
        else:
219
            out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
220
        # recursive calls are counted in entry.calls
221
        if entry.calls:
222
            calls = entry.calls
223
        else:
224
            calls = []
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
225
        if isinstance(code, str):
226
            lineno = 0
227
        else:
228
            lineno = code.co_firstlineno
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
229
        for subentry in calls:
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
230
            self._subentry(lineno, subentry)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
231
        out_file.write('\n')
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
232
233
    def _subentry(self, lineno, subentry):
234
        out_file = self.out_file
235
        code = subentry.code
236
        totaltime = int(subentry.totaltime * 1000)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
237
        #out_file.write('cob=%s\n' % (code.co_filename,))
238
        out_file.write('cfn=%s\n' % (label(code, True),))
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
239
        if isinstance(code, str):
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
240
            out_file.write('cfi=~\n')
241
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
242
        else:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
243
            out_file.write('cfi=%s\n' % (code.co_filename,))
244
            out_file.write('calls=%d %d\n' % (
245
                subentry.callcount, code.co_firstlineno))
246
        out_file.write('%d %d\n' % (lineno, totaltime))
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
247
1185.33.86 by Martin Pool
Add additional module for lsprof.
248
_fn2mod = {}
249
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
250
def label(code, calltree=False):
1185.33.86 by Martin Pool
Add additional module for lsprof.
251
    if isinstance(code, str):
252
        return code
253
    try:
254
        mname = _fn2mod[code.co_filename]
255
    except KeyError:
1711.2.124 by John Arbash Meinel
Use sys.modules.items() to prevent running into site.py problems
256
        for k, v in sys.modules.items():
1185.33.86 by Martin Pool
Add additional module for lsprof.
257
            if v is None:
258
                continue
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
259
            if getattr(v, '__file__', None) is None:
1185.33.86 by Martin Pool
Add additional module for lsprof.
260
                continue
261
            if not isinstance(v.__file__, str):
262
                continue
263
            if v.__file__.startswith(code.co_filename):
264
                mname = _fn2mod[code.co_filename] = k
265
                break
266
        else:
267
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
268
    if calltree:
269
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
270
    else:
271
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
1185.33.86 by Martin Pool
Add additional module for lsprof.
272
273
274
if __name__ == '__main__':
275
    import os
276
    sys.argv = sys.argv[1:]
277
    if not sys.argv:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
278
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
1185.33.86 by Martin Pool
Add additional module for lsprof.
279
        sys.exit(2)
280
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
281
    stats = profile(execfile, sys.argv[0], globals(), locals())
282
    stats.sort()
283
    stats.pprint()