/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: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

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