#!/usr/local/bin/perl -w
#------------------------------------------------------------------
# 
# 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...
#-----------------------------------------------------------------



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

use Getopt::Std;
use karma;
use strict;

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;

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

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

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

if ($main::opt_d) {
    $main::DEBUG_LEVEL = $main::opt_d;
}

if ($main::opt_s) {
    $ENV{ORACLE_SID} = $main::opt_s;
}
if ($main::opt_b) {
    $ENV{ORACLE_BASE} = $main::opt_b;
}
if ($main::opt_h) {
    $ENV{ORACLE_HOME} = $main::opt_h;
}
if ($main::opt_r) {
    reset_alertlog ();
}

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

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

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


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

}

#
# background ourselves
#
daemonize ();


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

$main::cALERT_POSITION = "alert_position";
$main::cBEGINNING_OF_FILE = 0;

@main::uptimes = ();
#$main::UPCMD = "uptime";
$main::uptimeString = "";

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


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

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

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

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

$main::prevTime = getDayMinutes (time ());
$main::currTime = 0;

$main::currLine = "";
$main::currPosition = 0;
if ($main::opt_j) {
    $main::currPosition = $main::opt_j;
} else {
    $main::currPosition = readPosition ($POSITION_FILE);
}

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

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

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

	$main::prevTime = $main::currTime;

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

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

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

	    }
	    
	}

	writePosition ($POSITION_FILE, $main::currPosition);

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

    $main::currTime = getDayMinutes (time ());
}




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

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

}


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

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

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

	debug_print ("ERRORS --- $inLine\n", undef);
	$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;

    
    my $pos_file = new IO::File "<$inFile";
    if ($pos_file) {
	while (<$pos_file>) {
	    $currLine = $_;
	    chomp $currLine;
	    if ($currLine =~ /$main::cALERT_POSITION/) {
		$currLine =~ s/^.*$main::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) = @_;

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

}

#--------------------------------------------------------------
#
#
#
#--------------------------------------------------------------
sub reset_alertlog () {

}

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

}
