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