/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

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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