#!/usr/bin/perl
#
# This script is a component of Warewulf,
# http://www.runlevelzero.net/greg/warewulf
#
#########################################################################
#
# Copyright (c) 2003, The Regents of the University of California, through
# Lawrence Berkeley National Laboratory (subject to receipt of any
# required approvals from the U.S. Dept. of Energy).  All rights reserved.
#
# 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.
#
# The GNU GPL Document can be found at:
# http://www.gnu.org/copyleft/gpl.html
#
#########################################################################
#
# Written and maintained by:
#       Greg Kurtzer, <gmkurtzer@lbl.gov>

BEGIN {
   push (@INC, "/usr/lib/warewulf/");
   push (@INC, "/usr/lib64/warewulf/");
}
use Warewulf;
use Getopt::Long;
use IO::Socket;
use Term::Screen;
use Sys::Hostname;
#use strict;

Getopt::Long::Configure ("bundling");

my (
   $usage,
   $help,
   $only_summary,
   $quiet,
   $entry,
   $value,
   $nodename,
   %nodestatus,
   %nodes,
   $string,
   $summary,
   $total_cpu,
   $nodes_up,
   $nodes_down,
   $nodes_disabled,
   $nodes_unavailable,
   $nodes_unknown,
   @ready,
   @unavail,
   @other,
   @error,
   @down,
   $master,
   @nodes,
   @node_config,
   %filter,
   %node_access,
   @master_config,
   %mastercfg,
   %swapstat,
   %config,
   @nodes_ready,
   @nodes_down,
   @nodes_unavailable,
   @nodes_unknown,
   $uptime_high,
   $uptime_low,
   $uptime_avg,
   $uptime_total,
   $tmp_uptime,
   $load_high,
   $load_low,
   $load_avg,
   $load_total,
   $tasks_high,
   $tasks_low,
   $tasks_avg,
   $tasks_total,
   $cpu_high,
   $cpu_low,
   $cpu_avg,
   $cpu_total,
   $mem_high,
   $mem_low,
   $mem_avg,
   $mem_total,
   $cpu_mhz,
   $mem_avail,
   $mem_used,
   $uptime,
   $l1,
   $l2,
   $l3,
   $l4,
   $l5,
   $rows,
   $key,
   $Second,
   $Minute,
   $Hour,
   $Day,
   $Month,
   $Year,
   $WeekDay,
   $DayOfYear,
   $IsDST,
   $time,
   $hostname,
   $net_h,
   $cpu_sort_util,
);

$usage = "USAGE: $0 [options]
  About:
    wwtop is the Warewulf 'top' like monitor. It shows the nodes ordered by
    the highest utilization, and important statics about each node and
    general summary type data. This is an interactive curses based tool.

  Options:
   -h, --help       Show this banner

  This tool is part of the Warewulf cluster distribution
     http://warewulf-cluster.org/
";

GetOptions(
   'help|h'      => \$help,
);

if ( $help ) {
   print $usage;
   exit;
}

%config = &client_conf();
if ( $ARGV[0] ) {
   $master = $hostname = $ARGV[0];
} else {
   $master = $config{'warewulf master'};
   if ( $master eq "localhost" ) {
      $hostname = hostname();
   } else {
      $hostname = $config{'warewulf master'};
   }
}

%nodestatus = &node_status($master);
%filter = &users_filter();
#die "\rAck! Help me, please!\n\n\r" unless %nodestatus;

sub cpu_sort {
	if ( $cpu_sort_util == 0 ) {
		$a<=>$b;
	} else {
		$nodestatus{$b}{CPUUTIL} <=> $nodestatus{$a}{CPUUTIL};
	}
}

$scr = new Term::Screen;
unless ($scr) { die "Ack! Couldn't get control of the screen!\n"; }
$scr->clrscr();

$nodes_up = $nodes_error = $nodes_disabled = $nodes_down = $nodes_unavailable = '0';

while (1) {
   $scr->resize();
   $rows = $scr->rows();
   $line=7;
   $nodes_shown = $nodes_total = $nodes_up = $nodes_down = $nodes_unavailable = $nodes_unknown = 0;
   @nodes_ready = ();
   @nodes_down = ();
   @nodes_unavailable = ();
   @nodes_unknown = ();
   $uptime_high = $uptime_low = $uptime_total = ();
   $load_high = $load_low = $load_total = $load_avg = ();
   $tasks_high = $tasks_low = $tasks_total = $tasks_avg = ();
   $cpu_high = $cpu_low = $cpu_total = $cpu_avg = $cpu_mhz = ();
   $mem_high = $mem_low = $mem_total = $mem_avg = $mem_used = ();

   foreach ( sort keys %nodestatus ) {
      $nodes_total++;
      if ( ! $filter{$_} and defined %filter ) {
         next;
      }
      if ( $nodestatus{$_}{NODESTATUS} eq 'READY' ) {
         push(@nodes_ready, $_);
         $total_cpu += $nodestatus{$_}{CPUUTIL};
         $tmp_uptime = $nodestatus{$_}{UPTIME} / 86400;
         if ( $uptime_high < $tmp_uptime or ! $uptime_high ) {
            $uptime_high = $tmp_uptime;
         }
         if ( $uptime_low > $tmp_uptime or ! $uptime_low ) {
            $uptime_low = $tmp_uptime;
         }
         $uptime_total += ( $nodestatus{$_}{UPTIME} / 86400 );
         if ( $load_high < $nodestatus{$_}{LOADAVG} or ! $load_high) {
            $load_high = $nodestatus{$_}{LOADAVG};
         }
         if ( $load_low > $nodestatus{$_}{LOADAVG} or ! $load_low ) {
            $load_low = $nodestatus{$_}{LOADAVG};
         }
         $load_total += $nodestatus{$_}{LOADAVG};
         if ( $tasks_high < $nodestatus{$_}{PROCS} or ! $tasks_high) {
            $tasks_high = $nodestatus{$_}{PROCS};
         }
         if ( $tasks_low > $nodestatus{$_}{PROCS} or ! $tasks_low ) {
            $tasks_low = $nodestatus{$_}{PROCS};
         }
         $tasks_total += $nodestatus{$_}{PROCS};
         if ( $cpu_high < $nodestatus{$_}{CPUUTIL} or ! $cpu_high) {
            $cpu_high = $nodestatus{$_}{CPUUTIL};
         }
         if ( $cpu_low > $nodestatus{$_}{CPUUTIL} or ! $cpu_low ) {
            $cpu_low = $nodestatus{$_}{CPUUTIL};
         }
         if ( $mem_high < $nodestatus{$_}{MEMUSED} or ! $mem_high) {
            $mem_high = $nodestatus{$_}{MEMUSED};
         }
         if ( $mem_low > $nodestatus{$_}{MEMUSED} or ! $mem_low) {
            $mem_low = $nodestatus{$_}{MEMUSED};
         }
         $cpu_total += $nodestatus{$_}{CPUCOUNT};
         $cpu_mhz += $nodestatus{$_}{CPUCLOCK};
         $mem_total += $nodestatus{$_}{MEMTOTAL};
         $mem_avail += $nodestatus{$_}{MEMAVAIL};
         $mem_used += $nodestatus{$_}{MEMUSED};
         $cpu_avg += $nodestatus{$_}{CPUUTIL};
         $nodes_up++;
      } elsif ( $nodestatus{$_}{NODESTATUS} eq 'DOWN' ) {
         push(@nodes_down, $_);
         $nodes_down++;
      } elsif ( $nodestatus{$_}{NODESTATUS} eq 'unavailable' ) {
         push(@nodes_unavailable, $_);
         $nodes_unavailable++;
      } elsif ( $nodestatus{$_}{NODESTATUS} eq 'ERROR' ) {
         push(@nodes_unavailable, $_);
         $nodes_unavailable++;
      } else {
         push(@nodes_unknown, $_);
         $nodes_unknown++;
      }
   }

   if ( $nodes_up ) {
      $uptime_avg = sprintf("%d", $uptime_total / $nodes_up );
      $tasks_avg = sprintf("%d", $tasks_total / $nodes_up );
      $load_avg = sprintf("%.2f", $load_total / $nodes_up );
      $mem_avg = sprintf("%d", $mem_used / $nodes_up );
      $cpu_avg = sprintf("%d", $cpu_avg / $nodes_up );
   }
   
   ($Second, $Minute, $Hour, $Day, $Month, $Year, $WeekDay, $DayOfYear, $IsDST) = localtime(time);
   
   $l1 = sprintf("Top: %0d nodes, %0d cpus, %0d MHz, %0.2f GB mem, %0d procs, uptime %0d days",
   	$nodes_up,
   	$cpu_total,
   	$cpu_mhz,
   	$mem_total / 1024,
   	$tasks_total,
   	$uptime_total,
   );
   
   $l2 = sprintf("Avg:  %3d%s cputil, %6.2f MB memutil, load %0.2f, %3d procs, uptime %3d day(s)",
   	$cpu_avg, "%",
   	$mem_avg,
   	$load_avg,
   	$tasks_avg,
   	$uptime_avg,
   );
   
   $l3 = sprintf("High: %3d%s cputil, %6.2f MB memutil, load %0.2f, %3d procs, uptime %3d day(s)",
   	$cpu_high, "%",
   	$mem_high,
   	$load_high,
   	$tasks_high,
   	$uptime_high,
   );
   
   $l4 = sprintf("Low:  %3d%s cputil, %6.2f MB memutil, load %0.2f, %3d procs, uptime %3d day(s)",
   	$cpu_low, "%",
   	$mem_low,
   	$load_low,
   	$tasks_low,
   	$uptime_low,
   );
   
   $l5 = sprintf("Node status: %4d ready, %4d unavailable, %4d down, %4d unknown",
   	$nodes_up,
   	$nodes_unavailable,
   	$nodes_down,
   	$nodes_unknown);
   $scr->at(0,0);
   $scr->clreol();
   $scr->puts("$l1");
   $scr->at(1,0);
   $scr->clreol();
   $scr->puts("$l2");
   $scr->at(2,0);
   $scr->clreol();
   $scr->puts("$l3");
   $scr->at(3,0);
   $scr->clreol();
   $scr->puts("$l4");
   $scr->at(4,0);
   $scr->clreol();
   $scr->puts("$l5");
   
   $scr->at(6,0);
   $scr->clreol();
   $scr->reverse();
   $scr->puts("Node name    CPU  MEM SWAP Uptime   MHz   ARCH  PROC   Load  Net:KB/s Stats/Util");
   $scr->normal();
   
   foreach ( sort cpu_sort sort @nodes_ready ) {
      if ( $nodes_shown + 6 >= $rows ) {
          last;
      }
      $nodes_shown++;
      $uptime = sprintf("%.2f", $nodestatus{$_}{UPTIME} / 86400);
      if ( $nodestatus{$_}{CPUUTIL} > '95' or 
           $nodestatus{$_}{MEMPERCENT} > '95' or 
           $nodestatus{$_}{LOADAVG} > $nodestatus{$_}{CPUCOUNT} * 2 ) {
         $scr->bold();
      }
      if ( $nodestatus{$_}{CPUUTIL} > '95' or $nodestatus{$_}{MEMPERCENT} > '95' ) {
         $status = "|=======>|";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '82' or $nodestatus{$_}{MEMPERCENT} > '82' ) {
         $status = "|======> |";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '69' or $nodestatus{$_}{MEMPERCENT} > '69' ) {
         $status = "|=====>  |";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '56' or $nodestatus{$_}{MEMPERCENT} > '56' ) {
         $status = "|====>   |";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '43' or $nodestatus{$_}{MEMPERCENT} > '43' ) {
         $status = "|===>    |";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '30' or $nodestatus{$_}{MEMPERCENT} > '30' ) {
         $status = "|==>     |";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '17' or $nodestatus{$_}{MEMPERCENT} > '17' ) {
         $status = "|=>      |";
      } elsif ( $nodestatus{$_}{CPUUTIL} > '4' or $nodestatus{$_}{MEMPERCENT} > '4' ) {
         $status = "|>       |";
      } else {
         $status = "|  IDLE  |";
      }
      $net_h = $nodestatus{$_}{NETTRANSMIT} + $nodestatus{$_}{NETRECIEVE};
      $net_h =~ s/(\d)(\d\d\d)$/$1,$2/g;
      $out = sprintf("%-11.11s %4s %4s %4s %6.6s %5.5s %7.7s %4.4s %6.6s %9.9s %10.10s",
   	$_, 
   	"$nodestatus{$_}{CPUUTIL}%", 
   	"$nodestatus{$_}{MEMPERCENT}%", 
   	"$nodestatus{$_}{SWAPPERCENT}%", 
   	$uptime, 
   	$nodestatus{$_}{CPUCLOCK}, 
   	$nodestatus{$_}{MACHINE}, 
   	$nodestatus{$_}{PROCS}, 
   	$nodestatus{$_}{LOADAVG}, 
   	$net_h,
   	$status);
      $scr->at($line,0);
      $scr->clreol();
      $scr->puts("$out");
      $scr->normal();
      $line++;
   
   }

   foreach ( sort @nodes_unavailable ) {
      if ( $nodes_shown + 6 >= $rows ) {
          last;
      }
      $nodes_shown++;
      $uptime = sprintf("%.2f", $nodestatus{$_}{UPTIME} / 86400);
      $out = sprintf("%-11s %4s %4s %4s %6.6s %5.5s %7.7s %4.4s %6.6s %9.9s %10.10s",
   	$_, "----", "----", "----", "------", "-----", "-------", "----", "------", "-------", "|NOACCESS|");
      $scr->at($line,0);
      $scr->clreol();
      $scr->puts("$out");
      $line++;
   
   }
   foreach ( sort @nodes_down) {
      if ( $nodes_shown + 6 >= $rows ) {
          last;
      }
      $nodes_shown++;
      $uptime = sprintf("%.2f", $nodestatus{$_}{UPTIME} / 86400);
      $out = sprintf("%-11s %4s %4s %4s %6.6s %5.5s %7.7s %4.4s %6.6s %9.9s %10.10s",
   	$_, "----", "----", "----", "------", "-----", "-------", "----", "------", "-------", "|  DOWN  |");
      $scr->at($line,0);
      $scr->clreol();
      $scr->puts("$out");
      $line++;
   
   }
   foreach ( sort @nodes_unknown) {
      if ( $nodes_shown + 6 >= $rows ) {
          last;
      }
      $nodes_shown++;
      $uptime = sprintf("%.2f", $nodestatus{$_}{UPTIME} / 86400);
      $out = sprintf("%-11s %4s %4s %4s %6.6s %5.5s %7.7s %4.4s %6.6s %9.9s %10.10s",
   	$_, "----", "----", "----", "------", "-----", "-------", "----", "------", "-------", "|UN-KNOWN|");
      $scr->at($line,0);
      $scr->clreol();
      $scr->puts("$out");
      $line++;
   
   }
   $time = sprintf("%02d:%02d:%02d", $Hour,$Minute,$Second);
   $scr->at(5,0);
   $scr->clreol();
   $scr->puts("$time ");
   $scr->puts("$hostname> ");



   if ( $scr->key_pressed(1) ) {
      $key = $scr->getch();
      if ( $key eq 'q' ) {
         $scr->flush_input();
         $scr->at(5,0);
         $scr->clreol();
         $scr->bold();
         $scr->puts("Quitting!");
         $scr->normal();
         $scr->at($rows,0);
         print "\n";
         exit;
      } elsif ( $key eq 's' ) {
         $scr->flush_input();
         $scr->at(5,0);
         $scr->clreol();
         $scr->bold();
         if ( $cpu_sort_util == 0 ) {
            $scr->puts("Sorting by utilization...");
            $cpu_sort_util = '1';
         } else {
            $scr->puts("Sorting by node name...");
            $cpu_sort_util = '0';
         }
         sleep 1;
         $scr->normal();
         $scr->at($rows,0);
      } elsif ( $key eq 'r' ) {
         $scr->clrscr();
         $scr->resize();
         $rows = $scr->rows();
         $scr->flush_input();
      } elsif ( $key eq 'p' ) {
         $scr->flush_input();
         $scr->at(5,0);
         $scr->clreol();
         $scr->bold();
         $scr->puts("Press any key to unpause");
         $scr->normal();
	 $scr->key_pressed(100000);
         $scr->flush_input();
      } else {
         $scr->at(5,0);
         $scr->clreol();
         $scr->bold();
         $scr->puts("Huh?! ([q]uit, [p]ause, [r]draw, [s]ort by utilization)");
         $scr->normal();
         sleep 3;
         $scr->flush_input();
      }
   }
   %nodestatus = &node_status($master);
   %filter = &users_filter();
#   die "\rAck! Help me, please!\n\n\r" unless %nodestatus;
}
