#!/usr/local/bin/perl
#------------------------------------------------------------------
# 
# Karma Copyright (C) 1999  Sean Hull <shull@pobox.com>
#
#   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
#
#
#------------------------------------------------------------------
#
# karmagentd
#
# This script runs on the server machine (of each database you're
# monitoring, to get status of the OS (load average, % idle)
# and populate karma_os_stats with this information.  It also
# gathers information about any new ORA- errors in the alert.log
# and populates the karma_alertlog_errors table with it.
#
# Notes:  You don't HAVE to use this daemon at all.  If you don't,
# the OS and alert log columns will not be displayed in karma.
#
# You need to run one of these daemons for each instance which you
# want to gather these stats on (for now).  Sorry.
#
# Needs to run as the Oracle user...
#-----------------------------------------------------------------


$VERSION = "0.7.0";

#----------------------------------------------
# 
# PROTOTYPES
#
#----------------------------------------------
sub checkLine ($);
sub readPosition ($);
sub writePosition ($$);
sub debugPrint ($$);
sub print_help ();

use Getopt::Std;

BEGIN {
    unless (eval "require DBI") {
	print 
	    "You must have DBI installed to use karma.\n",
	    "Please install it first, and try again.\n";
	exit;
    }
}

BEGIN {
    unless (eval "require DBD::Oracle") {
	print
	    "You must have DBD::Oracle installed to use karma.\n",
	    "Please install it first, and try again.\n";
	exit;
    }
}


use IO::File;
require 5.004;

$CMD_UPTIME = "/usr/bin/uptime";

#
# get the command line options
#
$opt_h = undef;
$opt_t = undef;
$opt_u = undef;
$opt_f = undef;
$opt_r = undef;
$opt_j = undef;
$opt_p = undef;
$opt_k = undef;
$opt_s = undef;
$opt_b = undef;
$opt_h = undef;
$opt_d = undef;
$opt_w = undef;
$opt_v = undef;
$opt_l = undef;
getopts('ht:u:f:a:rp:j:s:b:h:d:k:vwl:');

if ($opt_v) {
    print_version ();
}
if ($opt_w) {
    print_warranty ();
}
if ($opt_h) {
    print_help ();
}

my $DEBUG_LEVEL = 0;
if ($opt_d) {
    $DEBUG_LEVEL = $opt_d;
}

if ($opt_s) {
    $ENV{ORACLE_SID} = $opt_s;
}
if ($opt_b) {
    $ENV{ORACLE_BASE} = $opt_b;
}
if ($opt_h) {
    $ENV{ORACLE_HOME} = $opt_h;
}

my $TNS = "";
if ($opt_t) {
    $TNS = $opt_t;
} elsif (defined ($ENV{DBI_DSN})) {
    $TNS = $ENV{DBI_DSN};
}
#
# default to the OFA location
#
my $ALERTLOG_FILE = "$ENV{ORACLE_BASE}/admin/$ENV{ORACLE_SID}/bdump/alert_$ENV{ORACLE_SID}.log";
if ($opt_a) {
    $ALERTLOG_FILE = $opt_a;
}
my $PASS = "manager";
if ($opt_p) {
    $PASS = $opt_p;
} elsif (defined ($ENV{DBI_PASS})) {
    $PASS = $ENV{DBI_PASS};
} else {
    print ("Password: ");
    $PASS = <STDIN>;
    chomp ($PASS);
}

my $USER = "karma";
if ($opt_u) {
    $USER = $opt_u;
} elsif (defined ($ENV{DBI_USER})) {
    $USER = $ENV{DBI_USER};
}
my $FREQUENCY = 60;
if ($opt_f) {
    $FREQUENCY = $opt_f * 60;
}
my $POSITION_FILE = "karmagent.sav";
if ($opt_k) {
    $POSITION_FILE = $opt_k;
}

#
# set the logfile name
#
$logfile_name = "-";
if (defined ($opt_l)) {
    $logfile_name = "$opt_l";
}


$logfile = new IO::File ">>$logfile_name";
if (not (defined $logfile)) {
    $logfile = new IO::File ">>-";
    log_message ("Cannot open logfile: $logfile_name, using STDOUT");

}

#
# signal handlers...
#
$SIG{TERM} = \&catch_term;

my $cALERT_POSITION = "alert_position";
my $cBEGINNING_OF_FILE = 0;

my @uptimes = ();
my $UPCMD = "uptime";
my $uptimeString = "";

print ("\nStarting karma monitoring daemon...\n");
debug_print ("DEBUG_LEVEL:$DEBUG_LEVEL\n");
debug_print ("-- Environment Settings --\n");
debug_print ("ORACLE_BASE:$ENV{ORACLE_BASE}\n");
debug_print ("ORACLE_HOME:$ENV{ORACLE_HOME}\n");
debug_print ("ORACLE_SID:$ENV{ORACLE_SID}\n");


#
# database handle
#
debug_print ("Connecting - TNS:$TNS USER:$USER PASS:$PASS\n", 2);
$dbh = DBI->connect ("DBI:Oracle:$TNS", $USER, $PASS);

if ($dbh) {
    log_message ("Successfully connected to $USER\@$TNS...\n");
} else {
    log_message ("Failed to connect to $USER\@$TNS - Exiting.\n");
}

# 
# statements for inserting data into the db
#
my $uptimeStatement =
    "INSERT INTO karma_os_stats VALUES (sysdate, ?, ?, ?, 0)";
my $alertlogStatement =
    "INSERT INTO karma_alertlog_errors VALUES (sysdate, ?, ?, ?)";

#
# prepare the statements
#
my $uptimeSth = $dbh->prepare ($uptimeStatement);
my $alertlogSth = $dbh->prepare ($alertlogStatement);

my $prevTime = getDayMinutes (time ());
my $currTime = 0;

my $currPosition = 0;
if ($opt_j) {
    $currPosition = $opt_j;
} else {
    $currPosition = readPosition ($POSITION_FILE);
}

debug_print ("Reading alertlog: $ALERTLOG_FILE\n");
debug_print ("Start reading at byte $currPosition\n");
$alert_file = new IO::File "<$ALERTLOG_FILE";
if (not ($alert_file)) {
    log_message ("Could not open alertlog file.  Exiting.\n");
    exit;
}

seek ($alert_file, $currPosition, $cBEGINNING_OF_FILE);

while (1) {
#    if (($currTime == 0) || ($currTime >= $prevTime + $FREQUENCY)) {

	$prevTime = $currTime;

	# 
	# fetch the most current uptime stats
	#
	$uptimeString = `$CMD_UPTIME`;
	chop ($uptimeString);
	$uptimeString =~ s/^.*://;
	@uptimes = split (',', $uptimeString);
	debug_print ("INSERTING OS VALUES: $uptimes[0], $uptimes[1], $uptimes[2]\n", 2);
	$uptimeSth->execute ($uptimes[0], $uptimes[1], $uptimes[2]);

	#
	# fetch any new alert log errors
	#
	while (<$alert_file>) {
	    
	    $currLine = $_;
	    chomp ($currLine);
	    $lineNum++;	
	    if ($lineNum % 50) {
		debug_print ("LINE:$lineNum\n", 2);
	    }
	    $currPosition = tell;

	    debug_print ("TESTING - $currLine\n", 2);
	    $lineRef = checkLine ($currLine);
	    
	    if ($lineRef->[0]) {
		debug_print ("INSERTING ALERTLOG VALS: $lineRef->[0], $lineRef->[1], $lineRef->[2]\n", 2);
		$alertlogSth->execute ($lineRef->[0],
				       $lineRef->[1],
				       $lineRef->[2]);

	    }
	    
	}

	writePosition ($POSITION_FILE, $currPosition);

	debug_print ("Sleeping for $FREQUENCY seconds...\n");
	sleep ($FREQUENCY);

    $currTime = getDayMinutes (time ());
}




#-----------------------------------------------------------------------
#
# returns the time of day in minutes
#
#-----------------------------------------------------------------------
sub getDayMinutes ($) {
    ($inTime) = @_;


    my $sec =  $min = $hour = $mday = $mon = 
       $year = $wday = $yday = $isdst = 0;
    my $dayMinutes = 0;

    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime ($inTime);

    $dayMinutes = $hour * 60 + $min;

    return $dayMinutes;
}

#-----------------------------------------------------------------------
#
# returns the time of day in minutes
#
#-----------------------------------------------------------------------
sub print_help () {

    print ("\n");
    print (" h - print this help info\n");
    print (" f - fequency in minutes to wakeup & check things (default 1)\n");
    print (" r - reset the alert.log, and truncate it's table\n");
    print (" u - user to login as (default karma)\n");
    print (" p - oracle login password (otherwise you're prompted)\n");
    print (" j - jump j bytes in file (takes precedence over save file)\n");
    print (" t - tnsname of the database to watch (default local)\n");
    print (" a - specify alert.log file (default OFA)\n");
    print (" k - use this file to store seek position\n");
    print (" b - specify ORACLE_BASE (takes precedence over env)\n");
    print (" h - specify ORACLE_HOME (takes precedence over env)\n");
    print (" s - specify ORACLE_SID (takes precedence over env)\n");
    print (" d - debug level (default 0, no debugging)\n");
    print (" w - print the warranty and exit\n");
    print (" l - specify logfile to write messages to \n");
    print ("\n");
    print ("$0 [-h] [-f \#] [-r] [-u karma] [-p pass] [-j \#] [-t DB]\n");
    print ("\t[-a alert.log] [-k karmagent.sav] [-b ORACLE_BASE]\n"); 
    print ("\t[-h ORACLE_HOME] [-s ORACLE_SID] [-d \#]\n");
    
    exit;

}


#--------------------------------------------------------------
#
# checkLine
#
#--------------------------------------------------------------
sub checkLine ($) {
    my ($inLine) = @_;

    my $facility = "";
    my $errNum = 0;
    my $errText = "";

    if ($inLine =~ /^ORA-/) {

	debug_print ("ERRORS --- $inLine\n");
	$facility = "ORA";
	$errNum = $inLine;
	$errNum =~ s/^ORA-//;
	$errNum =~ s/\s.*$//;
	$errText = $inLine;
	$errText =~ s/^ORA-//;
	$errText =~ s/^\d*\s//;
	$noErrors = 0;

	return [$facility, $errNum, $errText];
    }
    
    return [];
}

#--------------------------------------------------------------
#
#
#
#--------------------------------------------------------------
sub readPosition ($) {
    my ($inFile) = @_;

    my $currLine = "";
    my $retPos = 1;

    
    $pos_file = new IO::File "<$inFile";
    if ($pos_file) {
	while (<$pos_file>) {
	    $currLine = $_;
	    chomp $currLine;
	    if ($currLine =~ /$cALERT_POSITION/) {
		$currLine =~ s/^.*$cALERT_POSITION://;
		$currLine =~ s/\D*//;
		$retPos = $currLine;
	    }
	}
	
	$pos_file->close;
    } else {
	log_message ("Can't open position file: $!\n");
    }
    
    return $retPos;
}

#--------------------------------------------------------------
#
#
#
#--------------------------------------------------------------
sub writePosition ($$) {
    my ($inFile, $inPos) = @_;

    $pos_file = new IO::File ">$inFile";
    if (defined $pos_file) {
	print $pos_file ("$cALERT_POSITION:$inPos\n");
	$pos_file->close; 
    } else {
	log_message ("Cannot write to position file.  $!\nPosition will not be saved.\n");
    }

}



#--------------------------------------------------------------
#
#
#
#--------------------------------------------------------------
sub debug_print ($$) {
    my ($inMessage, $inLevel) = @_;

    if (defined ($inLevel)) {
	if ($DEBUG_LEVEL >= $inLevel) {
	    print $inMessage;
	}
    } elsif ($DEBUG_LEVEL > 0) {
	print ("$inMessage\n");
    }
}

#-----------------------------------------------------------------------
#
# print version and exit
#
#-----------------------------------------------------------------------
sub print_version () {
    print 
	"\n",
	"  Karma v$VERSION Copyright (C) 1999 Sean Hull <shull\@pobox.com>\n",
	"  Karma comes with ABSOLUTELY NO WARRANTY; for details\n",
	"  type \"karmagentd -w\".  This is free software, and you are\n",
	"  welcome to redistribute it under certain conditions.\n";

    exit ;
}



#-----------------------------------------------------------------------
#
# GNU General Public License Warranty
#
#-----------------------------------------------------------------------
sub print_warranty () {
    print 
	"\n",
	"   Copyright (C) 1999  Sean Hull <shull\@pobox.com>\n",
	"\n",
	"   This program is free software; you can redistribute it and/or modify\n",
	"   it under the terms of the GNU General Public License as published by\n",
	"   the Free Software Foundation; either version 2 of the License, or\n",
	"   (at your option) any later version.\n",
	"\n",
	"   This program is distributed in the hope that it will be useful,\n",
	"   but WITHOUT ANY WARRANTY; without even the implied warranty of\n",
	"   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n",
	"   GNU General Public License for more details.\n",
	"\n",
	"   You should have received a copy of the GNU General Public License\n",
	"   along with this program; if not, write to the Free Software\n",
	"   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA\n",
	"\n";


   exit;
}


#
# write message to logfile
#
sub log_message ($) {
    my ($message) = @_;

    if (defined ($logfile)) {
	print $logfile ($message);
    }
}


#
# normal kill
#
sub catch_term {
    writePosition ($POSITION_FILE, $currPosition);
    
    close (AFILE);
    #
    # cleanup and exit
    #
    if ($uptimeSth) {
	$uptimeSth->finish;
    }
    if ($alertlogSth) {
	$alertlogSth->finish;
    }
    if ($dbh) {
	$dbh->disconnect;
    }
    exit 1;

}
