#!/usr/bin/perl -w

# Author:   Benny Verplancke
#
#  Benny Verplancke
#  DB2 Advanced Problem Determination
#  IBM Information Management, IBM Belgium
#
#  Original version July 2015 
#  First Release January 2017
#  Review May 2017 

use Time::HiRes qw( usleep );
use strict;
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
use Math::BigInt;

sub deltatime 
{
   my ( $start, $stop ) = @_;
#   if ( defined $start  ) {  printf "start = %s\n", $start; }
#    else { printf "start is not defined " ;  } 
#   if ( defined $stop ) { printf "stop = %s\n", $stop; } 
#   else { printf "stop is not defined "; exit; }

   if ( defined $start && defined $stop )
   {
    #  printf "deltatime start = %s\tstop = %s\n", $start, $stop;
       return $stop - $start;
   }
   else
   {
      return;
   } 
}

my $tid = 1;
my $opt_debug   = 0;
my ($opt_help, $opt_man, $opt_versions);

#variable declarations
my %tracepoints;
my $FH;

sub openfile
{
   my ( $fname ) = @_;
   open ( FH, "<$fname" ) or die "Could not open $fname .. $!\n";
}

sub closefile
{
   my ( $fname ) = @_;
   close $fname;
}

sub Trace 
{
   return if !$opt_debug;
   my( $file, $line )  = ( caller )[ 1,2 ];
   warn @_, "at $file:$line\n";
}

### version information
my $db2prof_VER = "1.1.0";

### input parsing
my $summ;
my $top = 0x7fffffff;
my $apphdl;
my $filename;

GetOptions( 
            'debug=i'        =>  \$opt_debug,
            'help!'          =>  \$opt_help,
            'man!'           =>  \$opt_man,
            'versions!'      =>  \$opt_versions,
	    'apphdl=i'       =>  \$apphdl,
            'file=s'         => \$filename
           ) ;

pod2usage( -verbose => 1 ) if $opt_help;
pod2usage( -verbose => 2 ) if $opt_man;


 if(defined $opt_versions){
    print
      "\nModules, Perl, OS, Program info:\n",
      "  Pod::Usage            $Pod::Usage::VERSION\n",
      "  Getopt::Long          $Getopt::Long::VERSION\n",
      "  strict                $strict::VERSION\n",
      "  Perl                  $]\n",
      "  OS                    $^O\n",
      "  db2prof               $db2prof_VER\n",
      "  $0\n",
      "\n\n";
     exit;
  }

my $num_args = $#ARGV + 1;

#printf "%d", $num_args ;
if ( not defined $apphdl and not defined $filename  )
{
  pod2usage( -verbose => 1 )  ;
  exit;
}

### variables needed for parsing

my $ln;
my @line;
my $state=0;
my @arr;
my @tmpArr;
my $time = 0;
my $hightime = 0;
my $i;
my $add2;
my $value;

#lookup hashes

my $PD;
my %workarea = ();    # hash by lolepop, containing start time, cumulative
my %workarea2 = ();   # hash by lolepop, containing start time,  non-cumulative 
my %level = ();       # hash by lolepop, levelindicator 
my %pertidcount = (); # hash by tid and lolepop, total count
my %pertidtime = ();  # hash by tid and lolepop, total time
my %pertidtime2 = (); # hash by tid and lolepop, non-cumulative time

my %operators = ();

my $mode = 0;

if (  defined $filename  ) 
{
    open( FH, "$filename") || die "Failed : $!\n";
    $mode = 0;
}
else
{
   $mode = 1;

   my $cmd = "db2pd -alldbs -applications |";
   open( PD, "db2pd -alldbs -applications | " ) || die "Failed to execute db2pd -alldbs -applications $!\n" ;

   my $db;
   my $anch;
   my $uid; 
   $state = 0; 
   while ( <PD> ) 
   {
       if ( $state == 2 ) 
       {
         last;
       }
       if ( /Database*/ )
       {
          @tmpArr = split(/\s+/, $_);
          $db = $tmpArr[5];
       }
       if ( /.*NumAgents./ )
       {
         $state = 1;
       }
       if ( /0x.*/ && $state == 1 )
       {
         @tmpArr = split(/\s+/, $_);
         if ( $tmpArr[1] == $apphdl )
         {
           $state = 2;
           $anch = $tmpArr[6];
           $uid = $tmpArr[7];
         } 
       }
       if ( /External Connection Attributes/ ) 
       {
          $state = 0;
       }
   } 
   if ( $state < 2 ) 
   {
      printf "Could not find application handle %d in %s\n", $apphdl, $cmd ;
      exit;
   }
   elsif ( $anch == 0 && $uid == 0 )
   {
      printf "Currently, there doesn't appear to be any dynamic sql statement executing for this application handle.\n";
      printf "Please review the output of \ndb2pd -db %s -applications\n", $db;  
      exit;
   }
   else 
   {
       printf "Database = %s\t, C-AnchID = %d, C-StmtUID = %d\n", $db, $anch, $uid ;
   }
   # Now getting the variation and environment id from the package cache.

   $cmd = sprintf "db2pd -db %s -dynamic |", $db;
   open ( PD, "$cmd" );
   $state = 0;
   my $envid = 0;
   my $varid = 0;
   while ( <PD> )

   {
      if ( /Dynamic SQL Variations:/ )
      {
         $state = 1;
      }
      if ( /0x.*/ && $state == 1 )
      {
         @tmpArr = split(/\s+/, $_);
         if ( $tmpArr[1] == $anch && $tmpArr[2] == $uid )
         {
            $envid = $tmpArr[3];
            $varid = $tmpArr[4];
            $state = 2;
            printf  "Environment id = %d, Variation id = %d\n", $envid, $varid ;
         }

      }
   }
     
   system( "db2trc" , "on", "-apphdl", $apphdl, "-t" , "-Madd" , "SQLRI", "-l", "128M" );
   printf  "Obtaining a masked db2trc ... \n" ;
   system( "sleep" , "10" );
   system( "db2trc", "stop" );
   system( "db2trc", "dmp", "trc_temp.dmp" );
   system( "db2trc" , "off" );

   printf "Checking if the statement didn't change during tracing... \n";
   open( PD, "db2pd -alldbs -applications | " ) || die "Failed to execute db2pd -alldbs -applications $!\n" ;
   $state=0;
   while ( <PD> )
   {
       if ( $state == 2 )
       {
         last;
       }
       if ( /Database*/ )
       {
          @tmpArr = split(/\s+/, $_);
          $db = $tmpArr[5];
       }
       if ( /.*NumAgents./ )
       {
         $state = 1;
       }
       if ( /0x.*/ && $state == 1 )
       {
         @tmpArr = split(/\s+/, $_);
         if ( $tmpArr[1] == $apphdl )
         {
           if (  $tmpArr[6] == $anch && $tmpArr[7] == $uid ) 
           {
             $state = 2;
           }
         }
       }
       if ( /External Connection Attributes/ )
       {
          $state = 0;
       }

   }
   if ( $state < 2 ) 
   {
      printf "Statement changed during data collection, aborting profiling.\n";
      exit;
   }

   ## Section to obtain explain information from the package cache ##
    
   printf "Collecting db2expln info from the package cache\n";
   system( "db2expln" , "-d" , $db, "-o", "expln.out", "-opids", "-graph", "-cache" , "$anch,$uid,$envid,$varid" );
   $cmd = sprintf "db2expln -d %s -t -opids -graph -cache %d,%d,%d,%d", $db, $anch, $uid, $envid, $varid;
   #printf "cmd = %s\n", $cmd;

   open ( PD, "$cmd |" ) || die "Failed to execute db2expln\n";
   $state = 0;

   my $opNum = 0;
   my $operation;
   while ( <PD> )
   {
      chomp;
      if ( /Estimated Cardinality/ ) 
      {
         $state = 1;
      }
      if ( $state == 1 && /\(\s+\d\)+/ )
      {
         $opNum = substr($_,2,4);
         $operation = substr($_,8);
         $operation =~ tr/|//d;
         $operation =~ s/^\s+//;

   #     print "$opNum";
   #     print "$operation";

         $operators{ $opNum }  = $operation;
      }
      if ( /End of section/ )
      {
         $state = 0;
      }
   }
   #print Dumper ( \%operators ); 


 ## Section for Formatting the db2trc ##

   system( "db2trc", "flw" , "-t", "-rds", "trc_temp.dmp", "trc_temp.flw" );
   printf  "Formatting db2trc.. \n" ;
   open( FH, "trc_temp.flw" ) || die "Failed : $!\n"; 
}

## Section to parse the db2trc ## 

printf "Start processing db2trc.. \n";

my $previouslevel = 0;
my $prevexit = 0;
my $currentlevel = 0;
my $prevlolepop = 0;
my $lolepop = 0;
my $nestedcall = 0;
my $l;

while ( <FH> ) 
{

   $ln = $_;
   $prevlolepop = $lolepop;

   if ( /.*tid =.*/  )
   {
      #starting a new thread..  re-initialize some fields
      foreach $l ( keys ( %level ) )
      {
          $level{ $l } = 0;
      }
      if ( $time > $hightime ) 
      {
        $hightime = $time;
      }
      $prevlolepop = 0;
      $prevexit = 0;

      @tmpArr = split(/\s+/, $_);
#pid = 13568 tid = 46912942106944 node = 2
      $tid = $tmpArr[2] . "_" . $tmpArr[5] . "_" . $tmpArr[8];
   }

   if ( /.*lolepop:.*/ )
   {
      @tmpArr = split(/\s+/, $_);
     $time = $tmpArr[1];
     #printf " time = %s \n" , $time ; 

     for ( $i=@tmpArr-1; $i >= 0; $i-- )
     {
        if ( $tmpArr[$i] =~ /lolepop:/ ) 
        {
            chop($tmpArr[$i + 1 ] );
            $prevlolepop = $lolepop;
            $lolepop = $tmpArr[$i + 1 ];
        } 
        elsif ( $tmpArr[$i] =~ /entry/ ) 
        {
             if ( not exists (  $level{ $lolepop } )  )
             {
                $workarea{ $lolepop } = $time;
                $workarea2 { $lolepop } = 0;
                $level{ $lolepop } = 1;
             }
             elsif  ( $level{ $lolepop } == 0 )
             {
                $workarea{ $lolepop } = $time;
                $workarea2{ $lolepop } = 0;
                $level{ $lolepop } = 1;
             }
             else
             {
                $level{ $lolepop } += 1;  
               # do not adjust the time, keep the time  as set at level 1
             }
             if ( exists ( $level{ $prevlolepop } )  && 
                  $prevlolepop != 0 &&
                  $level{ $prevlolepop } != 0 )
             {
                $workarea2{ $prevlolepop } += deltatime ( $workarea{ $prevlolepop }, $time  );
             }

        }
        elsif ( $tmpArr[$i] =~ /exit/ )
        {

           if ( defined $level{ $lolepop } && $level{ $lolepop } == 1 )
           {
              if ( $prevexit > 0 && $prevlolepop > 0 && $prevlolepop != $lolepop )
              {
                  $workarea2{ $lolepop } += deltatime ( $prevexit, $time );
              }
              else
              {
                  $workarea2{ $lolepop } += deltatime ( $workarea{ $lolepop }, $time );
              }

              $level{ $lolepop } = 0;
              $prevexit = $time;
              if ( exists ($pertidcount{ $tid }{ $lolepop }) )
              { 
                 $pertidcount{ $tid }{ $lolepop } += 1; 
                 $pertidtime{ $tid }{ $lolepop } += deltatime( $workarea{ $lolepop}, $time );
                 $pertidtime2{ $tid }{ $lolepop } += $workarea2{ $lolepop };
               #  printf "if branch tid = %d\tlolepop = %d, time = %10.2f\n", $tid, $lolepop, ( deltatime( $workarea { $lolepop }, $time ) );
              }
              else 
              {
                 $pertidcount{ $tid }{ $lolepop } = 1;
               #  printf( " time = %6.2f \t start = %6.2f\n", $time, ($workarea{ $lolepop }) ;
                 $pertidtime{ $tid }{ $lolepop } = deltatime( $workarea { $lolepop }, $time );
                 $pertidtime2{ $tid }{ $lolepop } = $workarea2{ $lolepop };
               #  printf "else branch tid = %d\tlolepop = %d, time = %10.2f\n", $tid, $lolepop, ( deltatime( $workarea { $lolepop }, $time ) );
              }
#              print Dumper(\%pertidtime2);

           }
           elsif ( defined $level{ $lolepop } )
           {
              $level{ $lolepop } -= 1;
           }
         }
       }
      $state = 0;
   }
}

if ( $time > $hightime ) 
{
    $hightime = $time;
}

#print Dumper(\%pertidtime2); 

########################################
#
#  Start processing section.
#
########################################


my $t;
my $a;
my $b;
my $pct;

printf "%4s\t\t%10s\t\t%15s\t\t%12s\t%s\n", "Operator", "Count", "Cumulative (s)", "Per Operator (s)", "%";
foreach $t ( keys ( %pertidtime2 ) ) 
{
    printf "Thread id = %s\n", $t;
    foreach $l ( keys ( %{ $pertidtime2{ $t } }  ) )
    {
      $pct = ($pertidtime2{ $t } { $l } * 100)   / $hightime ;
      printf "%10s\t\t%10s\t\t%15.9f\t\t%15.9f\t\t%5.2f\n", $l, $pertidcount{ $t }{ $l }, $pertidtime{ $t }{ $l }, $pertidtime2{ $t }{ $l }, $pct;
    } 
}


if ( keys  %operators )
{
   my $op;
   printf "\nOperators\n";
   foreach $op ( keys ( %operators ) )
   {
      printf "%8d\t\t%20s\n", $op, $operators{ $op };
   } 
   printf "\n";
}

########################################
#
#  Start reporting section.
#
########################################

my $add;



########################################


=head1 NAME

 db2prof

=head1 SYNOPSIS

 db2prof -arguments

=head1 DESCRIPTION

 Build a profile of a (long) running SQL statement.

=head1 ARGUMENTS
 
 --help      print Options and Arguments
 --man       print complete man page
 --apphdl <apphdl> Create profile for this application handle    
 --file <filename> Use an already collected trace as input.
         Note : options --file and --apphdl are mutually exclusive
 --time <number of seconds>        


=head1 OPTIONS

 --versions   print Modules, Perl, OS, Program info
 --debug 0    don't print debugging information (default)
 --debug 1    print debugging information

=head1 AUTHOR

Benny.Verplancke@be.ibm.com

=head1 BUGS

None that I know of.

=head1 TODO

=cut

