~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-16 21:38:29 UTC
  • mto: (3697.2.5 1.7)
  • mto: This revision was merged to the branch mainline in revision 3710.
  • Revision ID: john@arbash-meinel.com-20080916213829-g18jj2b94k70fkxj
Merge vila's init[-repo] changes and add a NEWS entry.

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
    """XXX docstring"""
 
31
    global _g_threadmap
 
32
    p = Profiler()
 
33
    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
    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
 
46
    finally:
 
47
        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)
 
57
 
 
58
 
 
59
class Stats(object):
 
60
    """XXX docstring"""
 
61
 
 
62
    def __init__(self, data, threads):
 
63
        self.data = data
 
64
        self.threads = threads
 
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:])
 
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))
 
220
 
 
221
_fn2mod = {}
 
222
 
 
223
def label(code, calltree=False):
 
224
    if isinstance(code, str):
 
225
        return code
 
226
    try:
 
227
        mname = _fn2mod[code.co_filename]
 
228
    except KeyError:
 
229
        for k, v in sys.modules.items():
 
230
            if v is None:
 
231
                continue
 
232
            if getattr(v, '__file__', None) is None:
 
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
 
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)
 
245
 
 
246
 
 
247
if __name__ == '__main__':
 
248
    import os
 
249
    sys.argv = sys.argv[1:]
 
250
    if not sys.argv:
 
251
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
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()