/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 breezy/lsprof.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-08-23 01:15:41 UTC
  • mfrom: (7520.1.4 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200823011541-nv0oh7nzaganx2qy
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/389690

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