#!/usr/local/bin/perl
# Purpose: Parse a DC/PC log and print out WNS, TNS, AREA, & DRC vs. time 
# Author: Kevin Croysdale
# Version: 1.0
# $Id: graph_log,v 1.14 2002/02/06 23:36:06 $

# Getopt::Long is not is all perl installations. If you don't have it, 
# add the correct installation path to the next line and remove the 
# leading '#'.
#use lib "insert_installation_path_here/graph_log_files";

use Getopt::Long;

# Get program name and path to graph_log
@progarray = split "/", $0;
$progname  = @progarray[$#progarray];
$progpath  = join "/", @progarray[0 .. $#progarray-1];

&init_vars ();
#  Get command line options
GetOptions (
             "tns"  => \$do_tns,
             "wns"  => \$do_wns,
             "drc"  => \$do_drc,
             "area" => \$do_area,
             "xgraph:s" => \$xgraph_bin,
             "nxg"  => \$nxg,
             "uncompress"  => \$uncompress,
             "help" => \$help,
             "debug" => \$debug,
             "format:s" => \$custom_format,
             "pdf:s"  => \$pdf ) || ($bad_usage = 1);

$snps_log_file  = $ARGV[0];

&usage if ($help or $bad_usage) ;
&usage ("Error: No log file specified") if (!defined $ARGV[0]); 
&usage ("Error: Log file not readable") unless (-r $snps_log_file);

$xgraph_bin = &get_xgraph_binary_name () unless ( defined $xgraph_bin || $nxg ) ;
print "Information: Using $xgraph_bin\n" if $debug ;

# If no metrics are selected then select all 
if ($do_tns==0 && $do_wns==0 && $do_drc==0 & $do_area==0) {
   $do_tns=1; $do_wns=1; $do_drc=1; $do_area=1;
}
$format = $custom_format if defined $custom_format;

$tns_file  = "/tmp/xgraph$$.tns";
$wns_file  = "/tmp/xgraph$$.wns";
$drc_file  = "/tmp/xgraph$$.drc";
$area_file = "/tmp/xgraph$$.area";

open (TNS,  ">$tns_file")  || die "Unable to write to $tns_file\n" if $do_tns  ;
open (WNS,  ">$wns_file")  || die "Unable to write to $wns_file\n" if $do_wns ;
open (DRC,  ">$drc_file")  || die "Unable to write to $drc_file\n" if $do_drc ;
open (AREA, ">$area_file") || die "Unable to write to $area_file\n" if $do_area ;

open (INLOG,"$snps_log_file")   || die "Unable to open '$snps_log_file'\n";

&print_tns  ("title = Total Neg Slack\n");
&print_wns  ("title = Worst Neg Slack\n");
&print_drc  ("title = DRC\n");
&print_area ("title = Area\n"); 
&print_all  ("title_x = Time (hours)\n");
&print_all  ("TEXT 8 1\n$snps_log_file\n");
&print_all  ("  Compile Phase - color:\n   DelayOpt - white\n   DRC - red\n   TimOpt - blue\n   Area - green\n   Legalization - violet\n   Critical Path Opt - yellow\n");
&print_all  ("END_TEXT\n");

while (<INLOG>) {
# Decode log format when header is present
  if (/ ELAPSED / || / WORST / || / TOTAL /) {
     next if defined $custom_format ;
     $tmp_format = &decode_log_format ; 
     $format = $tmp_format;
  }

# Set different colors for different stages of PC
   unless ($legal_flag) {
      if (/^ *Beginning Delay Optimization Phase/)       { &print_all_vals_force ();&change_color ("white") };
      if (/^ *Beginning Phase \S+ Design Rule Fixing/)   { &print_all_vals_force ();&change_color ("red")   };
      if (/^ *Beginning Timing Optimizations/)           { &print_all_vals_force ();&change_color ("blue")  };
      if (/^ *Beginning Area-Recovery/)                  { &print_all_vals_force ();&change_color ("green") }; 
      if (/^ *Beginning Critical Range Optimization/)    { &print_all_vals_force ();&change_color ("yellow") }; 
   }

# Account for legalization
   if (/^Initial legalization/) {
       $prelegal_color = $current_color;
       &print_all_vals_force (); 
       &change_color ("violet") ;
       $legal_flag = 1;
       $legal_disp = 0;
   }
   if (/^(Warning: The average displacement of a cell after legalization is (\S+))/) {
       $legal_disp_mess = $1 ;
       $legal_disp = $2 ;
       print STDERR "Line $.: $_";
       print STDERR "Displacement annotated on graph\n";
   }


# Assume a timestamp followed by at least 2 columns.
# Or a column, then timestamp followed by at least 2 columns.
    if ( (/^ *(\d+:\d+:\d+)\s+(\S+)\s+(\S+)/) ||
         (/^ *(\S+)\s+(\d+:\d+:\d+)\s+(\S+)\s+(\S+)/) ){
      next if $_ =~ /proc /;
      $timestamp = $1;
# $format default is "($timestamp, $area, $wns, $tns, $drc)"
    # Parse the log lines
    # Look for common formats and speed up the run-time for those cases.
    # Add your own format to decrease run-time 70-80%. Use -debug to get format.
      @_ = split ' ', $_;
      if ($format eq '($timestamp, $area, $wns, $tns, $drc )') {
          ($timestamp, $area, $wns, $tns, $drc )  = @_;
       } elsif ($format eq '($timestamp, $area, $null, $null, $wns, $tns, $drc )') {
          ($timestamp, $area, $null, $null, $wns, $tns, $drc ) = @_;
       } elsif ($format eq '($timestamp, $area, $null, $null, $wns, $tns, $drc, $null, $null )') {
          ($timestamp, $area, $null, $null, $wns, $tns, $drc, $null, $null ) = @_;
       } elsif ($format eq '($timestamp, $null, $null, $area, $null, $wns, $tns, $drc, $null )') {
          ($timestamp, $null, $null, $area, $null, $wns, $tns, $drc, $null ) = @_;
       } else {
          eval "$format = \@_";
       }

       next if (( $tns !~ /\d+\.*\d+/) && $do_tns );
       next if (( $wns !~ /\d+\.*\d+/) && $do_wns );
       next if (( $drc !~ /\d+\.*\d+/) && $do_drc );
       next if (( $area !~ /\d+\.*\d+/) && $do_area );
       
# Convert timestamp to time in hours
      ($hour, $min, $sec) = split ':', $timestamp;
      $time_hours = sprintf "%6.5f", ($hour + $min/60 + $sec/3600) ;
      $time_hours += $time_adjustment ;
      next if (($time_hours == $old_time_hours) && (!$uncompress));


# Advance the time if the time ever goes backwards. This might happen
# if you follow a compile run with a physopt run.
      if ($time_hours < $old_time_hours) {
         $time_adjustment += $old_time_hours;
         $time_hours += $old_time_hours;
       # Blank out space between runs
         $saved_color = $current_color;
         &change_color ("black");
         &print_all ("$old_time_hours 0\n");  
         &print_all ("$time_hours 0\n");  
         &print_all_anno ("New run", "offset = $time_adjustment");
         &print_all_vals_force();
         &change_color ($saved_color);
         $old_tns = $tns; $old_wns = $wns; $old_drc = $drc; $old_area = $area;
         next;
      }
      $old_time_hours = $time_hours;

# Account for legalization
     if ($legal_flag) {
         &change_color ("violet") ;
         &print_all_vals;
         $legal_flag = 0;
         &print_all_anno ($legal_disp, $legal_disp_mess) if $legal_disp;
         &change_color ("$prelegal_color") ;
     }

     &print_all_vals;
     $old_tns = $tns; $old_wns = $wns; $old_drc = $drc; $old_area = $area;
     $timestamp_loop_entered = 1;
   }
}

# Print one last set of data to catch the case where data stops changing.
&print_all_vals_force (); 

# Close files
close (TNS) if $do_tns;
close (WNS) if $do_wns;
close (DRC) if $do_drc;
close (AREA) if $do_area;
close (INLOG);

if (!$timestamp_loop_entered) {
   print STDERR "No log information found\n" ; exit 1 ;
}

if (defined $pdf) { 
   if ($pdf) { # True if $pdf set to a string.
      $tns_out = "$pdf.tns.pdf" ; $wns_out = "$pdf.wns.pdf" ; $drc_out = "$pdf.drc.pdf" ; $area_out = "$pdf.area.pdf" ;
   } else { 
      $tns_out = "xgraph.tns.pdf" ; $wns_out = "xgraph.wns.pdf" ; $drc_out = "xgraph.drc.pdf" ; $area_out = "xgraph.area.pdf" ;
   }
   &sysprint ("$xgraph_bin $tns_file  -pdf -out_file $tns_out")    if $do_tns;
   &sysprint ("$xgraph_bin $wns_file  -pdf -out_file $wns_out")    if $do_wns;
   &sysprint ("$xgraph_bin $drc_file  -pdf -out_file $drc_out")    if $do_drc;
   &sysprint ("$xgraph_bin $area_file -pdf -out_file $area_out")   if $do_area;
   &sysprint ("rm /tmp/xgraph$$.*");
} elsif  ($nxg == 0) {
   &sysprint ("( $xgraph_bin $tns_file ; /bin/rm $tns_file ) &")   if $do_tns; 
   &sysprint ("( $xgraph_bin $wns_file ; /bin/rm $wns_file ) &")   if $do_wns;
   &sysprint ("( $xgraph_bin $drc_file ; /bin/rm $drc_file ) &")   if $do_drc;
   &sysprint ("( $xgraph_bin $area_file ; /bin/rm $area_file ) &") if $do_area; 
} else { # -nxg
   print STDERR ("TNS xgraph file written to $tns_file\n") if $do_tns;
   print STDERR ("WNS xgraph file written to $wns_file\n") if $do_wns;
   print STDERR ("DRC xgraph file written to $drc_file\n") if $do_drc;
   print STDERR ("AREA xgraph file written to $area_file\n") if $do_area;
}

sub sysprint {
# Function: Print command to STDOUT, then run system ("command")
   my @strings = @_;
   print @strings, "\n";
   system @strings;
}

sub change_color {
# Function: Change color of line in Xgraph
   my ($color) = @_;
   $current_color = $color ;
   &print_all ("color = $color\n");
}

sub print_all_vals {
# Function: Print values to all active logs if value is new
   return if !$time_hours;
   if ($uncompress) { &print_all_vals_force ; return ; }
   if ($do_tns) {
      if ($tns != $old_tns) {
      print TNS  "$time_hours $old_tns\n" if $skip_tns;
      print TNS  "$time_hours $tns\n";
      $skip_tns = 0;
      } else {
         $skip_tns = 1;
      }
   }
   if ($do_wns) {
      if ($wns != $old_wns) {
      print WNS  "$time_hours $old_wns\n" if $skip_wns;
      print WNS  "$time_hours $wns\n";
      $skip_wns = 0;
      } else {
         $skip_wns = 1;
      }
   }
   if ($do_drc) {
      if ($drc != $old_drc) {
      print DRC  "$time_hours $old_drc\n" if $skip_drc;
      print DRC  "$time_hours $drc\n";
      $skip_drc = 0;
      } else {
         $skip_drc = 1;
      }
   }
   if ($do_area) {
      if ($area != $old_area) {
      print AREA  "$time_hours $old_area\n" if $skip_area;
      print AREA  "$time_hours $area\n";
      $skip_area = 0;
      } else {
         $skip_area = 1;
      }
   }
}

sub print_all_anno {
# Function: Always print values to all active logs
   my ($anno_string, $hypernote) = @_;
   return if !$time_hours;
   print TNS  "anno $time_hours $tns $anno_string\n"  if $do_tns ;
   print WNS  "anno $time_hours $wns $anno_string\n"  if $do_wns ;
   print DRC  "anno $time_hours $drc $anno_string\n"  if $do_drc ;
   print AREA "anno $time_hours $area $anno_string\n" if $do_area ;
   if ($hypernote) {
      print TNS  "<hypernote> $time_hours $tns $hypernote </hypernote>\n"  if $do_tns ;
      print WNS  "<hypernote> $time_hours $wns $hypernote </hypernote>\n"  if $do_wns ;
      print DRC  "<hypernote> $time_hours $drc $hypernote </hypernote>\n"  if $do_drc ;
      print AREA "<hypernote> $time_hours $area $hypernote </hypernote>\n" if $do_area ;
   }
}

sub print_all_vals_force {
# Function: Always print values to all active logs
   return if !$time_hours;
   print TNS  "$time_hours $tns\n"  if $do_tns ;
   print WNS  "$time_hours $wns\n"  if $do_wns ;
   print DRC  "$time_hours $drc\n"  if $do_drc ;
   print AREA "$time_hours $area\n" if $do_area ;
}

sub print_all {
# Function: Print a $string to all active output files
   ($string) = @_;
      print TNS  $string if $do_tns;
      print WNS  $string if $do_wns;
      print DRC  $string if $do_drc;
      print AREA $string if $do_area;
}

sub print_tns {
# Function: Print a $string to TNS logfile
   ($string) = @_;
      print TNS  $string if $do_tns;
}

sub print_wns {
# Function: Print a $string to WNS logfile
   ($string) = @_;
      print WNS  $string if $do_wns;
}

sub print_drc {
# Function: Print a $string to DRC logfile
   ($string) = @_;
      print DRC  $string if $do_drc;
}

sub print_area {
# Function: Print a $string to AREA logfile
   ($string) = @_;
      print AREA $string if $do_area;
}

sub get_xgraph_binary_name {
# Function: Determine the correct xgraph binary to use

# Determine the $ARCH
   $OS = `uname -s`;
   chomp $OS;
   OS: {
    ($OS eq "SunOS") && do { $arch = "sparcOS5" ; last OS;} ;
    ($OS eq "HP-UX") && do { $arch = "hpux10" ; last OS; } ;
    ($OS eq "Linux") && do { $arch = "linux" ; last OS; } ;
    ($arch = "linux");
   }

# Check for the existance of xgraph.$arch in graph_log install directory
   $xgraph_install_bin = $progpath . "/graph_log_files/xgraph.$arch" ;
   return $xgraph_install_bin if (-x $xgraph_install_bin);

# Check for the existance of xgraph.$arch in $PATH
   $xgraph_fullpath_bin = &perlwhich ("xgraph.$arch");
   return $xgraph_fullpath_bin if (-x $xgraph_fullpath_bin);

# Check for the existance of xgraph in $PATH
   $xgraph_fullpath_bin = &perlwhich ("xgraph");
   return $xgraph_fullpath_bin if (-x $xgraph_fullpath_bin);

   print STDERR "\nError: xgraph not found in the installation or in \$PATH\n";
   print STDERR "       Checked for  '$xgraph_install_bin'\n";
   print STDERR "       Checked for 'xgraph.$arch' in \$PATH\n";
   print STDERR "       Checked for 'xgraph' in \$PATH\n\n";
   exit 1;
}

sub perlwhich {
   my ($search_file) = @_;

   @path_list = split /:/, $ENV{"PATH"};

   foreach $dir (@path_list){
      opendir(DIR, $dir);
      while ($file = readdir(DIR)) {
         return "$dir/$file" if ($file eq "$search_file")
      }
      closedir(DIR);
   }
   return 0;
}

sub decode_log_format { 
# Function: Determine the order of the columns in the log file.
#
# Sample formats 
# DC default:
#   ELAPSED            WORST NEG TOTAL NEG  DESIGN                            
#    TIME      AREA      SLACK     SLACK   RULE COST         ENDPOINT
   # '($timestamp, $area, $wns, $tns, $drc)';
# 
#                     MAX    MIN   WORST   TOTAL   DESIGN
# ELAPSED            DELAY  DELAY   NEG     NEG     RULE
#  TIME      AREA     COST   COST  SLACK   SLACK    COST  PATH GROUP         ENDPOINT
#---------- ------ -------- ------ ------ -------- ------ ------------- -------------
   # '($timestamp, $area, $null, $null, $wns, $tns, $drc)';
#
# ELAPSED                          WORST NEG TOTAL NEG  DESIGN
#  TIME    MBYTES   AREA    TRIALS   SLACK     SLACK   RULE COST         ENDPOINT
#--------- ------ --------- ------ --------- --------- --------- -------------------------
   # '($timestamp, $null, $area, $null, $wns, $tns, $drc)';

my ($i, $char, $line, $fieldno, $cutstart, $cutend, $format_line, $space);
my (@spaces, @lines);
my (@fields) = ('', '', '', '');
 
   while ($_ !~ /-----------/) {
      $lines[$line++] = $_;
      $_ = <INLOG>;
   }
   chop $_;
   @dash_line = split //, $_ ;

# Get space locations from @dash_line
   foreach $char (@dash_line) {
      $spaces[$space++] = $i if ($char eq ' ');
      $i++;
   }
   $spaces[$space++] = $i-1;

# Splice up the lines
   foreach $line (@lines) {
      $fieldno = 0; $cutstart = 0;
      foreach $cutend (@spaces) {
         @line_chars = split //, $line;
         $field =  join "", splice (@line_chars, $cutstart, ($cutend-$cutstart));
         $field =~ s/ //g;
         $fields[$fieldno] .= $field;
         $cutstart = $cutend;
         $fieldno ++;
      }
   }

# Determine the $format_line based on order of fields
my ($timestamp_f, $area_f, $wns_f, $tns_f, $drc_f) = (0, 0, 0, 0, 0);
   $format_line = '(';
   foreach $field (@fields) {
      next if ($field eq '');
      if ($field eq "ELAPSEDTIME") {
         $format_line .= '$timestamp, '; $timestamp_f = 1;
      } elsif ($field eq "AREA") {
         $format_line .= '$area, '; $area_f = 1;
      } elsif ($field eq "WORSTNEGSLACK") {
         $format_line .= '$wns, '; $wns_f = 1;
      } elsif ($field eq "TOTALNEGSLACK") {
         $format_line .= '$tns, '; $tns_f = 1;
      } elsif ($field eq "DESIGNRULECOST") {
         $format_line .= '$drc, '; $drc_f = 1;
      } else {
         $format_line .= '$null, ';
      }
   }
   if ($timestamp_f == 0) {
      print "Error: 'ELAPSED TIME' column is missing from log (line $.)\n"; 
      exit 1;
   }
   if ($wns_f == 0) {
      print "Warning: 'WNS' column is missing from log (line $.)\n" unless $wns_warn;
      $do_wns = 0; $wns_warn = 1;
   }
   if ($tns_f == 0) {
      print "Warning: 'TNS' column is missing from log (line $.)\n" unless $tns_warn; 
      $do_tns = 0; $tns_warn = 1;
   }
   if ($area_f == 0) {
      print "Warning: 'AREA' column is missing from log (line $.)\n" unless $area_warn; 
      $do_area = 0; $area_warn = 1;
   }
   if ($drc_f == 0) {
      print "Warning: 'DRC' column is missing from log (line $.)\n" unless $drc_warn;
      $do_drc = 0; $drc_warn = 1;
   }
   $format_line =~ s/, $/ )/; # Replace final , with )
   print STDERR "Format = $format_line\n" if $debug;
   return $format_line;
}

sub init_vars {

   $wns = $tns = $drc = $area = 0;
   $do_wns = $do_tns = $do_drc = $do_area = 0;
   $old_wns = $old_tns = $old_drc = $old_area = 0;
   $nxg = 0;
   $time_adjustment = $old_time_hours = 0;
   $current_color = 0;
}

sub usage {
   my (@strings) = @_;

   print @strings, "\n\n";
   print "Usage: $progname [-wns] [-tns] [-drc] [-area] [-help] [-xgraph] [-pdf name] logfile\n";
   print "    -wns         : Xgraph WNS over time\n";
   print "    -tns         : Xgraph TNS over time\n";
   print "    -drc         : Xgraph DRC over time\n";
   print "    -area        : Xgraph AREA over time\n";
   print "    -xgraph      : Path to xgraph binary.\n";
   print "    -pdf <name>  : Output pdf files to <name>.*.pdf\n";
# Hidden Options
   # print "    -nxg         : Create files in /tmp. Don't run xgraph.\n";
   # print "    -format <fmt>: Log format \n";
   # print "    -uncompress : Don't compress data \n"; 
   # print "    -debug : Print debug informatio \n"; 
   print "\n";
   print "If no -wns, -tns, -drc, or -area options are given, then all 4 are plotted.\n";
   exit;
}
