#!/usr/bin/perl
#
# monshow - concise, user view-based opstatus summary
#
# Jim Trocki, trockij@transmeta.com
#
# $Id: monshow,v 1.10 1999/04/25 16:56:58 trockij Exp $
#
#    Copyright (C) 1998, Jim Trocki
#
#    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.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
use Getopt::Long;
use Socket;
use English;
use Mon::Client;

GetOptions (\%opt, "showall", "auth", "help", "full", "disabled", "state",
	"rcfile=s", "login=s", "server=s", "port=i", "prot=s", "old");

%OPSTAT = %Mon::Client::OPSTAT;
$WORD = '[a-zA-Z0-9_-]+';
$= = 1000;

#
# forward declarations
#
sub usage;
sub secs_to_hms;
sub get_auth;
sub read_cf;

$cl = Mon::Client->new;

$RC = $opt{"rcfile"} || "$ENV{HOME}/.monshowrc";

if ($opt{"help"}) {
    usage;
    exit 1;
}

$SIG{INT} = \&handle_sig;
$SIG{TERM} = \&handle_sig;

read_cf ($RC) if (-f $RC);

$cl->host ($opt{"server"}) if ($opt{"server"});
$cl->port ($opt{"port"}) if ($opt{"port"});
$cl->prot ($opt{"prot"}) if ($opt{"prot"});
if ($opt{"old"}) {
    $cl->prot ("0.37.0");
    $cl->port (32777);
}

$ALL           = 1 if ($opt{"showall"} || ! -f $RC);
$SHOW_DISABLED = 1 if ($opt{"disabled"});
$SHOW_STATE    = 1 if ($opt{"state"});

get_auth() if ($opt{"auth"});

$cl->connect;
if ($cl->error) {
    die "Could not connect to server: " . $cl->error . "\n";
}

#
# authenticate self to the server if necessary
#
if ($opt{"auth"} && !defined ($cl->login)) {
    die "Could not log in: " . $cl->error . "\n";
}

#
# get disabled things
#
%disabled = $cl->list_disabled;
if ($cl->error) {
    print STDERR "could not get disabled: " . $cl->error . "\n";
    $cl->disconnect;
    exit 1;
}

#
# get stats
#
($running, $t) = $cl->list_state;
if ($cl->error) {
    print STDERR "could not get state: " . $cl->error . "\n";
    $cl->disconnect;
    exit 1;
}

if ($running) {
    $STATE = $t;
} else {
    $STATE = "scheduler stopped since " . localtime ($t);
}

#
# get opstatus
#
%opstatus = $cl->list_opstatus;
if ($cl->error) {
    print STDERR "could not get opstatus: " . $cl->error . "\n";
    $cl->disconnect;
    exit 1;
}

#
# log out
#
if (!defined $cl->disconnect) {
    print STDERR "error while disconnecting: " . $cl->error . "\n";
    exit 1;
}

#
# display everything real nice
#
if ($ALL || @RC == 0) {
    foreach $g (keys %opstatus) {
	foreach $s (keys %{$opstatus{$g}}) {
	    push (@WHAT, [$g, $s]);
	}
    }
} else {
    @WHAT = @RC;
}

foreach $l (@WHAT) {
    ($group, $service) = ($l->[0], $l->[1]);

    next if (!defined $opstatus{$group}->{$service}->{"opstatus"});

    if (defined $disabled{"watches"}->{$group}) {
    	next;

    } elsif (defined $disabled{"services"}->{$group}->{$service}) {
	next;

    } elsif ($opstatus{$group}->{$service}->{"opstatus"} ==
		$OPSTAT{"untested"}) {
    	push (@UNTESTED, [$group, $service]);

    } elsif ($opstatus{$group}->{$service}->{"opstatus"} == $OPSTAT{"ok"}) {
    	push (@OK, [$group, $service]);

    } elsif ($opstatus{$group}->{$service}->{"opstatus"} == $OPSTAT{"fail"}) {
    	push (@FAIL, [$group, $service]);
    }
}

if ($opt{"full"}) {
    @FINAL = (@FAIL, @OK, @UNTESTED);
} else {
    @FINAL = (@FAIL, @UNTESTED);
}

foreach $l (@FINAL) {
    ($group, $service) = ($l->[0], $l->[1]);

    if (!defined $opstatus{$group}->{$service}->{"opstatus"}) {
    	print "watch $group and service $service not in opstatus table\n";
	next;
    }

    $STATUS = "unknown";

    if ($opstatus{$group}->{$service}->{"opstatus"} == $OPSTAT{"untested"}) {
    	$STATUS = "untested";
	$last = "last_success";
    } elsif ($opstatus{$group}->{$service}->{"opstatus"} == $OPSTAT{"ok"}) {
    	$STATUS = "-";
	$last = "last_success";
    } elsif ($opstatus{$group}->{$service}->{"opstatus"} == $OPSTAT{"fail"}) {
    	$STATUS = "FAIL";
	$last = "last_failure";
    }

    if ($displayed{$group}) {
	$GROUP = "";
    } else {
	$GROUP = $group;
    }
    $displayed{$group} = 1;

    $SERVICE = $service;
    $TIME = secs_to_hms (time - $opstatus{$group}->{$service}->{$last});
    $NEXT = secs_to_hms ($opstatus{$group}->{$service}->{"timer"});
    $SUMMARY = $opstatus{$group}->{$service}->{"last_summary"};

    $fmt = "format STDOUT =
@<<<<<<<<<<<<<< @<<<<<<<<<<< @<<<<<<<<<  @<<<<<<  @<<<<<<<<< @";
    $fmt .= "<" x length($SUMMARY);
    $fmt .= '
$GROUP, $SERVICE, $STATUS, $TIME, $NEXT, $SUMMARY
.
';
    eval $fmt;
    write;
}

if ($SHOW_STATE) {
    print "\nSTATE: $STATE\n";
}

if ($SHOW_DISABLED) {
    if (keys %{$disabled{"watches"}}) {
	print "\nDISABLED WATCHES:\n";
	foreach $watch (keys %{$disabled{"watches"}}) {
	    print "$watch\n";
	}
    }

    foreach $watch (keys %{$disabled{"services"}}) {
	foreach $service (keys %{$disabled{"services"}{$watch}}) {
	    push (@disabled_services, "$watch $service");;
	}
    }
    if (@disabled_services) {
	print "\nDISABLED SERVICES\n";
	for (@disabled_services) {
	    print "$_\n";
	}
    }

    if (keys %{$disabled{"hosts"}}) {
	print "\nDISABLED HOSTS:\n";
	foreach $group (keys %{$disabled{"hosts"}}) {
	    @HOSTS = ();
	    foreach $host (keys %{$disabled{"hosts"}{$group}}) {
		push (@HOSTS, $host);
	    }
	    printf ("%-15s %s\n", $group, "@HOSTS");
	}
    }
}

print "\n";

exit;

#-----------------------------------------------------------------------------

format STDOUT_TOP =

GROUP           SERVICE      STATUS      LAST     NEXT       SUMMARY
.


#
# usage
#
sub usage {
    print <<EOF;

usage: monshow [--auth] [--showall] [--full] [--login user] [--disabled]
               [--state] [--server host] [--port num] [--rcfile file]
    --showall      do not read rcfile and show opstatus of all services
    --full         show disabled, failures, successes, and untested
    --auth         authenticate to mon server
    --disabled     show disabled
    --state        show server state
    --prot ver     set protocol version, must match 1.2.3 format
    --old          use old protocol (0.37.0) and old port (32777)
    --login user   use "login" as username while authenticating
    --server host  use "host" as monhost, instead of MONHOST
    --port num     use "num" as port number instead of default
    --rcfile file  use "file" as config file instead of \$HOME/.monshowrc

EOF
}


#
# signal handler
#
sub handle_sig {
    system "stty echo";
    exit;
}


#
# get auth info
#
sub get_auth {
    if ($opt{"login"}) {
	$cl->username ($opt{"login"});

    } else {
	die "could not determine username\n"
	    unless defined ($cl->username (getpwuid($EUID)));
    }

    if (-t STDIN) {
	system "stty -echo";
	print "Password: ";
	chop ($PASS = <STDIN>);
	print "\n";
	system "stty echo";
	die "invalid password\n" if ($PASS =~ /^\s*$/);
	$cl->password ($PASS);

    } elsif (!@ARGV) {
	$cmd = <$H>;
	while (defined ($cmd) && $cmd =~ /user=|pass=/i) {
	    chomp $cmd;
	    if ($cmd =~ /^user=(\S+)$/i) {
		$cl->username ($1) if (!defined ($cl->username));
	    } elsif ($cmd =~ /^pass=(\S+)$/i) {
		$cl->password ($1);
	    }
	    
	    $cmd = <$H>;
	}

    }

    die "inadequate authentication information supplied\n"
	if ($cl->username eq "" || $cl->password eq "");
}

#
# config file
#
sub read_cf {
    my ($group, $service);

    open (IN, $RC) || die "could not read $RC: $!\n";

    while (<IN>) {
    	next if (/^\s*#/ || /^\s*$/);
	chomp;
	s/^\s*//;
	s/\s*$//;
	if (/^set \s+ (.*)/ix) {
	    my $cmd = $1;
	    if ($cmd eq "show-disabled") {
	    	$SHOW_DISABLED = 1;
	    } elsif ($cmd eq "show-state") {
	    	$SHOW_STATE = 1;
	    } else {
	    	print STDERR "unknown set, line $.\n";
	    }
	} else {
	    ($group, $service) = split;
	    push (@RC, [$group, $service]);
	}
    }
    close (IN);
}

sub secs_to_hms {
    my ($s) = @_;
    my ($dd, $hh, $mm, $ss);

    $dd = int ($s / 86400);
    $s -= $dd * 86400;

    $hh = int ($s / 3600);
    $s -= $hh * 3600;

    $mm = int ($s / 60);
    $s -= $mm * 60;

    $ss = $s;

    if ($dd == 0) {
        sprintf("%02d:%02d", $hh, $mm);
    } else {
        sprintf("%d days, %02d:%02d", $dd, $hh, $mm);
    }
}
