~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

merge merge tweaks from aaron, which includes latest .dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
6
 
import cPickle
7
 
import os
8
 
import sys
9
 
import thread
10
 
import threading
11
 
from _lsprof import Profiler, profiler_entry
12
 
 
13
 
 
14
 
__all__ = ['profile', 'Stats']
15
 
 
16
 
_g_threadmap = {}
17
 
 
18
 
 
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:
26
 
    p.enable(subcalls=True, builtins=True)
27
 
 
28
 
 
29
 
def profile(f, *args, **kwds):
30
 
    """Run a function profile.
31
 
    
32
 
    :return: The functions return value and a stats object.
33
 
    """
34
 
    global _g_threadmap
35
 
    p = Profiler()
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.
42
 
    try:
43
 
        try:
44
 
            ret = f(*args, **kwds)
45
 
        except (KeyboardInterrupt, Exception), e:
46
 
            import bzrlib.trace
47
 
            bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
48
 
            ret = 3
49
 
    finally:
50
 
        p.disable()
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)
60
 
 
61
 
 
62
 
class Stats(object):
63
 
    """XXX docstring"""
64
 
 
65
 
    def __init__(self, data, threads):
66
 
        self.data = data
67
 
        self.threads = threads
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:])
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:])
115
 
        for s in self.threads.values():
116
 
            s.freeze()
117
 
 
118
 
    def calltree(self, file):
119
 
        """Output profiling data in calltree format (for KCacheGrind)."""
120
 
        _CallTreeFilter(self.data).output(file)
121
 
 
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
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.
132
 
        """
133
 
        if format is None:
134
 
            basename = os.path.basename(filename)
135
 
            if basename.startswith('callgrind.out'):
136
 
                format = "callgrind"
137
 
            else:
138
 
                ext = os.path.splitext(filename)[1]
139
 
                if len(ext) > 1:
140
 
                    format = ext[1:]
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
 
 
153
 
 
154
 
class _CallTreeFilter(object):
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
 
    """
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        
170
 
        out_file.write('events: Ticks\n')
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)
180
 
        self.out_file.write('summary: %d\n' % (max_cost,))
181
 
 
182
 
    def _entry(self, entry):
183
 
        out_file = self.out_file
184
 
        code = entry.code
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')
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  %s\n' % (inlinetime,))
194
 
        else:
195
 
            out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
196
 
        # recursive calls are counted in entry.calls
197
 
        if entry.calls:
198
 
            calls = entry.calls
199
 
        else:
200
 
            calls = []
201
 
        if isinstance(code, str):
202
 
            lineno = 0
203
 
        else:
204
 
            lineno = code.co_firstlineno
205
 
        for subentry in calls:
206
 
            self._subentry(lineno, subentry)
207
 
        out_file.write('\n')
208
 
 
209
 
    def _subentry(self, lineno, subentry):
210
 
        out_file = self.out_file
211
 
        code = subentry.code
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,))
218
 
        else:
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))
223
 
 
224
 
_fn2mod = {}
225
 
 
226
 
def label(code, calltree=False):
227
 
    if isinstance(code, str):
228
 
        return code
229
 
    try:
230
 
        mname = _fn2mod[code.co_filename]
231
 
    except KeyError:
232
 
        for k, v in sys.modules.items():
233
 
            if v is None:
234
 
                continue
235
 
            if getattr(v, '__file__', None) is None:
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
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)
248
 
 
249
 
 
250
 
if __name__ == '__main__':
251
 
    import os
252
 
    sys.argv = sys.argv[1:]
253
 
    if not sys.argv:
254
 
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
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()