1
# this is copied from the lsprof distro because somehow
 
 
2
# it is not installed by distutils
 
 
3
# I made one modification to profile so that it returns a pair
 
 
4
# instead of just the Stats object
 
 
11
from _lsprof import Profiler, profiler_entry
 
 
14
__all__ = ['profile', 'Stats']
 
 
19
def _thread_profile(f, *args, **kwds):
 
 
20
    # we lose the first profile point for a new thread in order to trampoline
 
 
21
    # a new Profile object into place
 
 
23
    thr = thread.get_ident()
 
 
24
    _g_threadmap[thr] = p = Profiler()
 
 
25
    # this overrides our sys.setprofile hook:
 
 
26
    p.enable(subcalls=True, builtins=True)
 
 
29
def profile(f, *args, **kwds):
 
 
33
    p.enable(subcalls=True)
 
 
34
    threading.setprofile(_thread_profile)
 
 
35
    # Note: The except clause is needed below so that profiling data still
 
 
36
    # gets dumped even when exceptions are encountered. The except clause code
 
 
37
    # is taken straight from run_bzr_catch_errrors() in commands.py and ought
 
 
38
    # to be kept in sync with it.
 
 
41
            ret = f(*args, **kwds)
 
 
42
        except (KeyboardInterrupt, Exception), e:
 
 
44
            bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
 
 
48
        for pp in _g_threadmap.values():
 
 
50
        threading.setprofile(None)
 
 
53
    for tid, pp in _g_threadmap.items():
 
 
54
        threads[tid] = Stats(pp.getstats(), {})
 
 
56
    return ret, Stats(p.getstats(), threads)
 
 
62
    def __init__(self, data, threads):
 
 
64
        self.threads = threads
 
 
66
    def sort(self, crit="inlinetime"):
 
 
68
        if crit not in profiler_entry.__dict__:
 
 
69
            raise ValueError, "Can't sort by %s" % crit
 
 
70
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
 
 
74
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
 
 
77
    def pprint(self, top=None, file=None):
 
 
84
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
 
 
85
        hcols = "% 12s %12s %12s %12s %s\n"
 
 
86
        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
 
 
87
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
 
 
88
                            "Inline(ms)", "module:lineno(function)"))
 
 
90
            file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
 
 
91
                               e.inlinetime, label(e.code)))
 
 
94
                    file.write(cols % ("+%s" % se.callcount, se.reccallcount,
 
 
95
                                       se.totaltime, se.inlinetime,
 
 
96
                                       "+%s" % label(se.code)))
 
 
99
        """Replace all references to code objects with string
 
 
100
        descriptions; this makes it possible to pickle the instance."""
 
 
102
        # this code is probably rather ickier than it needs to be!
 
 
103
        for i in range(len(self.data)):
 
 
105
            if not isinstance(e.code, str):
 
 
106
                self.data[i] = type(e)((label(e.code),) + e[1:])
 
 
108
                for j in range(len(e.calls)):
 
 
110
                    if not isinstance(se.code, str):
 
 
111
                        e.calls[j] = type(se)((label(se.code),) + se[1:])
 
 
112
        for s in self.threads.values():
 
 
115
    def calltree(self, file):
 
 
116
        """Output profiling data in calltree format (for KCacheGrind)."""
 
 
117
        _CallTreeFilter(self.data).output(file)
 
 
119
    def save(self, filename, format=None):
 
 
120
        """Save profiling data to a file.
 
 
122
        :param filename: the name of the output file
 
 
123
        :param format: 'txt' for a text representation;
 
 
124
            'callgrind' for calltree format;
 
 
125
            otherwise a pickled Python object. A format of None indicates
 
 
126
            that the format to use is to be found from the filename. If
 
 
127
            the name starts with callgrind.out, callgrind format is used
 
 
128
            otherwise the format is given by the filename extension.
 
 
131
            basename = os.path.basename(filename)
 
 
132
            if basename.startswith('callgrind.out'):
 
 
135
                ext = os.path.splitext(filename)[1]
 
 
138
        outfile = open(filename, 'wb')
 
 
140
            if format == "callgrind":
 
 
141
                self.calltree(outfile)
 
 
142
            elif format == "txt":
 
 
143
                self.pprint(file=outfile)
 
 
146
                cPickle.dump(self, outfile, 2)
 
 
151
class _CallTreeFilter(object):
 
 
152
    """Converter of a Stats object to input suitable for KCacheGrind.
 
 
154
    This code is taken from http://ddaa.net/blog/python/lsprof-calltree
 
 
155
    with the changes made by J.P. Calderone and Itamar applied. Note that
 
 
156
    isinstance(code, str) needs to be used at times to determine if the code 
 
 
157
    object is actually an external code object (with a filename, etc.) or
 
 
161
    def __init__(self, data):
 
 
165
    def output(self, out_file):
 
 
166
        self.out_file = out_file        
 
 
167
        out_file.write('events: Ticks\n')
 
 
168
        self._print_summary()
 
 
169
        for entry in self.data:
 
 
172
    def _print_summary(self):
 
 
174
        for entry in self.data:
 
 
175
            totaltime = int(entry.totaltime * 1000)
 
 
176
            max_cost = max(max_cost, totaltime)
 
 
177
        self.out_file.write('summary: %d\n' % (max_cost,))
 
 
179
    def _entry(self, entry):
 
 
180
        out_file = self.out_file
 
 
182
        inlinetime = int(entry.inlinetime * 1000)
 
 
183
        #out_file.write('ob=%s\n' % (code.co_filename,))
 
 
184
        if isinstance(code, str):
 
 
185
            out_file.write('fi=~\n')
 
 
187
            out_file.write('fi=%s\n' % (code.co_filename,))
 
 
188
        out_file.write('fn=%s\n' % (label(code, True),))
 
 
189
        if isinstance(code, str):
 
 
190
            out_file.write('0  %s\n' % (inlinetime,))
 
 
192
            out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
 
 
193
        # recursive calls are counted in entry.calls
 
 
198
        if isinstance(code, str):
 
 
201
            lineno = code.co_firstlineno
 
 
202
        for subentry in calls:
 
 
203
            self._subentry(lineno, subentry)
 
 
206
    def _subentry(self, lineno, subentry):
 
 
207
        out_file = self.out_file
 
 
209
        totaltime = int(subentry.totaltime * 1000)
 
 
210
        #out_file.write('cob=%s\n' % (code.co_filename,))
 
 
211
        out_file.write('cfn=%s\n' % (label(code, True),))
 
 
212
        if isinstance(code, str):
 
 
213
            out_file.write('cfi=~\n')
 
 
214
            out_file.write('calls=%d 0\n' % (subentry.callcount,))
 
 
216
            out_file.write('cfi=%s\n' % (code.co_filename,))
 
 
217
            out_file.write('calls=%d %d\n' % (
 
 
218
                subentry.callcount, code.co_firstlineno))
 
 
219
        out_file.write('%d %d\n' % (lineno, totaltime))
 
 
223
def label(code, calltree=False):
 
 
224
    if isinstance(code, str):
 
 
227
        mname = _fn2mod[code.co_filename]
 
 
229
        for k, v in sys.modules.items():
 
 
232
            if getattr(v, '__file__', None) is None:
 
 
234
            if not isinstance(v.__file__, str):
 
 
236
            if v.__file__.startswith(code.co_filename):
 
 
237
                mname = _fn2mod[code.co_filename] = k
 
 
240
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
 
 
242
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
 
 
244
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
 
 
247
if __name__ == '__main__':
 
 
249
    sys.argv = sys.argv[1:]
 
 
251
        sys.stderr.write("usage: lsprof.py <script> <arguments...>\n")
 
 
253
    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
 
 
254
    stats = profile(execfile, sys.argv[0], globals(), locals())