#!/usr/bin/perl
#
#  This file belongs to the Cleo project (version 3 and above)
#
#  qlogs analysator...
#
#  Use short log as input!
#
#  * Created 20 Nov 2001 by Sergey Zhumatiy
#  * Modified 23 Oct 2003 by Sergey Zhumatiy
#    - time of tasks, which was started before log interval begins
#      is added too
#    - tasks, which are not ended in time window are added
#  * Modified 29.01.2004 by Sergey Zhumatiy
#    - keys changed (GetOptsTillCan added)
#    - corrected support for 'cutted' tasks (beginns too far or too early)
#    - added key to add cutted tasks time to total time
#    - debug messages added
#

use Time::Local;
use integer;
use strict;

my ($minUtime,$maxUtime,$curUtime,%newU,%diff,%day,%np,%tasks_count,%min,%max,%sum);
my ($flag,$u,$i,$q,$queue,$diff,$day,$user,$Ttime,$maxtime_line);
my (%tasks_count_cutted,%diff_cutted,%sum_cutted,$cutted);
my ($line,$tmpU_t,$id,$out,%q);
my ($last_line);

my ($tmp_m, $tmp_d,$tmp_t,$m,$m2,$d,$dd);
my ($opt_q,$opt_d,$opt_a,$opt_g);

my %month=('jan'=>0,'feb'=>1,'mar'=>2,'apr'=>3,
           'may'=>4,'jun'=>5,'jul'=>6,'aug'=>7,
           'sep'=>8,'oct'=>9,'nov'=>10,'dec'=>11);

my ($sec,$min,$hours,$mday,$mon,$year);

sub get_line( $ ){
  my $ret=$_[0];
  if(defined $last_line){
    $$ret=$last_line;
    undef $last_line;
  }
  else{
    return 0 if eof(STDIN);
    $$ret=<STDIN>;
  }
  return 1;
}

sub min($$){return ($_[1]<$_[0])?$_[1]:$_[0];}
sub max($$){return ($_[0]<$_[1])?$_[1]:$_[0];}

sub debug( $ ){};

sub get_date(){
  my ($m2,$m,$d,$y);
  my ($m2);

  ($d,$m,$y)= ($ARGV[0] =~ /(\d+)\.([^.]+)(?:\.(\d+))?/);
  if(!defined $m || !defined $d){
    warn "Illegal date: $ARGV[0]\n";
    usage();
    exit(1);
  }
  shift;

  $m=lc($m);
  if($m>0){
    $m2=$m-1;
  }
  else{
    $m2=$month{$m};
  }
  unless(defined $m2){
    warn "Illegal month - $m\n";
    usage();
    exit(1);
  }
  if($y<1){
    $y=$year;
  }
  elsif($y<100){
    if($y<70){
      $y+=2000;
    }
    else{
      $y+=1900;
    }
  }
  return timelocal(0,0,0,$d,$m2,$y);
}

sub usage(){
  warn "Usage: $0 [-d][-q][-a][-g] [begin_date [end_date]] <short_log_file\n -d - show stats by days\n -q - show stats by queues\n -a - summarize cutted tasks time to total\n -g - print debug info\n date format: DD.MM[.YY[YY]]\n\n";
}

$minUtime=0;
$maxUtime=time();
$curUtime=$maxUtime;
($sec,$min,$hours,$mday,$mon,$year)=localtime($maxUtime);


#my @ms=(0,2678400,5097600,7776000,10368000,13046400,
#        15638400,18230400,20908800,23500800,26179200,28771200,31449600);


GetOptsTillCan(
               'd=' => \$opt_d,
               'g=' => \$opt_g,
               'a=' => \$opt_a,
               'q=' => \$opt_q,
               );

if($ARGV[0]){
  $minUtime=get_date();
  shift;
  if($ARGV[0]){
    $maxUtime=get_date();
  }
}

if($opt_g){
  print "Debug mode\n";
  eval("sub debug( \$ ){print \"\$_[0]\";};");
  debug("test\n");
}

while(get_line(\$_)){

  chomp;
  unless(/\[(.+)\]\ (\S+)\s*\:(.*)/){ next; }#warn "bad line: '$_'\n"; next;}
  $line =$3;
  $queue=$2;
  $Ttime =$1;
  $cutted=0;

  $q{$queue}=1;
  ($tmp_m,$tmp_d)= ($Ttime =~ /^\S+\s+(\S+)\s+(\d+)/) or next;
#  $tmpU_t=timelocal($sec,$min,$hours,$tmp_d,$month{lc($tmp_m)},$year);
  $tmpU_t=gettime($Ttime);

  if($tmpU_t<$minUtime or $tmpU_t>$maxUtime){
#    debug("Not in interval: $Ttime ($minUtime $tmpU_t $maxUtime\n");
#    debug("    ".localtime($minUtime)."  ".localtime($tmpU_t)."  ".localtime($maxUtime)."\n");
    next;
  }

  unless($minUtime){
    $minUtime=$tmpU_t;
    debug("MINTIME=$minUtime ($Ttime/".localtime($minUtime).")\n");
  }
  if($line =~ /RUN\s(\d+)\;\s(\S+)\;\s(\d+)/){ # id user np
    #$new{"$queue $1 $2"}=$time;
    my $i="$queue $1 $2";
    $newU{$i}=gettime($Ttime); #queue,id,user
    $np{$i}=$3;
    next;
  }
  if($line =~ /^END_TASK\s(\d+)\;\s(\S+)\;/){
    $id  =$1;
    $user=$2;
    $i="$queue $1 $2";
    my $next=<STDIN>;
    if($next =~ /END_TASK_NODES\s(\d+)\;\s(\S+)/){
      if($1 == $id){
        my $x=$2;
        my $count= $x =~ y/,/,/;
        $np{$i}=$count+1;
      }
      else{
        debug("Warning! Bad line: $next");
      }
    }
    else{
      $last_line=$next;
      debug("OLD log file...($id, $queue)\n");
    }
    unless(exists($newU{$i})){
      #      warn "Unexpected: $_\n";
      #next;
      $cutted=1;
      $newU{$i}=$minUtime #! cutted task ! It was started before 'mintime'
    }
    if($newU{$i}<0){
      warn "Duplicated $_\n";
      next;
    }
    unless($min{$user}{$queue}){$min{$user}{$queue}=100000;}
    if($np{$i}<1){
      debug("!! '$i' -> np=$np{$i}\n");
      $np{$i}=1;
    }
    $min{$user}{$queue}=min($min{$user}{$queue},$np{$i}) if $np{$i};
    $max{$user}{$queue}=max($max{$user}{$queue},$np{$i}) if $np{$i};
    #$diff=gettime($time)-gettime($new{$i})+1;
    $diff=gettime($Ttime)-$newU{$i}+1;
    if($cutted && !$opt_a){
      ++$tasks_count_cutted{$user}{$queue};
      debug("<<id.user.np: $id.$user.$np{$i} time: ".
            localtime($tmpU_t)."-".localtime($newU{$i}).
            " diff: $diff [$tasks_count_cutted{$user}{$queue}]\n");
    }
    else{
      ++$tasks_count{$user}{$queue};
      debug(">>id.user.np: $id.$user.$np{$i} time: ".
            localtime($tmpU_t)."-".localtime($newU{$i}).
            " diff: $diff\n");
    }
    if($diff<0){
      #print STDERR "$id for $user at $time(".gettime($time).") $new{$i}(".gettime($new{$i}).")\n";
      debug("!!$id for $user at $Ttime (".gettime($Ttime).") $newU{$i} ($newU{$i})\n");
    }
    else{
      if($cutted && !$opt_a){
        $diff_cutted{$user}{$queue}+=$diff;
        $sum_cutted{$user}{$queue}+=$np{$i}*$diff;
      }
      else{
        $diff{$user}{$queue}+=$diff;
        $sum{$user}{$queue}+=$np{$i}*$diff;
      }
#    printf("%10s: %4d:%02d   %s\n",$2,int($diff/60),int($diff%60),$new{"$2 $1"});
      $Ttime =~ /^\S+\s+(\S+\s+\d+)/;
      $day{$1}{$user}{$queue}+=$diff;
    }
    #!delete $new{$i};
    $newU{$i}=-1;
  }
}

# take in account all tasks, which are not finished yet...
$maxtime_line = localtime($maxUtime);
$maxtime_line =~ /^\S+\s+(\S+\s+\d+)/;
$day=$1;
foreach $i (keys(%newU)){
  next if($newU{$i}<0);

  $i =~ /^(\S+) (\S+) (\S+)/;
  ($queue,$id,$user)=($1,$2,$3);

  unless($min{$user}{$queue})
    {$min{$user}{$queue}=100000;}
  $min{$user}{$queue}=min($min{$user}{$queue},$np{$i}) if $np{$i};
  $max{$user}{$queue}=max($max{$user}{$queue},$np{$i}) if $np{$i};
  #$diff=gettime($maxtime)-gettime($new{$i})+1;
  $diff=$maxUtime-$newU{$i}+1;
  if($opt_a){
    ++$tasks_count{$user}{$queue};
  }
  else{
    ++$tasks_count_cutted{$user}{$queue};
  }
  debug("}}id.user.np: $id.$user.$np{$i} time: ".
        localtime($tmpU_t)."-".localtime($newU{$i}).
        " diff: $diff [$tasks_count_cutted{$user}{$queue}]\n");
  if($diff<0){
    #print STDERR "$id for $user at $maxtime(".gettime($maxtime).") $new{$i}(".gettime($new{$i}).")\n";
  }
  else{
    if($opt_a){
      $diff{$user}{$queue}+=$diff;
      $sum{$user}{$queue}+=$np{$i}*$diff;
    }else{
      $diff_cutted{$user}{$queue}+=$diff;
      $sum_cutted{$user}{$queue}+=$np{$i}*$diff;
    }
    #    printf("%10s: %4d:%02d   %s\n",$2,int($diff/60),int($diff%60),$new{"$2 $1"});
    $day{$day}{$user}{$queue}+=$diff;
  }
}

print "\nTime format is minutes:seconds.\n";
print "Interval from ".localtime($minUtime)." to ".localtime($maxUtime)."\n\n";
print "Total:       astr.time   sum.time min_np max_np  tasks\n\n";
my ($d,$s,$n,$x,$c,$dc,$sc,$cc);
my %k;
foreach $i (keys(%diff),keys(%diff_cutted)){
  $k{$i}=1;
}
foreach $u (sort(keys(%k))){
  ($d,$s,$n,$x,$c,$dc,$sc,$cc)=(0,0,0,0,0,0,0,0);
  foreach $i (values(%{$diff{$u}})){
    $d+=$i;
  }
  foreach $i (values(%{$diff_cutted{$u}})){
    $dc+=$i;
  }
  foreach $i (values(%{$sum{$u}})){
    $s+=$i;
  }
  foreach $i (values(%{$sum_cutted{$u}})){
    $sc+=$i;
  }
  foreach $i (values(%{$min{$u}})){
    $n+=$i;
  }
  foreach $i (values(%{$max{$u}})){
    $x+=$i;
  }
  foreach $i (values(%{$tasks_count{$u}})){
    $c+=$i;
  }
  foreach $i (values(%{$tasks_count_cutted{$u}})){
    $cc+=$i;
#    debug("::$u += $i => $cc\n");
  }
  printf("\%10s: \%7d:%02d \%7d:%02d \%6d \%6d \%6d\n",
          $u,int($d/60),int($d%60),
          int($s/60),int($s%60),$n,$x,$c);
  if($cc+$sc+$dc){
    printf("            \%+7d:%02d \%+7d:%02d               \%+6d\n",
           int($dc/60),int($dc%60),
           int($sc/60),int($sc%60),$cc);
  }
}

if($opt_q){
  foreach $q (keys(%q)){
    $out='';
    foreach $u (sort(keys(%diff))){
      $out.=sprintf("\%10s: \%7d:%02d \%7d:%02d \%6d \%6d \%6d\n",
                    $u,int($diff{$u}{$q}/60),int($diff{$u}{$q}%60),
                    int($sum{$u}{$q}/60),int($sum{$u}{$q}%60),
                    $min{$u}{$q},$max{$u}{$q},$tasks_count{$u}{$q})
        if(exists($diff{$u}{$q}));
      $out.=sprintf("            \%+7d:%02d \%+7d:%02d               \%+6d\n",
                    int($diff_cutted{$u}{$q}/60),int($diff_cutted{$u}{$q}%60),
                    int($sum_cutted{$u}{$q}/60),int($sum_cutted{$u}{$q}%60),
                    $tasks_count{$u}{$q})
        if(exists($diff_cutted{$u}{$q}));
    }
    print "\nQueue $q:\n\n$out" if($out);
  }
}

if($opt_d){
  print "\nTotal on each day (astronomy time):\n\n";
  foreach $d (sort(keys(%day))){
    $flag=0;
    foreach $u (sort(keys(%{$day{$d}}))){
      $dd=0;
      foreach $i (keys(%{$day{$d}->{$u}})){
        $dd+=$day{$d}->{$u}->{$i};
      }
      printf("%s %10s: %7d:%02d\n",$d,$u,int($dd/60),int($dd%60));
      $flag=1;
    }
    print "--------------------------\n" if $flag;
  }
}

if($opt_q){
  foreach $q (keys(%q)){
    $out='';
    foreach $d (sort(keys(%day))){
      $flag=0;
      foreach $u (sort(keys(%{$day{$d}}))){
        if(exists($day{$d}{$u}{$q})){
          $out.=sprintf("%s %10s: %7d:%02d\n",$d,$u,int($day{$d}{$u}{$q}/60),int($day{$d}{$u}{$q}%60));
          $flag=1;
        }
      }
      $out .= "--------------------------\n" if $flag;
    }
    print "\nQueue $q (astronomy time):\n\n$out" if $out;
  }
}

print "\n";


sub gettime($){ # get unix time from text representation
  my ($t)=@_;
  my $ret;
  my ($month,$day,$hour,$min,$sec,$year);

  unless(($month,$day,$hour,$min,$sec,$year) = 
         ($t =~ /^\S+\s+(\S+)\s+(\d+)\s(\d+)\:(\d+)\:(\d+)\s+(\d+)/)){
    debug("Ops! '$t'".(caller(0))[2]."\n");
    exit(1);
  }
#  $ret=$sec+60*$min+3600*$hour+86400*$day+$ms[$month{$month}];
#  $ret+=86400 if(($year%4 or ($year%100==0 and $x%400)) and $month{$month}>1);
  return timelocal($sec,$min,$hour,$day,$month{lc($month)},$year);
  return $ret;
}

#
#  Gets opts like this: ('X=i', \$Xoption,...) (this means "option '-X 10' to variable $Xoption=10)
#  The scans command line for options till founds argument '--' or non-specified
#  option, or not '-' prefixed argument.
#  Specifications of options (what goes after 'X='):
#  i - integer
#  s - string
#  + - cumulative value (variable MUST be a list)
#  nothing - flag
#
sub GetOptsTillCan{

  my %args=@_;
  my ($arg,$a_key,$a_value,$a,$next,%types);

  foreach $arg (keys(%args)){
    $arg =~ /^(\S+)(\=)(.*)/ or next;
    $a_key=$1;
    $a_value=$args{$arg};
    $types{$a_key} = $3;

    delete $args{$arg};
    $args{$a_key} = $a_value;
  }

  while($next=shift @ARGV){
    last if(substr($next,0,1) ne '-');
    last if($next eq '--');
    $a=substr($next,1);
    last unless(exists $args{$a});
    undef $next;
    if(($types{$a} eq 'i') || ($types{$a} eq 's')){
      $a=$args{$a};
      $$a=shift @ARGV;
    }
    elsif($types{$a} eq ''){
      $a=$args{$a};
      $$a=1;
    }elsif($types{$a} eq '+'){
      $a=$args{$a};
      push @$a, shift @ARGV;
    }
  }
  unshift @ARGV, $next if(defined $next);
}

