~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
14
import bzrlib.osutils
15
16
1185.33.86 by Martin Pool
Add additional module for lsprof.
17
__all__ = ['profile', 'Stats']
18
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
19
_g_threadmap = {}
20
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
21
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
22
def _thread_profile(f, *args, **kwds):
23
    # we lose the first profile point for a new thread in order to trampoline
24
    # a new Profile object into place
25
    global _g_threadmap
26
    thr = thread.get_ident()
27
    _g_threadmap[thr] = p = Profiler()
28
    # this overrides our sys.setprofile hook:
1706.2.7 by Robey Pointer
pull over some changes from the original lsprof
29
    p.enable(subcalls=True, builtins=True)
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
30
31
1185.33.86 by Martin Pool
Add additional module for lsprof.
32
def profile(f, *args, **kwds):
33
    """XXX docstring"""
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
34
    global _g_threadmap
1185.33.86 by Martin Pool
Add additional module for lsprof.
35
    p = Profiler()
36
    p.enable(subcalls=True)
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
37
    threading.setprofile(_thread_profile)
2805.7.1 by Ian Clatworthy
Dump profiling data even when exceptions are encountered
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.
1185.33.86 by Martin Pool
Add additional module for lsprof.
42
    try:
2805.7.4 by Ian Clatworthy
incorporate feedback from lifeless
43
        try:
44
            ret = f(*args, **kwds)
2805.7.6 by Ian Clatworthy
put back KeyboardInterrupt trapping as needed for Python 2.5
45
        except (KeyboardInterrupt, Exception), e:
2805.7.4 by Ian Clatworthy
incorporate feedback from lifeless
46
            import bzrlib.trace
47
            bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
48
            ret = 3
1185.33.86 by Martin Pool
Add additional module for lsprof.
49
    finally:
50
        p.disable()
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
51
        for pp in _g_threadmap.values():
52
            pp.disable()
53
        threading.setprofile(None)
54
    
55
    threads = {}
56
    for tid, pp in _g_threadmap.items():
57
        threads[tid] = Stats(pp.getstats(), {})
58
    _g_threadmap = {}
59
    return ret, Stats(p.getstats(), threads)
1185.33.86 by Martin Pool
Add additional module for lsprof.
60
61
62
class Stats(object):
63
    """XXX docstring"""
64
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
65
    def __init__(self, data, threads):
1185.33.86 by Martin Pool
Add additional module for lsprof.
66
        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
67
        self.threads = threads
1185.33.86 by Martin Pool
Add additional module for lsprof.
68
69
    def sort(self, crit="inlinetime"):
70
        """XXX docstring"""
71
        if crit not in profiler_entry.__dict__:
72
            raise ValueError, "Can't sort by %s" % crit
73
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
74
                                        getattr(b, crit)))
75
        for e in self.data:
76
            if e.calls:
77
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
78
                                              getattr(b, crit)))
79
80
    def pprint(self, top=None, file=None):
81
        """XXX docstring"""
82
        if file is None:
83
            file = sys.stdout
84
        d = self.data
85
        if top is not None:
86
            d = d[:top]
87
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
88
        hcols = "% 12s %12s %12s %12s %s\n"
89
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
90
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
91
                            "Inline(ms)", "module:lineno(function)"))
92
        for e in d:
93
            file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
94
                               e.inlinetime, label(e.code)))
95
            if e.calls:
96
                for se in e.calls:
97
                    file.write(cols % ("+%s" % se.callcount, se.reccallcount,
98
                                       se.totaltime, se.inlinetime,
99
                                       "+%s" % label(se.code)))
100
101
    def freeze(self):
102
        """Replace all references to code objects with string
103
        descriptions; this makes it possible to pickle the instance."""
104
105
        # this code is probably rather ickier than it needs to be!
106
        for i in range(len(self.data)):
107
            e = self.data[i]
108
            if not isinstance(e.code, str):
109
                self.data[i] = type(e)((label(e.code),) + e[1:])
1706.2.7 by Robey Pointer
pull over some changes from the original lsprof
110
            if e.calls:
111
                for j in range(len(e.calls)):
112
                    se = e.calls[j]
113
                    if not isinstance(se.code, str):
114
                        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
115
        for s in self.threads.values():
116
            s.freeze()
117
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
118
    def calltree(self, file):
119
        """Output profiling data in calltree format (for KCacheGrind)."""
120
        _CallTreeFilter(self.data).output(file)
121
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
122
    def save(self, filename, format=None):
123
        """Save profiling data to a file.
124
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
2654.2.2 by Ian Clatworthy
Put all format detection stuff in one spot as requested by John Arbash Meinel
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.
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
132
        """
133
        if format is None:
2805.7.4 by Ian Clatworthy
incorporate feedback from lifeless
134
            basename = bzrlib.osutils.basename(filename)
2805.7.2 by Ian Clatworthy
use basename, not full path, when checking for callgrind.out file prefix
135
            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
136
                format = "callgrind"
137
            else:
2805.7.4 by Ian Clatworthy
incorporate feedback from lifeless
138
                ext = bzrlib.osutils.splitext(filename)[1]
2654.2.2 by Ian Clatworthy
Put all format detection stuff in one spot as requested by John Arbash Meinel
139
                if len(ext) > 1:
140
                    format = ext[1:]
2493.2.3 by Ian Clatworthy
changes requested in jameinel's review incorporated
141
        outfile = open(filename, 'wb')
142
        try:
143
            if format == "callgrind":
144
                self.calltree(outfile)
145
            elif format == "txt":
146
                self.pprint(file=outfile)
147
            else:
148
                self.freeze()
149
                cPickle.dump(self, outfile, 2)
150
        finally:
151
            outfile.close()
152
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
153
154
class _CallTreeFilter(object):
2493.2.2 by Ian Clatworthy
Incorporate feedback from Robert Collins
155
    """Converter of a Stats object to input suitable for KCacheGrind.
156
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
161
    a Python built-in.
162
    """
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
163
164
    def __init__(self, data):
165
        self.data = data
166
        self.out_file = None
167
168
    def output(self, out_file):
169
        self.out_file = out_file        
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
170
        out_file.write('events: Ticks\n')
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
171
        self._print_summary()
172
        for entry in self.data:
173
            self._entry(entry)
174
175
    def _print_summary(self):
176
        max_cost = 0
177
        for entry in self.data:
178
            totaltime = int(entry.totaltime * 1000)
179
            max_cost = max(max_cost, totaltime)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
180
        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
181
182
    def _entry(self, entry):
183
        out_file = self.out_file
184
        code = entry.code
185
        inlinetime = int(entry.inlinetime * 1000)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
186
        #out_file.write('ob=%s\n' % (code.co_filename,))
187
        if isinstance(code, str):
188
            out_file.write('fi=~\n')
189
        else:
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  ' + inlinetime + '\n')
194
        else:
195
            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
196
        # recursive calls are counted in entry.calls
197
        if entry.calls:
198
            calls = entry.calls
199
        else:
200
            calls = []
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
201
        if isinstance(code, str):
202
            lineno = 0
203
        else:
204
            lineno = code.co_firstlineno
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
205
        for subentry in calls:
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
206
            self._subentry(lineno, subentry)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
207
        out_file.write('\n')
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
208
209
    def _subentry(self, lineno, subentry):
210
        out_file = self.out_file
211
        code = subentry.code
212
        totaltime = int(subentry.totaltime * 1000)
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
213
        #out_file.write('cob=%s\n' % (code.co_filename,))
214
        out_file.write('cfn=%s\n' % (label(code, True),))
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
215
        if isinstance(code, str):
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
216
            out_file.write('cfi=~\n')
217
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
2493.2.1 by Ian Clatworthy
make profiling information easier to view and better documented
218
        else:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
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))
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
223
1185.33.86 by Martin Pool
Add additional module for lsprof.
224
_fn2mod = {}
225
1553.7.3 by Robey Pointer
patch from ddaa to add kcachegrind output-format support to lsprof
226
def label(code, calltree=False):
1185.33.86 by Martin Pool
Add additional module for lsprof.
227
    if isinstance(code, str):
228
        return code
229
    try:
230
        mname = _fn2mod[code.co_filename]
231
    except KeyError:
1711.2.124 by John Arbash Meinel
Use sys.modules.items() to prevent running into site.py problems
232
        for k, v in sys.modules.items():
1185.33.86 by Martin Pool
Add additional module for lsprof.
233
            if v is None:
234
                continue
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
235
            if getattr(v, '__file__', None) is None:
1185.33.86 by Martin Pool
Add additional module for lsprof.
236
                continue
237
            if not isinstance(v.__file__, str):
238
                continue
239
            if v.__file__.startswith(code.co_filename):
240
                mname = _fn2mod[code.co_filename] = k
241
                break
242
        else:
243
            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
244
    if calltree:
245
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
246
    else:
247
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
1185.33.86 by Martin Pool
Add additional module for lsprof.
248
249
250
if __name__ == '__main__':
251
    import os
252
    sys.argv = sys.argv[1:]
253
    if not sys.argv:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
254
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
1185.33.86 by Martin Pool
Add additional module for lsprof.
255
        sys.exit(2)
256
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
257
    stats = profile(execfile, sys.argv[0], globals(), locals())
258
    stats.sort()
259
    stats.pprint()