/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: Canonical.com Patch Queue Manager
  • Date: 2006-04-24 10:31:28 UTC
  • mfrom: (1684.1.2 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060424103128-a637f56a7c529bad
(mbp) tutorial improvements

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
20
 
import threading
21
 
from _lsprof import Profiler, profiler_entry
22
 
 
23
 
from . import errors
 
7
from _lsprof import Profiler, profiler_entry, profiler_subentry
24
8
 
25
9
__all__ = ['profile', 'Stats']
26
10
 
27
 
 
28
11
def profile(f, *args, **kwds):
29
 
    """Run a function profile.
30
 
 
31
 
    Exceptions are not caught: If you need stats even when exceptions are to be
32
 
    raised, pass in a closure that will catch the exceptions and transform them
33
 
    appropriately for your driver function.
34
 
 
35
 
    Important caveat: only one profile can execute at a time. See BzrProfiler
36
 
    for details.
37
 
 
38
 
    :return: The functions return value and a stats object.
39
 
    """
40
 
    profiler = BzrProfiler()
41
 
    profiler.start()
 
12
    """XXX docstring"""
 
13
    p = Profiler()
 
14
    p.enable(subcalls=True)
42
15
    try:
43
16
        ret = f(*args, **kwds)
44
17
    finally:
45
 
        stats = profiler.stop()
46
 
    return ret, stats
47
 
 
48
 
 
49
 
class BzrProfiler(object):
50
 
    """Bzr utility wrapper around Profiler.
51
 
 
52
 
    For most uses the module level 'profile()' function will be suitable.
53
 
    However profiling when a simple wrapped function isn't available may
54
 
    be easier to accomplish using this class.
55
 
 
56
 
    To use it, create a BzrProfiler and call start() on it. Some arbitrary
57
 
    time later call stop() to stop profiling and retrieve the statistics
58
 
    from the code executed in the interim.
59
 
 
60
 
    Note that profiling involves a threading.Lock around the actual profiling.
61
 
    This is needed because profiling involves global manipulation of the python
62
 
    interpreter state. As such you cannot perform multiple profiles at once.
63
 
    Trying to do so will lock out the second profiler unless the global
64
 
    breezy.lsprof.BzrProfiler.profiler_block is set to 0. Setting it to 0 will
65
 
    cause profiling to fail rather than blocking.
66
 
    """
67
 
 
68
 
    profiler_block = 1
69
 
    """Serialise rather than failing to profile concurrent profile requests."""
70
 
 
71
 
    profiler_lock = threading.Lock()
72
 
    """Global lock used to serialise profiles."""
73
 
 
74
 
    def start(self):
75
 
        """Start profiling.
76
 
 
77
 
        This hooks into threading and will record all calls made until
78
 
        stop() is called.
79
 
        """
80
 
        self._g_threadmap = {}
81
 
        self.p = Profiler()
82
 
        permitted = self.__class__.profiler_lock.acquire(
83
 
            self.__class__.profiler_block)
84
 
        if not permitted:
85
 
            raise errors.InternalBzrError(msg="Already profiling something")
86
 
        try:
87
 
            self.p.enable(subcalls=True)
88
 
            threading.setprofile(self._thread_profile)
89
 
        except BaseException:
90
 
            self.__class__.profiler_lock.release()
91
 
            raise
92
 
 
93
 
    def stop(self):
94
 
        """Stop profiling.
95
 
 
96
 
        This unhooks from threading and cleans up the profiler, returning
97
 
        the gathered Stats object.
98
 
 
99
 
        :return: A breezy.lsprof.Stats object.
100
 
        """
101
 
        try:
102
 
            self.p.disable()
103
 
            for pp in self._g_threadmap.values():
104
 
                pp.disable()
105
 
            threading.setprofile(None)
106
 
            p = self.p
107
 
            self.p = None
108
 
            threads = {}
109
 
            for tid, pp in self._g_threadmap.items():
110
 
                threads[tid] = Stats(pp.getstats(), {})
111
 
            self._g_threadmap = None
112
 
            return Stats(p.getstats(), threads)
113
 
        finally:
114
 
            self.__class__.profiler_lock.release()
115
 
 
116
 
    def _thread_profile(self, f, *args, **kwds):
117
 
        # we lose the first profile point for a new thread in order to
118
 
        # trampoline a new Profile object into place
119
 
        thr = _thread.get_ident()
120
 
        self._g_threadmap[thr] = p = Profiler()
121
 
        # this overrides our sys.setprofile hook:
122
 
        p.enable(subcalls=True, builtins=True)
 
18
        p.disable()
 
19
    return ret,Stats(p.getstats())
123
20
 
124
21
 
125
22
class Stats(object):
126
 
    """Wrapper around the collected data.
127
 
 
128
 
    A Stats instance is created when the profiler finishes. Normal
129
 
    usage is to use save() to write out the data to a file, or pprint()
130
 
    to write human-readable information to the command line.
131
 
    """
132
 
 
133
 
    def __init__(self, data, threads):
 
23
    """XXX docstring"""
 
24
 
 
25
    def __init__(self, data):
134
26
        self.data = data
135
 
        self.threads = threads
136
 
 
137
 
    def sort(self, crit="inlinetime", reverse=True):
138
 
        """Sort the data by the supplied critera.
139
 
 
140
 
        :param crit: the data attribute used as the sort key."""
141
 
        if crit not in profiler_entry.__dict__ or crit == 'code':
142
 
            raise ValueError("Can't sort by %s" % crit)
143
 
 
144
 
        key_func = operator.attrgetter(crit)
145
 
        self.data.sort(key=key_func, reverse=reverse)
146
 
 
 
27
 
 
28
    def sort(self, crit="inlinetime"):
 
29
        """XXX docstring"""
 
30
        if crit not in profiler_entry.__dict__:
 
31
            raise ValueError, "Can't sort by %s" % crit
 
32
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
 
33
                                        getattr(b, crit)))
147
34
        for e in self.data:
148
35
            if e.calls:
149
 
                e.calls.sort(key=key_func, reverse=reverse)
 
36
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
 
37
                                              getattr(b, crit)))
150
38
 
151
39
    def pprint(self, top=None, file=None):
152
 
        """Pretty-print the data as plain text for human consumption.
153
 
 
154
 
        :param top: only output the top n entries.
155
 
            The default value of None means output all data.
156
 
        :param file: the output file; if None, output will
157
 
            default to stdout."""
 
40
        """XXX docstring"""
158
41
        if file is None:
159
42
            file = sys.stdout
160
43
        d = self.data
162
45
            d = d[:top]
163
46
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
164
47
        hcols = "% 12s %12s %12s %12s %s\n"
 
48
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
165
49
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
166
50
                            "Inline(ms)", "module:lineno(function)"))
167
51
        for e in d:
182
66
            e = self.data[i]
183
67
            if not isinstance(e.code, str):
184
68
                self.data[i] = type(e)((label(e.code),) + e[1:])
185
 
            if e.calls:
186
 
                for j in range(len(e.calls)):
187
 
                    se = e.calls[j]
188
 
                    if not isinstance(se.code, str):
189
 
                        e.calls[j] = type(se)((label(se.code),) + se[1:])
190
 
        for s in self.threads.values():
191
 
            s.freeze()
192
 
 
193
 
    def calltree(self, file):
194
 
        """Output profiling data in calltree format (for KCacheGrind)."""
195
 
        _CallTreeFilter(self.data).output(file)
196
 
 
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
 
 
229
 
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
 
 
239
 
    def __init__(self, data):
240
 
        self.data = data
241
 
        self.out_file = None
242
 
 
243
 
    def output(self, out_file):
244
 
        self.out_file = out_file
245
 
        out_file.write('events: Ticks\n')
246
 
        self._print_summary()
247
 
        for entry in self.data:
248
 
            self._entry(entry)
249
 
 
250
 
    def _print_summary(self):
251
 
        max_cost = 0
252
 
        for entry in self.data:
253
 
            totaltime = int(entry.totaltime * 1000)
254
 
            max_cost = max(max_cost, totaltime)
255
 
        self.out_file.write('summary: %d\n' % (max_cost,))
256
 
 
257
 
    def _entry(self, entry):
258
 
        out_file = self.out_file
259
 
        code = entry.code
260
 
        inlinetime = int(entry.inlinetime * 1000)
261
 
        if isinstance(code, str):
262
 
            out_file.write('fi=~\n')
263
 
        else:
264
 
            out_file.write('fi=%s\n' % (code.co_filename,))
265
 
        out_file.write('fn=%s\n' % (label(code, True),))
266
 
        if isinstance(code, str):
267
 
            out_file.write('0  %s\n' % (inlinetime,))
268
 
        else:
269
 
            out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
270
 
        # recursive calls are counted in entry.calls
271
 
        if entry.calls:
272
 
            calls = entry.calls
273
 
        else:
274
 
            calls = []
275
 
        if isinstance(code, str):
276
 
            lineno = 0
277
 
        else:
278
 
            lineno = code.co_firstlineno
279
 
        for subentry in calls:
280
 
            self._subentry(lineno, subentry)
281
 
        out_file.write('\n')
282
 
 
283
 
    def _subentry(self, lineno, subentry):
284
 
        out_file = self.out_file
285
 
        code = subentry.code
286
 
        totaltime = int(subentry.totaltime * 1000)
287
 
        if isinstance(code, str):
288
 
            out_file.write('cfi=~\n')
289
 
            out_file.write('cfn=%s\n' % (label(code, True),))
290
 
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
291
 
        else:
292
 
            out_file.write('cfi=%s\n' % (code.co_filename,))
293
 
            out_file.write('cfn=%s\n' % (label(code, True),))
294
 
            out_file.write('calls=%d %d\n' % (
295
 
                subentry.callcount, code.co_firstlineno))
296
 
        out_file.write('%d %d\n' % (lineno, totaltime))
297
 
 
 
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:])
298
74
 
299
75
_fn2mod = {}
300
76
 
301
 
 
302
 
def label(code, calltree=False):
 
77
def label(code):
303
78
    if isinstance(code, str):
304
79
        return code
305
80
    try:
306
81
        mname = _fn2mod[code.co_filename]
307
82
    except KeyError:
308
 
        for k, v in sys.modules.items():
 
83
        for k, v in sys.modules.iteritems():
309
84
            if v is None:
310
85
                continue
311
 
            if getattr(v, '__file__', None) is None:
 
86
            if not hasattr(v, '__file__'):
312
87
                continue
313
88
            if not isinstance(v.__file__, str):
314
89
                continue
316
91
                mname = _fn2mod[code.co_filename] = k
317
92
                break
318
93
        else:
319
 
            mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
320
 
    if calltree:
321
 
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
322
 
    else:
323
 
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
324
 
 
325
 
 
326
 
def main():
 
94
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
 
95
    
 
96
    return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
 
97
 
 
98
 
 
99
if __name__ == '__main__':
 
100
    import os
327
101
    sys.argv = sys.argv[1:]
328
102
    if not sys.argv:
329
 
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
103
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
330
104
        sys.exit(2)
331
 
    import runpy
332
 
    result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
 
105
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
 
106
    stats = profile(execfile, sys.argv[0], globals(), locals())
333
107
    stats.sort()
334
108
    stats.pprint()
335
 
 
336
 
 
337
 
if __name__ == '__main__':
338
 
    main()