/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: 2010-05-05 00:05:29 UTC
  • mto: This revision was merged to the branch mainline in revision 5206.
  • Revision ID: robertc@robertcollins.net-20100505000529-ltmllyms5watqj5u
Make 'pydoc bzrlib.tests.build_tree_shape' useful.

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
 
6
import cPickle
14
7
import os
15
8
import sys
16
 
try:
17
 
    import _thread
18
 
except ImportError:
19
 
    import thread as _thread
 
9
import thread
20
10
import threading
21
11
from _lsprof import Profiler, profiler_entry
22
12
 
23
 
from . import errors
24
13
 
25
14
__all__ = ['profile', 'Stats']
26
15
 
27
 
 
28
16
def profile(f, *args, **kwds):
29
17
    """Run a function profile.
30
18
 
32
20
    raised, pass in a closure that will catch the exceptions and transform them
33
21
    appropriately for your driver function.
34
22
 
35
 
    Important caveat: only one profile can execute at a time. See BzrProfiler
36
 
    for details.
37
 
 
38
23
    :return: The functions return value and a stats object.
39
24
    """
40
25
    profiler = BzrProfiler()
48
33
 
49
34
class BzrProfiler(object):
50
35
    """Bzr utility wrapper around Profiler.
51
 
 
 
36
    
52
37
    For most uses the module level 'profile()' function will be suitable.
53
38
    However profiling when a simple wrapped function isn't available may
54
39
    be easier to accomplish using this class.
56
41
    To use it, create a BzrProfiler and call start() on it. Some arbitrary
57
42
    time later call stop() to stop profiling and retrieve the statistics
58
43
    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
44
    """
67
45
 
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
46
    def start(self):
75
47
        """Start profiling.
76
 
 
 
48
        
77
49
        This hooks into threading and will record all calls made until
78
50
        stop() is called.
79
51
        """
80
52
        self._g_threadmap = {}
81
53
        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
 
54
        self.p.enable(subcalls=True)
 
55
        threading.setprofile(self._thread_profile)
92
56
 
93
57
    def stop(self):
94
58
        """Stop profiling.
96
60
        This unhooks from threading and cleans up the profiler, returning
97
61
        the gathered Stats object.
98
62
 
99
 
        :return: A breezy.lsprof.Stats object.
 
63
        :return: A bzrlib.lsprof.Stats object.
100
64
        """
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()
 
65
        self.p.disable()
 
66
        for pp in self._g_threadmap.values():
 
67
            pp.disable()
 
68
        threading.setprofile(None)
 
69
        p = self.p
 
70
        self.p = None
 
71
        threads = {}
 
72
        for tid, pp in self._g_threadmap.items():
 
73
            threads[tid] = Stats(pp.getstats(), {})
 
74
        self._g_threadmap = None
 
75
        return Stats(p.getstats(), threads)
115
76
 
116
77
    def _thread_profile(self, f, *args, **kwds):
117
78
        # we lose the first profile point for a new thread in order to
118
79
        # trampoline a new Profile object into place
119
 
        thr = _thread.get_ident()
 
80
        thr = thread.get_ident()
120
81
        self._g_threadmap[thr] = p = Profiler()
121
82
        # this overrides our sys.setprofile hook:
122
83
        p.enable(subcalls=True, builtins=True)
123
84
 
124
85
 
125
86
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
 
    """
 
87
    """XXX docstring"""
132
88
 
133
89
    def __init__(self, data, threads):
134
90
        self.data = data
135
91
        self.threads = threads
136
92
 
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
 
 
 
93
    def sort(self, crit="inlinetime"):
 
94
        """XXX docstring"""
 
95
        if crit not in profiler_entry.__dict__:
 
96
            raise ValueError, "Can't sort by %s" % crit
 
97
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
 
98
                                        getattr(b, crit)))
147
99
        for e in self.data:
148
100
            if e.calls:
149
 
                e.calls.sort(key=key_func, reverse=reverse)
 
101
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
 
102
                                              getattr(b, crit)))
150
103
 
151
104
    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."""
 
105
        """XXX docstring"""
158
106
        if file is None:
159
107
            file = sys.stdout
160
108
        d = self.data
162
110
            d = d[:top]
163
111
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
164
112
        hcols = "% 12s %12s %12s %12s %s\n"
 
113
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
165
114
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
166
115
                            "Inline(ms)", "module:lineno(function)"))
167
116
        for e in d:
213
162
                ext = os.path.splitext(filename)[1]
214
163
                if len(ext) > 1:
215
164
                    format = ext[1:]
216
 
        with open(filename, 'wb') as outfile:
 
165
        outfile = open(filename, 'wb')
 
166
        try:
217
167
            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))
 
168
                self.calltree(outfile)
222
169
            elif format == "txt":
223
 
                self.pprint(file=codecs.getwriter('utf-8')(outfile))
 
170
                self.pprint(file=outfile)
224
171
            else:
225
172
                self.freeze()
226
 
                pickle.dump(self, outfile, 2)
 
173
                cPickle.dump(self, outfile, 2)
 
174
        finally:
 
175
            outfile.close()
227
176
 
228
177
 
229
178
class _CallTreeFilter(object):
258
207
        out_file = self.out_file
259
208
        code = entry.code
260
209
        inlinetime = int(entry.inlinetime * 1000)
 
210
        #out_file.write('ob=%s\n' % (code.co_filename,))
261
211
        if isinstance(code, str):
262
212
            out_file.write('fi=~\n')
263
213
        else:
284
234
        out_file = self.out_file
285
235
        code = subentry.code
286
236
        totaltime = int(subentry.totaltime * 1000)
 
237
        #out_file.write('cob=%s\n' % (code.co_filename,))
 
238
        out_file.write('cfn=%s\n' % (label(code, True),))
287
239
        if isinstance(code, str):
288
240
            out_file.write('cfi=~\n')
289
 
            out_file.write('cfn=%s\n' % (label(code, True),))
290
241
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
291
242
        else:
292
243
            out_file.write('cfi=%s\n' % (code.co_filename,))
293
 
            out_file.write('cfn=%s\n' % (label(code, True),))
294
244
            out_file.write('calls=%d %d\n' % (
295
245
                subentry.callcount, code.co_firstlineno))
296
246
        out_file.write('%d %d\n' % (lineno, totaltime))
297
247
 
298
 
 
299
248
_fn2mod = {}
300
249
 
301
 
 
302
250
def label(code, calltree=False):
303
251
    if isinstance(code, str):
304
252
        return code
316
264
                mname = _fn2mod[code.co_filename] = k
317
265
                break
318
266
        else:
319
 
            mname = _fn2mod[code.co_filename] = '<%s>' % code.co_filename
 
267
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
320
268
    if calltree:
321
269
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
322
270
    else:
323
271
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
324
272
 
325
273
 
326
 
def main():
 
274
if __name__ == '__main__':
 
275
    import os
327
276
    sys.argv = sys.argv[1:]
328
277
    if not sys.argv:
329
278
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
330
279
        sys.exit(2)
331
 
    import runpy
332
 
    result, stats = profile(runpy.run_path, sys.argv[0], run_name='__main__')
 
280
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
 
281
    stats = profile(execfile, sys.argv[0], globals(), locals())
333
282
    stats.sort()
334
283
    stats.pprint()
335
 
 
336
 
 
337
 
if __name__ == '__main__':
338
 
    main()