~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

MergeĀ inĀ upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
# I made one modification to profile so that it returns a pair
4
4
# instead of just the Stats object
5
5
 
6
 
import cPickle
7
 
import os
8
6
import sys
9
 
import thread
10
 
import threading
11
 
from _lsprof import Profiler, profiler_entry
12
 
 
 
7
from _lsprof import Profiler, profiler_entry, profiler_subentry
13
8
 
14
9
__all__ = ['profile', 'Stats']
15
10
 
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
11
def profile(f, *args, **kwds):
30
12
    """XXX docstring"""
31
 
    global _g_threadmap
32
13
    p = Profiler()
33
14
    p.enable(subcalls=True)
34
 
    threading.setprofile(_thread_profile)
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.
39
15
    try:
40
 
        try:
41
 
            ret = f(*args, **kwds)
42
 
        except (KeyboardInterrupt, Exception), e:
43
 
            import bzrlib.trace
44
 
            bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
45
 
            ret = 3
 
16
        ret = f(*args, **kwds)
46
17
    finally:
47
18
        p.disable()
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)
 
19
    return ret,Stats(p.getstats())
57
20
 
58
21
 
59
22
class Stats(object):
60
23
    """XXX docstring"""
61
24
 
62
 
    def __init__(self, data, threads):
 
25
    def __init__(self, data):
63
26
        self.data = data
64
 
        self.threads = threads
65
27
 
66
28
    def sort(self, crit="inlinetime"):
67
29
        """XXX docstring"""
104
66
            e = self.data[i]
105
67
            if not isinstance(e.code, str):
106
68
                self.data[i] = type(e)((label(e.code),) + e[1:])
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:])
112
 
        for s in self.threads.values():
113
 
            s.freeze()
114
 
 
115
 
    def calltree(self, file):
116
 
        """Output profiling data in calltree format (for KCacheGrind)."""
117
 
        _CallTreeFilter(self.data).output(file)
118
 
 
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
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.
129
 
        """
130
 
        if format is None:
131
 
            basename = os.path.basename(filename)
132
 
            if basename.startswith('callgrind.out'):
133
 
                format = "callgrind"
134
 
            else:
135
 
                ext = os.path.splitext(filename)[1]
136
 
                if len(ext) > 1:
137
 
                    format = ext[1:]
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
 
 
150
 
 
151
 
class _CallTreeFilter(object):
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
 
    """
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        
167
 
        out_file.write('events: Ticks\n')
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)
177
 
        self.out_file.write('summary: %d\n' % (max_cost,))
178
 
 
179
 
    def _entry(self, entry):
180
 
        out_file = self.out_file
181
 
        code = entry.code
182
 
        inlinetime = int(entry.inlinetime * 1000)
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):
190
 
            out_file.write('0  %s\n' % (inlinetime,))
191
 
        else:
192
 
            out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
193
 
        # recursive calls are counted in entry.calls
194
 
        if entry.calls:
195
 
            calls = entry.calls
196
 
        else:
197
 
            calls = []
198
 
        if isinstance(code, str):
199
 
            lineno = 0
200
 
        else:
201
 
            lineno = code.co_firstlineno
202
 
        for subentry in calls:
203
 
            self._subentry(lineno, subentry)
204
 
        out_file.write('\n')
205
 
 
206
 
    def _subentry(self, lineno, subentry):
207
 
        out_file = self.out_file
208
 
        code = subentry.code
209
 
        totaltime = int(subentry.totaltime * 1000)
210
 
        #out_file.write('cob=%s\n' % (code.co_filename,))
211
 
        out_file.write('cfn=%s\n' % (label(code, True),))
212
 
        if isinstance(code, str):
213
 
            out_file.write('cfi=~\n')
214
 
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
215
 
        else:
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))
 
69
                if e.calls:
 
70
                    for j in range(len(e.calls)):
 
71
                        se = e.calls[j]
 
72
                        if not isinstance(se.code, str):
 
73
                            e.calls[j] = type(se)((label(se.code),) + se[1:])
220
74
 
221
75
_fn2mod = {}
222
76
 
223
 
def label(code, calltree=False):
 
77
def label(code):
224
78
    if isinstance(code, str):
225
79
        return code
226
80
    try:
227
81
        mname = _fn2mod[code.co_filename]
228
82
    except KeyError:
229
 
        for k, v in sys.modules.items():
 
83
        for k, v in sys.modules.iteritems():
230
84
            if v is None:
231
85
                continue
232
 
            if getattr(v, '__file__', None) is None:
 
86
            if not hasattr(v, '__file__'):
233
87
                continue
234
88
            if not isinstance(v.__file__, str):
235
89
                continue
238
92
                break
239
93
        else:
240
94
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
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)
 
95
    
 
96
    return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
245
97
 
246
98
 
247
99
if __name__ == '__main__':
248
100
    import os
249
101
    sys.argv = sys.argv[1:]
250
102
    if not sys.argv:
251
 
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
103
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
252
104
        sys.exit(2)
253
105
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
254
106
    stats = profile(execfile, sys.argv[0], globals(), locals())