~ubuntu-branches/ubuntu/utopic/exabgp/utopic

« back to all changes in this revision

Viewing changes to QA/profile/pyprof2calltree.py

  • Committer: Package Import Robot
  • Author(s): Henry-Nicolas Tourneur
  • Date: 2014-03-08 19:07:00 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20140308190700-xjbibpg1g6001c9x
Tags: 3.3.1-1
* New upstream release
* Bump python minimal required version (2.7)
* Closes: #726066 Debian packaging improvements proposed by Vincent Bernat
* Closes: #703774 not existent rundir (/var/run/exabgp) after reboot

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
 
3
 
# Copyright (c) 2006-2008, David Allouche, Jp Calderone, Itamar Shtull-Trauring,
4
 
# Johan Dahlin, Olivier Grisel <olivier.grisel@ensta.org>
5
 
#
6
 
# All rights reserved.
7
 
#
8
 
# Permission is hereby granted, free of charge, to any person obtaining a copy
9
 
# of this software and associated documentation files (the "Software"), to deal
10
 
# in the Software without restriction, including without limitation the rights
11
 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
 
# copies of the Software, and to permit persons to whom the Software is
13
 
# furnished to do so, subject to the following conditions:
14
 
#
15
 
# The above copyright notice and this permission notice shall be included in
16
 
# all copies or substantial portions of the Software.
17
 
#
18
 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 
# THE SOFTWARE.
25
 
"""pyprof2calltree: profiling output which is readable by kcachegrind
26
 
 
27
 
This script can either take raw cProfile.Profile.getstats() log entries or
28
 
take a previously recorded instance of the pstats.Stats class.
29
 
"""
30
 
 
31
 
import cProfile
32
 
import pstats
33
 
import optparse
34
 
import os
35
 
import sys
36
 
import tempfile
37
 
 
38
 
__all__ = ['convert', 'visualize', 'CalltreeConverter']
39
 
 
40
 
class Code(object):
41
 
    pass
42
 
 
43
 
class Entry(object):
44
 
    pass
45
 
 
46
 
def pstats2entries(data):
47
 
    """Helper to convert serialized pstats back to a list of raw entries
48
 
 
49
 
    Converse opperation of cProfile.Profile.snapshot_stats()
50
 
    """
51
 
    entries = dict()
52
 
    allcallers = dict()
53
 
 
54
 
    # first pass over stats to build the list of entry instances
55
 
    for code_info, call_info in data.stats.items():
56
 
        # build a fake code object
57
 
        code = Code()
58
 
        code.co_filename, code.co_firstlineno, code.co_name = code_info
59
 
 
60
 
        # build a fake entry object
61
 
        cc, nc, tt, ct, callers = call_info
62
 
        entry = Entry()
63
 
        entry.code = code
64
 
        entry.callcount = cc
65
 
        entry.reccallcount = nc - cc
66
 
        entry.inlinetime = tt
67
 
        entry.totaltime = ct
68
 
 
69
 
        # to be filled during the second pass over stats
70
 
        entry.calls = list()
71
 
 
72
 
        # collect the new entry
73
 
        entries[code_info] = entry
74
 
        allcallers[code_info] = callers.items()
75
 
 
76
 
    # second pass of stats to plug callees into callers
77
 
    for entry in entries.itervalues():
78
 
        entry_label = cProfile.label(entry.code)
79
 
        entry_callers = allcallers.get(entry_label, [])
80
 
        for entry_caller, call_info in entry_callers:
81
 
            entries[entry_caller].calls.append((entry, call_info))
82
 
 
83
 
    return entries.values()
84
 
 
85
 
class CalltreeConverter(object):
86
 
    """Convert raw cProfile or pstats data to the calltree format"""
87
 
 
88
 
    kcachegrind_command = "kcachegrind %s"
89
 
 
90
 
    def __init__(self, profiling_data):
91
 
        if isinstance(profiling_data, basestring):
92
 
            # treat profiling_data as a filename of pstats serialized data
93
 
            self.entries = pstats2entries(pstats.Stats(profiling_data))
94
 
        elif isinstance(profiling_data, pstats.Stats):
95
 
            # convert pstats data to cProfile list of entries
96
 
            self.entries = pstats2entries(profiling_data)
97
 
        else:
98
 
            # assume this are direct cProfile entries
99
 
            self.entries = profiling_data
100
 
        self.out_file = None
101
 
 
102
 
    def output(self, out_file):
103
 
        """Write the converted entries to out_file"""
104
 
        self.out_file = out_file
105
 
        print >> out_file, 'events: Ticks'
106
 
        self._print_summary()
107
 
        for entry in self.entries:
108
 
            self._entry(entry)
109
 
 
110
 
    def visualize(self):
111
 
        """Launch kcachegrind on the converted entries
112
 
 
113
 
        kcachegrind must be present in the system path
114
 
        """
115
 
 
116
 
        if self.out_file is None:
117
 
            _, outfile = tempfile.mkstemp(".log", "pyprof2calltree")
118
 
            f = file(outfile, "wb")
119
 
            self.output(f)
120
 
            use_temp_file = True
121
 
        else:
122
 
            use_temp_file = False
123
 
 
124
 
        try:
125
 
            os.system(self.kcachegrind_command % self.out_file.name)
126
 
        finally:
127
 
            # clean the temporary file
128
 
            if use_temp_file:
129
 
                f.close()
130
 
                os.remove(outfile)
131
 
                self.out_file = None
132
 
 
133
 
    def _print_summary(self):
134
 
        max_cost = 0
135
 
        for entry in self.entries:
136
 
            totaltime = int(entry.totaltime * 1000)
137
 
            max_cost = max(max_cost, totaltime)
138
 
        print >> self.out_file, 'summary: %d' % (max_cost,)
139
 
 
140
 
    def _entry(self, entry):
141
 
        out_file = self.out_file
142
 
 
143
 
        code = entry.code
144
 
        #print >> out_file, 'ob=%s' % (code.co_filename,)
145
 
 
146
 
        co_filename, co_firstlineno, co_name = cProfile.label(code)
147
 
        print >> out_file, 'fi=%s' % (co_filename,)
148
 
        print >> out_file, 'fn=%s %s:%d' % (
149
 
            co_name, co_filename, co_firstlineno)
150
 
 
151
 
        inlinetime = int(entry.inlinetime * 1000)
152
 
        if isinstance(code, str):
153
 
            print >> out_file, '0 ', inlinetime
154
 
        else:
155
 
            print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
156
 
 
157
 
        # recursive calls are counted in entry.calls
158
 
        if entry.calls:
159
 
            calls = entry.calls
160
 
        else:
161
 
            calls = []
162
 
 
163
 
        if isinstance(code, str):
164
 
            lineno = 0
165
 
        else:
166
 
            lineno = code.co_firstlineno
167
 
 
168
 
        for subentry, call_info in calls:
169
 
            self._subentry(lineno, subentry, call_info)
170
 
        print >> out_file
171
 
 
172
 
    def _subentry(self, lineno, subentry, call_info):
173
 
        out_file = self.out_file
174
 
        code = subentry.code
175
 
        #print >> out_file, 'cob=%s' % (code.co_filename,)
176
 
        co_filename, co_firstlineno, co_name = cProfile.label(code)
177
 
        print >> out_file, 'cfn=%s %s:%d' % (
178
 
            co_name, co_filename, co_firstlineno)
179
 
        print >> out_file, 'cfi=%s' % (co_filename,)
180
 
        print >> out_file, 'calls=%d %d' % (call_info[0], co_firstlineno)
181
 
 
182
 
        totaltime = int(call_info[3] * 1000)
183
 
        print >> out_file, '%d %d' % (lineno, totaltime)
184
 
 
185
 
def main():
186
 
    """Execute the converter using parameters provided on the command line"""
187
 
 
188
 
    usage = "%s [-k] [-o output_file_path] [-i input_file_path] [-r scriptfile [args]]"
189
 
    parser = optparse.OptionParser(usage=usage % sys.argv[0])
190
 
    parser.allow_interspersed_args = False
191
 
    parser.add_option('-o', '--outfile', dest="outfile",
192
 
                      help="Save calltree stats to <outfile>", default=None)
193
 
    parser.add_option('-i', '--infile', dest="infile",
194
 
                      help="Read python stats from <infile>", default=None)
195
 
    parser.add_option('-r', '--run-script', dest="script",
196
 
                      help="Name of the python script to run to collect"
197
 
                      " profiling data", default=None)
198
 
    parser.add_option('-k', '--kcachegrind', dest="kcachegrind",
199
 
                      help="Run the kcachegrind tool on the converted data",
200
 
                      action="store_true")
201
 
    options, args = parser.parse_args()
202
 
 
203
 
 
204
 
    outfile = options.outfile
205
 
 
206
 
    if options.script is not None:
207
 
        # collect profiling data by running the given script
208
 
 
209
 
        sys.argv[:] = [options.script] + args
210
 
        if not options.outfile:
211
 
            outfile = '%s.log' % os.path.basename(options.script)
212
 
 
213
 
        prof = cProfile.Profile()
214
 
        try:
215
 
            try:
216
 
                prof = prof.run('execfile(%r)' % (sys.argv[0],))
217
 
            except SystemExit:
218
 
                pass
219
 
        finally:
220
 
            kg = CalltreeConverter(prof.getstats())
221
 
 
222
 
    elif options.infile is not None:
223
 
        # use the profiling data from some input file
224
 
        if not options.outfile:
225
 
            outfile = '%s.log' % os.path.basename(options.infile)
226
 
 
227
 
        if options.infile == outfile:
228
 
            # prevent name collisions by appending another extension
229
 
            outfile += ".log"
230
 
 
231
 
        kg = CalltreeConverter(pstats.Stats(options.infile))
232
 
 
233
 
    else:
234
 
        # at least an input file or a script to run is required
235
 
        parser.print_usage()
236
 
        sys.exit(2)
237
 
 
238
 
    if options.outfile is not None or not options.kcachegrind:
239
 
        # user either explicitely required output file or requested by not
240
 
        # explicitely asking to launch kcachegrind
241
 
        print "writing converted data to: " + outfile
242
 
        kg.output(file(outfile, 'wb'))
243
 
 
244
 
    if options.kcachegrind:
245
 
        print "launching kcachegrind"
246
 
        kg.visualize()
247
 
 
248
 
 
249
 
def visualize(profiling_data):
250
 
    """launch the kcachegrind on `profiling_data`
251
 
 
252
 
    `profiling_data` can either be:
253
 
        - a pstats.Stats instance
254
 
        - the filename of a pstats.Stats dump
255
 
        - the result of a call to cProfile.Profile.getstats()
256
 
    """
257
 
    converter = CalltreeConverter(profiling_data)
258
 
    converter.visualize()
259
 
 
260
 
def convert(profiling_data, outputfile):
261
 
    """convert `profiling_data` to calltree format and dump it to `outputfile`
262
 
 
263
 
    `profiling_data` can either be:
264
 
        - a pstats.Stats instance
265
 
        - the filename of a pstats.Stats dump
266
 
        - the result of a call to cProfile.Profile.getstats()
267
 
 
268
 
    `outputfile` can either be:
269
 
        - a file() instance open in write mode
270
 
        - a filename
271
 
    """
272
 
    converter = CalltreeConverter(profiling_data)
273
 
    if isinstance(outputfile, basestring):
274
 
        f = file(outputfile, "wb")
275
 
        try:
276
 
            converter.output(f)
277
 
        finally:
278
 
            f.close()
279
 
    else:
280
 
        converter.output(outputfile)
281
 
 
282
 
 
283
 
if __name__ == '__main__':
284
 
    sys.exit(main())