From cfccedd9c8db3af36d7c5635ca212fa170bb6ff5 Mon Sep 17 00:00:00 2001 From: Timothy Pearson Date: Mon, 30 Jan 2012 20:20:24 -0600 Subject: Part 2 of prior commit --- tdecachegrind/converters/Makefile.am | 2 + tdecachegrind/converters/README | 24 ++ tdecachegrind/converters/dprof2calltree | 199 +++++++++++++++ tdecachegrind/converters/hotshot2calltree | 394 ++++++++++++++++++++++++++++++ tdecachegrind/converters/memprof2calltree | 38 +++ tdecachegrind/converters/op2calltree | 238 ++++++++++++++++++ tdecachegrind/converters/pprof2calltree | 218 +++++++++++++++++ 7 files changed, 1113 insertions(+) create mode 100644 tdecachegrind/converters/Makefile.am create mode 100644 tdecachegrind/converters/README create mode 100644 tdecachegrind/converters/dprof2calltree create mode 100644 tdecachegrind/converters/hotshot2calltree create mode 100755 tdecachegrind/converters/memprof2calltree create mode 100755 tdecachegrind/converters/op2calltree create mode 100644 tdecachegrind/converters/pprof2calltree (limited to 'tdecachegrind/converters') 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 for + dprof2calltree and pprof2calltree, +* Jörg Beyer 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 +# 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 [-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 +# +# 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 +# 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 +# +# 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 +# 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 + +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; + exit(1); +} +?> -- cgit v1.2.1