#! @PERL@ ##--------------------------------------------------------------------## ##--- Cachegrind's differencer. cg_diff.in ---## ##--------------------------------------------------------------------## # This file is part of Cachegrind, a Valgrind tool for cache # profiling programs. # # Copyright (C) 2002-2017 Nicholas Nethercote # njn@valgrind.org # # This program 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; either version 2 of the # License, or (at your option) any later version. # # 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; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307, USA. # # The GNU General Public License is contained in the file COPYING. #---------------------------------------------------------------------------- # This is a very cut-down and modified version of cg_annotate. #---------------------------------------------------------------------------- use warnings; use strict; #---------------------------------------------------------------------------- # Global variables #---------------------------------------------------------------------------- # Version number my $version = "@VERSION@"; # Usage message. my $usage = <<END usage: cg_diff [options] <cachegrind-out-file1> <cachegrind-out-file2> options for the user, with defaults in [ ], are: -h --help show this message -v --version show version --mod-filename=<expr> a Perl search-and-replace expression that is applied to filenames, eg. --mod-filename='s/prog[0-9]/projN/' --mod-funcname=<expr> like --mod-filename, but applied to function names cg_diff is Copyright (C) 2002-2017 Nicholas Nethercote. and licensed under the GNU General Public License, version 2. Bug reports, feedback, admiration, abuse, etc, to: njn\@valgrind.org. END ; # --mod-filename expression my $mod_filename = undef; # --mod-funcname expression my $mod_funcname = undef; #----------------------------------------------------------------------------- # Argument and option handling #----------------------------------------------------------------------------- sub process_cmd_line() { my ($file1, $file2) = (undef, undef); for my $arg (@ARGV) { if ($arg =~ /^-/) { # --version if ($arg =~ /^-v$|^--version$/) { die("cg_diff-$version\n"); } elsif ($arg =~ /^--mod-filename=(.*)/) { $mod_filename = $1; } elsif ($arg =~ /^--mod-funcname=(.*)/) { $mod_funcname = $1; } else { # -h and --help fall under this case die($usage); } } elsif (not defined($file1)) { $file1 = $arg; } elsif (not defined($file2)) { $file2 = $arg; } else { die($usage); } } # Must have specified two input files. if (not defined $file1 or not defined $file2) { die($usage); } return ($file1, $file2); } #----------------------------------------------------------------------------- # Reading of input file #----------------------------------------------------------------------------- sub max ($$) { my ($x, $y) = @_; return ($x > $y ? $x : $y); } # Add the two arrays; any '.' entries are ignored. Two tricky things: # 1. If $a2->[$i] is undefined, it defaults to 0 which is what we want; we turn # off warnings to allow this. This makes things about 10% faster than # checking for definedness ourselves. # 2. We don't add an undefined count or a ".", even though it's value is 0, # because we don't want to make an $a2->[$i] that is undef become 0 # unnecessarily. sub add_array_a_to_b ($$) { my ($a, $b) = @_; my $n = max(scalar @$a, scalar @$b); $^W = 0; foreach my $i (0 .. $n-1) { $b->[$i] += $a->[$i] if (defined $a->[$i] && "." ne $a->[$i]); } $^W = 1; } sub sub_array_b_from_a ($$) { my ($a, $b) = @_; my $n = max(scalar @$a, scalar @$b); $^W = 0; foreach my $i (0 .. $n-1) { $a->[$i] -= $b->[$i]; # XXX: doesn't handle '.' entries } $^W = 1; } # Add each event count to the CC array. '.' counts become undef, as do # missing entries (implicitly). sub line_to_CC ($$) { my ($line, $numEvents) = @_; my @CC = (split /\s+/, $line); (@CC <= $numEvents) or die("Line $.: too many event counts\n"); return \@CC; } sub read_input_file($) { my ($input_file) = @_; open(INPUTFILE, "< $input_file") || die "Cannot open $input_file for reading\n"; # Read "desc:" lines. my $desc; my $line; while ($line = <INPUTFILE>) { if ($line =~ s/desc:\s+//) { $desc .= $line; } else { last; } } # Read "cmd:" line (Nb: will already be in $line from "desc:" loop above). ($line =~ s/^cmd:\s+//) or die("Line $.: missing command line\n"); my $cmd = $line; chomp($cmd); # Remove newline # Read "events:" line. We make a temporary hash in which the Nth event's # value is N, which is useful for handling --show/--sort options below. $line = <INPUTFILE>; (defined $line && $line =~ s/^events:\s+//) or die("Line $.: missing events line\n"); my @events = split(/\s+/, $line); my $numEvents = scalar @events; my $currFileName; my $currFileFuncName; my %CCs; # hash("$filename#$funcname" => CC array) my $currCC = undef; # CC array my $summaryCC; # Read body of input file. while (<INPUTFILE>) { s/#.*$//; # remove comments if (s/^(\d+)\s+//) { my $CC = line_to_CC($_, $numEvents); defined($currCC) || die; add_array_a_to_b($CC, $currCC); } elsif (s/^fn=(.*)$//) { defined($currFileName) || die; my $tmpFuncName = $1; if (defined $mod_funcname) { eval "\$tmpFuncName =~ $mod_funcname"; } $currFileFuncName = "$currFileName#$tmpFuncName"; $currCC = $CCs{$currFileFuncName}; if (not defined $currCC) { $currCC = []; $CCs{$currFileFuncName} = $currCC; } } elsif (s/^fl=(.*)$//) { $currFileName = $1; if (defined $mod_filename) { eval "\$currFileName =~ $mod_filename"; } # Assume that a "fn=" line is followed by a "fl=" line. $currFileFuncName = undef; } elsif (s/^\s*$//) { # blank, do nothing } elsif (s/^summary:\s+//) { $summaryCC = line_to_CC($_, $numEvents); (scalar(@$summaryCC) == @events) or die("Line $.: summary event and total event mismatch\n"); } else { warn("WARNING: line $. malformed, ignoring\n"); } } # Check if summary line was present if (not defined $summaryCC) { die("missing final summary line, aborting\n"); } close(INPUTFILE); return ($cmd, \@events, \%CCs, $summaryCC); } #---------------------------------------------------------------------------- # "main()" #---------------------------------------------------------------------------- # Commands seen in the files. Need not match. my $cmd1; my $cmd2; # Events seen in the files. They must match. my $events1; my $events2; # Individual CCs, organised by filename/funcname/line_num. # hashref("$filename#$funcname", CC array) my $CCs1; my $CCs2; # Total counts for summary (an arrayref). my $summaryCC1; my $summaryCC2; #---------------------------------------------------------------------------- # Read the input files #---------------------------------------------------------------------------- my ($file1, $file2) = process_cmd_line(); ($cmd1, $events1, $CCs1, $summaryCC1) = read_input_file($file1); ($cmd2, $events2, $CCs2, $summaryCC2) = read_input_file($file2); #---------------------------------------------------------------------------- # Check the events match #---------------------------------------------------------------------------- my $n = max(scalar @$events1, scalar @$events2); $^W = 0; # turn off warnings, because we might hit undefs foreach my $i (0 .. $n-1) { ($events1->[$i] eq $events2->[$i]) || die "events don't match, aborting\n"; } $^W = 1; #---------------------------------------------------------------------------- # Do the subtraction: CCs2 -= CCs1 #---------------------------------------------------------------------------- while (my ($filefuncname, $CC1) = each(%$CCs1)) { my $CC2 = $CCs2->{$filefuncname}; if (not defined $CC2) { $CC2 = []; sub_array_b_from_a($CC2, $CC1); # CC2 -= CC1 $CCs2->{$filefuncname} = $CC2; } else { sub_array_b_from_a($CC2, $CC1); # CC2 -= CC1 } } sub_array_b_from_a($summaryCC2, $summaryCC1); #---------------------------------------------------------------------------- # Print the result, in CCs2 #---------------------------------------------------------------------------- print("desc: Files compared: $file1; $file2\n"); print("cmd: $cmd1; $cmd2\n"); print("events: "); for my $e (@$events1) { print(" $e"); } print("\n"); while (my ($filefuncname, $CC) = each(%$CCs2)) { my @x = split(/#/, $filefuncname); (scalar @x == 2) || die; print("fl=$x[0]\n"); print("fn=$x[1]\n"); print("0"); foreach my $n (@$CC) { print(" $n"); } print("\n"); } print("summary:"); foreach my $n (@$summaryCC2) { print(" $n"); } print("\n"); ##--------------------------------------------------------------------## ##--- end ---## ##--------------------------------------------------------------------##