/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lsprof.py

Merge from bzr.dev, resolving conflicts.

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
 
from __future__ import absolute_import
7
 
 
8
 
import codecs
9
 
try:
10
 
    import cPickle as pickle
11
 
except ImportError:
12
 
    import pickle
13
 
import operator
14
 
import os
15
6
import sys
16
 
try:
17
 
    import _thread
18
 
except ImportError:
19
 
    import thread as _thread
 
7
import thread
20
8
import threading
21
9
from _lsprof import Profiler, profiler_entry
22
10
 
23
 
from . import errors
24
 
 
25
11
__all__ = ['profile', 'Stats']
26
12
 
 
13
_g_threadmap = {}
 
14
 
 
15
 
 
16
def _thread_profile(f, *args, **kwds):
 
17
    # we lose the first profile point for a new thread in order to trampoline
 
18
    # a new Profile object into place
 
19
    global _g_threadmap
 
20
    thr = thread.get_ident()
 
21
    _g_threadmap[thr] = p = Profiler()
 
22
    # this overrides our sys.setprofile hook:
 
23
    p.enable(subcalls=True, builtins=True)
 
24
 
 
25
 
27
26
def profile(f, *args, **kwds):
28
 
    """Run a function profile.
29
 
 
30
 
    Exceptions are not caught: If you need stats even when exceptions are to be
31
 
    raised, pass in a closure that will catch the exceptions and transform them
32
 
    appropriately for your driver function.
33
 
 
34
 
    Important caveat: only one profile can execute at a time. See BzrProfiler
35
 
    for details.
36
 
 
37
 
    :return: The functions return value and a stats object.
38
 
    """
39
 
    profiler = BzrProfiler()
40
 
    profiler.start()
 
27
    """XXX docstring"""
 
28
    global _g_threadmap
 
29
    p = Profiler()
 
30
    p.enable(subcalls=True)
 
31
    threading.setprofile(_thread_profile)
41
32
    try:
42
33
        ret = f(*args, **kwds)
43
34
    finally:
44
 
        stats = profiler.stop()
45
 
    return ret, stats
46
 
 
47
 
 
48
 
class BzrProfiler(object):
49
 
    """Bzr utility wrapper around Profiler.
 
35
        p.disable()
 
36
        for pp in _g_threadmap.values():
 
37
            pp.disable()
 
38
        threading.setprofile(None)
50
39
    
51
 
    For most uses the module level 'profile()' function will be suitable.
52
 
    However profiling when a simple wrapped function isn't available may
53
 
    be easier to accomplish using this class.
54
 
 
55
 
    To use it, create a BzrProfiler and call start() on it. Some arbitrary
56
 
    time later call stop() to stop profiling and retrieve the statistics
57
 
    from the code executed in the interim.
58
 
 
59
 
    Note that profiling involves a threading.Lock around the actual profiling.
60
 
    This is needed because profiling involves global manipulation of the python
61
 
    interpreter state. As such you cannot perform multiple profiles at once.
62
 
    Trying to do so will lock out the second profiler unless the global 
63
 
    breezy.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
64
 
    cause profiling to fail rather than blocking.
65
 
    """
66
 
 
67
 
    profiler_block = 1
68
 
    """Serialise rather than failing to profile concurrent profile requests."""
69
 
 
70
 
    profiler_lock = threading.Lock()
71
 
    """Global lock used to serialise profiles."""
72
 
 
73
 
    def start(self):
74
 
        """Start profiling.
75
 
        
76
 
        This hooks into threading and will record all calls made until
77
 
        stop() is called.
78
 
        """
79
 
        self._g_threadmap = {}
80
 
        self.p = Profiler()
81
 
        permitted = self.__class__.profiler_lock.acquire(
82
 
            self.__class__.profiler_block)
83
 
        if not permitted:
84
 
            raise errors.InternalBzrError(msg="Already profiling something")
85
 
        try:
86
 
            self.p.enable(subcalls=True)
87
 
            threading.setprofile(self._thread_profile)
88
 
        except:
89
 
            self.__class__.profiler_lock.release()
90
 
            raise
91
 
 
92
 
    def stop(self):
93
 
        """Stop profiling.
94
 
 
95
 
        This unhooks from threading and cleans up the profiler, returning
96
 
        the gathered Stats object.
97
 
 
98
 
        :return: A breezy.lsprof.Stats object.
99
 
        """
100
 
        try:
101
 
            self.p.disable()
102
 
            for pp in self._g_threadmap.values():
103
 
                pp.disable()
104
 
            threading.setprofile(None)
105
 
            p = self.p
106
 
            self.p = None
107
 
            threads = {}
108
 
            for tid, pp in self._g_threadmap.items():
109
 
                threads[tid] = Stats(pp.getstats(), {})
110
 
            self._g_threadmap = None
111
 
            return Stats(p.getstats(), threads)
112
 
        finally:
113
 
            self.__class__.profiler_lock.release()
114
 
 
115
 
    def _thread_profile(self, f, *args, **kwds):
116
 
        # we lose the first profile point for a new thread in order to
117
 
        # trampoline a new Profile object into place
118
 
        thr = _thread.get_ident()
119
 
        self._g_threadmap[thr] = p = Profiler()
120
 
        # this overrides our sys.setprofile hook:
121
 
        p.enable(subcalls=True, builtins=True)
 
40
    threads = {}
 
41
    for tid, pp in _g_threadmap.items():
 
42
        threads[tid] = Stats(pp.getstats(), {})
 
43
    _g_threadmap = {}
 
44
    return ret, Stats(p.getstats(), threads)
122
45
 
123
46
 
124
47
class Stats(object):
125
 
    """Wrapper around the collected data.
126
 
 
127
 
    A Stats instance is created when the profiler finishes. Normal
128
 
    usage is to use save() to write out the data to a file, or pprint()
129
 
    to write human-readable information to the command line.
130
 
    """
 
48
    """XXX docstring"""
131
49
 
132
50
    def __init__(self, data, threads):
133
51
        self.data = data
134
52
        self.threads = threads
135
53
 
136
 
    def sort(self, crit="inlinetime", reverse=True):
137
 
        """Sort the data by the supplied critera.
138
 
 
139
 
        :param crit: the data attribute used as the sort key."""
140
 
        if crit not in profiler_entry.__dict__ or crit == 'code':
141
 
            raise ValueError("Can't sort by %s" % crit)
142
 
 
143
 
        key_func = operator.attrgetter(crit)
144
 
        self.data.sort(key=key_func, reverse=reverse)
145
 
 
 
54
    def sort(self, crit="inlinetime"):
 
55
        """XXX docstring"""
 
56
        if crit not in profiler_entry.__dict__:
 
57
            raise ValueError, "Can't sort by %s" % crit
 
58
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
 
59
                                        getattr(b, crit)))
146
60
        for e in self.data:
147
61
            if e.calls:
148
 
                e.calls.sort(key=key_func, reverse=reverse)
 
62
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
 
63
                                              getattr(b, crit)))
149
64
 
150
65
    def pprint(self, top=None, file=None):
151
 
        """Pretty-print the data as plain text for human consumption.
152
 
 
153
 
        :param top: only output the top n entries.
154
 
            The default value of None means output all data.
155
 
        :param file: the output file; if None, output will
156
 
            default to stdout."""
 
66
        """XXX docstring"""
157
67
        if file is None:
158
68
            file = sys.stdout
159
69
        d = self.data
194
104
        """Output profiling data in calltree format (for KCacheGrind)."""
195
105
        _CallTreeFilter(self.data).output(file)
196
106
 
197
 
    def save(self, filename, format=None):
198
 
        """Save profiling data to a file.
199
 
 
200
 
        :param filename: the name of the output file
201
 
        :param format: 'txt' for a text representation;
202
 
            'callgrind' for calltree format;
203
 
            otherwise a pickled Python object. A format of None indicates
204
 
            that the format to use is to be found from the filename. If
205
 
            the name starts with callgrind.out, callgrind format is used
206
 
            otherwise the format is given by the filename extension.
207
 
        """
208
 
        if format is None:
209
 
            basename = os.path.basename(filename)
210
 
            if basename.startswith('callgrind.out'):
211
 
                format = "callgrind"
212
 
            else:
213
 
                ext = os.path.splitext(filename)[1]
214
 
                if len(ext) > 1:
215
 
                    format = ext[1:]
216
 
        with open(filename, 'wb') as outfile:
217
 
            if format == "callgrind":
218
 
                # The callgrind format states it is 'ASCII based':
219
 
                # <http://valgrind.org/docs/manual/cl-format.html>
220
 
                # But includes filenames so lets ignore and use UTF-8.
221
 
                self.calltree(codecs.getwriter('utf-8')(outfile))
222
 
            elif format == "txt":
223
 
                self.pprint(file=codecs.getwriter('utf-8')(outfile))
224
 
            else:
225
 
                self.freeze()
226
 
                pickle.dump(self, outfile, 2)
227
 
 
228
107
 
229
108
class _CallTreeFilter(object):
230
 
    """Converter of a Stats object to input suitable for KCacheGrind.
231
 
 
232
 
    This code is taken from http://ddaa.net/blog/python/lsprof-calltree
233
 
    with the changes made by J.P. Calderone and Itamar applied. Note that
234
 
    isinstance(code, str) needs to be used at times to determine if the code
235
 
    object is actually an external code object (with a filename, etc.) or
236
 
    a Python built-in.
237
 
    """
238
109
 
239
110
    def __init__(self, data):
240
111
        self.data = data
241
112
        self.out_file = None
242
113
 
243
114
    def output(self, out_file):
244
 
        self.out_file = out_file
245
 
        out_file.write('events: Ticks\n')
 
115
        self.out_file = out_file        
 
116
        print >> out_file, 'events: Ticks'
246
117
        self._print_summary()
247
118
        for entry in self.data:
248
119
            self._entry(entry)
252
123
        for entry in self.data:
253
124
            totaltime = int(entry.totaltime * 1000)
254
125
            max_cost = max(max_cost, totaltime)
255
 
        self.out_file.write('summary: %d\n' % (max_cost,))
 
126
        print >> self.out_file, 'summary: %d' % (max_cost,)
256
127
 
257
128
    def _entry(self, entry):
258
129
        out_file = self.out_file
259
130
        code = entry.code
260
131
        inlinetime = int(entry.inlinetime * 1000)
261
 
        #out_file.write('ob=%s\n' % (code.co_filename,))
262
 
        if isinstance(code, str):
263
 
            out_file.write('fi=~\n')
264
 
        else:
265
 
            out_file.write('fi=%s\n' % (code.co_filename,))
266
 
        out_file.write('fn=%s\n' % (label(code, True),))
267
 
        if isinstance(code, str):
268
 
            out_file.write('0  %s\n' % (inlinetime,))
269
 
        else:
270
 
            out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
 
132
        #print >> out_file, 'ob=%s' % (code.co_filename,)
 
133
        print >> out_file, 'fi=%s' % (code.co_filename,)
 
134
        print >> out_file, 'fn=%s' % (label(code, True),)
 
135
        print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
271
136
        # recursive calls are counted in entry.calls
272
137
        if entry.calls:
273
138
            calls = entry.calls
274
139
        else:
275
140
            calls = []
276
 
        if isinstance(code, str):
277
 
            lineno = 0
278
 
        else:
279
 
            lineno = code.co_firstlineno
280
141
        for subentry in calls:
281
 
            self._subentry(lineno, subentry)
282
 
        out_file.write('\n')
 
142
            self._subentry(code.co_firstlineno, subentry)
 
143
        print >> out_file
283
144
 
284
145
    def _subentry(self, lineno, subentry):
285
146
        out_file = self.out_file
286
147
        code = subentry.code
287
148
        totaltime = int(subentry.totaltime * 1000)
288
 
        #out_file.write('cob=%s\n' % (code.co_filename,))
289
 
        if isinstance(code, str):
290
 
            out_file.write('cfi=~\n')
291
 
            out_file.write('cfn=%s\n' % (label(code, True),))
292
 
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
293
 
        else:
294
 
            out_file.write('cfi=%s\n' % (code.co_filename,))
295
 
            out_file.write('cfn=%s\n' % (label(code, True),))
296
 
            out_file.write('calls=%d %d\n' % (
297
 
                subentry.callcount, code.co_firstlineno))
298
 
        out_file.write('%d %d\n' % (lineno, totaltime))
 
149
        #print >> out_file, 'cob=%s' % (code.co_filename,)
 
150
        print >> out_file, 'cfn=%s' % (label(code, True),)
 
151
        print >> out_file, 'cfi=%s' % (code.co_filename,)
 
152
        print >> out_file, 'calls=%d %d' % (
 
153
            subentry.callcount, code.co_firstlineno)
 
154
        print >> out_file, '%d %d' % (lineno, totaltime)
 
155
 
299
156
 
300
157
_fn2mod = {}
301
158
 
323
180
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
324
181
 
325
182
 
326
 
def main():
 
183
if __name__ == '__main__':
 
184
    import os
327
185
    sys.argv = sys.argv[1:]
328
186
    if not sys.argv:
329
 
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
187
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
330
188
        sys.exit(2)
331
 
    import runpy
332
 
    result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
 
189
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
 
190
    stats = profile(execfile, sys.argv[0], globals(), locals())
333
191
    stats.sort()
334
192
    stats.pprint()
335
 
 
336
 
 
337
 
if __name__ == '__main__':
338
 
    main()