~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

  • Committer: Martin Pool
  • Date: 2005-06-27 01:39:26 UTC
  • Revision ID: mbp@sourcefrog.net-20050627013926-49413d5928809350
- import effbot.org http client

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
 
__all__ = ['profile', 'Stats']
14
 
 
15
 
_g_threadmap = {}
16
 
 
17
 
 
18
 
def _thread_profile(f, *args, **kwds):
19
 
    # we lose the first profile point for a new thread in order to trampoline
20
 
    # a new Profile object into place
21
 
    global _g_threadmap
22
 
    thr = thread.get_ident()
23
 
    _g_threadmap[thr] = p = Profiler()
24
 
    # this overrides our sys.setprofile hook:
25
 
    p.enable(subcalls=True, builtins=True)
26
 
 
27
 
 
28
 
def profile(f, *args, **kwds):
29
 
    """XXX docstring"""
30
 
    global _g_threadmap
31
 
    p = Profiler()
32
 
    p.enable(subcalls=True)
33
 
    threading.setprofile(_thread_profile)
34
 
    try:
35
 
        ret = f(*args, **kwds)
36
 
    finally:
37
 
        p.disable()
38
 
        for pp in _g_threadmap.values():
39
 
            pp.disable()
40
 
        threading.setprofile(None)
41
 
    
42
 
    threads = {}
43
 
    for tid, pp in _g_threadmap.items():
44
 
        threads[tid] = Stats(pp.getstats(), {})
45
 
    _g_threadmap = {}
46
 
    return ret, Stats(p.getstats(), threads)
47
 
 
48
 
 
49
 
class Stats(object):
50
 
    """XXX docstring"""
51
 
 
52
 
    def __init__(self, data, threads):
53
 
        self.data = data
54
 
        self.threads = threads
55
 
 
56
 
    def sort(self, crit="inlinetime"):
57
 
        """XXX docstring"""
58
 
        if crit not in profiler_entry.__dict__:
59
 
            raise ValueError, "Can't sort by %s" % crit
60
 
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
61
 
                                        getattr(b, crit)))
62
 
        for e in self.data:
63
 
            if e.calls:
64
 
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
65
 
                                              getattr(b, crit)))
66
 
 
67
 
    def pprint(self, top=None, file=None):
68
 
        """XXX docstring"""
69
 
        if file is None:
70
 
            file = sys.stdout
71
 
        d = self.data
72
 
        if top is not None:
73
 
            d = d[:top]
74
 
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
75
 
        hcols = "% 12s %12s %12s %12s %s\n"
76
 
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
77
 
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
78
 
                            "Inline(ms)", "module:lineno(function)"))
79
 
        for e in d:
80
 
            file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
81
 
                               e.inlinetime, label(e.code)))
82
 
            if e.calls:
83
 
                for se in e.calls:
84
 
                    file.write(cols % ("+%s" % se.callcount, se.reccallcount,
85
 
                                       se.totaltime, se.inlinetime,
86
 
                                       "+%s" % label(se.code)))
87
 
 
88
 
    def freeze(self):
89
 
        """Replace all references to code objects with string
90
 
        descriptions; this makes it possible to pickle the instance."""
91
 
 
92
 
        # this code is probably rather ickier than it needs to be!
93
 
        for i in range(len(self.data)):
94
 
            e = self.data[i]
95
 
            if not isinstance(e.code, str):
96
 
                self.data[i] = type(e)((label(e.code),) + e[1:])
97
 
            if e.calls:
98
 
                for j in range(len(e.calls)):
99
 
                    se = e.calls[j]
100
 
                    if not isinstance(se.code, str):
101
 
                        e.calls[j] = type(se)((label(se.code),) + se[1:])
102
 
        for s in self.threads.values():
103
 
            s.freeze()
104
 
 
105
 
    def calltree(self, file):
106
 
        """Output profiling data in calltree format (for KCacheGrind)."""
107
 
        _CallTreeFilter(self.data).output(file)
108
 
 
109
 
    def save(self, filename, format=None):
110
 
        """Save profiling data to a file.
111
 
 
112
 
        :param filename: the name of the output file
113
 
        :param format: 'txt' for a text representation;
114
 
            'callgrind' for calltree format;
115
 
            otherwise a pickled Python object. A format of None indicates
116
 
            that the format to use is to be found from the filename. If
117
 
            the name starts with callgrind.out, callgrind format is used
118
 
            otherwise the format is given by the filename extension.
119
 
        """
120
 
        if format is None:
121
 
            if filename.startswith('callgrind.out'):
122
 
                format = "callgrind"
123
 
            else:
124
 
                ext = os.path.splitext(filename)[1]
125
 
                if len(ext) > 1:
126
 
                    format = ext[1:]
127
 
        outfile = open(filename, 'wb')
128
 
        try:
129
 
            if format == "callgrind":
130
 
                self.calltree(outfile)
131
 
            elif format == "txt":
132
 
                self.pprint(file=outfile)
133
 
            else:
134
 
                self.freeze()
135
 
                cPickle.dump(self, outfile, 2)
136
 
        finally:
137
 
            outfile.close()
138
 
 
139
 
 
140
 
class _CallTreeFilter(object):
141
 
    """Converter of a Stats object to input suitable for KCacheGrind.
142
 
 
143
 
    This code is taken from http://ddaa.net/blog/python/lsprof-calltree
144
 
    with the changes made by J.P. Calderone and Itamar applied. Note that
145
 
    isinstance(code, str) needs to be used at times to determine if the code 
146
 
    object is actually an external code object (with a filename, etc.) or
147
 
    a Python built-in.
148
 
    """
149
 
 
150
 
    def __init__(self, data):
151
 
        self.data = data
152
 
        self.out_file = None
153
 
 
154
 
    def output(self, out_file):
155
 
        self.out_file = out_file        
156
 
        print >> out_file, 'events: Ticks'
157
 
        self._print_summary()
158
 
        for entry in self.data:
159
 
            self._entry(entry)
160
 
 
161
 
    def _print_summary(self):
162
 
        max_cost = 0
163
 
        for entry in self.data:
164
 
            totaltime = int(entry.totaltime * 1000)
165
 
            max_cost = max(max_cost, totaltime)
166
 
        print >> self.out_file, 'summary: %d' % (max_cost,)
167
 
 
168
 
    def _entry(self, entry):
169
 
        out_file = self.out_file
170
 
        code = entry.code
171
 
        inlinetime = int(entry.inlinetime * 1000)
172
 
        #print >> out_file, 'ob=%s' % (code.co_filename,)
173
 
        if isinstance(code, str):
174
 
            print >> out_file, 'fi=~'
175
 
        else:
176
 
            print >> out_file, 'fi=%s' % (code.co_filename,)
177
 
        print >> out_file, 'fn=%s' % (label(code, True),)
178
 
        if isinstance(code, str):
179
 
            print >> out_file, '0 ', inlinetime
180
 
        else:
181
 
            print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
182
 
        # recursive calls are counted in entry.calls
183
 
        if entry.calls:
184
 
            calls = entry.calls
185
 
        else:
186
 
            calls = []
187
 
        if isinstance(code, str):
188
 
            lineno = 0
189
 
        else:
190
 
            lineno = code.co_firstlineno
191
 
        for subentry in calls:
192
 
            self._subentry(lineno, subentry)
193
 
        print >> out_file
194
 
 
195
 
    def _subentry(self, lineno, subentry):
196
 
        out_file = self.out_file
197
 
        code = subentry.code
198
 
        totaltime = int(subentry.totaltime * 1000)
199
 
        #print >> out_file, 'cob=%s' % (code.co_filename,)
200
 
        print >> out_file, 'cfn=%s' % (label(code, True),)
201
 
        if isinstance(code, str):
202
 
            print >> out_file, 'cfi=~'
203
 
            print >> out_file, 'calls=%d 0' % (subentry.callcount,)
204
 
        else:
205
 
            print >> out_file, 'cfi=%s' % (code.co_filename,)
206
 
            print >> out_file, 'calls=%d %d' % (
207
 
                subentry.callcount, code.co_firstlineno)
208
 
        print >> out_file, '%d %d' % (lineno, totaltime)
209
 
 
210
 
_fn2mod = {}
211
 
 
212
 
def label(code, calltree=False):
213
 
    if isinstance(code, str):
214
 
        return code
215
 
    try:
216
 
        mname = _fn2mod[code.co_filename]
217
 
    except KeyError:
218
 
        for k, v in sys.modules.items():
219
 
            if v is None:
220
 
                continue
221
 
            if getattr(v, '__file__', None) is None:
222
 
                continue
223
 
            if not isinstance(v.__file__, str):
224
 
                continue
225
 
            if v.__file__.startswith(code.co_filename):
226
 
                mname = _fn2mod[code.co_filename] = k
227
 
                break
228
 
        else:
229
 
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
230
 
    if calltree:
231
 
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
232
 
    else:
233
 
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
234
 
 
235
 
 
236
 
if __name__ == '__main__':
237
 
    import os
238
 
    sys.argv = sys.argv[1:]
239
 
    if not sys.argv:
240
 
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
241
 
        sys.exit(2)
242
 
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
243
 
    stats = profile(execfile, sys.argv[0], globals(), locals())
244
 
    stats.sort()
245
 
    stats.pprint()