#!/usr/bin/perl
use strict;
use diagnostics;
use CGI qw(:standard);
use Time::localtime;
use File::Find;

#*******************************************************************
# SETI Monitor by steffen merunka
#   take process node information from text file (updated by SETIparse.pl)
#   and render html
#
# Patched/Tuned by Markus Drechsler, Bavaria, Germany
#   Minor revisions:
#     2001-04-06: v1.0.1 - bugfixes, extensions and design change
#     2001-09-08: v1.0.2 - update of changed seti links
#*******************************************************************

# declare misc variables
my(
   $bgcolor,               # background color
   $bglabel,               # background color of labels
   $bgdata,                # background color of data
   $ftcolor,               # font color
   $ftlink,                # font color links
   $refresh,               # refresh cycle in s
   $wleft,                 # width single
   $wright,                # width summary
   
   $myemail,               # identify on seti
   $targethost,            # where are the infos
   
   $pic_dir,               #directory for all used picture files
   $pic_top_name,          #name of picture file for top title
   $pic_run_name,          #name of picture file for active nodes
   $pic_halt_name,         #name of picture file for inactive nodes
   $nodes_file_name,       #name of flat text file for nodes data
   $target_dir,            #directory for nodes text data file
   @nodes_data,            #list of all SETI processes
   @tmp_nodes_data,        #temp. list, for sorting
   %tmp_node,              #tmp node
   %old_node_data,         #hash with nodesdata for the previous run
   $tmp_node_name,
   $rtmp_node,             #tmp reference to a node
   $TMPFILE,               #filehandle for config files
   @tmplist,               #misc. temp. variables for use in between
   $tmpstr,
   $tmp_node,
   $tmp_prog,
   $tmp_cpu,
   $tmp_fl,
   $tmp_peak,
   $tmp_prog_delta,
   $tmp_delta_icon,
   $tmp_old_prog,
   $tmp_old_cpu,
   $total_work_processes,    #total number of workprocesses in target tree
   $total_work_units,        #total number of work units received from SETI
   $total_results,           #total number of results sent back to SETI
   $total_cpu_time,          #total CPU time as reported to SETI
#   $total_cpu_time_str,      #total CPU time as reported to SETI
#   $total_cpu_year_str,
   $tmp_pcnt_per_hour,       #% CPU per hour of calculations (calculated by SETIMon)
   $tmp_time_left,           #time left until 100% reached   (calculated by SETIMon)
   $SETI_DATA_FILE,          #output of collected data into flat text file
   $sDataUpdated,            #time of last update (create data file)
   $sHTMLUpdated,            #time of last cgi run

   $sort_type,               #sort type, transmitted by browser or set to default (node)
   $cgi_dataset,             #cgi container
   
   $setihome,                #location of seti homepage 
   $setistat                 #location of seti statistics
  );

$setihome        = "http://setiathome.ssl.berkeley.edu";
$setistat        = "http://setiathome.ssl.berkeley.edu";
#temporary moved to:
#$setistat        = "http://iosef.ssl.berkeley.edu";

$pic_dir         = "/seti/icons";                 # subdir of webserver
$target_dir      = "/opt/pub/seti";               # absolute path to seti data
$pic_top_name    = "$pic_dir/seti.jpg";           # top pic
$pic_run_name    = "$pic_dir/delta_green.gif";    # state 'on'
$pic_halt_name   = "$pic_dir/delta_red_anim.gif"; # state 'off'
$nodes_file_name = "$target_dir/nodesdata.txt";   # scanned infos
$myemail         = "your\@domain.de";             # access official seti state
$targethost      = "wega";                        # where are the infos

#$bgcolor  = '"#006666"';  # background color
$bgcolor  = '#000000';    # background color
$bglabel  = '#303080';    # background color of labels
$bgdata   = '#303030';    # background color of data
$ftcolor  = '#FFFFFF';    # font color
$ftlink   = '#cc99ff';    # font color links
$refresh  = '240';        # refresh cycle in s
$wleft    = 60;           # width single
$wright   = 100-$wleft;   # width summary

$sDataUpdated = "";
$total_work_processes=0;
$total_work_units=0;
$total_results=0;
$total_cpu_time=0;

%old_node_data = ();        #init hash

#create CGI object
$cgi_dataset = CGI->new();

#read node data from text file
open (SETI_DATA_FILE, $nodes_file_name)
  or die ("cannot open $nodes_file_name: S!");
# read the whole file, extract user info values,
# and create node array of hashes
while (<SETI_DATA_FILE>) {
  my $ignore;

  #                node      progress    cputime    flops peak
  if (/^OLD/) {
    #found OLD entry line, save into our OLD data hash, and append information
    #later when we've found the corresponding new value.
    my $node;
    $node=(split())[1];
    $old_node_data{$node} = $_;
    #print STDERR "OLD :$_\n";
    
  } elsif (/^NODE:/) {
    #found node data record
    #save grepped values, as we will do a new grep for the OLD: values
    #($tmp_node, $tmp_prog,  $tmp_cpu, $tmp_fl, $tmp_peak) = (split())[1,2,3,4,5];
    ($tmp_node, $tmp_prog,  $tmp_cpu, $tmp_fl, $tmp_peak) = (split())[1,2,3,4,5];
    # convert 'em
    next unless defined($tmp_prog);
    next unless defined($tmp_cpu);
    my $tim_prog = $tmp_prog;
    if(!($tmp_prog =~ /^[+-]?\d*(\.\d*)?$/)) {
        $tmp_prog = '0';
    }
    my $tim_cpu = $tmp_cpu;
    if(!($tmp_cpu =~  /^[+-]?\d*(\.\d*)?$/)) {
        $tmp_cpu = '0';
    }
    #debugging/test
    #printf STDERR "NODE:%s---%s---%s---%s\n", $tmp_prog,$tim_prog,$tmp_cpu,$tim_cpu;
    #print STDERR "NODE:$_\n";
    
    #calculate: percent per hour
    if ($tmp_cpu != 0) {
        $tmp_pcnt_per_hour = ($tmp_prog * 100) / $tmp_cpu * 3600;
    }
    else {
        $tmp_pcnt_per_hour = 0;
    }
    #calculate: time left in seconds
    if ($tmp_prog != 0) {
        $tmp_time_left =  $tmp_cpu * (1 / $tmp_prog - 1);
    }
    else {
        $tmp_time_left =  1000;
    }
    
    # retrieve old node data from the hash
    # we just built from all the OLD: prefix lines
    $tmp_old_prog = '0';
    $tmp_old_cpu  = '0';
    if ( $tmpstr = $old_node_data{$tmp_node} ) {
      $tmpstr =~ /^OLD:/;
      ($tmp_old_prog,$tmp_old_cpu) = (split(/\s/,$tmpstr))[2,3];
    }

    #create anonymous hash for node values and
    #store a reference in the global nodes array
    push (@nodes_data, {
            "node" => $tmp_node,
            "prog" => $tmp_prog,
            "cpu" => $tmp_cpu,
            "per_hour" => $tmp_pcnt_per_hour,
            "time_left" => $tmp_time_left,
            "fl" => $tmp_fl,
            "peak"=> $tmp_peak,
            "old_prog" => $tmp_old_prog,
            "old_cpu" => $tmp_old_cpu
                       }
     );
  } elsif (/^TOTALS:/) {
    #found TOTALS line in data file
    ($total_work_processes, $total_work_units,
     $total_results, $total_cpu_time) = (split())[1,2,3,4] ;
  } elsif (/^DATAupdate: (.+)$/) {
    #found DATAupdate line in data file
    $sDataUpdated = $1;
  }
}
close (SETI_DATA_FILE);

#*******************************************************************
# sorting:
# check for sort parameter, default is sorting by node
#*******************************************************************
$sort_type = $cgi_dataset->param("sort") || "node" ;

#
# Hash with anonymous sort functions accessed
# by the "sort" parameter's value
#
my %sort_fct = ( 
     'node' =>
        sub { $$a{"node"} cmp $$b{"node"} },
     'prog_asc' =>
        sub { $$a{"prog"} <=> $$b{"prog"} },
     'prog_desc' =>
        sub { $$b{"prog"} <=> $$a{"prog"} },
     'cpu_asc' =>
        sub { $$a{"cpu"} <=> $$b{"cpu"} },
     'cpu_desc' =>
        sub { $$b{"cpu"} <=> $$a{"cpu"} },
     'per_hour_asc' =>
        sub { $$a{"per_hour"} <=> $$b{"per_hour"} },
     'per_hour_desc' =>
        sub { $$b{"per_hour"} <=> $$a{"per_hour"} },
     'time_left_asc' =>
        sub { $$a{"time_left"} <=> $$b{"time_left"} },
     'time_left_desc' =>
        sub { $$b{"time_left"} <=> $$a{"time_left"} },
     'peak' =>
        sub { $$a{"peak"} <=> $$a{"peak"} }
);

my $fct = exists $sort_fct{$sort_type} ?
  $sort_fct{$sort_type} : $sort_fct{"node"};

@nodes_data = sort  { &$fct } @nodes_data;


#*******************************************************************
# BUILD HTML page
#*******************************************************************
print header(-refresh=>$refresh);
print start_html(-bgcolor=>$bgcolor, 
                 -text=>$ftcolor, 
		 -link=>$ftlink, 
		 -vlink=>$ftlink, 
		 -title=>"SETI Monitor");

#*******************************************************************
# heading section
# table w/ logo(s)
# update time entry
#*******************************************************************
$sHTMLUpdated = ctime();    #retrieve current time (at server)

my $total_cpu_temp_str = seconds2hours( $total_cpu_time/$total_results );
my $total_cpu_time_str = seconds2hours( $total_cpu_time );
my $total_cpu_year_str = seconds2years( $total_cpu_time );

print <<END;
\n
<TABLE bgcolor="$bgcolor" cellpadding="1" cellspacing="2" border="0" width="99%" align="center">
  <TR>
    <TD valign="top" align="left" bgcolor="$bgcolor" width="$wleft%">
      <A href="setimon.pl">
        <IMG SRC="$pic_top_name" width="275" height="72" border="0"
           alt="Welcome to SETIMon"></A>
    </TD>
    <TD valign="bottom" align="right" bgcolor="$bgcolor" width="$wright%">
      <TABLE bgcolor="$bgcolor" border="0" width="99%" align="center">
        <TR>
          <TH colspan="2" valign="top" align="left" bgcolor="$bglabel">
            Last <A href="setimon.pl">update</A> on $targethost:
          </TH>
        </TR>
        <TR>
          <TD valign="top" align="left" bgcolor="$bgdata">
            [Web Site]:
         </TD>
          <TD valign="top" align="right" bgcolor="$bgdata">
            $sHTMLUpdated
         </TD>
        </TR>
        <TR>
          <TD valign="top" align="left" bgcolor="$bgdata">
            [Process Data]:
         </TD>
          <TD valign="top" align="right" bgcolor="$bgdata">
            $sDataUpdated
         </TD>
        </TR>
      </TABLE>
    </TD>
  </TR>
</TABLE>
<br />
<hr />
<br />
END

#large table for both node data and TOTALS
print <<END;
<TABLE bgcolor="$bgcolor" cellpadding="1" cellspacing="2" border="0" width="99%" align="center">
  <TR>    <!-- #first and only table row -->
    <TD valign="top" align="left" valign="top" width="$wleft%">    <!-- #cell node data table -->
END

#create href links for table headers (with sort param set and color)
my %task_table_headers;
my $url = $cgi_dataset->url();
$url .="?sort=";
%task_table_headers = (
   'node',
       a( {-href=>$url . "node"},
          "<FONT COLOR=$ftcolor>NODE</FONT>" ),
   'prog',
       "% Done<br />". 
       a( {-href=> $url ."prog_asc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">0..100</FONT>" ).
          "<FONT SIZE=\"-2\">&nbsp; &nbsp;</FONT>".
       a( {-href=> $url . "prog_desc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">100..0</FONT>" ),
   'per_hour',
       "% / Hour<br />".
       a( {-href=> $url . "per_hour_asc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">0..max</FONT>" ).
          "<FONT SIZE=\"-2\">&nbsp; &nbsp;</FONT>".
       a( {-href=> $url . "per_hour_desc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">max..0</FONT>" ),
   'cpu_time',
       "CPU Time<br />".
       a( {-href=> $url . "cpu_asc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">0..max</FONT>" ).
          "<FONT SIZE=\"-2\">&nbsp; &nbsp;</FONT>".
       a( {-href=> $url ."cpu_desc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">max..0</FONT>" ),
   'time_left',
       "Time left<br />".
       a( {-href=> $url . "time_left_asc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">0..max</FONT>" ).
          "<FONT SIZE=\"-2\">&nbsp; &nbsp;</FONT>".
       a( {-href=> $url."?sort=time_left_desc"},
          "<FONT COLOR=$ftcolor SIZE=\"-2\">max..0</FONT>" ),
   'peak',
       a( {-href=>$url . "peak"},
          "<FONT COLOR=$ftcolor>Peak</FONT>" )
);

my $head;

foreach ('node','prog','per_hour','cpu_time','time_left','peak') {
  $head .= "<th valign=\"top\" bgcolor=$bglabel\n" .
    "<FONT COLOR=$ftcolor> $task_table_headers{$_}</FONT></th>\n";
} # foreach

#now print the table

print <<END;
  <table bgcolor="$bgcolor" cellpadding="1" cellspacing="2" border="0">\n
  <!-- # id task prio status -->
    <tr>$head</tr>\n
END

foreach $rtmp_node (@nodes_data) {
  print "<tr>";

  #calculate percent progress change
  $tmp_prog_delta = ($$rtmp_node{"prog"} - $$rtmp_node{"old_prog"}) * 100;

  #define icon for progress change
  if ($tmp_prog_delta > 0.001) {
    $tmp_delta_icon = "<IMG SRC=\"$pic_run_name\" " . "ALT=\"Progress: OK\">";
  }
  else {
    $tmp_delta_icon = "<IMG SRC=\"$pic_halt_name\" ". "ALT=\"Progress: Not OK\">";
  }

  #               node
  #               progress in percent, subscript (progress delta)
  #               %per hour (calulated!)
  #               cputime used so far
  #               cputime time left (calulated!)
  #               peak
  printf STDOUT ("<td bgcolor=$bglabel>$tmp_delta_icon  &nbsp; %s</td>\n
                    <td bgcolor=$bgdata align=right>%.3f <SUB>(+%.3f)</SUB> &nbsp;</td>\n
                    <td bgcolor=$bgdata align=right>%.2f &nbsp;</td>\n
                    <td bgcolor=$bgdata align=right>%s &nbsp;</td>\n
                    <td bgcolor=$bgdata align=right>%s &nbsp;</td>\n
                    <td bgcolor=$bgdata align=right>%.2f</td>\n",
         $$rtmp_node{"node"},
         $$rtmp_node{"prog"}*100,
         $tmp_prog_delta,
         $$rtmp_node{"per_hour"},
         seconds2hours( $$rtmp_node{"cpu"} ),
         seconds2hours( $$rtmp_node{"time_left"} ),
         $$rtmp_node{"peak"}
        );
  print "    </tr>\n\n";
}                #foreach $entry

print <<END;
  </table>    <!-- #end of node status table -->
      </TD>   <!-- #end of table cell nodes data-->

      <TD valign="top" align="left" valign="top" width="$wright%">  <!-- #TOTALS table (cell)-->

<TABLE bgcolor="$bgcolor" cellpadding="1" cellspacing="2" border="0" width="99%" align="center">
  <TR>
    <TH colspan="2" valign="top" align="left" bgcolor="$bglabel">
      Totals:
    </TH>
  </TR>
  <TR>
    <TD valign="top" align="left" bgcolor="$bgdata">
      Registered processes:<br />
    </TD>
    <TD valign="top" align="right" bgcolor="$bgdata">
      $total_work_processes<br />
    </TD>
  </TR>
      <!--
      no more supported<br />
  <TR>
    <TD valign="top" align="left" bgcolor="$bgdata">
      Total work units (from SETI):<br />
    </TD>
    <TD valign="top" align="right" bgcolor="$bgdata">
      $total_work_units<br />
    </TD>
  </TR>
      //-->
  <TR>
    <TD valign="top" align="left" bgcolor="$bgdata">
      Total results sent:<br />
    </TD>
    <TD valign="top" align="right" bgcolor="$bgdata">
      $total_results<br />
    </TD>
  </TR>
  <TR>
    <TD valign="top" align="left" bgcolor="$bgdata">
      Medium CPU time:<br />
    </TD>
    <TD valign="top" align="right" bgcolor="$bgdata">
      $total_cpu_temp_str [hours]<br />
    </TD>
  </TR>
  <TR>
    <TD valign="top" align="left" bgcolor="$bgdata">
      Total CPU time:<br />
    </TD>
    <TD valign="top" align="right" bgcolor="$bgdata">
      $total_cpu_time_str [hours]<br />
    </TD>
  </TR>
  <TR>
    <TD valign="top" align="left" bgcolor="$bgdata">
      &nbsp;<br />
    </TD>
    <TD valign="top" align="right" bgcolor="$bgdata">
       $total_cpu_year_str [years]<br />
    </TD>
  </TR>
  <!-- a bit of space
  <TR>
    <TH colspan="2" valign="top" align="left" bgcolor="$bgcolor">
      &nbsp;
    </TH>
  </TR>
  //-->
  <TR>
    <TH colspan="2" valign="top" align="left" bgcolor="$bglabel">
      Links:
    </TH>
  </TR>
  <TR>
    <TD colspan="2" valign="top" align="left" bgcolor="$bgdata">
      <A HREF=$setihome/index.html>SETI Home</A><br />
    </TD>
  </TR>
  <TR>
    <TD colspan="2" valign="top" align="left" bgcolor="$bgdata">
      <A HREF=$setihome/stats.html>Current Statistics</A><br />
    </TD>
  <TR>
    <TD colspan="2" valign="top" align="left" bgcolor="$bgdata">
      <A HREF=$setihome/stats/team/team_type_1.html>Small Company Teams</A><br />
    </TD>
  </TR>
  <TR>
    <TD colspan="2" valign="top" align="left" bgcolor="$bgdata">
      <A HREF=$setihome/stats/team/team_type_2.html>Medium Company Teams</A><br />
    </TD>
  </TR>
  <TR>
    <TD colspan="2" valign="top" align="left" bgcolor="$bgdata">
      <A HREF=$setihome/stats/team/team_type_3.html>Large Company Teams</A><br />
    </TD>
  </TR>
  <!-- a bit of space
  <TR>
    <TH colspan="2" valign="top" align="left" bgcolor="$bgcolor">
      &nbsp;
    </TH>
  </TR>
  //-->
  <TR>
    <TH colspan="2" valign="top" align="left" bgcolor="$bglabel">
      Your personal stats:
    </TH>
  </TR>
  <TR>
    <TD colspan="2" valign="top" align="left" bgcolor="$bgdata">
<form action="$setistat/cgi-bin/cgi">
email address<br />
<input name="email" value="$myemail" size="25" />
<input type="submit" value="OK" />
<input type="hidden" name="cmd" value="user_stats_new" />
</form>
    </TD>
  </TR>
</TABLE>
      </TD>       <!-- #end of TOTALS table cell -->
    </TR>
  </TABLE>        <!-- #end of main table -->
END

print end_html();


#*******************************************************************
#    misc. functions
#*******************************************************************

#take a given time in seconds and format a hr:min string
sub seconds2hours {
  my $tmp_secs;
  my $tmp_hrs;
  my $tmp_str;
  use integer;

  $tmp_hrs = $_[0] / 3600;
  $tmp_secs = ($_[0] % 3600) / 60;

  $tmp_str = sprintf("% 2u:%02u",$tmp_hrs,$tmp_secs);
}

sub seconds2years {
  my $tmp_str;

  $tmp_str = sprintf("%.2f",$_[0] / 31536000);
}
