~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

Add finished() notifications to transactions.

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
 
7
from _lsprof import Profiler, profiler_entry, profiler_subentry
12
8
 
13
9
__all__ = ['profile', 'Stats']
14
10
 
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
11
def profile(f, *args, **kwds):
29
12
    """XXX docstring"""
30
 
    global _g_threadmap
31
13
    p = Profiler()
32
14
    p.enable(subcalls=True)
33
 
    threading.setprofile(_thread_profile)
34
15
    try:
35
16
        ret = f(*args, **kwds)
36
17
    finally:
37
18
        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)
 
19
    return ret,Stats(p.getstats())
47
20
 
48
21
 
49
22
class Stats(object):
50
23
    """XXX docstring"""
51
24
 
52
 
    def __init__(self, data, threads):
 
25
    def __init__(self, data):
53
26
        self.data = data
54
 
        self.threads = threads
55
27
 
56
28
    def sort(self, crit="inlinetime"):
57
29
        """XXX docstring"""
94
66
            e = self.data[i]
95
67
            if not isinstance(e.code, str):
96
68
                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)
 
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:])
209
74
 
210
75
_fn2mod = {}
211
76
 
212
 
def label(code, calltree=False):
 
77
def label(code):
213
78
    if isinstance(code, str):
214
79
        return code
215
80
    try:
216
81
        mname = _fn2mod[code.co_filename]
217
82
    except KeyError:
218
 
        for k, v in sys.modules.items():
 
83
        for k, v in sys.modules.iteritems():
219
84
            if v is None:
220
85
                continue
221
 
            if getattr(v, '__file__', None) is None:
 
86
            if not hasattr(v, '__file__'):
222
87
                continue
223
88
            if not isinstance(v.__file__, str):
224
89
                continue
227
92
                break
228
93
        else:
229
94
            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)
 
95
    
 
96
    return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
234
97
 
235
98
 
236
99
if __name__ == '__main__':