~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

  • Committer: Wouter van Heyst
  • Date: 2006-06-06 22:37:30 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: larstiq@larstiq.dyndns.org-20060606223730-a308c5429fc6c617
change branch.{get,set}_parent to store a relative path but return full urls

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
 
    """Run a function profile.
31
 
    
32
 
    :return: The functions return value and a stats object.
33
 
    """
34
 
    global _g_threadmap
 
12
    """XXX docstring"""
35
13
    p = Profiler()
36
14
    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
15
    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
 
16
        ret = f(*args, **kwds)
49
17
    finally:
50
18
        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)
 
19
    return ret,Stats(p.getstats())
60
20
 
61
21
 
62
22
class Stats(object):
63
23
    """XXX docstring"""
64
24
 
65
 
    def __init__(self, data, threads):
 
25
    def __init__(self, data):
66
26
        self.data = data
67
 
        self.threads = threads
68
27
 
69
28
    def sort(self, crit="inlinetime"):
70
29
        """XXX docstring"""
107
66
            e = self.data[i]
108
67
            if not isinstance(e.code, str):
109
68
                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))
 
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:])
223
74
 
224
75
_fn2mod = {}
225
76
 
226
 
def label(code, calltree=False):
 
77
def label(code):
227
78
    if isinstance(code, str):
228
79
        return code
229
80
    try:
230
81
        mname = _fn2mod[code.co_filename]
231
82
    except KeyError:
232
 
        for k, v in sys.modules.items():
 
83
        for k, v in sys.modules.iteritems():
233
84
            if v is None:
234
85
                continue
235
 
            if getattr(v, '__file__', None) is None:
 
86
            if not hasattr(v, '__file__'):
236
87
                continue
237
88
            if not isinstance(v.__file__, str):
238
89
                continue
241
92
                break
242
93
        else:
243
94
            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)
 
95
    
 
96
    return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
248
97
 
249
98
 
250
99
if __name__ == '__main__':
251
100
    import os
252
101
    sys.argv = sys.argv[1:]
253
102
    if not sys.argv:
254
 
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
103
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
255
104
        sys.exit(2)
256
105
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
257
106
    stats = profile(execfile, sys.argv[0], globals(), locals())