~ubuntu-branches/ubuntu/trusty/kcachegrind/trusty-proposed

« back to all changes in this revision

Viewing changes to converters/hotshot2calltree.cmake

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2014-03-19 10:40:34 UTC
  • mfrom: (1.1.14)
  • Revision ID: package-import@ubuntu.com-20140319104034-rkcy2v3z2ih24ccs
Tags: 4:4.12.90-0ubuntu1
New upstream beta release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# _*_ coding: latin1 _*_
 
3
 
 
4
#
 
5
# Copyright (c) 2003 by WEB.DE, Karlsruhe
 
6
# Autor: Jörg Beyer <job@webde-ag.de>
 
7
#
 
8
# hotshot2cachegrind is free software; you can redistribute it and/or
 
9
# modify it under the terms of the GNU General Public
 
10
# License as published by the Free Software Foundation, version 2.
 
11
#
 
12
# This program is distributed in the hope that it will be useful,
 
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
15
# General Public License for more details.
 
16
 
 
17
# You should have received a copy of the GNU General Public License
 
18
# along with this program; see the file COPYING.  If not, write to
 
19
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
20
# Boston, MA 02110-1301, USA.
 
21
#
 
22
#
 
23
# This script transforms the pstat output of the hotshot
 
24
# python profiler into the input of kcachegrind. 
 
25
#
 
26
# example usage:
 
27
# modify you python script to run this code:
 
28
#
 
29
# import hotshot
 
30
# filename = "pythongrind.prof"
 
31
# prof = hotshot.Profile(filename, lineevents=1)
 
32
# prof.runcall(run) # assuming that "run" should be called.
 
33
# prof.close()
 
34
#
 
35
# it will run the "run"-method under profiling and write
 
36
# the results in a file, called "pythongrind.prof".
 
37
#
 
38
# then call this script:
 
39
# hotshot2cachegrind -o <output> <input>
 
40
# or here:
 
41
# hotshot2cachegrind cachegrind.out.0 pythongrind.prof
 
42
#
 
43
# then call kcachegrind:
 
44
# kcachegrind cachegrind.out.0
 
45
#
 
46
# TODO: 
 
47
#  * es gibt Probleme mit rekursiven (direkt und indirekt) Aufrufen - dann
 
48
#    stimmen die Kosten nicht.
 
49
#
 
50
#  * einige Funktionen werden mit "?" als Name angezeigt. Evtl sind
 
51
#    das nur die C/C++ extensions.
 
52
#
 
53
#  * es fehlt noch ein Funktionsnamen Mangling, dass die Filenamen berücksichtigt,
 
54
#    zZ sind alle __init__'s und alle run's schwer unterscheidbar :-(
 
55
#
 
56
version = "Version ${KCACHEGRIND_VERSION}"
 
57
progname = "hotshot2cachegrind"
 
58
 
 
59
import os, sys
 
60
from hotshot import stats,log
 
61
import os.path 
 
62
 
 
63
file_limit=0
 
64
 
 
65
what2text = { 
 
66
    log.WHAT_ADD_INFO    : "ADD_INFO", 
 
67
    log.WHAT_DEFINE_FUNC : "DEFINE_FUNC", 
 
68
    log.WHAT_DEFINE_FILE : "DEFINE_FILE", 
 
69
    log.WHAT_LINENO      : "LINENO", 
 
70
    log.WHAT_EXIT        : "EXIT", 
 
71
    log.WHAT_ENTER       : "ENTER"}
 
72
 
 
73
# a pseudo caller on the caller stack. This represents
 
74
# the Python interpreter that executes the given python 
 
75
# code.
 
76
root_caller = ("PythonInterpreter",0,"execute")
 
77
 
 
78
class CallStack:
 
79
    """A tiny Stack implementation, based on python lists"""
 
80
    def __init__(self):
 
81
       self.stack = []
 
82
       self.recursion_counter = {}
 
83
    def push(self, elem):
 
84
        """put something on the stack"""
 
85
        self.stack.append(elem)
 
86
        rc = self.recursion_counter.get(elem, 0)
 
87
        self.recursion_counter[elem] = rc + 1
 
88
 
 
89
    def pop(self):
 
90
        """get the head element of the stack and remove it from the stack"""
 
91
        elem = self.stack[-1:][0]
 
92
        rc = self.recursion_counter.get(elem) - 1
 
93
        if rc>0:
 
94
            self.recursion_counter[elem] = rc
 
95
        else:
 
96
            del self.recursion_counter[elem]
 
97
        return self.stack.pop()
 
98
 
 
99
    def top(self):
 
100
        """get the head element of the stack, stack is unchanged."""
 
101
        return self.stack[-1:][0]
 
102
    def handleLineCost(self, tdelta):
 
103
        p, c = self.stack.pop()
 
104
        self.stack.append( (p,c + tdelta) )
 
105
    def size(self):
 
106
        """ return how many elements the stack has"""
 
107
        return len(self.stack)
 
108
 
 
109
    def __str__(self):
 
110
        return "[stack: %s]" % self.stack
 
111
 
 
112
    def recursion(self, pos):
 
113
        return self.recursion_counter.get(pos, 0)
 
114
        #return self.recursion_dict.has_key((entry[0][0], entry[0][2]))
 
115
 
 
116
def return_from_call(caller_stack, call_dict, cost_now):
 
117
    """return from a function call
 
118
       remove the function from the caller stack,
 
119
       add the costs to the calling function.
 
120
    """
 
121
    called, cost_at_enter = caller_stack.pop()
 
122
    caller, caller_cost = caller_stack.top()
 
123
 
 
124
    #print "return_from_call: %s ruft %s" % (caller, called,)
 
125
 
 
126
    per_file_dict = call_dict.get(called[0], {})
 
127
    per_caller_dict = per_file_dict.get(called[2], {})
 
128
    cost_so_far, call_counter = per_caller_dict.get(caller, (0, 0))
 
129
 
 
130
    if caller_stack.recursion(called):
 
131
        per_caller_dict[caller] = (cost_so_far, call_counter + 1)
 
132
    else:
 
133
        per_caller_dict[caller] = (cost_so_far + cost_now - cost_at_enter, call_counter + 1)
 
134
 
 
135
    per_file_dict[called[2]] = per_caller_dict
 
136
    call_dict[called[0]] = per_file_dict
 
137
 
 
138
 
 
139
def updateStatus(filecount):
 
140
    sys.stdout.write("reading File #%d    \r" % filecount)
 
141
    sys.stdout.flush()
 
142
def convertProfFiles(output, inputfilenames):
 
143
    """convert all the given input files into one kcachegrind 
 
144
       input file.
 
145
    """
 
146
    call_dict = {}
 
147
    cost_per_pos = {}
 
148
    cost_per_function = {}
 
149
    caller_stack = CallStack()
 
150
    caller_stack.push((root_caller, 0))
 
151
 
 
152
    total_cost = 0
 
153
    filecount = 1
 
154
    number_of_files = len(inputfilenames)
 
155
    for inputfilename in inputfilenames:
 
156
        updateStatus(filecount)
 
157
        cost, filecount = convertHandleFilename(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount)
 
158
        total_cost += cost
 
159
        if (file_limit > 0) and (filecount > file_limit):
 
160
            break
 
161
    
 
162
    print
 
163
    print "total_cost: % d Ticks",total_cost
 
164
    dumpResults(output, call_dict, total_cost, cost_per_pos, cost_per_function)
 
165
 
 
166
def convertHandleFilename(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount):
 
167
    updateStatus(filecount)
 
168
    if not ((file_limit > 0) and (filecount > file_limit)):
 
169
        if os.path.isdir(inputfilename):
 
170
            cost, filecount = convertProfDir(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount)
 
171
        elif os.path.isfile(inputfilename):
 
172
            cost = convertProfFile(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function)
 
173
            filecount += 1 
 
174
        else:
 
175
            sys.stderr.write("warn: ignoring '%s', is no file and no directory\n" % inputfilename)
 
176
            cost = 0
 
177
    return (cost, filecount)
 
178
 
 
179
def convertProfDir(start, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount):
 
180
    cost = 0
 
181
    filenames = os.listdir(start)
 
182
    for f in filenames:
 
183
        if (file_limit > 0) and (filecount > file_limit): 
 
184
            break
 
185
        full = os.path.join(start, f)
 
186
        c, filecount = convertHandleFilename(full, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount)
 
187
        cost += c;
 
188
    return (cost, filecount)
 
189
 
 
190
def handleCostPerPos(cost_per_pos, pos, current_cost):
 
191
    """
 
192
       the cost per source position are managed in a dict in a dict.
 
193
 
 
194
       the cost are handled per file and there per function.
 
195
       so, the per-file-dict contains some per-function-dicts
 
196
       which sum up the cost per line (in this function and in 
 
197
       this file).
 
198
    """
 
199
    filename  = pos[0]
 
200
    lineno    = pos[1]
 
201
    funcname  = pos[2]
 
202
    file_dict = cost_per_pos.get(filename, {})
 
203
    func_dict = file_dict.get(funcname, {})
 
204
    func_dict.setdefault(lineno, 0)
 
205
    func_dict[lineno] += current_cost
 
206
    file_dict[funcname] = func_dict
 
207
    cost_per_pos[filename] = file_dict
 
208
 
 
209
def convertProfFile(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function):
 
210
    """convert a single input file into one kcachegrind
 
211
       data.
 
212
 
 
213
       this is the most expensive function in this python source :-)
 
214
    """
 
215
 
 
216
    total_cost = 0
 
217
    try:
 
218
        logreader = log.LogReader(inputfilename)
 
219
        current_cost = 0
 
220
        hc = handleCostPerPos # shortcut
 
221
        for item in logreader:
 
222
            what, pos ,tdelta = item
 
223
            (file, lineno, func) = pos
 
224
            #line = "%s %s %d %s %d" % (what2text[what], file, lineno, func, tdelta)
 
225
            #print line
 
226
            # most common cases first
 
227
            if what == log.WHAT_LINENO:
 
228
                # add the current cost to the current function
 
229
                hc(cost_per_pos, pos, tdelta)
 
230
                total_cost += tdelta
 
231
            elif what == log.WHAT_ENTER:
 
232
                caller_stack.push((pos, total_cost))
 
233
                hc(cost_per_pos, pos, tdelta)
 
234
                total_cost += tdelta
 
235
            elif what == log.WHAT_EXIT:
 
236
                hc(cost_per_pos, pos, tdelta)
 
237
                total_cost += tdelta
 
238
                return_from_call(caller_stack, call_dict, total_cost)
 
239
            else:
 
240
                assert 0, "duh: %d" % what
 
241
 
 
242
 
 
243
        # I have no idea, why sometimes the stack is not empty - we
 
244
        # have to rewind the stack to get 100% for the root_caller
 
245
        while caller_stack.size() > 1:
 
246
            return_from_call(caller_stack, call_dict, total_cost)
 
247
 
 
248
    except IOError:
 
249
        print "could not open inputfile '%s', ignore this." % inputfilename
 
250
    except EOFError, m:
 
251
        print "EOF: %s" % (m,)
 
252
    return total_cost
 
253
 
 
254
def pretty_name(file, function):
 
255
    #pfile = os.path.splitext(os.path.basename(file)) [0]
 
256
    #return "%s_[%s]" % (function, file)
 
257
    return "%s" % function
 
258
    #return "%s::%s" % (file, function)
 
259
    #return "%s_%s" % (pfile, function)
 
260
 
 
261
class TagWriter:
 
262
    def __init__(self, output):
 
263
        self.output = output
 
264
        self.last_values = {}
 
265
 
 
266
    def clearTag(self, tag):
 
267
        if self.last_values.has_key(tag):
 
268
            del self.last_values[ tag ]
 
269
    def clear(self):
 
270
        self.last_values = {}
 
271
 
 
272
    def write(self, tag, value):
 
273
        self.output.write("%s=%s\n" % (tag, value))
 
274
        #if (not self.last_values.has_key(tag)) or self.last_values[tag] != value:
 
275
        #    self.last_values[ tag ] = value
 
276
        #    self.output.write("%s=%s\n" % (tag, value))
 
277
 
 
278
def dumpResults(output, call_dict, total_cost, cost_per_pos, cost_per_function):
 
279
    """write the collected results in the format kcachegrind
 
280
       could read.
 
281
    """
 
282
    # the intro
 
283
    output.write("events: Tick\n")
 
284
    output.write("summary: %d\n" % total_cost)
 
285
    output.write("cmd: your python script\n")
 
286
    output.write("\n")
 
287
    tagwriter = TagWriter(output)
 
288
 
 
289
    # now the costs per line
 
290
    for file in cost_per_pos.keys():
 
291
        func_dict = cost_per_pos[file]
 
292
        for func in func_dict.keys():
 
293
            line_dict = func_dict[func]
 
294
            tagwriter.write("ob", file)
 
295
            tagwriter.write("fn", func)# pretty_name(file, func)) ; output.write("# ^--- 2\n")
 
296
            tagwriter.write("fl", file)
 
297
            for line in line_dict:
 
298
                output.write("%d %d\n" %( line, line_dict[line] ))
 
299
 
 
300
    output.write("\n\n")
 
301
    # now the function calls. For each caller all the called
 
302
    # functions and their costs are written.
 
303
    for file in call_dict.keys():
 
304
        per_file_dict = call_dict[file]
 
305
        #print "file %s -> %s" % (file, per_file_dict)
 
306
        for called_x in per_file_dict.keys():
 
307
            #print "called_x:",called_x
 
308
            per_caller_dict = per_file_dict[called_x]
 
309
            #print "called_x %s wird gerufen von: %s" % (called_x, per_caller_dict)
 
310
            for caller_x in per_caller_dict.keys():
 
311
                tagwriter.write("ob", caller_x[0])
 
312
                tagwriter.write("fn", caller_x[2])# pretty_name(caller_x[2], caller_x[0])) ; output.write("# ^--- 1\n")
 
313
                tagwriter.write("fl", caller_x[0])
 
314
                tagwriter.write("cob", file)
 
315
                tagwriter.write("cfn", called_x) #pretty_name(file, called_x))
 
316
                tagwriter.write("cfl", file)
 
317
                cost, count = per_caller_dict[caller_x]
 
318
                #print "called_x:",called_x
 
319
                output.write("calls=%d\n%d %d\n" % (count, caller_x[1], cost))
 
320
                tagwriter.clear()
 
321
                #tagwriter.clearTag("cob")
 
322
                # is it a bug in kcachegrind, that the "cob=xxx" line has
 
323
                # to be rewritten after a calls entry with costline ?
 
324
                #assert cost <= total_cost, "caller_x: %s, per_caller_dict: %s " % (caller_x, per_caller_dict, )
 
325
                #output.write("calls=%d\n%d %d\n" % (count, caller_x[1], cost))
 
326
                output.write("\n")
 
327
 
 
328
def run_without_optparse():
 
329
    """parse the options without optparse, use sys.argv"""
 
330
    if  len(sys.argv) < 4 or sys.argv[1] != "-o" :
 
331
        print "usage: hotshot2cachegrind -o outputfile in1 [in2 [in3 [...]]]"
 
332
        return
 
333
    outputfilename = sys.argv[2]
 
334
    try:
 
335
        output = file(outputfilename, "w")
 
336
        args = sys.argv[3:]
 
337
        convertProfFiles(output, args)
 
338
        output.close()
 
339
    except IOError:
 
340
        print "could not open '%s' for writing." % outputfilename
 
341
 
 
342
def run_with_optparse():
 
343
    """parse the options with optparse"""
 
344
 
 
345
    global file_limit
 
346
 
 
347
    versiontext = "%s version: %s" % ( progname, version.split()[1], )
 
348
    parser = OptionParser(version=versiontext)
 
349
    parser.add_option("-o", "--output",
 
350
      action="store", type="string", dest="outputfilename",
 
351
      help="write output into FILE")
 
352
    parser.add_option("--file-limit",
 
353
      action="store", dest="file_limit", default=0,
 
354
      help="stop after given number of input files")
 
355
    output = sys.stdout
 
356
    close_output = 0
 
357
    (options, args) = parser.parse_args()
 
358
    file_limit = int(options.file_limit)
 
359
    try:
 
360
        if options.outputfilename and options.outputfilename != "-":
 
361
            output = file(options.outputfilename, "w")
 
362
            close_output = 1
 
363
    except IOError:
 
364
        print "could not open '%s' for writing." % options.outputfilename
 
365
    if output:
 
366
        convertProfFiles(output, args)
 
367
        if close_output:
 
368
            output.close()
 
369
 
 
370
 
 
371
def profile_myself():
 
372
    import hotshot
 
373
    filename = "self.prof"
 
374
    if not os.path.exists(filename):
 
375
        prof = hotshot.Profile(filename, lineevents=1)
 
376
        prof.runcall(run)
 
377
        prof.close()
 
378
    else:
 
379
        print "not profiling myself, since '%s' exists, running normal" % filename
 
380
        run()
 
381
 
 
382
# check if optparse is available.
 
383
try:
 
384
    from optparse import OptionParser
 
385
    run = run_with_optparse
 
386
except ImportError:
 
387
    run = run_without_optparse
 
388
 
 
389
if __name__ == "__main__":
 
390
    try:
 
391
        run()
 
392
        #profile_myself()
 
393
    except KeyboardInterrupt:
 
394
        sys.exit(1)