summaryrefslogtreecommitdiffstats
path: root/tdecachegrind/converters
diff options
context:
space:
mode:
Diffstat (limited to 'tdecachegrind/converters')
-rw-r--r--tdecachegrind/converters/Makefile.am2
-rw-r--r--tdecachegrind/converters/README24
-rw-r--r--tdecachegrind/converters/dprof2calltree199
-rw-r--r--tdecachegrind/converters/hotshot2calltree394
-rwxr-xr-xtdecachegrind/converters/memprof2calltree38
-rwxr-xr-xtdecachegrind/converters/op2calltree238
-rw-r--r--tdecachegrind/converters/pprof2calltree218
7 files changed, 1113 insertions, 0 deletions
diff --git a/tdecachegrind/converters/Makefile.am b/tdecachegrind/converters/Makefile.am
new file mode 100644
index 00000000..08b3696b
--- /dev/null
+++ b/tdecachegrind/converters/Makefile.am
@@ -0,0 +1,2 @@
+bin_SCRIPTS = hotshot2calltree op2calltree pprof2calltree dprof2calltree \
+ memprof2calltree
diff --git a/tdecachegrind/converters/README b/tdecachegrind/converters/README
new file mode 100644
index 00000000..c27d3c6d
--- /dev/null
+++ b/tdecachegrind/converters/README
@@ -0,0 +1,24 @@
+This directory contains some scripts to convert output of different
+profiling tools into the format which can be loaded by KCachegrind.
+See the comment at start of every script for details.
+
+In the long run, these should be replaced by import filters in
+KCachegrind directly, but I can't promise anything. Partly, this
+is because some scripts are provided as contribution from others.
+
+hotshot2calltree Converter from Python Hotshot Profiler.
+op2calltree Converter from OProfile sampling data.
+dprof2calltree Converter from PERL::DProf Profiler.
+pprof2calltree Converter from APD PHP Profiler.
+
+Thanks go to
+* George Schlossnagle <george@omniti.com> for
+ dprof2calltree and pprof2calltree,
+* Jörg Beyer <job@webde-ag.de> for
+ hotshot2calltree
+
+If you want to write a converter, have a look at the calltree format
+description on the web site (tdecachegrind.sf.net).
+
+Josef
+
diff --git a/tdecachegrind/converters/dprof2calltree b/tdecachegrind/converters/dprof2calltree
new file mode 100644
index 00000000..f276e188
--- /dev/null
+++ b/tdecachegrind/converters/dprof2calltree
@@ -0,0 +1,199 @@
+#!/usr/bin/perl
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# - All advertising materials mentioning features or use of this software
+# must display the following acknowledgement: This product includes software
+# developed by OmniTI Computer Consulting.
+#
+# - Neither name of the company nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS `AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Copyright (c) 2004 OmniTI Computer Consulting
+# All rights reserved
+# The following code was written by George Schlossnagle <george@omniti.com>
+# and is provided completely free and without any warranty.
+#
+
+#
+# This script is designed to convert the tmon.out output emitted
+# from Perl's Devel::DProf profiling package. To use this:
+#
+# 1) Run your perl script as
+# > perl -d:DProf yoursript.pl
+# This will create a file called tmon.out. If you want to
+# inspect it on the command line, look at the man page
+# for dprofp for details.
+#
+# 2) Run
+# > dprof2calltree -f tmon.out
+# or
+# > dprof2calltree -f tmon.out -o cachegrind.out.foo
+#
+# This creates a cachegrind-style file called cachgrind.out.tmon.out or
+# cachegrind.out.foo, respecitvely.
+#
+# 3) Run tdecachegrind cachegrind.out.foo
+#
+# 4) Enjoy!
+
+use strict;
+use Config;
+use Getopt::Std;
+use IO::File;
+
+my @callstack;
+my %function_info;
+my $tree = {};
+my $total_cost = 0;
+my %opts;
+
+getopt('f:o:', \%opts);
+
+my $infd;
+usage() unless ($opts{'f'} && ($infd = IO::File->new($opts{'f'}, "r")));
+
+my $outfd;
+my $outfile = $opts{'o'};
+unless($outfile) {
+ $opts{'f'} =~ m!([^/]+)$!;
+ $outfile = "cachegrind.out.$1";
+}
+$outfd = new IO::File $outfile, "w";
+usage() unless defined $outfd;
+
+while(<$infd>) {
+ last if /^PART2/;
+}
+while(<$infd>) {
+ chomp;
+ my @args = split;
+ if($args[0] eq '@') {
+ # record timing event
+ my $call_element = pop @callstack;
+ if($call_element) {
+ $call_element->{'cost'} += $args[3];
+ $call_element->{'cumm_cost'} += $args[3];
+ $total_cost += $args[3];
+ push @callstack, $call_element;
+ }
+ }
+ elsif($args[0] eq '&') {
+ # declare function
+ $function_info{$args[1]}->{'package'} = $args[2];
+ if($args[2] ne 'main') {
+ $function_info{$args[1]}->{'name'} = $args[2]."::".$args[3];
+ } else {
+ $function_info{$args[1]}->{'name'} = $args[3];
+ }
+ }
+ elsif($args[0] eq '+') {
+ # push myself onto the stack
+ my $call_element = { 'specifier' => $args[1], 'cost' => 0 };
+ push @callstack, $call_element;
+ }
+ elsif($args[0] eq '-') {
+ my $called = pop @callstack;
+ my $called_id = $called->{'specifier'};
+ my $caller = pop @callstack;
+ if (exists $tree->{$called_id}) {
+ $tree->{$called_id}->{'cost'} += $called->{'cost'};
+ }
+ else {
+ $tree->{$called_id} = $called;
+ }
+ if($caller) {
+ $caller->{'child_calls'}++;
+ my $caller_id = $caller->{'specifier'};
+ if(! exists $tree->{$caller_id} ) {
+ $tree->{$caller_id} = { 'specifier' => $caller_id, 'cost' => 0 };
+# $tree->{$caller_id} = $caller;
+ }
+ $caller->{'cumm_cost'} += $called->{'cumm_cost'};
+ $tree->{$caller_id}->{'called_funcs'}->[$tree->{$caller_id}->{'call_counter'}++]->{$called_id} += $called->{'cumm_cost'};
+ push @callstack, $caller;
+ }
+ }
+ elsif($args[0] eq '*') {
+ # goto &func
+ # replace last caller with self
+ my $call_element = pop @callstack;
+ $call_element->{'specifier'} = $args[1];
+ push @callstack, $call_element;
+ }
+ else {print STDERR "Unexpected line: $_\n";}
+}
+
+#
+# Generate output
+#
+my $output = '';
+$output .= "events: Tick\n";
+$output .= "summary: $total_cost\n";
+$output .= "cmd: your script\n\n";
+foreach my $specifier ( keys %$tree ) {
+ my $caller_package = $function_info{$specifier}->{'package'} || '???';
+ my $caller_name = $function_info{$specifier}->{'name'} || '???';
+ my $include = find_include($caller_package);
+ $output .= "ob=\n";
+ $output .= sprintf "fl=%s\n", find_include($caller_package);
+ $output .= sprintf "fn=%s\n", $caller_name;
+ $output .= sprintf "1 %d\n", $tree->{$specifier}->{'cost'};
+ if(exists $tree->{$specifier}->{'called_funcs'}) {
+ foreach my $items (@{$tree->{$specifier}->{'called_funcs'}}) {
+ while(my ($child_specifier, $costs) = each %$items) {
+ $output .= sprintf "cfn=%s\n", $function_info{$child_specifier}->{'name'};
+ $output .= sprintf "cfi=%s\n", find_include($function_info{$child_specifier}->{'package'});
+ $output .= "calls=1\n";
+ $output .= sprintf "1 %d\n", $costs;
+ }
+ }
+ }
+ $output .= "\n";
+}
+print STDERR "Writing tdecachegrind output to $outfile\n";
+$outfd->print($output);
+
+
+
+sub find_include {
+ my $module = shift;
+ $module =~ s!::!/!g;
+ for (@INC) {
+ if ( -f "$_/$module.pm" ) {
+ return "$_/$module.pm";
+ }
+ if ( -f "$_/$module.so" ) {
+ return "$_/$module.so";
+ }
+ }
+ return "???";
+}
+
+sub usage() {
+ print STDERR "dprof2calltree -f <tmon.out> [-o outfile]\n";
+ exit -1;
+}
+
+
+# vim: set sts=2 ts=2 bs ai expandtab :
diff --git a/tdecachegrind/converters/hotshot2calltree b/tdecachegrind/converters/hotshot2calltree
new file mode 100644
index 00000000..f62a46e3
--- /dev/null
+++ b/tdecachegrind/converters/hotshot2calltree
@@ -0,0 +1,394 @@
+#!/usr/bin/env python
+# _*_ coding: latin1 _*_
+
+#
+# Copyright (c) 2003 by WEB.DE, Karlsruhe
+# Autor: Jörg Beyer <job@webde-ag.de>
+#
+# hotshot2cachegrind is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation, version 2.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+#
+# This script transforms the pstat output of the hotshot
+# python profiler into the input of tdecachegrind.
+#
+# example usage:
+# modify you python script to run this code:
+#
+# import hotshot
+# filename = "pythongrind.prof"
+# prof = hotshot.Profile(filename, lineevents=1)
+# prof.runcall(run) # assuming that "run" should be called.
+# prof.close()
+#
+# it will run the "run"-method under profiling and write
+# the results in a file, called "pythongrind.prof".
+#
+# then call this script:
+# hotshot2cachegrind -o <output> <input>
+# or here:
+# hotshot2cachegrind cachegrind.out.0 pythongrind.prof
+#
+# then call tdecachegrind:
+# tdecachegrind cachegrind.out.0
+#
+# TODO:
+# * es gibt Probleme mit rekursiven (direkt und indirekt) Aufrufen - dann
+# stimmen die Kosten nicht.
+#
+# * einige Funktionen werden mit "?" als Name angezeigt. Evtl sind
+# das nur die C/C++ extensions.
+#
+# * es fehlt noch ein Funktionsnamen Mangling, dass die Filenamen berücksichtigt,
+# zZ sind alle __init__'s und alle run's schwer unterscheidbar :-(
+#
+version = "$Revision$"
+progname = "hotshot2cachegrind"
+
+import os, sys
+from hotshot import stats,log
+import os.path
+
+file_limit=0
+
+what2text = {
+ log.WHAT_ADD_INFO : "ADD_INFO",
+ log.WHAT_DEFINE_FUNC : "DEFINE_FUNC",
+ log.WHAT_DEFINE_FILE : "DEFINE_FILE",
+ log.WHAT_LINENO : "LINENO",
+ log.WHAT_EXIT : "EXIT",
+ log.WHAT_ENTER : "ENTER"}
+
+# a pseudo caller on the caller stack. This represents
+# the Python interpreter that executes the given python
+# code.
+root_caller = ("PythonInterpreter",0,"execute")
+
+class CallStack:
+ """A tiny Stack implementation, based on python lists"""
+ def __init__(self):
+ self.stack = []
+ self.recursion_counter = {}
+ def push(self, elem):
+ """put something on the stack"""
+ self.stack.append(elem)
+ rc = self.recursion_counter.get(elem, 0)
+ self.recursion_counter[elem] = rc + 1
+
+ def pop(self):
+ """get the head element of the stack and remove it from teh stack"""
+ elem = self.stack[-1:][0]
+ rc = self.recursion_counter.get(elem) - 1
+ if rc>0:
+ self.recursion_counter[elem] = rc
+ else:
+ del self.recursion_counter[elem]
+ return self.stack.pop()
+
+ def top(self):
+ """get the head element of the stack, stack is unchanged."""
+ return self.stack[-1:][0]
+ def handleLineCost(self, tdelta):
+ p, c = self.stack.pop()
+ self.stack.append( (p,c + tdelta) )
+ def size(self):
+ """ return how many elements the stack has"""
+ return len(self.stack)
+
+ def __str__(self):
+ return "[stack: %s]" % self.stack
+
+ def recursion(self, pos):
+ return self.recursion_counter.get(pos, 0)
+ #return self.recursion_dict.has_key((entry[0][0], entry[0][2]))
+
+def return_from_call(caller_stack, call_dict, cost_now):
+ """return from a function call
+ remove the function from the caller stack,
+ add the costs to the calling function.
+ """
+ called, cost_at_enter = caller_stack.pop()
+ caller, caller_cost = caller_stack.top()
+
+ #print "return_from_call: %s ruft %s" % (caller, called,)
+
+ per_file_dict = call_dict.get(called[0], {})
+ per_caller_dict = per_file_dict.get(called[2], {})
+ cost_so_far, call_counter = per_caller_dict.get(caller, (0, 0))
+
+ if caller_stack.recursion(called):
+ per_caller_dict[caller] = (cost_so_far, call_counter + 1)
+ else:
+ per_caller_dict[caller] = (cost_so_far + cost_now - cost_at_enter, call_counter + 1)
+
+ per_file_dict[called[2]] = per_caller_dict
+ call_dict[called[0]] = per_file_dict
+
+
+def updateStatus(filecount):
+ sys.stdout.write("reading File #%d \r" % filecount)
+ sys.stdout.flush()
+def convertProfFiles(output, inputfilenames):
+ """convert all the given input files into one tdecachegrind
+ input file.
+ """
+ call_dict = {}
+ cost_per_pos = {}
+ cost_per_function = {}
+ caller_stack = CallStack()
+ caller_stack.push((root_caller, 0))
+
+ total_cost = 0
+ filecount = 1
+ number_of_files = len(inputfilenames)
+ for inputfilename in inputfilenames:
+ updateStatus(filecount)
+ cost, filecount = convertHandleFilename(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount)
+ total_cost += cost
+ if (file_limit > 0) and (filecount > file_limit):
+ break
+
+ print
+ print "total_cost: % d Ticks",total_cost
+ dumpResults(output, call_dict, total_cost, cost_per_pos, cost_per_function)
+
+def convertHandleFilename(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount):
+ updateStatus(filecount)
+ if not ((file_limit > 0) and (filecount > file_limit)):
+ if os.path.isdir(inputfilename):
+ cost, filecount = convertProfDir(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount)
+ elif os.path.isfile(inputfilename):
+ cost = convertProfFile(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function)
+ filecount += 1
+ else:
+ sys.stderr.write("warn: ignoring '%s', is no file and no directory\n" % inputfilename)
+ cost = 0
+ return (cost, filecount)
+
+def convertProfDir(start, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount):
+ cost = 0
+ filenames = os.listdir(start)
+ for f in filenames:
+ if (file_limit > 0) and (filecount > file_limit):
+ break
+ full = os.path.join(start, f)
+ c, filecount = convertHandleFilename(full, caller_stack, call_dict, cost_per_pos, cost_per_function, filecount)
+ cost += c;
+ return (cost, filecount)
+
+def handleCostPerPos(cost_per_pos, pos, current_cost):
+ """
+ the cost per source position are managed in a dict in a dict.
+
+ the cost are handled per file and there per function.
+ so, the per-file-dict contains some per-function-dicts
+ which sum up the cost per line (in this function and in
+ this file).
+ """
+ filename = pos[0]
+ lineno = pos[1]
+ funcname = pos[2]
+ file_dict = cost_per_pos.get(filename, {})
+ func_dict = file_dict.get(funcname, {})
+ func_dict.setdefault(lineno, 0)
+ func_dict[lineno] += current_cost
+ file_dict[funcname] = func_dict
+ cost_per_pos[filename] = file_dict
+
+def convertProfFile(inputfilename, caller_stack, call_dict, cost_per_pos, cost_per_function):
+ """convert a single input file into one tdecachegrind
+ data.
+
+ this is the most expensive function in this python source :-)
+ """
+
+ total_cost = 0
+ try:
+ logreader = log.LogReader(inputfilename)
+ current_cost = 0
+ hc = handleCostPerPos # shortcut
+ for item in logreader:
+ what, pos ,tdelta = item
+ (file, lineno, func) = pos
+ #line = "%s %s %d %s %d" % (what2text[what], file, lineno, func, tdelta)
+ #print line
+ # most common cases first
+ if what == log.WHAT_LINENO:
+ # add the current cost to the current function
+ hc(cost_per_pos, pos, tdelta)
+ total_cost += tdelta
+ elif what == log.WHAT_ENTER:
+ caller_stack.push((pos, total_cost))
+ hc(cost_per_pos, pos, tdelta)
+ total_cost += tdelta
+ elif what == log.WHAT_EXIT:
+ hc(cost_per_pos, pos, tdelta)
+ total_cost += tdelta
+ return_from_call(caller_stack, call_dict, total_cost)
+ else:
+ assert 0, "duh: %d" % what
+
+
+ # I have no idea, why sometimes the stack is not empty - we
+ # have to rewind the stack to get 100% for the root_caller
+ while caller_stack.size() > 1:
+ return_from_call(caller_stack, call_dict, total_cost)
+
+ except IOError:
+ print "could not open inputfile '%s', ignore this." % inputfilename
+ except EOFError, m:
+ print "EOF: %s" % (m,)
+ return total_cost
+
+def pretty_name(file, function):
+ #pfile = os.path.splitext(os.path.basename(file)) [0]
+ #return "%s_[%s]" % (function, file)
+ return "%s" % function
+ #return "%s::%s" % (file, function)
+ #return "%s_%s" % (pfile, function)
+
+class TagWriter:
+ def __init__(self, output):
+ self.output = output
+ self.last_values = {}
+
+ def clearTag(self, tag):
+ if self.last_values.has_key(tag):
+ del self.last_values[ tag ]
+ def clear(self):
+ self.last_values = {}
+
+ def write(self, tag, value):
+ self.output.write("%s=%s\n" % (tag, value))
+ #if (not self.last_values.has_key(tag)) or self.last_values[tag] != value:
+ # self.last_values[ tag ] = value
+ # self.output.write("%s=%s\n" % (tag, value))
+
+def dumpResults(output, call_dict, total_cost, cost_per_pos, cost_per_function):
+ """write the collected results in the format tdecachegrind
+ could read.
+ """
+ # the intro
+ output.write("events: Tick\n")
+ output.write("summary: %d\n" % total_cost)
+ output.write("cmd: your python script\n")
+ output.write("\n")
+ tagwriter = TagWriter(output)
+
+ # now the costs per line
+ for file in cost_per_pos.keys():
+ func_dict = cost_per_pos[file]
+ for func in func_dict.keys():
+ line_dict = func_dict[func]
+ tagwriter.write("ob", file)
+ tagwriter.write("fn", func)# pretty_name(file, func)) ; output.write("# ^--- 2\n")
+ tagwriter.write("fl", file)
+ for line in line_dict:
+ output.write("%d %d\n" %( line, line_dict[line] ))
+
+ output.write("\n\n")
+ # now the function calls. For each caller all the called
+ # functions and their costs are written.
+ for file in call_dict.keys():
+ per_file_dict = call_dict[file]
+ #print "file %s -> %s" % (file, per_file_dict)
+ for called_x in per_file_dict.keys():
+ #print "called_x:",called_x
+ per_caller_dict = per_file_dict[called_x]
+ #print "called_x %s wird gerufen von: %s" % (called_x, per_caller_dict)
+ for caller_x in per_caller_dict.keys():
+ tagwriter.write("ob", caller_x[0])
+ tagwriter.write("fn", caller_x[2])# pretty_name(caller_x[2], caller_x[0])) ; output.write("# ^--- 1\n")
+ tagwriter.write("fl", caller_x[0])
+ tagwriter.write("cob", file)
+ tagwriter.write("cfn", called_x) #pretty_name(file, called_x))
+ tagwriter.write("cfl", file)
+ cost, count = per_caller_dict[caller_x]
+ #print "called_x:",called_x
+ output.write("calls=%d\n%d %d\n" % (count, caller_x[1], cost))
+ tagwriter.clear()
+ #tagwriter.clearTag("cob")
+ # is it a bug in tdecachegrind, that the "cob=xxx" line has
+ # to be rewritten after a calls entry with costline ?
+ #assert cost <= total_cost, "caller_x: %s, per_caller_dict: %s " % (caller_x, per_caller_dict, )
+ #output.write("calls=%d\n%d %d\n" % (count, caller_x[1], cost))
+ output.write("\n")
+
+def run_without_optparse():
+ """parse the options without optparse, use sys.argv"""
+ if len(sys.argv) < 4 or sys.argv[1] != "-o" :
+ print "usage: hotshot2cachegrind -o outputfile in1 [in2 [in3 [...]]]"
+ return
+ outputfilename = sys.argv[2]
+ try:
+ output = file(outputfilename, "w")
+ args = sys.argv[3:]
+ convertProfFiles(output, args)
+ output.close()
+ except IOError:
+ print "could not open '%s' for writing." % outputfilename
+
+def run_with_optparse():
+ """parse the options with optparse"""
+
+ global file_limit
+
+ versiontext = "%s version: %s" % ( progname, version.split()[1], )
+ parser = OptionParser(version=versiontext)
+ parser.add_option("-o", "--output",
+ action="store", type="string", dest="outputfilename",
+ help="write output into FILE")
+ parser.add_option("--file-limit",
+ action="store", dest="file_limit", default=0,
+ help="stop after given number of input files")
+ output = sys.stdout
+ close_output = 0
+ (options, args) = parser.parse_args()
+ file_limit = int(options.file_limit)
+ try:
+ if options.outputfilename and options.outputfilename != "-":
+ output = file(options.outputfilename, "w")
+ close_output = 1
+ except IOError:
+ print "could not open '%s' for writing." % options.outputfilename
+ if output:
+ convertProfFiles(output, args)
+ if close_output:
+ output.close()
+
+
+def profile_myself():
+ import hotshot
+ filename = "self.prof"
+ if not os.path.exists(filename):
+ prof = hotshot.Profile(filename, lineevents=1)
+ prof.runcall(run)
+ prof.close()
+ else:
+ print "not profiling myself, since '%s' exists, running normal" % filename
+ run()
+
+# check if optparse is available.
+try:
+ from optparse import OptionParser
+ run = run_with_optparse
+except ImportError:
+ run = run_without_optparse
+
+if __name__ == "__main__":
+ try:
+ run()
+ #profile_myself()
+ except KeyboardInterrupt:
+ sys.exit(1)
diff --git a/tdecachegrind/converters/memprof2calltree b/tdecachegrind/converters/memprof2calltree
new file mode 100755
index 00000000..e82d6e85
--- /dev/null
+++ b/tdecachegrind/converters/memprof2calltree
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+#
+# Convert the memory profiles of memprof to calltree format,
+# loadable with KCachegrind
+#
+# (C) 2004, Josef Weidendorfer
+
+print "events: Allocated\n";
+
+while(<>) {
+ if (/^(\S.*)$/) {
+ $next = 0;
+ print "\nfn=$1\n";
+ next;
+ }
+ if (/^ children:/) {
+ $next = 1; #children
+ next;
+ }
+ if (/^ inherited:/) {
+ $next = 2; #inherited
+ next;
+ }
+ if (/^ total:/) {
+ # ignore, is calculated
+ next;
+ }
+ if (/^ self:\s*(\d+)/) {
+ if ($1 ne "0") {
+ print "0 $1\n";
+ }
+ next;
+ }
+ if (/^\s+(\S.*?):\s*(\d+)$/) {
+ if ($next < 2) { next; }
+ print "cfn=$1\ncalls=0 0\n0 $2\n";
+ }
+}
diff --git a/tdecachegrind/converters/op2calltree b/tdecachegrind/converters/op2calltree
new file mode 100755
index 00000000..ca121a2a
--- /dev/null
+++ b/tdecachegrind/converters/op2calltree
@@ -0,0 +1,238 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2004
+# Author: Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+#
+# op2calltree is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation, version 2.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+#
+# Converter from OProfile's output of "opreport -gdf" (v 0.8)
+# into callgrind format.
+#
+# Generate a OProfile report with opreport and flags -gdf
+# and pipe this as standard input into this script.
+# This will generate separate cachegrind files for every application.
+#
+
+
+# parse symbol line. example (with 1 event type, $has_image==0):
+# 308 0.1491 /path/source.c:6 /path/app main
+sub parseSymSpec {
+ $e = 0;
+ while($e < $eventCount) {
+ ($line) = ($line =~ /\d+\s+\S+\s+(.*)/);
+ $e++;
+ }
+ if ($line =~ s/^\(no location information\)\s+//) {
+ $file = "???";
+ $linenr = 0;
+ }
+ else {
+ ($file,$linenr) = ($line =~ s/(\S+?):(\d+)\s+//);
+ }
+ if ($has_image) {
+ if ($line =~ s/^(\S+)\s+//) { $img = $1; }
+ }
+ if ($has_app) {
+ if ($line =~ s/^(\S+)\s+//) { $app = $1; }
+ if (!$has_image) { $img = $app; }
+ }
+ $sym = $line;
+
+ $app =~ s/^.*\///;
+ if ($sym eq "(no symbols)") { $sym = "???"; }
+ $file{$sym} = $file;
+ $linenr{$sym} = $linenr;
+ $app{$sym} = $app;
+ $img{$app,$sym} = $img;
+ $syms{$app}++;
+
+ if ($app ne $oldApp) {
+ $oldApp = $app;
+ print "\n\nApp $app\n";
+ }
+ print " Symbol $sym (Image $img)\n";
+}
+
+
+
+$eventCount = 0;
+$descCount = 0;
+$lnr = 0;
+$has_image = 0;
+$has_app = 0;
+$app = "unnamed";
+$img = "???";
+
+# first loop till first symbol specification
+while(<>) {
+ $lnr++;
+ chomp;
+ if (/^CPU:/) {
+ $desc[$descCount++] = $_;
+ next;
+ }
+ if (/^Counted\s*(\S+)/) {
+ $desc[$descCount++] = $_;
+ $eventCount++;
+ $events[$eventCount] = $1;
+ next;
+ }
+ if (/^(Profiling through timer.*)/) {
+ $desc[$descCount++] = $_;
+ $eventCount++;
+ $events[$eventCount] = "Timer";
+ next;
+ }
+ if (/^vma/) {
+ # title row: adapt to separation options of OProfile
+ if (/image/) { $has_image = 1; }
+ if (/app/) { $has_app = 1; }
+ next;
+ }
+ if (/^([0-9a-fA-F]+)\s*(.*)$/) {
+ $vmaSym = $1;
+ $line = $2;
+ last;
+ }
+}
+
+if ($eventCount == 0) {
+ die "No Events found";
+}
+
+print "Description:\n";
+foreach $d (@desc) { print " $d\n"; }
+print "\n";
+
+print "Events:";
+foreach $e (@events) { print " $e"; }
+print "\n";
+
+parseSymSpec;
+
+while(<>) {
+ $lnr++;
+ if (/^([0-9a-fA-F]+)\s*(.*)$/) {
+ $vmaSym = $1;
+ $line = $2;
+
+ parseSymSpec;
+ next;
+ }
+ if (/^\s+([0-9a-fA-F]+)\s*(.*)$/) {
+
+ $sampleCount{$app,$sym}++;
+ $sc = $sampleCount{$app,$sym};
+
+ $vma{$app,$sym,$sc} = $1;
+ $line = $2;
+
+ $e = 1;
+ while($e <= $eventCount) {
+ ($cost, $line) = ($line =~ /(\d+)\s+\S+\s+(.*)/);
+ $summary{$app,$e} += $cost;
+ $cost{"$app,$sym,$sc,$e"} = $cost;
+ $e++;
+ }
+ if ($line =~ /\(no location information\)/) {
+ $file = "???";
+ $linenr = 0;
+ }
+ else {
+ ($file,$linenr) = ($line =~ /(\S+?):(\d+)/);
+ }
+ $sFile{$app,$sym,$sc} = $file;
+ $linenr{$app,$sym,$sc} = $linenr;
+
+ $file =~ s/^.*\///;
+ print " Sample $sc: $vma{$app,$sym,$sc} ($file:$linenr):";
+ foreach $e (1 .. $eventCount) { $c = $cost{"$app,$sym,$sc,$e"} ; print " $c"; }
+ print "\n";
+ next;
+ }
+ die "ERROR: Reading line $lnr '$_'\n";
+}
+
+foreach $app (keys %syms) {
+ if ($app eq "") { next; }
+ print "Generating dump for App '$app'...\n";
+
+ $out = "# Generated by op2cg, using OProfile with opreport -gdf\n";
+ $out .= "positions: instr line\n";
+
+ $out .= "events:";
+ foreach $e (@events) { $out .= " $e"; }
+ $out .= "\n";
+
+ $out .= "summary:";
+ foreach $e (1 .. $eventCount) { $out .= " $summary{$app,$e}"; }
+ $out .= "\n\n";
+
+ %fileNum = ();
+ $fileNum = 1;
+ $sf = "";
+
+ $img = "";
+
+ foreach $sym (keys %file) {
+ if ($sampleCount{$app,$sym} eq "") { next; }
+
+ if ($img{$app,$sym} ne $img) {
+ $img = $img{$app,$sym};
+ $out .= "ob=$img\n";
+ }
+
+ $file = $file{$sym};
+ if ($sf ne $file) {
+ if ($fileNum{$file} eq "") {
+ $fileNum{$file} = $fileNum;
+ $out .= "fl=($fileNum) $file\n";
+ $fileNum++;
+ }
+ else {
+ $out .= "fl=($fileNum{$file})\n";
+ }
+ $sf = $file;
+ }
+
+ $out .= "fn=$sym\n";
+ foreach $sc (1 .. $sampleCount{$app,$sym}) {
+ if ($sf ne $sFile{$app,$sym,$sc}) {
+ $sf = $sFile{$app,$sym,$sc};
+ if ($sf eq $file) {
+ $out .= "fe=($fileNum{$file})\n";
+ }
+ else {
+ if ($fileNum{$sf} eq "") {
+ $fileNum{$sf} = $fileNum;
+ $out .= "fi=($fileNum) $sf\n";
+ $fileNum++;
+ }
+ else {
+ $out .= "fi=($fileNum{$sf})\n";
+ }
+ }
+ }
+ $out .= "0x$vma{$app,$sym,$sc} $linenr{$app,$sym,$sc}";
+ foreach $e (1 .. $eventCount) { $c = $cost{"$app,$sym,$sc,$e"} ; $out .= " $c"; }
+ $out .= "\n";
+ }
+ }
+
+ open OUT, ">oprof.out.$app";
+ print OUT $out;
+ close OUT;
+}
diff --git a/tdecachegrind/converters/pprof2calltree b/tdecachegrind/converters/pprof2calltree
new file mode 100644
index 00000000..0e70e1c2
--- /dev/null
+++ b/tdecachegrind/converters/pprof2calltree
@@ -0,0 +1,218 @@
+#!/usr/bin/env php
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# - All advertising materials mentioning features or use of this software
+# must display the following acknowledgement: This product includes software
+# developed by OmniTI Computer Consulting.
+#
+# - Neither name of the company nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS `AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Copyright (c) 2004 OmniTI Computer Consulting
+# All rights reserved
+# The following code was written by George Schlossnagle <george@omniti.com>
+# and is provided completely free and without any warranty.
+#
+# This script is designed to convert the pprof output from
+# APD (http://pecl.php.net/apd/) to one readable by tdecachegrind. To use
+# this script:
+#
+# 1) Install APD.
+# 2) Profile your script with APD accordingto the directions in it's
+# README file.
+# 3) Take the pprof trace file for your script (pprof.XXXXX.Y) and run it
+# through this script as follows:
+# > pprof2calltree -f pprof.12345.1
+# This creates a new file cachegrind.out.12345.1
+# 4) View your trace with pprof2calltree cachegrind.out.12345.1
+
+<?php
+
+require "Console/Getopt.php";
+
+$con = new Console_Getopt;
+$args = $con->readPHPArgv();
+array_shift($args);
+$shortoptions = 'f:';
+$retval = $con->getopt( $args, $shortoptions);
+if(is_object($retval)) {
+ usage();
+}
+foreach ($retval[0] as $kv_array) {
+ $opt[$kv_array[0]] = $kv_array[1];
+}
+if(!$opt['f']) {
+ usage();
+}
+if(!file_exists($opt['f'])) {
+ print "Trace file ${opt['f']} does not exist\n";
+ exit;
+}
+$IN = fopen($opt['f'], "r");
+if(!$IN) {
+ print "Trace file ${opt['f']} could not be opened\n";
+ exit;
+}
+
+$path_parts = pathinfo($opt['f']);
+$outfile = "cachegrind.out.".$path_parts['basename'];
+$OUT = fopen($outfile, "w");
+if(!$OUT) {
+ print "Destination file $outfile could not be opened.\n";
+ exit;
+}
+
+while(($line = fgets($IN)) !== false) {
+ $line = rtrim($line);
+ if($line == "END_HEADER") {
+ break;
+ }
+}
+$tree = array();
+$callstack = array();
+while(($line = fgets($IN)) !== false) {
+ $line = rtrim($line);
+ $args = explode(" ", $line);
+ if($args[0] == '!') {
+ $file_lookup[$args[1]] = $args[2];
+ }
+ else if($args[0] == '&') {
+ $function_lookup[$args[1]] = $args[2];
+ $function_type[$args[1]] = ($args[3] == 2)?"USER":"INTERNAL";
+ }
+ else if($args[0] == '+') {
+ $val = array(function_id => $args[1],
+ file_id => $args[2],
+ line => $args[3],
+ cost => 0);
+ array_push($callstack, $val);
+ }
+ else if($args[0] == '-') {
+ // retrieve $called to discard
+ $called = array_pop($callstack);
+ // retrieve $caller for reference
+ $caller = array_pop($callstack);
+ $called_id = $called['function_id'];
+
+ // Set meta data if not already set'
+ if(!array_key_exists($called_id, $tree)) {
+ $tree[$called_id] = $called;
+ // initialize these to 0
+ $tree[$called_id]['cost_per_line'] = array();
+ }
+ if($caller !== null) {
+ $caller['child_calls']++;
+ $caller_id = $caller['function_id'];
+ if(!array_key_exists($caller_id, $tree)) {
+ $tree[$caller_id] = $caller;
+ }
+ $caller['cost'] += $called['cost'];
+ $tree[$caller_id]['called_funcs'][$tree[$caller_id]['call_counter']++][$called_id][$called['file_id']][$called['line']] += $called['cost'];
+ array_push($callstack, $caller);
+ }
+ if(is_array($called['cost_per_line'])) {
+ foreach($called[cost_per_line] as $file => $lines) {
+ foreach($lines as $line => $cost) {
+ $tree[$called_id]['cost_per_line'][$file][$line] += $cost;
+ }
+ }
+ }
+ }
+ else if($args[0] == '@') {
+ $called = array_pop($callstack);
+ switch(count($args)) {
+ // support new and old-style pprof data
+ case 6:
+ $file = $args[1];
+ $line = $args[2];
+ $real_tm = $args[5];
+ break;
+ case 4:
+ $file = $called['file_id'];
+ $line = $called['line'];
+ $real_tm = $args[3];
+ break;
+
+ }
+ $called['cost_per_line'][$file][$line] += $real_tm;
+ $called['cost'] += $real_tm;
+ $total_cost += $real_tm;
+ array_push($callstack, $called);
+ }
+}
+
+ob_start();
+print "events: Tick\n";
+print "summary: $total_cost\n";
+printf("cmd: %s\n", $file_lookup[1]);
+print "\n";
+
+foreach($tree as $caller => $data) {
+ $filename = $file_lookup[$data['file_id']]?$file_lookup[$data['file_id']]:"???";
+ printf("ob=%s\n", $function_type[$caller]);
+ printf("fl=%s\n", $filename);
+ printf("fn=%s\n", $function_lookup[$caller]);
+ if(is_array($data['cost_per_line'])) {
+ foreach($data['cost_per_line'] as $file => $lines) {
+ foreach($lines as $line => $cost) {
+ print "$line $cost\n";
+ }
+ }
+ }
+ else if ($data['cost']) {
+ printf("COST %s %s\n", $items['line'], $items['cost']);
+ }
+ else {
+ print_r($items);
+ }
+ if(is_array($data['called_funcs'])) {
+ foreach($data['called_funcs'] as $counter => $items) {
+ foreach($items as $called_id => $costs) {
+ if(is_array($costs)) {
+ printf("cfn=%s\n", $function_lookup[$called_id]);
+ foreach($costs as $file => $lines) {
+ printf("cfi=%s\ncalls=1\n", $file_lookup[$file]);
+ foreach($lines as $line => $cost) {
+ print "$line $cost\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ print "\n";
+}
+print "\ntotals=$total_cost\n";
+$buffer = ob_get_clean();
+print "Writing tdecachegrind compatible output to $outfile\n";
+fwrite($OUT, $buffer);
+
+function usage()
+{
+ print <<<EOD
+pprof2calltree -f <tracefile>
+
+EOD;
+ exit(1);
+}
+?>