/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: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

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
15
 
import threading
16
 
from _lsprof import Profiler, profiler_entry
17
 
 
18
 
from . import errors
 
7
from _lsprof import Profiler, profiler_entry, profiler_subentry
19
8
 
20
9
__all__ = ['profile', 'Stats']
21
10
 
22
 
 
23
11
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()
 
12
    """XXX docstring"""
 
13
    p = Profiler()
 
14
    p.enable(subcalls=True)
37
15
    try:
38
16
        ret = f(*args, **kwds)
39
17
    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)
 
18
        p.disable()
 
19
    return ret,Stats(p.getstats())
118
20
 
119
21
 
120
22
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
 
    """
127
 
 
128
 
    def __init__(self, data, threads):
 
23
    """XXX docstring"""
 
24
 
 
25
    def __init__(self, data):
129
26
        self.data = data
130
 
        self.threads = threads
131
 
 
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
 
 
 
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)))
142
34
        for e in self.data:
143
35
            if e.calls:
144
 
                e.calls.sort(key=key_func, reverse=reverse)
 
36
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
 
37
                                              getattr(b, crit)))
145
38
 
146
39
    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."""
 
40
        """XXX docstring"""
153
41
        if file is None:
154
42
            file = sys.stdout
155
43
        d = self.data
157
45
            d = d[:top]
158
46
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
159
47
        hcols = "% 12s %12s %12s %12s %s\n"
 
48
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
160
49
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
161
50
                            "Inline(ms)", "module:lineno(function)"))
162
51
        for e in d:
177
66
            e = self.data[i]
178
67
            if not isinstance(e.code, str):
179
68
                self.data[i] = type(e)((label(e.code),) + e[1:])
180
 
            if e.calls:
181
 
                for j in range(len(e.calls)):
182
 
                    se = e.calls[j]
183
 
                    if not isinstance(se.code, str):
184
 
                        e.calls[j] = type(se)((label(se.code),) + se[1:])
185
 
        for s in self.threads.values():
186
 
            s.freeze()
187
 
 
188
 
    def calltree(self, file):
189
 
        """Output profiling data in calltree format (for KCacheGrind)."""
190
 
        _CallTreeFilter(self.data).output(file)
191
 
 
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
 
 
224
 
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
 
 
234
 
    def __init__(self, data):
235
 
        self.data = data
236
 
        self.out_file = None
237
 
 
238
 
    def output(self, out_file):
239
 
        self.out_file = out_file
240
 
        out_file.write('events: Ticks\n')
241
 
        self._print_summary()
242
 
        for entry in self.data:
243
 
            self._entry(entry)
244
 
 
245
 
    def _print_summary(self):
246
 
        max_cost = 0
247
 
        for entry in self.data:
248
 
            totaltime = int(entry.totaltime * 1000)
249
 
            max_cost = max(max_cost, totaltime)
250
 
        self.out_file.write('summary: %d\n' % (max_cost,))
251
 
 
252
 
    def _entry(self, entry):
253
 
        out_file = self.out_file
254
 
        code = entry.code
255
 
        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))
265
 
        # recursive calls are counted in entry.calls
266
 
        if entry.calls:
267
 
            calls = entry.calls
268
 
        else:
269
 
            calls = []
270
 
        if isinstance(code, str):
271
 
            lineno = 0
272
 
        else:
273
 
            lineno = code.co_firstlineno
274
 
        for subentry in calls:
275
 
            self._subentry(lineno, subentry)
276
 
        out_file.write('\n')
277
 
 
278
 
    def _subentry(self, lineno, subentry):
279
 
        out_file = self.out_file
280
 
        code = subentry.code
281
 
        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))
292
 
 
 
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:])
293
74
 
294
75
_fn2mod = {}
295
76
 
296
 
 
297
 
def label(code, calltree=False):
 
77
def label(code):
298
78
    if isinstance(code, str):
299
79
        return code
300
80
    try:
301
81
        mname = _fn2mod[code.co_filename]
302
82
    except KeyError:
303
 
        for k, v in sys.modules.items():
 
83
        for k, v in sys.modules.iteritems():
304
84
            if v is None:
305
85
                continue
306
 
            if getattr(v, '__file__', None) is None:
 
86
            if not hasattr(v, '__file__'):
307
87
                continue
308
88
            if not isinstance(v.__file__, str):
309
89
                continue
311
91
                mname = _fn2mod[code.co_filename] = k
312
92
                break
313
93
        else:
314
 
            mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
315
 
    if calltree:
316
 
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
317
 
    else:
318
 
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
319
 
 
320
 
 
321
 
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
322
101
    sys.argv = sys.argv[1:]
323
102
    if not sys.argv:
324
 
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
103
        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
325
104
        sys.exit(2)
326
 
    import runpy
327
 
    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())
328
107
    stats.sort()
329
108
    stats.pprint()
330
 
 
331
 
 
332
 
if __name__ == '__main__':
333
 
    main()