~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

  • Committer: aaron.bentley at utoronto
  • Date: 2005-08-27 04:42:41 UTC
  • mfrom: (1092.1.43)
  • mto: (1185.3.4)
  • mto: This revision was merged to the branch mainline in revision 1178.
  • Revision ID: aaron.bentley@utoronto.ca-20050827044241-23d676133b9fc981
Merge of robertc@robertcollins.net-20050826013321-52eee1f1da679ee9

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 extension of
117
 
            filename.
118
 
        """
119
 
        if format is None:
120
 
            ext = os.path.splitext(filename)[1]
121
 
            if len(ext) > 1:
122
 
                format = ext[1:]
123
 
        outfile = open(filename, 'wb')
124
 
        try:
125
 
            if format == "callgrind":
126
 
                self.calltree(outfile)
127
 
            elif format == "txt":
128
 
                self.pprint(file=outfile)
129
 
            else:
130
 
                self.freeze()
131
 
                cPickle.dump(self, outfile, 2)
132
 
        finally:
133
 
            outfile.close()
134
 
 
135
 
 
136
 
class _CallTreeFilter(object):
137
 
    """Converter of a Stats object to input suitable for KCacheGrind.
138
 
 
139
 
    This code is taken from http://ddaa.net/blog/python/lsprof-calltree
140
 
    with the changes made by J.P. Calderone and Itamar applied. Note that
141
 
    isinstance(code, str) needs to be used at times to determine if the code 
142
 
    object is actually an external code object (with a filename, etc.) or
143
 
    a Python built-in.
144
 
    """
145
 
 
146
 
    def __init__(self, data):
147
 
        self.data = data
148
 
        self.out_file = None
149
 
 
150
 
    def output(self, out_file):
151
 
        self.out_file = out_file        
152
 
        print >> out_file, 'events: Ticks'
153
 
        self._print_summary()
154
 
        for entry in self.data:
155
 
            self._entry(entry)
156
 
 
157
 
    def _print_summary(self):
158
 
        max_cost = 0
159
 
        for entry in self.data:
160
 
            totaltime = int(entry.totaltime * 1000)
161
 
            max_cost = max(max_cost, totaltime)
162
 
        print >> self.out_file, 'summary: %d' % (max_cost,)
163
 
 
164
 
    def _entry(self, entry):
165
 
        out_file = self.out_file
166
 
        code = entry.code
167
 
        inlinetime = int(entry.inlinetime * 1000)
168
 
        #print >> out_file, 'ob=%s' % (code.co_filename,)
169
 
        if isinstance(code, str):
170
 
            print >> out_file, 'fi=~'
171
 
        else:
172
 
            print >> out_file, 'fi=%s' % (code.co_filename,)
173
 
        print >> out_file, 'fn=%s' % (label(code, True),)
174
 
        if isinstance(code, str):
175
 
            print >> out_file, '0 ', inlinetime
176
 
        else:
177
 
            print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
178
 
        # recursive calls are counted in entry.calls
179
 
        if entry.calls:
180
 
            calls = entry.calls
181
 
        else:
182
 
            calls = []
183
 
        if isinstance(code, str):
184
 
            lineno = 0
185
 
        else:
186
 
            lineno = code.co_firstlineno
187
 
        for subentry in calls:
188
 
            self._subentry(lineno, subentry)
189
 
        print >> out_file
190
 
 
191
 
    def _subentry(self, lineno, subentry):
192
 
        out_file = self.out_file
193
 
        code = subentry.code
194
 
        totaltime = int(subentry.totaltime * 1000)
195
 
        #print >> out_file, 'cob=%s' % (code.co_filename,)
196
 
        print >> out_file, 'cfn=%s' % (label(code, True),)
197
 
        if isinstance(code, str):
198
 
            print >> out_file, 'cfi=~'
199
 
            print >> out_file, 'calls=%d 0' % (subentry.callcount,)
200
 
        else:
201
 
            print >> out_file, 'cfi=%s' % (code.co_filename,)
202
 
            print >> out_file, 'calls=%d %d' % (
203
 
                subentry.callcount, code.co_firstlineno)
204
 
        print >> out_file, '%d %d' % (lineno, totaltime)
205
 
 
206
 
_fn2mod = {}
207
 
 
208
 
def label(code, calltree=False):
209
 
    if isinstance(code, str):
210
 
        return code
211
 
    try:
212
 
        mname = _fn2mod[code.co_filename]
213
 
    except KeyError:
214
 
        for k, v in sys.modules.items():
215
 
            if v is None:
216
 
                continue
217
 
            if getattr(v, '__file__', None) is None:
218
 
                continue
219
 
            if not isinstance(v.__file__, str):
220
 
                continue
221
 
            if v.__file__.startswith(code.co_filename):
222
 
                mname = _fn2mod[code.co_filename] = k
223
 
                break
224
 
        else:
225
 
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
226
 
    if calltree:
227
 
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
228
 
    else:
229
 
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
230
 
 
231
 
 
232
 
if __name__ == '__main__':
233
 
    import os
234
 
    sys.argv = sys.argv[1:]
235
 
    if not sys.argv:
236
 
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
237
 
        sys.exit(2)
238
 
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
239
 
    stats = profile(execfile, sys.argv[0], globals(), locals())
240
 
    stats.sort()
241
 
    stats.pprint()