#!/usr/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
#
#
#------------------------------------------------------------------
#
# karmad -
#
# Oracle monitoring software.
#
#------------------------------------------------------------------

#
# current version
#
$VERSION="0.7.0";

#
# check for the Mail::Send package before using..
#
$USE_EMAIL_NOTIFICATION = 1;
unless (eval "require Mail::Send") {
    $USE_EMAIL_NOTIFICATION = 0;
}

if ($USE_EMAIL_NOTIFICATION == 1) {
    log_message ("Using EMAIL notification.\n");
} else {
    log_message ("Using LOGFILE notification.\n");
}

require 5.004;
use Socket;
#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 File::Basename;
use Getopt::Std;
use IO::File;


#---------------------------------
#
# Plain Old Documentation (pod...)
#
#---------------------------------

=head1 NAME

Karma - Oracle Database Monitoring Software

=head1 SYNOPSIS

Karma is an Web based Oracle monitoring utility.  Use it to watch
your alert.log, rollback segments, extents, tablespace quotas, 
latch contention, redologs, fragmentation, and hitratios.  It'll
also help you find slow running sql queries.  

=head1 NOTES

Get started with karma by first figuring out the databases you
would like to monitor.  Edit the karma.conf file and add lines
like this:

karma:TNS_NAME:username:password:X

Check the karma.conf file for more info, or do:

$ perldoc karma.conf

=head1 RECENT CHANGES

o added final more-info pages to "up", "os", "db",
  "extents", and "fragmentation"

o item finished the server daemon, which allows
  monitoring the alert.log and uptime on the remote db.

o item added these pod docs

=cut


#
# get the command line options
#
$opt_v = undef;
$opt_w = undef;
$opt_c = undef;
$opt_h = 0;
$opt_c = "";
getopts('hc:k:l:vw');

if ($opt_v) {
    print_version ();
}

#
# -c option is used for specifying config file
#
#if ($opt_c) {
#    print_conditions ();
#}

if ($opt_w) {
    print_warranty ();
}

if ($opt_h) {
    print_help ();
}

#-----------------------------------------------------------------------
#
# TYPES
#
#-----------------------------------------------------------------------


#-----------------------------------------------------------------------
#
# CONSTANTS
#
#-----------------------------------------------------------------------
$cDAYSECS = 86400;

$cOK_STATUS = 1;
$cNO_STATUS = 2;
$cWARNING_STATUS = 3;
$cALERT_STATUS = 4;

$cSHORT_EMAIL = "short";
#$cFULL_EMAIL = "full";

$cNOTIFY_ALRT="notify_alert";
$cNOTIFY_WARN="notify_warning";
$cNOTIFY_EMAIL="notify_email";

$cSTATUS_HEIGHT=25;
$cSTATUS_WIDTH=35;
$cHEAD_HEIGHT=50;
#
# constants for db_info{TNS} array
#
$cDB_HANDLE=0;
$cDB_USER=1;
$cDB_PASS=2;
$cDB_REFRESH=3;
$cDB_PREFGROUP=4;

$cDEF_GRP_NAME = "default";

#
# HTML COLORS
#
# karma page colors
#
$cKARMA_TEXT_COLOR="#FF9966";
#$cKARMA_LINK_COLOR=$cKARMA_TEXT_COLOR;
$cKARMA_LINK_COLOR="#CC6600";
$cTEXT_COLOR="#FF9933";
$cEMPHASIS_TEXT="#CC3300";

#$cBODY_BG_COLOR="#3366CC";
$cINFO_BG_COLOR="#003399";
$cHEAD_BG_COLOR="#000066";
$cMAIN_TABLE_BG="#006666";

$cBORDER_COLOR="#00CCCC";
$cBORD_COL_DARK="#003333";

#
# misc page colors
#

$KARMA_HOME = "./";
if (defined $ENV{KARMA_HOME}) {
     $KARMA_HOME=$ENV{KARMA_HOME};
} else {
     log_message ("KARMA_HOME not set, using current directory...\n");
}

$KARMA_FIFO_NAME = "$KARMA_HOME/.karmafifo";
$PID_FILE_NAME = "$KARMA_HOME/.karma.pid";
$KARMA_PID = $$;

write_pid_file ();

#
# how often (in minutes) to wakeup and check which services need
# to be updated
#
#$cWAKEUP_FREQUENCY=1;

#
# this specifies the base location of generated karma html files
#
$cKARMA_DOC_ROOT = "$KARMA_HOME/doc_root";
if ($opt_k) {
    $cKARMA_DOC_ROOT = $opt_k;
}

if (not (-e $cKARMA_DOC_ROOT) ||
    not (-d $cKARMA_DOC_ROOT) ||
    not (-w $cKARMA_DOC_ROOT)) {

    print ("Please make sure that doc_root is a directory and\n");
    print ("that it is writable.\n");
    exit;
}

@test_array = (["Status", "Name", "Value"], 
	       [$cOK_STATUS, "Aeon", "25"],
	       [$cWARNING_STATUS, "Scafandra", "50"],
	       [$cALERT_STATUS, "Una", "75"],
	       [$cNO_STATUS, "Trevor", "100"]);
 
$testRef = \@test_array; 

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

#
# you can specify these in the karma.conf file if you want 'em 
# different
#
$USE_BLINK_WARNING=0;
$USE_BLINK_ALERT=0;

#-----------------------------------------------------------------------
#
# GLOBALS 
#
# Organized as follows:
#
# TNS is one of the TNS_NAMES specified in "karma:" directive lines
#    in the karma.conf file
#
# SERVICE is one of (redolog, rollback, latch, tablespace, slowsql,
#                    hitratios, extents, fragmentation, mts, os,
#                    alertlog, up)
#
# FTYPE is one of (help, info)
#
# GLOBAL VARIABLE          DESCRIPTION
# -----------------------  --------------------------------
# $statements{SERVICE}     a string holding the sql statements
#                          executed in the getSERVICEInfo routine
#                          to judge the status of this service
#
# $files{FTYPE}{SERVICE}   a string holding a filename
#
# $config_info{default}{SERVICE}    an array of values for this service
#                          gathered from the karma.conf file
#
# $db_info{TNS}            an array of info related to each
#                          database we're connecting to
#                          [0]  database handle
#                          [1]  username
#                          [2]  password
#                          [3]  refresh
#
# $stats{SERVICE}{TNS}[0]  stores the status of each service
#                          monitored for each database.
#                          1 - OK STATUS
#                          2 - NO STATUS
#                          3 - WARNING STATUS
#                          4 - ALERT STATUS
# $stats{SERVICE}{TNS}[1]  time last updated (in seconds - unix time)
#
# $names{SERVICE}          Long and short versions of the service names
#                          for display as main.html column headers, and
#                          info page titles.
#
#-----------------------------------------------------------------------

#----------------------------------------------
#
# sql statements
#
#----------------------------------------------

#
# redo log query
#
$statements{redolog} = "
SELECT TO_CHAR(first_time, 'J'), TO_CHAR (first_time, 'SSSSS'),
       group#, sequence#, TO_CHAR (first_time, 'DD/MM/YYYY HH24:MI')
FROM v\$log
ORDER BY 1,2";

# 
# rollback segment query
#
#$statements{rollback} = "
#select ((gets-waits) * 100/gets)
#from v\$rollstat";

$statements{rollback} = "
select a.name, b.status, b.gets, b.waits
from v\$rollname a, v\$rollstat b
where a.usn = b.usn";

#
# latch query
#
#$statements{latch} = "
#select (gets-misses) * 100 / gets
#from v\$latch
#where gets > 0";

$statements{latch} = "
SELECT   name, gets, misses
FROM     v\$latch
ORDER BY name";

#
# tablespace query
#
$statements{tablespace} = "
SELECT  a.tablespace_name, a.total, b.used
FROM    (SELECT  tablespace_name, SUM (bytes) total
         FROM    dba_data_files
         GROUP BY tablespace_name) a,
        (SELECT  tablespace_name, SUM (bytes) used
         FROM    dba_segments
         GROUP BY tablespace_name) b
WHERE   a.tablespace_name = b.tablespace_name (+)";

#
# slow sql query
#
#$statements{slowsql} = "
#SELECT disk_reads / DECODE (executions, 0, 1, executions), 
#       sql_text
#FROM v\$sqlarea
#WHERE disk_reads / DECODE (executions, 0, 1, executions) > ?";

$statements{slowsql} = "
SELECT disk_reads, executions, sql_text
FROM v\$sqlarea";

#
# hit ratios query
#
$statements{hitratios} = "
SELECT name, value
FROM v\$sysstat
WHERE name IN ('consistent gets', 'db block gets', 'physical reads')";

#
# extents query
#
$statements{extents} = "
SELECT segment_name, max_extents, count(*), owner
FROM dba_segments
WHERE owner NOT IN ('SYS', 'SYSTEM')
GROUP BY segment_name, owner, max_extents";

#
# fragmentation query
#
$statements{fragmentation} = "
SELECT tablespace_name, initial_extent, next_extent, pct_increase
FROM dba_tablespaces
WHERE tablespace_name NOT IN ('SYSTEM')";

#
# mts query
#
$statements{mts} = "
SELECT name, busy, idle
FROM v\$dispatcher";

#
# OS query
#
$statements{os} = "
SELECT load_one, load_five, load_fifteen, pctidle,
       TO_CHAR (timestamp, 'HH24:MI')
FROM karma_os_stats";

#
# alert log query
#
$statements{alertlog} = "
SELECT facility, errno, TO_CHAR (timestamp, 'HH24:MI'), text
FROM karma_alertlog_errors
WHERE timestamp > (SYSDATE - 1) ORDER BY timestamp DESC";

#
# up query (none needed for now)
#
$statements{up} = "
SELECT name, value
FROM v\$sysstat
ORDER BY name";

#
# db info query
#
$statements{db} = "
SELECT name, value
FROM v\$parameter
ORDER BY name";

#
# more info file extentsions... actual filename will be
# dbname.$REDOLOG_FILE for example.
#
$INDEX_FILE_NAME = "$cKARMA_DOC_ROOT/karma.html";

$files{info}{redolog}       = "redolog.html";
$files{info}{rollback}      = "rollback.html";
$files{info}{slowsql}       = "slowsql.html";
$files{info}{alertlog}      = "alertlog.html";
$files{info}{hitratios}     = "hitratios.html";
$files{info}{extents}       = "extents.html";
$files{info}{latch}         = "latch.html";
$files{info}{fragmentation} = "fragmentation.html";
$files{info}{mts}           = "mts.html";
$files{info}{tablespace}    = "tablespace.html";
$files{info}{os}            = "os.html";
$files{info}{up}            = "up.html";
$files{info}{db}            = "db.html";

#
# help files
#
$files{help}{redolog}       = "redolog_help.html";
$files{help}{rollback}      = "rollback_help.html";
$files{help}{slowsql}       = "slowsql_help.html";
$files{help}{alertlog}      = "alertlog_help.html";
$files{help}{hitratios}     = "hitratios_help.html";
$files{help}{extents}       = "extents_help.html";
$files{help}{latch}         = "latch_help.html";
$files{help}{fragmentation} = "fragmentation_help.html";
$files{help}{mts}           = "mts_help.html";
$files{help}{tablespace}    = "tablespace_help.html";
$files{help}{os}            = "os_help.html";
$files{help}{up}            = "up_help.html";
$files{help}{db}            = "db_help.html";

#
# shortened service names (no more than 5 characters)
#
$names{short}{redolog}       = "rdlg";
$names{short}{rollback}      = "rlbk";
$names{short}{slowsql}       = "ssql";
$names{short}{alertlog}      = "alog";
$names{short}{hitratios}     = "hitr";
$names{short}{extents}       = "exts";
$names{short}{latch}         = "latch";
$names{short}{fragmentation} = "frag";
$names{short}{mts}           = "mts";
$names{short}{tablespace}    = "tbsp";
$names{short}{os}            = "os";
$names{short}{up}            = "up";
$names{short}{db}            = "name";

#
# long service names (for info page titles)
#
$names{long}{redolog}       = "Redolog Switching";
$names{long}{rollback}      = "Rollback Segment Contention";
$names{long}{slowsql}       = "Slow SQL";
$names{long}{alertlog}      = "Alertlog Errors";
$names{long}{hitratios}     = "Hit Ratios";
$names{long}{extents}       = "Extents";
$names{long}{latch}         = "Latch Contention";
$names{long}{fragmentation} = "Fragmentation";
$names{long}{mts}           = "Multi-threaded Server";
$names{long}{tablespace}    = "Tablespace Quotas";
$names{long}{os}            = "OS Statistics";
$names{long}{up}            = "Database Up";
$names{long}{db}            = "Database Name";


#
# which columns will be displayed and how often
# stored in a hash of arrays where the array contains
# frequency, alert, and warn values respectively
#
# THESE ARE THE "FACTORY" default settings...

# check every five minutes, alert if switching more the every 15 minutes
# warn if switching more than every 30
#
$config_info{factory}{redolog}      = [(5,30,15)];

# check every minute... need to set the rest
#
$config_info{factory}{rollback}      = [(5,99,97)];

# check every 15 minutes
#
$config_info{factory}{slowsql}       = [(15,100,200)];

# check the alertlog table every 5 minutes, alert if there's been
# an error in the last hour, warn if there's been an error in the
# last day
#
$config_info{factory}{alertlog}      = [(5,60,86400)];

# check the hitratios every 5 minutes, alert if less than 70%
# warn if less than 95%
#
$config_info{factory}{hitratios}     = [(5,95,70)];

# check for fragmentation every 15 minutes... not sure how to
# set the rest
#
$config_info{factory}{extents}       = [(15,2,1)];

# check for latch contention every 5 minutes... need to set the rest 
#
$config_info{factory}{latch}         = [(5,99,97)];

#
# check for fragmentation every 15 minutes... 
# setting the thresholds is as yet undefined
#
$config_info{factory}{fragmentation} = [(15,0,0)];

# check mts contention every 5 minutes
#
$config_info{factory}{mts}           = [(5,50,75)];

# check every minute, if greater than 95% alert, if greater than 85%
# send warning
#
$config_info{factory}{tablespace}    = [(1,85,95)];

# check os stats every 5 minutes, alert for load over 10, warn for
# load over 5
#
$config_info{factory}{os}            = [(5,5,10)];


# 
# always check that the db is up, default every 5 minutes, refresh
# html page (with tag) every 60 seconds by default
#
$config_info{factory}{up}            = [(5,60,0)];


#$currTime = 0;

#-----------------------------------------------------------------------
#
# FUNCTION PROTOTYPES
#
# I know they're not necessary, but I like 'em...
#
#-----------------------------------------------------------------------
sub main ();

sub getStatus ($$$);
sub getRedologStatus ($$$);
sub getRollbackStatus ($$$);
sub getLatchStatus ($$$);
sub getTablespaceStatus ($$$);
sub getSlowsqlStatus ($$$);
sub getAlertlogStatus ($$$);
sub getHitratiosStatus ($$$);
sub getMTSStatus ($$$);
sub getExtentsStatus ($$$);
sub getFragmentationStatus ($$$);
sub getDbStatus ($);
sub getUpStatus ($);
sub getInfo ($$);
sub getTimeString ($);
sub getStatusStr ($);
sub getDayMinutes ($);
sub readConfig ($);
sub showInfoPage ($$$$);
sub showKarmaTableRow ($$);
sub showKarmaTableHeader ();
sub showKarmaHeadMain ();
sub showKarmaFootMain ();
sub showInfoHead ($$);
sub showInfoFoot ($$);
sub showServiceStatus ($$);
sub showIndexPage ($);
sub exit_karma ();
sub log_message ($);
sub log_message_wtime ($);
sub send_email ($$@);
sub set_config ($$$$$);
sub set_config_notify ($$$$$);
sub set_config_email ($$);
sub do_notification ($$);
sub send_notification ($$$@);
sub check_service ($$);
sub shouldUpdateService ($$$);
sub shouldShowService ($$);
sub shouldShowServiceHeader ($);
sub getServiceWarn ($$);
sub getServiceAlert ($$);
sub setDefConfig ();
sub isValidTNS ($);
sub doDBChecks ();
sub write_pid_file ();

# signal handlers
sub catch_hup;
sub catch_term;
sub show_status;

#
# install signal handlers
#
$SIG{HUP} = \&catch_hup;        # reread config file
$SIG{TERM} = \&catch_term;      # normal kill, die gracefully, cleanup
$SIG{USR1} = \&show_status;      # user signal, return status


#
# open the error logfile
# this shouldn't be global...
#
$logfile = new IO::File ">>$logfile_name";
if (not (defined $logfile)) {
    $logfile = new IO::File ">>-";
    print $logfile ("Cannot open logfile: $logfile_name, using STDOUT");

}
#open (LFILE, ">>$logfile_name");


#
# this ensures the perl will not buffer output to the logfile
# - thanks to Duncan Lawie <duncanl@demon.net> 
#
select ($logfile); $| = 1;
select (STDOUT);


$USE_DBI_VARS = 0;


#
# read in configuration information from the 
# conf file specified on the command line
#
if (($opt_c) && (-f $opt_c) && (-r $opt_c)) {
    $conf_file_name = $opt_c;

#
# try the KARMA_HOME directory...
#
#} elsif ((-f "$KARMA_HOME/karma.conf") && (-r "$KARMA_HOME/karma.conf")) {
} elsif ((-f "$KARMA_HOME/karma.conf") && (-r "$KARMA_HOME/karma.conf")) {
    $conf_file_name = "$KARMA_HOME/karma.conf";

#
# try the home directory (assume name ".karma.conf"
#
} elsif (( -f "$ENV{HOME}/.karma.conf") && (-r "$ENV{HOME}/.karma.conf")) {
    $conf_file_name = "$ENV{HOME}/.karma.conf";


#
# if none was specified on the command line, check
# for /etc/karma.conf
#
} elsif ((-f "/etc/karma.conf") && (-r "/etc/karma.conf")) {
    $conf_file_name = "/etc/karma.conf";

#
# if we still haven't found the karma.conf file,
# check the current directory
#
} elsif ((-f "./karma.conf") && (-r "./karma.conf")) {
    $conf_file_name = "./karma.conf";

#
# last chance, try the DBI_USER, DBI_PASS, DBI_DSN, and use all
# factory defaults
#
} elsif ((defined ($ENV{DBI_DSN})) && (length($ENV{DBI_DSN}) > 0) &&
	 (defined ($ENV{DBI_USER})) && (length($ENV{DBI_USER}) > 0) &&
	 (defined ($ENV{DBI_PASS})) && (length($ENV{DBI_PASS}) > 0)) {
    $USE_DBI_VARS = 1;


# 
# without a config file we just exit
#
} else {

    print ("The karma.conf configuration file was not found.  Please\n");
    print ("make sure the file /etc/karma.conf or ./karma.conf exists\n");
    print ("and is readable, otherwise specify another file with -c. \n");
    exit;
}



$startTime = time();
$currTime = $startTime;


if ($USE_DBI_VARS == 1) {
    setDefConfig ();
} else {
    readConfig ($conf_file_name);
}

#show_pref_groups ();
#show_notify_config ();
#exit;

#
# ok, now we're ready to run
#
main ();

#
# safe exit... we really only ever exit via HUP signal, so we 
# probably never reach here.
#
exit_karma ();

#-----------------------------------------------------------------------
#
# SUBROUTINES
#
#-----------------------------------------------------------------------



#-----------------------------------------------------------------------
#
# main
#
#-----------------------------------------------------------------------
sub main () {

    #
    # loop forever... only way to exit is via catch_term
    #
    while (1) {
	doDBChecks ();
	sleep (60);
	$currTime = time ();
    }
}


#-----------------------------------------------------------------------
#
# print the html page on stdout
#
#-----------------------------------------------------------------------
sub showIndexPage ($) {
    my ($inMinutes) = @_;

    # 
    # ideally would like to pass this handle to the showKarmaHeadMain
    # showKarmaTableHeader etc routines, but right now it's not working
    #
    $index_file = new IO::File ">$INDEX_FILE_NAME";

    if (not (defined ($index_file))) {
	log_message ("Cannot write to index file: $INDEX_FILE_NAME\n");
    } else {

#    open (INDEX_FILE, ">$INDEX_FILE_NAME");

	showKarmaHeadMain ();

	#
	# build status table
	#
	print $index_file ("<table border=\"1\" cellpadding=\"0\" cellspacing=\"0\" bordercolor=\"$cBORDER_COLOR\">\n");
	
	
	#
	# title row
	#
	showKarmaTableHeader ();
	
	#
	# one row per database
	#
	foreach $key (keys %db_info) {
	    showKarmaTableRow ($key, $inMinutes);
	}
	
	print $index_file ("</TABLE>\n");
	
	showKarmaFootMain ();
	
	
	$index_file->close;
    }

#    close (INDEX_FILE);
}


#-----------------------------------------------------------------------
#
# first parameter is the type of statistic
# second parameter is the tns name
#
#-----------------------------------------------------------------------
sub showServiceStatus ($$) {
    my ($inType, $inTNS) = @_;

    my $theFile = $files{info}{$inType};
    my $theStatus = $cNO_STATUS; 
    if (defined $stats{$inType}{$inTNS}[0]) {
	$theStatus = $stats{$inType}{$inTNS}[0];
    }

#    if (not ($theStatus)) {
#	print ("Working on T: $inType TNS:$inTNS\n");
#    }

    if ($theStatus == $cALERT_STATUS) {
	if ($USE_BLINK_ALERT == 1) {
	    $theImage = "blink_red_status";
	} else {
	    $theImage = "red_status";
	}
	$theMessage = "ALRT";
    } elsif ($theStatus == $cWARNING_STATUS) {
	if ($USE_BLINK_WARNING == 1) {
	    $theImage = "blink_yellow_status";
	} else {
	    $theImage = "yellow_status";
	}
	$theMessage = "WARN";
    } elsif ($theStatus == $cNO_STATUS) {
	$theImage = "purple_status";
	$theMessage = "NR";
    } else {
	$theImage = "green_status";
	$theMessage = "OK";
    }

    #
    # if the database is not up, all links are off except "up" status
    # link, which will be ALERT_STATUS, and will basically just say
    # the database is down.
    # 
    if (($stats{up}{$inTNS}[0] == $cALERT_STATUS) &&
	(not ($inType =~ /^up$/i))) {
	print $index_file ("<IMG SRC=\"images/$theImage\" BORDER=0 border=\"0\" ALT=\"$inTNS - DB DOWN\">\n");
    } else {
	print $index_file ("<A HREF=\"info/$inTNS.$theFile\" target=\"_self\"><IMG SRC=\"images/$theImage\" BORDER=0 ALT=\"$inTNS - $inType Info\"></A>\n");
    }
}

#-----------------------------------------------------------------------
#
# check how often redo logs switch.  If they're more often than 30
# minutes, put us at alert status
# (Should we also check v$loghist? probably yes)
#
#-----------------------------------------------------------------------
sub getRedologStatus ($$$) {
    my ($redolog_threshold_warn, $redolog_threshold_alert, $inTNS) = @_;

    my $redologStatus = $cNO_STATUS;
    my $currStatus = $cNO_STATUS;
    my $redoTableRef = [()];
    my $redoRowCount = 0;
    my @redoTable = ();

#    if ($dbh) {
#    if ($dbh{$inTNS}) {
    if ($db_info{$inTNS}[$cDB_HANDLE]) {

	my $curr_row = [];
#	my $prev_row = [];
	my $currtime = 0;
	my $prevtime = 0;
	my $diff = 0;
#	my $sth = $dbh{$inTNS}->prepare ($statements{redolog});
	my $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{redolog});
	my $rv = $sth->execute ();

	#$prev_row = $sth->fetchrow_arrayref ();

	#
	# redolog more info table titles
	#
	@{$redoTableRef->[$redoRowCount]} = ("Level", "Group#",
					     "Sequence#", "Timestamp");
	$redoRowCount = 1;
	$curr_row = [];
	$currtime = 0;
	$prevtime = 0;
	$diff = 0;
	$curr_row = $sth->fetchrow_arrayref;
	while (defined ($curr_row->[0])) {


	    $prevtime = $currtime;
	    $currtime = $cDAYSECS * $curr_row->[0] + $curr_row->[1];
	    $diff = $currtime - $prevtime;
	    
	    
	    if ($diff < ($redolog_threshold_alert * 60)) {
		$currStatus = $cALERT_STATUS;
	    } elsif ($diff < ($redolog_threshold_warn * 60)) {
		$currStatus = $cWARNING_STATUS;
	    } else {
		$currStatus = $cOK_STATUS;
	    }
	    
	    
	    #
	    # escalate warning/alert level if necessary
	    #
	    if (($redologStatus == $cNO_STATUS) || 
		($redologStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$redologStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($redologStatus == $cWARNING_STATUS)) {
		$redologStatus = $currStatus;
	    }
	    
	    
	    
	    @{$redoTableRef->[$redoRowCount]} = ($currStatus, $curr_row->[2],
						 $curr_row->[3], $curr_row->[4]);
	    
	    $curr_row = $sth->fetchrow_arrayref;
	    $redoRowCount++;
	}
	
	if ($redoRowCount == 1) {
	    $redologStatus = $cWARNING_STATUS;
	}

	if ($sth) {
	    $sth->finish;
	}
    }


    showInfoPage ($redoTableRef, "redolog", $inTNS, "Redo Contention Info Page");
    return $redologStatus;
}

#-----------------------------------------------------------------------
#
# check for rollback segment contention

# (gets-waits)*100/gets is the hitratio for that rollback segment
# We're simply checking that this is > 99.
#
#-----------------------------------------------------------------------
sub getRollbackStatus ($$$) {
    my ($roll_threshold_alert, $roll_threshold_warn, $inTNS) = @_;

    my $rollbackStatus = $cNO_STATUS;
    my $rollbackTableRef = [()];
    my $rollbackRowCount = 0;
    my $gets = 0;
    my $gets_waits = 0;
    my $currStatus = $cNO_STATUS;

#    if ($dbh{$inTNS}) {
    if ($db_info{$inTNS}[$cDB_HANDLE]) {

#	$sth = $dbh{$inTNS}->prepare ($statements{rollback});
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{rollback});
	$rv = $sth->execute;
	$gets = 0;
	$gets_waits = 0;
	$currStatus = $cNO_STATUS;

	#
	# rollback segment more info table titles
	#
	@{$rollbackTableRef->[0]} = ("Level", 
				     "Name",
				     "Status",
				     "Gets",
				     "Waits",
				     "Hitratio");
	$rollbackRowCount = 1;
	$curr_row = $sth->fetchrow_arrayref;
	while (defined ($curr_row->[0])) {
	    $gets = $curr_row->[2];
	    $gets_waits = $gets - $curr_row->[3];
	    $currStatus = $cNO_STATUS;
	    if ($gets > 0) {
		$rollbackRatio = ($gets_waits) * 100 / $gets;

		if ($rollbackRatio < $roll_threshold_alert) {
		    $currStatus = $cALERT_STATUS;
		} elsif ($rollbackRatio < $roll_threshold_warn) {
		    $currStatus = $cWARNING_STATUS;
		} else {
		    $currStatus = $cOK_STATUS;
		}
	    }

	    #
	    # escalate warning/alert level if necessary
	    #
	    if (($rollbackStatus == $cNO_STATUS) || 
		($rollbackStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$rollbackStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($rollbackStatus == $cWARNING_STATUS)) {
		$rollbackStatus = $currStatus;
	    }
	    

	    @{$rollbackTableRef->[$rollbackRowCount]} = ($currStatus, 
							 $curr_row->[0],
							 $curr_row->[1],
							 $curr_row->[2],
							 $curr_row->[3],
							 $rollbackRatio);

	    $rollbackRowCount++;
	    $curr_row = $sth->fetchrow_arrayref;
	}
	if ($sth) {
	    $sth->finish;
	}
    }


    showInfoPage ($rollbackTableRef, "rollback", $inTNS, "Rollback Info Page");
    return $rollbackStatus;
}

#-----------------------------------------------------------------------
# 
# check for latch contention
# not sure if this query is strictly correct.  Need a confirmation.
#
#-----------------------------------------------------------------------
sub getLatchStatus ($$$) {
    my ($latch_threshold_alert, $latch_threshold_warn, $inTNS) = @_;

    my $latchStatus = $cNO_STATUS;
    my $currStatus = $cNO_STATUS;
    my $latchTableRef = [()];
    my $latchRowCount = 0;
    my $latchRatio = 0;
    my 	$curr_row = [];
    my $gets = 0;
    my $misses = 0;
    my $gets_misses = 0;

#    if ($dbh{$inTNS}) {
    if ($db_info{$inTNS}[$cDB_HANDLE]) {

#	$sth = $dbh{$inTNS}->prepare ($statements{latch});
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{latch});
	$rv = $sth->execute;

	$latchStatus = $cOK_STATUS;
	$latchRatio = 0;
	$gets = 0;
	$misses = 0;
	$gets_misses = 0;
	
        #
	# latch more info table titles
	#
	@{$latchTableRef->[0]} = ("Level", 
				  "Name",
				  "Gets",
				  "Misses",
				  "Hitratio");
	$latchRowCount = 1;
	$curr_row = $sth->fetchrow_arrayref;
	while (defined ($curr_row->[0])) {

	    $currStatus = $cNO_STATUS;
	    $gets = $curr_row->[1];
	    $misses = $curr_row->[2];
	    $gets_misses = $gets - $misses;
	    if (($gets == 0) && ($misses == 0)) {
		$latchRatio = 100;
	    } elsif ($gets > 0) {
		$latchRatio = ($gets_misses) * 100 / $gets;
	    }
	    if ($latchRatio < $latch_threshold_alert) {
		$currStatus = $cALERT_STATUS;
	    } elsif ($latchRatio < $latch_threshold_warn) {
		$currStatus = $cWARNING_STATUS;
	    } else {
		$currStatus = $cOK_STATUS;
	    }
	    

	    #
	    # escalate warning/alert level if necessary
	    #
	    if (($latchStatus == $cNO_STATUS) || 
		($latchStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$latchStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($latchStatus == $cWARNING_STATUS)) {
		$latchStatus = $currStatus;
	    }
	    

	    @{$latchTableRef->[$latchRowCount]} = ($currStatus, 
						   $curr_row->[0],
						   $curr_row->[1],
						   $curr_row->[2],
						   $latchRatio);


	    $latchRowCount++;
	    $curr_row = $sth->fetchrow_arrayref;
	}

	if ($sth) {
	    $sth->finish;
	}

    }


    showInfoPage ($latchTableRef, "latch", $inTNS, "Latch Info Page");
    return $latchStatus;
}



#-----------------------------------------------------------------------
# 
# Right now this is checking that tablespaces are not above a certain
# threshold (%used).  It *SHOULD* just check if there is an object
# whose next extent is bigger than the largest free extent, or the
# largest free space...
#
#-----------------------------------------------------------------------
sub getTablespaceStatus ($$$) {
    my ($tablespace_threshold_warn, $tablespace_threshold_alert, $inTNS) = @_;

    my $tablespaceStatus = $cNO_STATUS;
    my $curr_row = [()];
    my $currStatus = $cNO_STATUS;
    my $pctused = 0;
    my $used_size = 0;
    my $total_size = 0;
    my $tablespaceTableRef = [()];
    
#    if ($dbh{$inTNS}) {
    if ($db_info{$inTNS}[$cDB_HANDLE]) {

#	$sth = $dbh{$inTNS}->prepare ($statements{tablespace});
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{tablespace});
	$rv = $sth->execute;
	$tablespaceStatus = $cNO_STATUS;
	$currStatus = $cNO_STATUS;

        #
	# latch more info table titles
	#
	@{$tablespaceTableRef->[0]} = ("Level", 
				       "Name",
				       "Used (K)",
				       "Total (K)",
				       "% Used");
	$tablespaceRowCount = 1;
	$curr_row = $sth->fetchrow_arrayref;
	while (defined ($curr_row->[0])) {

	    $currStatus = $cNO_STATUS;
	    $pctused = 0;
	    $total_size = $curr_row->[1];
	    $used_size = $curr_row->[2];
	    if (not defined ($used_size)) {
		$used_size = 0;
	    }

	    if ($total_size > 0) {
		$pctused = 100 * $used_size / $total_size;
#		print ("T:$total_size U:$used_size P:$pctused\n");
	    }

	    if ($pctused > $tablespace_threshold_alert) {
		$currStatus = $cALERT_STATUS;
	    } elsif ($pctused > $tablespace_threshold_warn) {
		$currStatus = $cWARNING_STATUS;
	    } else {
		$currStatus = $cOK_STATUS;
	    }

	    #
	    # escalate warning/alert level if necessary
	    #
	    if (($tablespaceStatus == $cNO_STATUS) || 
		($tablespaceStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$tablespaceStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($tablespaceStatus == $cWARNING_STATUS)) {
		$tablespaceStatus = $currStatus;
	    }
	    

#	    print ("C:$currStatus N:$curr_row->[0] U:$used_size T:$total_size PCT:$pctused\n");
	    @{$tablespaceTableRef->[$tablespaceRowCount]} = 
		($currStatus, 
		 $curr_row->[0],
		 $total_size / 1024,
		 $used_size / 1024,
		 $pctused);


	    $tablespaceRowCount++;
	    $curr_row = $sth->fetchrow_arrayref;

	}
	if ($sth) {
	    $sth->finish;
	}
    }

    showInfoPage ($tablespaceTableRef, 
		 "tablespace", 
		 $inTNS, 
		 "Tablespace Info Page");

    return $tablespaceStatus;
}




#-----------------------------------------------------------------------
# 
#
# check for slow sql queries based on the number of disk reads (v$sqlarea)
#
# NOTE/WARNING:  The way this routine is written may or may not be the
# best way.  I read ALL sql statements from v$sqlarea in through
# the cursor, then examine disk_reads per executions to find out if
# we should consider this a slow query.  We then determine whether to
# include  it in the more info page.
#
#-----------------------------------------------------------------------
sub getSlowsqlStatus ($$$) {
    my ($slowsql_threshold_warn, $slowsql_threshold_alert, $inTNS) = @_;

    my $slowsqlStatus = $cNO_STATUS;
    my $slowsqlTableRef = [()];

#    if ($dbh{$inTNS}) {
    if ($db_info{$inTNS}[$cDB_HANDLE]) {

#	$sth = $dbh{$inTNS}->prepare ($statements{slowsql});
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{slowsql});

	#
	# include all sql statements within 2 times the
	# warning level
	#
#	$rv = $sth->execute ($slowsql_threshold_warn * 2);
	$rv = $sth->execute ();

	$currStatus = $cNO_STATUS;

        #
	# latch more info table titles
	#
	@{$slowsqlTableRef->[0]} = ("Level", 
				    "Disk I/O",
				    "Query Text");
	$slowsqlRowCount = 1;
	$curr_row = $sth->fetchrow_arrayref;
	while ($curr_row->[0]) {

	    $disk_reads = $curr_row->[0];
	    $executions = $curr_row->[1];
	    if ($executions > 0) {
		$read_execs = $disk_reads / $executions;
	    } else {
		$read_execs = $disk_reads;
	    }

	    #
	    # default to ok status, if we find no rows
	    # to display at all
	    #
	    if ($slowsqlStatus == $cNO_STATUS) {
		$currStatus = $cOK_STATUS;
	    }

	    if ($read_execs > (2 * $slowsql_threshold_warn)) {

		if ($read_execs < $slowsql_threshold_alert) {
		    $currStatus = $cALERT_STATUS;
		} elsif ($read_execs < $slowsql_threshold_warn) {
		    $currStatus = $cWARNING_STATUS;
		} else {
		    $currStatus = $cOK_STATUS;
		}

	        # 
	        # escalate warning/alert level if necessary
	        #
		if (($slowsqlStatus == $cNO_STATUS) || 
		    ($slowsqlStatus == $cOK_STATUS) ||
		    ($currStatus == $cALERT_STATUS)) {
		    $slowsqlStatus = $currStatus;
		} elsif (($currStatus == $cALERT_STATUS) &&
			 ($slowsqlStatus == $cWARNING_STATUS)) {
		    $slowsqlStatus = $currStatus;
		}
		
		
		@{$slowsqlTableRef->[$slowsqlRowCount]} = 
		    ($currStatus, 
		     $read_execs,
		     $curr_row->[2]);
		
	    }
	    $slowsqlRowCount++;
	    $curr_row = $sth->fetchrow_arrayref;

	}

	if ($sth) {
	    $sth->finish;
	}
	
        # build more info page
	showInfoPage ($slowsqlTableRef, "slowsql", $inTNS, "Slow SQL Info Page");

    }




    return $slowsqlStatus;

}


#-----------------------------------------------------------------------
# 
# check the alert log for ORA errors
#
# if the KARMA_ALERTLOG_ERRORS table doesn't exist, we'll just report
# NO_STATUS, and exit.
#
#-----------------------------------------------------------------------
sub getAlertlogStatus ($$$) {
    my ($alertlog_threshold_warn, $alertlog_threshold_alert, $inTNS) = @_;

    my $alertlogStatus = $cNO_STATUS;
    my $alertlogTableRef = [()];

#    $sth = $dbh{$inTNS}->prepare ("SELECT table_name FROM user_tables WHERE table_name = \'KARMA_ALERTLOG_ERRORS\'");
    $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ("SELECT table_name FROM user_tables WHERE table_name = \'KARMA_ALERTLOG_ERRORS\'");
    $rv = $sth->execute;
    $row_ref = $sth->fetchrow_arrayref;

    # 
    # to fix the "Database DOWN!" messaage, which shouldn't come
    # up if the KARMA_* tables are missing... obviously
    #
    @{$alertlogTableRef->[0]} = ("Level",
				 "Facility",
				 "Error",
				 "Time",
				 "Error Text");
    
    $alertlogRowCount = 1;

#    print ("Were in getAlertlogStatus...\n");

    if (defined ($row_ref->[0])) {

#	print ("We have alertlog rows...\n");

	if ($sth) {
	    $sth->finish;
	}

#	$sth = $dbh{$inTNS}->prepare ($statements{alertlog});
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{alertlog});
	$rv = $sth->execute;

#	@{$alertlogTableRef->[0]} = ("Level",
#				     "Facility",
#				     "Error",
#				     "Time",
#				     "Error Text");

	$currStatus = $cOK_STATUS;
	$alertlogStatus = $cOK_STATUS;

#	$alertlogRowCount = 1;
	$curr_row = $sth->fetchrow_arrayref;


	while (defined ($curr_row->[0])) {

	    $currStatus = $cOK_STATUS;


#	    print ("We have rows, alertlog inside ORA check...\n");

	    if (($curr_row->[0] =~ /ORA/) && 
		(($curr_row->[1] == 600) || ($curr_row->[1] == 7445))) {
		$currStatus = $cALERT_STATUS;
	    } else {
		$currStatus = $cWARNING_STATUS;
	    }
		
	    # 
	    # escalate warning/alert level if necessary
            #
	    if (($alertlogStatus == $cNO_STATUS) || 
		($alertlogStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$alertlogStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($alertlogStatus == $cWARNING_STATUS)) {
		$alertlogStatus = $currStatus;
	    }
	    
	    
	    @{$alertlogTableRef->[$alertlogRowCount]} = 
		($currStatus, 
		 $curr_row->[0],
		 $curr_row->[1],
		 $curr_row->[2],
		 $curr_row->[3]);
	    
	    $alertlogRowCount++;
	    $curr_row = $sth->fetchrow_arrayref;
	

	}

    }

    if ($sth) {
	$sth->finish;
    }

#    print ("We found $alertlogRowCount rows for $inTNS...\n");
    showInfoPage ($alertlogTableRef, "alertlog", $inTNS, "Alert Log Info Page");

    return $alertlogStatus;

}


#-----------------------------------------------------------------------
# 
# check for low hit ratios (block buffer, dictionary cache etc)
#  
# problem here... Doesn't produce proper more info report.
#
#-----------------------------------------------------------------------
sub getHitratiosStatus ($$$) {
    my ($hitratios_threshold_warn, $hitratios_threshold_alert, $inTNS) = @_;

    my $hitratiosStatus = $cNO_STATUS;
    my $hitratiosRowCount = 0;
    my $consistent_gets = undef;
    my $physical_reads = undef;
    my $db_block_gets = undef;
    my $hitratiosTableRef = [()];
    my $hitratiosRow = [()];


    $hitratiosTableRef = [()];
    $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{hitratios});
    $rv = $sth->execute;

    @{$hitratiosTableRef->[0]} = ("Level",
				 "Consistent Gets",
				 "DB Block Gets",
				 "Physical Reads",
				 "Hit Ratio");
    
    $currStatus = $cNO_STATUS;
    $consistent_gets = undef;
    $physical_reads = undef;
    $db_block_gets = undef;

    $hitratiosRowCount = 1;
    $hitratiosRow = $sth->fetchrow_arrayref;

    while (defined ($hitratiosRow->[0])) {

	if ($hitratiosRow->[0] =~ /consistent gets/) {
	    $consistent_gets = $hitratiosRow->[1];
	} elsif ($hitratiosRow->[0] =~ /physical reads/) {
	    $physical_reads = $hitratiosRow->[1];
	} elsif ($hitratiosRow->[0] =~ /db block gets/) {
	    $db_block_gets = $hitratiosRow->[1];
	}


	$currStatus = $cOK_STATUS;

	if (defined ($consistent_gets) && 
	    defined ($physical_reads) && 
	    defined ($db_block_gets)) {

	    $mem_gets = $consistent_gets + $db_block_gets;
	
	    if ($mem_gets <= 0) {
		$currStatus = $cALERT_STATUS;
	    } else {
		$hitratio = (($mem_gets - $physical_reads) / $mem_gets) * 100; 

		if ($hitratio < $hitratios_threshold_alert) {
		    $currStatus = $cALERT_STATUS;
		} elsif ($hitratio < $hitratios_threshold_warn) {
		    $currStatus = $cWARNING_STATUS;
		} else {
		    $currStatus = $cOK_STATUS;
		}
	    }		
	    
	    # 
	    # escalate warning/alert level if necessary
            #
	    if (($hitratiosStatus == $cNO_STATUS) || 
		($hitratiosStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$hitratiosStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($hitratiosStatus == $cWARNING_STATUS)) {
		$hitratiosStatus = $currStatus;
	    }

	    
	    @{$hitratiosTableRef->[$hitratiosRowCount]} = 
		($currStatus, 
		 $consistent_gets,
		 $db_block_gets,
		 $physical_reads,
		 $hitratio);

	    $hitratiosRowCount++;
	}

	$hitratiosRow = $sth->fetchrow_arrayref;

    }

    if ($sth) {
	$sth->finish;
    }

    showInfoPage ($hitratiosTableRef, "hitratios", $inTNS, "Hit Ratios Info Page");

    return $hitratiosStatus;

}

#-----------------------------------------------------------------------
# 
# check for tablespace, heap, and b-tree fragmentation in db
# 
# 1. all extent sizes in a given tablespace must be the same
#    as the tablespace defaults, otherwise, flag each one as
#    potentially fragmentating (flags a WARNING because it 
#    may not mean any actual objects are affected...)
#
# perhaps this #2 should be with getExtentsStatus
#
# 2. extent size must be a multiple of db_block_size *
#    db_file_multiblock_read_count, which must be the same
#    as the initial/next extent of the tablespace
#
# Not sure if the above is the best implementation.  Looking
# for suggestions on how to implement this.
#
#-----------------------------------------------------------------------
sub getFragmentationStatus ($$$) {
    my ($fragmentation_threshold_warn, 
	$fragmentation_threshold_alert, 
	$inTNS) = @_;

    my $temp = $fragmentation_threshold_warn + $fragmentation_threshold_alert;
    my $fragmentationStatus = $cNO_STATUS;
    my $fragmentationRef = [];
    #my $fragmentationData = [];
    my $currStatus = $cNO_STATUS;
    $db_block_size = 0;
    $db_file_multiblock_read_count = 0;

    $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ("SELECT name, value FROM v\$parameter WHERE name IN ('db_block_size', 'db_file_multiblock_read_count')");
    $rv = $sth->execute;

    $array_ref = $sth->fetchrow_arrayref;
    while (defined ($array_ref->[0])) {


	if ($array_ref->[0] =~ /db_block_size/) {
	    $db_block_size = $array_ref->[1];
	} elsif ($array_ref->[0] =~ /db_file_multiblock_read_count/) {
	    $db_file_multiblock_read_count = $array_ref->[1];
	}


	$array_ref = $sth->fetchrow_arrayref;
    }
    if ($sth) {
	$sth->finish;
    }

    #
    #
    #
    $block_multiple = 0;
    if ($db_block_size && $db_file_multiblock_read_count) {
	$block_multiple = $db_block_size * $db_file_multiblock_read_count;
    }

    if ($block_multiple > 0) {


	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{fragmentation});
	$rv = $sth->execute;
	$array_ref = $sth->fetchrow_arrayref;

	if (defined ($array_ref->[0])) {
	    $fragmentationStatus = $cOK_STATUS;
	}

	@{$fragmentationTableRef->[0]} = ("Level",
					  "Type",
					  "Name",
					  "Initial",
					  "Next",
					  "% Increase");
	
	$currStatus = $cNO_STATUS;
	$fragmentationRowCount = 1;
	while (defined ($array_ref->[0])) {
	    
	    #
	    # store the tablespace data for later
	    # 
	    # This is the size we want all our initial and
	    # next extents to be
	    #
	    $fragmentationData{$array_ref->[0]} = $array_ref->[1];


	    #
	    # right now we're warning if any of our tablespaces have a 
	    # initial or next extent which is not a multiple of our
	    # db_block_size * db_file_multiblock_read_count or if
	    # they have a % increase which is non-zero.
	    #
	    if (($array_ref->[1] % $block_multiple != 0) || 
		($array_ref->[2] % $block_multiple != 0) ||
		($array_ref->[3] != 0)) {
#		print ("I:$array_ref->[1] N:$array_ref->[2] M:$block_multiple\n");
		$currStatus = $cWARNING_STATUS;

	    #
	    # otherwise ok status
            #
	    } else {
		$currStatus = $cOK_STATUS;
	    }

	    
	    # 
	    # escalate warning/alert level if necessary
            #
	    if (($fragmentationStatus == $cNO_STATUS) || 
		($fragmentationStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$fragmentationStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($fragmentationStatus == $cWARNING_STATUS)) {
		$fragmentationStatus = $currStatus;
	    }

	    
	    @{$fragmentationTableRef->[$fragmentationRowCount]} = 
		($currStatus, 
		 "Tablespace",
		 $array_ref->[0],
		 $array_ref->[1],
		 $array_ref->[2],
		 $array_ref->[3]);



	    $fragmentationRowCount++;

	    $array_ref = $sth->fetchrow_arrayref;
	}

	if ($sth) {
	    $sth->finish;
	}


	$astatement = "SELECT owner, segment_name, segment_type, tablespace_name, initial_extent, next_extent, pct_increase FROM dba_segments WHERE owner NOT IN ('SYS', 'SYSTEM')";
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($astatement);
	$rv = $sth->execute;

	$array_ref = $sth->fetchrow_arrayref;
	while ($array_ref->[0]) {

	    if (($array_ref->[4] != $fragmentationData{$array_ref->[3]}) ||
		($array_ref->[5] != $fragmentationData{$array_ref->[3]})) {
		$currStatus = $cALERT_STATUS;
	    } else {
		$currStatus = $cOK_STATUS;
	    }
	    
	    # 
	    # escalate warning/alert level if necessary
            #
	    if (($fragmentationStatus == $cNO_STATUS) || 
		($fragmentationStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$fragmentationStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($fragmentationStatus == $cWARNING_STATUS)) {
		$fragmentationStatus = $currStatus;
	    }

	    
	    @{$fragmentationTableRef->[$fragmentationRowCount]} = 
		($currStatus, 
		 $array_ref->[2],
		 "$array_ref->[0].$array_ref->[1]",
		 $array_ref->[4],
		 $array_ref->[5],
		 $array_ref->[6]);

	    $fragmentationRowCount++;
	    $array_ref = $sth->fetchrow_arrayref;
	}

    }

    showInfoPage ($fragmentationTableRef, "fragmentation", $inTNS, "Fragmentation Info Page");

    return $fragmentationStatus;

}

#-----------------------------------------------------------------------
# 
# returns checks load average, %idle, and i/o wait
# not sure how to implement this for remote machines yet
#
#-----------------------------------------------------------------------
sub getOSStatus ($$$) {
    my ($os_threshold_warn, $os_threshold_alert, $inTNS) = @_;

    my $osStatus = $cNO_STATUS;
    my $curr_row = [];
    my $osTableRef = [()];

#    $sth = $dbh{$inTNS}->prepare ("SELECT table_name FROM user_tables WHERE table_name = \'KARMA_OS_STATS\'");
    $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ("SELECT table_name FROM user_tables WHERE table_name = \'KARMA_OS_STATS\'");
    $rv = $sth->execute;
    $curr_row = $sth->fetchrow_arrayref;

    #
    # display one row regardless, so the "No Data Found." message
    # will come up as appropriate
    #
    $osTableRef = [()];
    @{$osTableRef->[0]} = ("Level", 
			   "1 Min", 
			   "5 Min", 
			   "15 Min",
			   "% Idle",
			   "Time");
    
    $osRowCount = 1;

    if (defined ($curr_row->[0])) {

#	$sth = $dbh{$inTNS}->prepare ($statements{os});
	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{os});
	$rv = $sth->execute;

	#$osTableRef = [()];
	#@{$osTableRef->[0]} = ("Level", 
	#		      "1 Min", 
	#		      "5 Min", 
	#		      "15 Min",
	#		      "% Idle",
	#		      "Time");
	#$osRowCount = 1;

	$curr_row = $sth->fetchrow_arrayref;
	while (defined ($curr_row->[0])) {
	    
	    if ($curr_row->[0] > $os_threshold_alert) {
		$currStatus = $cALERT_STATUS;
	    } elsif ($curr_row->[0] > $os_threshold_warn) {
		$currStatus = $cWARNING_STATUS;
	    } else {
		$currStatus = $cOK_STATUS;
	    }

	    #
	    # escalate warning/alert level if necessary
	    #
	    if (($osStatus == $cNO_STATUS) || 
		($osStatus == $cOK_STATUS) ||
		($currStatus == $cALERT_STATUS)) {
		$osStatus = $currStatus;
	    } elsif (($currStatus == $cALERT_STATUS) &&
		     ($osStatus == $cWARNING_STATUS)) {
		$osStatus = $currStatus;
	    }
	    
	    @{$osTableRef->[$osRowCount]} = 
		($currStatus, 
		 $curr_row->[0],
		 $curr_row->[1],
		 $curr_row->[2],
		 $curr_row->[3],
		 $curr_row->[4]);


	    $osRowCount++;
	    $curr_row = $sth->fetchrow_arrayref;
	}
    }

    if ($sth) {
	$sth->finish;
    }

    showInfoPage ($osTableRef, "os", $inTNS, "OS Info Page");

    return $osStatus;

}

#-----------------------------------------------------------------------
# 
# checks the MTS shared server and dispatcher processes to make sure
# there isn't too much contention
#
#-----------------------------------------------------------------------
sub getMTSStatus ($$$) {
    my ($mts_threshold_warn, $mts_threshold_alert, $inTNS) = @_;

    my $mtsStatus = $cNO_STATUS;
    my $busy = 0;
    my $busy_idle = 0;
    my $busy_percent = 0;
    my $curr_row = [()];
    my $mtsRowCount = 0;
    my $mtsTableRef = [()];

#    $sth = $dbh{$inTNS}->prepare($statements{mts});
    $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare($statements{mts});
    $rv = $sth->execute;
    
    $mtsTableRef = [()];
    @{$mtsTableRef->[0]} = ("Level", 
			   "Name", 
			   "Busy", 
			   "Idle",
			   "% Busy");
    
    
    $mtsRowCount = 1;

    $curr_row = $sth->fetchrow_arrayref;    
    while (defined ($curr_row->[0])) {
	$busy = $curr_row->[1];
	$idle = $curr_row->[2];
	$busy_idle = $busy + $idle;
	$busy_percent = 0;
	if ($busy_idle) {
	    $busy_percent = $busy/$busy_idle * 100;

	    if ($busy_percent > $mts_threshold_alert) {
		$currStatus = $cALERT_STATUS;
	    } elsif ($busy_percent > $mts_threshold_warn) {
		$currStatus = $cWARNING_STATUS;
	    } else {
		$currStatus = $cOK_STATUS;
	    }
	}

	#
	# escalate warning/alert level if necessary
	#
	if (($mtsStatus == $cNO_STATUS) || 
	    ($mtsStatus == $cOK_STATUS) ||
	    ($currStatus == $cALERT_STATUS)) {
	    $mtsStatus = $currStatus;
	} elsif (($currStatus == $cALERT_STATUS) &&
		 ($mtsStatus == $cWARNING_STATUS)) {
	    $mtsStatus = $currStatus;
	}
	
	@{$mtsTableRef->[$mtsRowCount]} = 
	    ($currStatus, 
	     $curr_row->[0],
	     $busy,
	     $idle,
	     $busy_percent,
	     $curr_row->[4]);


	$mtsRowCount++;
	$curr_row = $sth->fetchrow_arrayref;
    }

    if ($sth) {
	$sth->finish;
    }

    showInfoPage ($mtsTableRef, "mts", $inTNS, "MTS Info Page");

    return $mtsStatus;

}

#-----------------------------------------------------------------------
# 
#
#-----------------------------------------------------------------------
sub getUpStatus ($) {
    my ($inTNS) = @_;
    my $upStatus = $cALERT_STATUS;
    my $upRow = [()];
    my $upTableRef = [()];
    my $upRowCount = 0;

    $upStatus = $cALERT_STATUS;
    $upRow = [()];
    $upTableRef = [()];
    $upRowCount = 0;

    #
    # attempt to connect again at regular intervals
    # in case the db comes back up
    # 
    if (not (defined ($db_info{$inTNS}[$cDB_HANDLE]))) {
	$db_info{$inTNS}[$cDB_HANDLE] = DBI->connect ("DBI:Oracle:$inTNS", 
					     $db_info{$inTNS}[$cDB_USER],
					     $db_info{$inTNS}[$cDB_PASS],
						      {RaiseError => 0,
						       PrintError => 0});
	if (defined ($DBI::errstr))  {
	    log_message_wtime ("$DBI::errstr\n");
	}
    }


    #
    # if our database handle is ok, we're up...
    #
    if (defined ($db_info{$inTNS}[$cDB_HANDLE])) {

#	print ("$inTNS - We have db handle...\n");
	$upStatus = $cOK_STATUS;

	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare($statements{up});
	$rv = $sth->execute;
	
	$upTableRef = [()];
	@{$upTableRef->[0]} = ("Level", 
				"Statistic", 
				"Value");
	
	$upRowCount = 1;

	$upRow = $sth->fetchrow_arrayref;
	while (defined ($upRow->[0])) {
	    @{$upTableRef->[$upRowCount]} = 
		($cNO_STATUS, 
		 $upRow->[0],
		 $upRow->[1]);
	    	    
	    $upRowCount++;
	    $upRow = $sth->fetchrow_arrayref;

	}

	showInfoPage ($upTableRef, "up", $inTNS, "Up Info Page");

    } else {

	#
	# don't show unless we have a db handle
	#
	showInfoPage (undef, "up", $inTNS, "Up Info Page");
    }

    return $upStatus;
}


#-----------------------------------------------------------------------
# 
#
#-----------------------------------------------------------------------
sub getDbStatus ($) {
    my ($inTNS) = @_;
    my $dbStatus = $cOK_STATUS;
    my $dbRow = [()];
    my $dbTableRef = [()];
    my $dbRowCount = 0;


    if (defined ($db_info{$inTNS}[$cDB_HANDLE])) {
	
	$dbStatus = $cOK_STATUS;

	$sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare($statements{db});
	$rv = $sth->execute;
	
	$dbTableRef = [()];
	@{$dbTableRef->[0]} = ("Level", 
				"Parameter", 
				"Value");
	
	$dbRowCount = 1;

	$dbRow = $sth->fetchrow_arrayref;
	while (defined ($dbRow->[0])) {
	    @{$dbTableRef->[$dbRowCount]} = 
		($cNO_STATUS, 
		 $dbRow->[0],
		 $dbRow->[1]);
	    	    
	    $dbRowCount++;
	    $dbRow = $sth->fetchrow_arrayref;
	}

	showInfoPage ($dbTableRef, "db", $inTNS, "DB Info Page");

    } else {
	showInfoPage (undef, "db", $inTNS, "DB Info Page");
    }

    return $dbStatus;
}


#-----------------------------------------------------------------------
# 
# Check object extents 
#  1. an object which is at it's maxextents (or near)
# NOT IMPLEMENTED YET:
#  2. an object whose next extent won't fit in the tablespace
#
#-----------------------------------------------------------------------
sub getExtentsStatus ($$$) {
    my ($extents_threshold_warn, $extents_threshold_alert, $inTNS) = @_;

    my $extentsStatus = $cNO_STATUS;
    my $currStatus = $cNO_STATUS;
    my $extentsTableRef = [()];
    my $extentsRowCount = 1;
    my $extentsRow = [()];

    $extentsTableRef = [()];
    @{$extentsTableRef->[0]} = ("Level", 
				"Name",
				"Owner",
				"Current", 
				"Max");

    $extentsRowCount = 1;

    $sth = $db_info{$inTNS}[$cDB_HANDLE]->prepare ($statements{extents});
    
    $rv = $sth->execute;

    $extents_row = $sth->fetchrow_arrayref;
    while (defined ($extents_row->[0])) {

	#
	# looks like we're at alert status...
	#
	if ($extents_row->[2] ==
	    ($extents_row->[1] - $extents_threshold_alert)) {
	    $currStatus = $cALERT_STATUS;

	#
	# warn status... don't set if we're already at cALERT_STATUS
        #
	} elsif ($extents_row->[2] == 
		  ($extents_row->[1] - $extents_threshold_warn)) {
	    $currStatus = $cWARNING_STATUS;

	# 
	# if we haven't set the status yet, we're at ok status.  This
        # may change with subsequent iterations of this loop
	#
	} else {
	    $currStatus = $cOK_STATUS;
	}

	#
	# escalate warning/alert level if necessary
	#
	if (($extentsStatus == $cNO_STATUS) || 
	    ($extentsStatus == $cOK_STATUS) ||
	    ($currStatus == $cALERT_STATUS)) {
	    $extentsStatus = $currStatus;
	} elsif (($currStatus == $cALERT_STATUS) &&
		 ($extentsStatus == $cWARNING_STATUS)) {
	    $extentsStatus = $currStatus;
	}
	
	if (($currStatus == $cALERT_STATUS) ||
	    ($currStatus == $cWARNING_STATUS)) {
	    @{$extentsTableRef->[$extentsRowCount]} = 
		($currStatus, 
		 $extents_row->[0],
		 $extents_row->[3],
		 $extents_row->[2],
		 $extents_row->[1]);
	    

	    $extentsRowCount++;
	}
	$extents_row = $sth->fetchrow_arrayref;
    }
    
    if ($sth) {
	$sth->finish;
    }

    showInfoPage ($extentsTableRef, "extents", $inTNS,  "Extents Info Page");

    return $extentsStatus;

}


#-----------------------------------------------------------------------
#
# convert the given time to a string
#
#-----------------------------------------------------------------------
sub getTimeString ($) {
    my ($inTime) = @_;

    my $timeStr = "";
    my $hourStr = "";
    my $sec =  $min = $hour = $mday = $mon = 
       $year = $wday = $yday = $isdst = 0;


    #
    # gmtime is 4 hours forward... didn't know the correct way to handle
    # this...
    #
#    $inTime -= (4 * 60 * 60);
#    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = gmtime ($inTime);

    #
    # it's so much easier when you know the right function to do the job
    #
    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime ($inTime);

    #
    # adjust minutes, or just put hour and minutes together for time
    # string
    #
    if ($hour < 10) {
	$hourStr = "0$hour";
    } else {
	$hourStr = "$hour";
    }
    if ($min < 10) {
	$timeStr = "$hourStr:0$min";
    } else {
	$timeStr = "$hourStr:$min";
    }

    return $timeStr;
}

#-----------------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------------
sub getInfo ($$) {
    my ($inTNS, $inMinutes) = @_;

    #
    # we do this in readConfig now, and leave them between
    # iterations of "main" because not every service is updated
    # on the minute.  old values are then kept, and services
    # that are run have their values updated
    #
#    %theStatus = [()];


    foreach $key (keys %{$config_info{default}}) {
	if ($key =~ /^up$/) {
	    $theStatus{up} = $cALERT_STATUS;
	} else {
	    $theStatus{$key} = $cNO_STATUS;
	}
    }

    my $thePrefGroup = $db_info{$inTNS}[$cDB_PREFGROUP];

    $theStatus{up} = getStatus ($thePrefGroup, "up", $inTNS);
    $stats{up}{$inTNS}[0] = $theStatus{up};
    $stats{up}{$inTNS}[1] = $currTime;

    #
    # dbStatus doesn't seem to be used here...
    #
    #$theStatus{db} = getStatus ($thePrefGroup, "db", $inTNS);
    getStatus ($thePrefGroup, "db", $inTNS);

    if ($theStatus{up} == $cOK_STATUS) {

	foreach $key (keys %{$config_info{default}}) {
	    if (not ($key =~ /^up$/)) {
		$theStatus{$key} = $stats{$key}{$inTNS}[0];
	    }
	}
    }


    if ($theStatus{up} == $cOK_STATUS) {

	foreach $key (keys %{$config_info{default}}) {

	    if (shouldUpdateService ($thePrefGroup, $key, $inMinutes) == 1) {
		if ($key =~ /^os$/) {
		    showInfoPage ($testRef, "os", $inTNS, "OS Info Page");
		}
		if (not ($key =~ /^up$/)) {
		    $theStatus{$key} = getStatus ($thePrefGroup, $key, $inTNS);
		    $stats{$key}{$inTNS}[0] = $theStatus{$key};
		    
		    #
		    # is this the only place that the service update time
		    # is set?  How are they all not getting updated?
		    #
		    $stats{$key}{$inTNS}[1] = $currTime;
		}
	    }
	}

    }


	#
	# it'd be nice to return the hash
	#
    
#	return ($theStatus{up}, $theStatus{os}, $theStatus{mts},
#		$theStatus{redolog}, $theStatus{rollback}, 
#		$theStatus{latch}, $theStatus{tablespace},
#		$theStatus{slowsql}, $theStatus{alertlog},
#		$theStatus{hitratios}, $theStatus{fragmentation},
#		$theStatus{extents});
}


#-----------------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------------
sub showKarmaTableHeader () {
#    my ($index_file) = @_;

#    print $index_file ("<TR >\n");
#    print $index_file ("<TD bgcolor=\"$cHD_BG_COLOR\" cellspacing=0 cellpadding=0 ALIGN=CENTER><H4>\n");
#    print $index_file ("<I><A HREF=\"help/$files{help}{db}\">db</A></I>\n");
#    print $index_file ("</H4></TD>\n");
    print $index_file ("<tr bgcolor=\"$cMAIN_TABLE_BG\" align=\"center\" valign=\"middle\"> \n");

    #
    # this will be added to the hash...
    #
    print $index_file ("<td height=\"$cSTATUS_HEIGHT\" width=\"$cSTATUS_WIDTH\"><font size=\"4\"><b>\n");
    print $index_file ("<font face=\"Arial, Helvetica, sans-serif\"\n");
    print $index_file ("color=\"$cKARMA_TEXT_COLOR\">\n");
    print $index_file ("<A HREF=\"help/$files{help}{db}\" ALT=\"Help - $names{long}{db}\">$names{short}{db}</A>\n");
    print $index_file ("</font></b></font></td>\n");

    foreach $key (keys %{$names{long}}) {
#print ("HEADER: $key\n");
	#
	# need to use pref group prefs if defined
	#
	if ((not ($key =~ /^db$/i)) &&
	    (($key =~ /^up$/i) ||
#	    ($config_info{default}{$key}[0] > 0))) {
	     (shouldShowServiceHeader ($key) == 1))) {
#	    print $index_file ("<TD bgcolor=\"$cHD_BG_COLOR\" cellspacing=0 cellpadding=0 ALIGN=CENTER><H4>\n");
#	    print $index_file ("<I><A HREF=\"help/$files{help}{$key}\" ALT=\"Help - $names{long}{$key}\">$names{short}{$key}</A></I>\n");


#	    print ("Showing header - $names{short}{$key}\n");

	    #
	    # new stuff
	    #
	    print $index_file ("<td height=\"$cSTATUS_HEIGHT\" width=\"$cSTATUS_WIDTH\"><font size=\"4\"><b>\n");
	    print $index_file ("<font face=\"Arial, Helvetica, sans-serif\"\n");
	    print $index_file ("color=\"$cKARMA_TEXT_COLOR\">\n");

	    print $index_file ("<A HREF=\"help/$files{help}{$key}\">$names{short}{$key}</A>\n");


#	    print $index_file ("<I>$key</I>\n");
#	    print $index_file ("$key\n");
#	    print $index_file ("</H4></TD>\n");
	    print $index_file ("</font></b></font></td>\n");
	}
    }

    print $index_file ("</TR>\n");

}


#-----------------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------------
sub showKarmaTableRow ($$) { 
    #my ($inRow, $inTNS, $inMinutes) = @_;
    my ($inTNS, $inMinutes) = @_;

#    print $index_file ("<TR ><TD><H5>$db[$inRow][0]</H5></TD>\n");
#    print $index_file ("<TR ><TD><H5><A HREF=\"info/$inTNS.$files{info}{db}\">$inTNS</A></H5></TD>\n");


    print $index_file ("<td width=\"$cHEAD_HEIGHT\" height=\"$cSTATUS_HEIGHT\"><font face=\"Arial, Helvetica, sans-serif\" color=\"$cKARMA_TEXT_COLOR\"><a href=\"generic_info.html\" target=\"main\">\n");
    print $index_file ("<A HREF=\"info/$inTNS.$files{info}{db}\">$inTNS</A>\n");
    print $index_file ("</a></font></td>\n");

    my $thePrefGroup = $db_info{$inTNS}[$cDB_PREFGROUP];

    #
    # need to use pref group prefs if defined
    #
#	    ($config_info{default}{$key}[0] > 0))) {
	    #&& ($inMinutes % $config_info{default}{$key}[0] == 0)) {
	    #print $index_file ("<TD ALIGN=CENTER>\n");

    foreach $key (keys %{$names{long}}) {
	if ((not ($key =~ /^db$/i)) &&
	    (($key =~ /^up$/i) ||
	     (shouldShowService ($thePrefGroup, $key) == 1))) {
	    print $index_file ("<td align=\"center\" valign=\"middle\">\n");

	    showServiceStatus ($key, $inTNS);

	    print $index_file ("</TD>\n");
	    
	}
    }

    print $index_file ("</TR>\n");

}


#-----------------------------------------------------------------------
#
# print the help message and exit
#
#-----------------------------------------------------------------------
sub print_help {

    print ("\n");
    print (" v - print version info and exit\n");
    print (" h - print this help info\n");
    print (" k - karma root directory\n");
    print (" w - print the warranty\n");
    print (" c - specify the karma configuration file\n");
    print (" l - specify logfile (default is stdout)\n");
    print ("\n");
    print ("$0 [-h] [-k karma_root] [-c karma.conf]\n");
    
    exit;

}


#-----------------------------------------------------------------------
#
# 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 \"karmad -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;
}

#-----------------------------------------------------------------------
#
# read the configuration file
#
# only parameter is the file to open for reading config info
#
#-----------------------------------------------------------------------
sub readConfig ($) {
    my ($index_file) = @_;

    # open file
    $conf_file = new IO::File "$index_file";
    if (not (defined ($conf_file))) {
	log_message ("Cannot open config file, using factory defaults\n");
    }
#    open (FILE, "$index_file");

    # iterate on lines
    my $eofile = 0;
    my $db_count = 0;
    my $curr_line = "";
    my @curr_array = ();
    while (!$eofile) {
	
	$eofile = !($curr_line = <$conf_file>);
	
	#
	# ignore comment lines and blank lines
	#
	while ((!$eofile) && (($curr_line =~ /^ *\#/) ||
               ($curr_line =~ /^ *$/))) {
#	    $eofile = !($curr_line = <FILE>);
	    $eofile = !($curr_line = <$conf_file>);
	}

	if ($curr_line) {

	    # remove the eol character
	    chop ($curr_line);


	    @curr_array = split (":", $curr_line);
#	    print ("Reading config... @curr_array\n");
	    if ($curr_array[0] =~ /^karma$/i) {

		# tnsname
		$theTNS = uc $curr_array[2];

#		print ("readConfig - TNS:$theTNS\n");

		if (isValidTNS ($theTNS) == 1) {
		    $theUser = $curr_array[3];
		    $thePass = $curr_array[4];
		    
		    # connect to the db
		    $db_info{$theTNS}[$cDB_HANDLE] = DBI->connect 
			("DBI:Oracle:$theTNS", 
			 $theUser,
			 $thePass,
			 {RaiseError => 0,
			  PrintError => 0});
		    
		    # username
		    $db_info{$theTNS}[$cDB_USER] = $theUser;
		    
		    # password
		    $db_info{$theTNS}[$cDB_PASS] = $thePass;
		    
		    # karma refresh
		    $db_info{$theTNS}[$cDB_REFRESH] = $curr_array[4];
		    
		    # pref_group
		    $theGroup = $curr_array[1];
		    if (($theGroup =~ /^$/) ||
			($theGroup =~ /^\*$/) ||
			($theGroup =~ /^default$/i)) {
			
			$theGroup = $cDEF_GRP_NAME;
			$pref_groups{$cDEF_GRP_NAME} = 1;
		    } else {
			$theGroup = $curr_array[1];
			$pref_groups{$curr_array[1]} = 1;
		    }
		    $db_info{$theTNS}[$cDB_PREFGROUP] = $theGroup;

		    
		    #
		    # log any errors
		    #
		    if (defined ($DBI::errstr)) {
			log_message_wtime ("$DBI::errstr\n");
		    }

		} else {
		 
		    log_message_wtime ("Invalid TNS - $theTNS\n");
		}

	    } elsif ($curr_array[0] =~ /^refresh$/i) {
		$config_info{default}{up}[0] = $curr_array[1];
		$config_info{default}{up}[1] = $curr_array[2];
		$config_info{default}{up}[2] = 0;
	    } elsif (($curr_array[0] =~ /^redolog$/i) ||
		     ($curr_array[0] =~ /^os$/i) ||
		     ($curr_array[0] =~ /^rollback$/i) ||
		     ($curr_array[0] =~ /^tablespace$/i) ||
		     ($curr_array[0] =~ /^slowsql$/i) ||
		     ($curr_array[0] =~ /^alertlog$/i) ||
		     ($curr_array[0] =~ /^hitratios$/i) ||
		     ($curr_array[0] =~ /^fragmentation$/i) ||
		     ($curr_array[0] =~ /^extents$/i) ||
		     ($curr_array[0] =~ /^latch$/i) ||
		     ($curr_array[0] =~ /^mts$/i)) {
	
		set_config ("default",
			      $curr_array[0],
			      $curr_array[1],
			      $curr_array[2],
			      $curr_array[3]);
	    } elsif (($curr_array[0] =~ /^$cNOTIFY_ALRT$/i) ||
		     ($curr_array[0] =~ /^$cNOTIFY_WARN$/i)) {
		set_config_notify ("default",
				   $curr_array[0],
				   $curr_array[1],
				   $curr_array[2],
				   $curr_array[3]);
	    } elsif ($curr_array[0] =~ /^$cNOTIFY_EMAIL$/i) {
		set_config_email ("default",
				  $curr_array[1]);
	    } elsif ($curr_array[0] =~ /^warn_blink$/i) {
		if (($curr_array[1] =~ /^true$/i) || 
		    ($curr_array[1] =~ /^yes$/i) ||
		    ($curr_array[1] =~ /^1$/i)) {
		    $USE_BLINK_WARNING=1;		    
		}
	    } elsif ($curr_array[0] =~ /^alert_blink$/i) {
		if (($curr_array[1] =~ /^true$/i) || 
		    ($curr_array[1] =~ /^yes$/i) ||
		    ($curr_array[1] =~ /^1$/i)) {
		    $USE_BLINK_ALERT=1;
		}

	    } else {
		
		#
		# get prefgroup preferences
		# 
		# These are just like default preferences, except
		# they are preceded by a prefgroup name.
		#
		foreach $key (keys %pref_groups) {

#		    print ("SUB GROUP SECTION - $key\n");
		    #
		    # preferences are bound for a particular preference group
		    #
		    if ($curr_array[0] =~ /^$key$/i) {
			if (($curr_array[1] =~ /^redolog$/i) ||
			    ($curr_array[1] =~ /^os$/i) ||
			    ($curr_array[1] =~ /^rollback$/i) ||
			    ($curr_array[1] =~ /^tablespace$/i) ||
			    ($curr_array[1] =~ /^slowsql$/i) ||
			    ($curr_array[1] =~ /^alertlog$/i) ||
			    ($curr_array[1] =~ /^hitratios$/i) ||
			    ($curr_array[1] =~ /^fragmentation$/i) ||
			    ($curr_array[1] =~ /^extents$/i) ||
			    ($curr_array[1] =~ /^latch$/i) ||
			    ($curr_array[1] =~ /^mts$/i)) {
			    set_config ($key,
					$curr_array[1],
					$curr_array[2],
					$curr_array[3],
					$curr_array[4]);
			} elsif ($curr_array[1] =~ /^$cNOTIFY_EMAIL$/i) {
			    set_config_email ($key,
					      $curr_array[2]);
			} elsif (($curr_array[1] =~ /^$cNOTIFY_ALRT$/i) ||
				 ($curr_array[1] =~ /^$cNOTIFY_WARN$/i)) {
			    set_config_notify ($key,
					       $curr_array[1],
					       $curr_array[2],
					       $curr_array[3],
					       $curr_array[4]);
			}

#			print "Nothing right now\n";
		    }
		}


	    }

	}

    }

    #
    # initialize everything to $cNO_STATUS;
    #
    # iterate on all configured services
    foreach $servKey (keys %{$config_info{default}}) {
	foreach $tnsKey (keys %db_info) {
	    $stats{$servKey}{$tnsKey}[0] = $cNO_STATUS;
	    $stats{$servKey}{$tnsKey}[1] = $startTime;
	}
    }

    if ($conf_file) {
	$conf_file->close;
    }
    #close (FILE);
}

#---------------------------------------------------------------
#
# add the configuration information just read from the file
# to the right place in the config_info hash of hash of arrays
#
#---------------------------------------------------------------
sub set_config ($$$$$) {
    my ($inPrefGroup, $inService, $inInterval, $inWarn, $inAlert) = @_;

#    print ("SET_CONFIG...\n");
#    if ($inInterval > 1) {
#	print ("**set_config**: P:$inPrefGroup S:$inService I:$inInterval W:$inWarn A:$inAlert\n");
#    } else {
#	print ("set_config: P:$inPrefGroup S:$inService I:$inInterval W:$inWarn A:$inAlert\n");
#    }
#    print ("set_config PG:$inPrefGroup SV:$inService IN:$inInterval\n");
    if (not ((defined ($inInterval)) && ($inInterval =~ /\d*/))) {
	$config_info{$inPrefGroup}{$inService}[0] =
	    $config_info{factory}{$inService}[0];
#	print ("$inService using default interval: $config_info{factory}{$inService}[0]\n");
   } else {
	$config_info{$inPrefGroup}{$inService}[0] = $inInterval;
    }

    if (not ((defined ($inWarn)) && ($inWarn =~ /\d*/))) {
	$config_info{$inPrefGroup}{$inService}[1] =
	    $config_info{factory}{$inService}[1];
#	print ("$inService using default warning: $config_info{factory}{$inService}[1]\n");
    } else {
	$config_info{$inPrefGroup}{$inService}[1] = $inWarn;
    }

    if (not ((defined ($inAlert)) && ($inAlert =~ /\d*/))) {
	$config_info{$inPrefGroup}{$inService}[2] =
	    $config_info{factory}{$inService}[2];
#	print ("$inService using default alert: $config_info{factory}{$inService}[2]\n");
    } else {
	$config_info{$inPrefGroup}{$inService}[2] = $inAlert;
    }

#    print ("SET_CONFIG: $config_info{$inPrefGroup}{$inService}[0], $config_info{$inPrefGroup}{$inService}[1], $config_info{$inPrefGroup}{$inService}[2]\n");
 
}


#---------------------------------------------------------------
#
# 
# still need to handle "@" in the email address...
#
#---------------------------------------------------------------
sub set_config_email ($$) {
    my ($inPrefGroup, $inEmails) = @_;

    if (not (defined ($inEmails))) {
	print ("set_config_email:$inPrefGroup -  inEmails is undefined...\n");
    }

    my @emails = split ("," , $inEmails);
    my $count = scalar @emails;
    for ($i = 0; $i < $count; $i++) {
	push (@{$config_email{$inPrefGroup}}, $emails[$i]);
    }
}

#---------------------------------------------------------------
#
#
#
#---------------------------------------------------------------
sub showInfoPage ($$$$) {
    my ($inTableRef, $inType, $inTNS, $inTitle) = @_;

    #
    # top of page
    #
    my $info_file_name = "$cKARMA_DOC_ROOT/info/$inTNS.$files{info}{$inType}";
#print ("Trying to open file: $filename\n");

    $info_file = new IO::File ">$info_file_name";
    if (not (defined ($info_file))) {
	log_message ("Cannot open info page file: $info_file_name\n");
    } else {
#       open ($info_file, ">$filename");


	showInfoHead ($inTNS, $inType);
	
	my $i = 0;
	my $j = 0;
	
	if (defined ($inTableRef)) {
	    #
	    # content of page (table data)
	    #
#    print $info_file ("<TABLE>");
	    print $info_file ("<table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n");
	    
	    
	    if (defined ($inTableRef->[0][0])) {
#	print $info_file ("<TR>\n");
		print $info_file ("<tr bgcolor=\"$cMAIN_TABLE_BG\" align=\"left\" valign=\"middle\">\n");
		
		$i = 0;
		#
		# title row
		#
		while (defined ($inTableRef->[0][$i])) {
		    print $info_file ("<TD <font face=\"Arial, helvetica, sans-serif\" color=\"$cKARMA_TEXT_COLOR\">$inTableRef->[0][$i]</font></TD>\n");
		    $i++;
		}
		print $info_file ("</TR>\n");
		$j = 1;
		
		while (defined ($inTableRef->[$j][0])) {
		    
		    print $info_file ("<TR align=\"left\" valign=\"middle\">\n");
		    $i = 0;
#	    while ($inTableRef[$j][$i]) {
		    while (defined ($inTableRef->[$j][$i])) {
			if ($i == 0) {
			    if ($inTableRef->[$j][$i] == $cWARNING_STATUS) {
				print $info_file ("<TD><IMG SRC=\"../images/yellow_status\"></TD>\n"); 
			    } elsif ($inTableRef->[$j][$i] == $cALERT_STATUS) {
				print $info_file ("<TD height=\"30\"><IMG SRC=\"../images/red_status\"></TD>\n"); 
			    } elsif ($inTableRef->[$j][$i] == $cOK_STATUS) {
				print $info_file ("<TD><IMG SRC=\"../images/green_status\" ></TD>\n");  
			    } else {
				print $info_file ("<TD><IMG SRC=\"../images/purple_status\"></TD>\n");  
			    }
			} else {
			    print $info_file ("<TD height=\"25\">\n");
			    print $info_file ("<font face=\"Arial, Helvetica, sans-serif\" color=\"$cKARMA_TEXT_COLOR\">\n");
			    print $info_file ("$inTableRef->[$j][$i]\n");
			    print $info_file ("</font>\n");
			    print $info_file ("</TD>\n");
			}
			$i++;
		    }
		    print $info_file ("</TR>\n");
		    
		    $j++;
		    
		}      
	    }
	    
	    print $info_file ("</TABLE>");
	    
	    if ($j == 1) {
		print $info_file ("No Data Found.\n");
	    }
	} else {
	    print $info_file ("<font color=\"$cEMPHASIS_TEXT\" size=\"5\" face=\"Arial, Helvetica, sans-serif\">Database DOWN!</font>\n");
	}
	
	
	showInfoFoot ($inType, $inTNS);
	
	
	#   if (defined ($info_file)) {
	$info_file->close;
	#   }
    }

#    close ($info_file);

}

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


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

    #
    # gmtime is 4 hours forward... didn't know the correct way to handle
    # this...
    #
    $inTime -= (4 * 60 * 60);
    
    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = gmtime ($inTime);

    $dayMinutes = $hour * 60 + $min;

    return $dayMinutes;
}


#-----------------------------------------------------------------------
#
# prints the header of the "main" page
#
#-----------------------------------------------------------------------
sub showKarmaHeadMain () {
#    my ($index_file) = @_;


    print $index_file ("<html>\n");
    print $index_file ("<head>\n");
    print $index_file ("<title>main</title>\n");

    print $index_file ("<META HTTP-EQUIV=\"REFRESH\" CONTENT=\"60\">\n");

#    print $index_file ("<META HTTP-EQUIV=\"REFRESH\" CONTENT=\"$config_info{up}[1]\">\n");

#    print $index_file ("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n");
    print $index_file ("</head>\n\n");

    print $index_file ("<body bgcolor=\"$cINFO_BG_COLOR\" link=\"$cKARMA_LINK_COLOR\" vlink=\"$cKARMA_LINK_COLOR\" alink=\"$cKARMA_LINK_COLOR\">\n");

    print $index_file ("<table width=\"100\%\" border=\"0\" height=\"67\%\" cellpadding=\"10\" cellspacing=\"0\" bordercolordark=\"$cBORD_COL_DARK\" bordercolor=\"$cBORDER_COLOR\" >\n");



    print $index_file ("<tr bgcolor=\"$cHEAD_BG_COLOR\">\n"); 

    print $index_file ("<th align=\"left\" height=\"75\" ><img src=\"images/logo.jpg\" ></th>\n");
    print $index_file ("<th width=\"61\%\" height=\"50\"><font color=\"#FF9933\" face=\"Arial, Helvetica, sans-serif\" size=\"6\">Oracle Monitor</font></th>\n");
    print $index_file ("<th width=\"11\%\" height=\"50\" align=right><img src=\"images/sm_logo.jpg\"></th>\n");

    print $index_file ("</tr>\n");

#    print $index_file ("<tr bgcolor=\"$cBODY_BG_COLOR\" valign=\"middle\" align=\"center\">\n"); 
    print $index_file ("<tr bgcolor=\"$cINFO_BG_COLOR\" valign=\"middle\" align=\"center\">\n"); 
    print $index_file ("<td colspan=\"3\" height=\"250\">\n"); 

}

#-----------------------------------------------------------------------
#
# prints the footer of the main page
#
#-----------------------------------------------------------------------
sub showKarmaFootMain () {
#    my ($index_file) = @_;

#    print $index_file ("</TABLE>\n");
#    print $index_file ("</CENTER>\n");
#    print $index_file ("</TABLE>\n");
#    print $index_file ("</BODY></HTML>\n");


    my $timeStr = getCurrTimeStr ();
    print $index_file ("</td>\n");
    print $index_file ("</tr>\n");
    print $index_file ("<tr bgcolor=\"$cINFO_BG_COLOR\">\n"); 
    print $index_file ("<td colspan=\"2\" height=\"2\" bgcolor=\"$cHEAD_BG_COLOR\" colspan=\"2\"><font color=\"$cTEXT_COLOR\" face=\"Arial, Helvetica, sans-serif\" size=\"5\">Last \n");
    print $index_file ("updated <font color=\"$cEMPHASIS_TEXT\">$timeStr</font></font></td>\n");

    print $index_file ("<td bgcolor=\"$cHEAD_BG_COLOR\" cellpadding=\"0\" cellspacing=\"0\" align=\"right\"><a href=\"help.html\"><img border=0 src=\"images/help.jpg\"></a></td>\n");

    print $index_file ("</tr>\n");
    print $index_file ("</table>\n");
    print $index_file ("</body>\n");
    print $index_file ("</html>\n");

}


#-----------------------------------------------------------------------
#
# prints the footer of an info page
#
#-----------------------------------------------------------------------
sub showInfoFoot ($$) {
    ($inType, $inTNS) = @_;

#    print $info_file ("<BR><A HREF=\"../help/$files{help}{$inType}\"><IMG SRC=\"../images/question_symbol\"></A>\n");

    #
    # bottom of the page
    #
#    print $info_file ("</BODY>\n");
#    print $info_file ("</HTML>\n");

    my $timeStr = getCurrTimeStr ();

    print $info_file ("</td>\n");
    print $info_file ("</tr>\n");
    print $info_file ("<tr bgcolor=\"$cINFO_BG_COLOR\"> \n");
    print $info_file ("<td height=\"$cHEAD_HEIGHT\" bgcolor=\"$cHEAD_BG_COLOR\"\n");
    print $info_file ("width=\"178\%\" colspan=\"2\">\n");
    print $info_file ("<font color=\"$cTEXT_COLOR\" face=\"Arial, Helvetica, sans-serif\" size=\"5\">Info for\n");
    print $info_file ("<font color=\"$cEMPHASIS_TEXT\">\n");
    print $info_file ("$inTNS\n");
    print $info_file ("</font> Database last updated <font color=\"$cEMPHASIS_TEXT\">\n");
    print $info_file ("$timeStr</font></font></td>\n");
    print $info_file ("<td height=\"$cHEAD_HEIGHT\" bgcolor=\"$cHEAD_BG_COLOR\" width=\"$cHEAD_HEIGHT\">\n");
#    print $info_file ("<a href=\"generic_help.html\" target=\"_self\">\n");
    print $info_file ("<a href=\"../help/$files{help}{$inType}\" target=\"_self\">\n");
    print $info_file ("<img src=\"../images/help.jpg\" width=\"$cHEAD_HEIGHT\" height=\"59\" align=\"right\" border=\"0\"></a></td>\n");
    print $info_file ("</tr>\n");
    print $info_file ("</table>\n");
    print $info_file ("</body>\n");
    print $info_file ("</html>\n");


}

#-----------------------------------------------------------------------
#
# prints the header of an info page
#
#-----------------------------------------------------------------------
sub showInfoHead ($$) {
    ($inTNS, $inType) = @_;


    my $timeStr = getCurrTimeStr ();
    #
    # old header stuff
    #
#    print $info_file ("<HTML>\n");
#    print $info_file ("<HEAD>");
#    print $info_file ("<TITLE>Karma: $inTNS - more info</TITLE>\n");

#    print $info_file ("<BODY BACKGROUND=\"../images/info_bkgd\" TEXT=$cTEXT_COLOR LINK=$cLINK_COLOR VLINK=$cVLINK_COLOR>\n");
#    print $info_file ("<H1>$inTitle for $inTNS database - last updated at $timeStr</H1><BR>\n");


#    print ("TNS:$inTNS TYPE:$inType\n");

    print $info_file ("<html>\n");
    print $info_file ("<head>\n");
    print $info_file ("<title>$inTNS - $names{long}{$inType} Info</title>\n");
    print $info_file ("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n");
    print $info_file ("</head>\n");
    
    print $info_file ("<body bgcolor=\"$cINFO_BG_COLOR\">\n");
    
    print $info_file ("<table width=\"100\%\" border=\"0\" height=\"300\" cellpadding=\"5\" cellspacing=\"0\" bordercolordark=\"$cBORD_COL_DARK\" bordercolor=\"$cBORDER_COLOR\" >\n");



    print $info_file ("<tr bgcolor=\"$cHEAD_BG_COLOR\">\n"); 
    print $info_file ("<th align=\"left\" height=\"$cHEAD_HEIGHT\"><font color=\"$cTEXT_COLOR\" face=\"Arial, Helvetica, sans-serif\" size=\"7\"><img src=\"../images/logo.jpg\" width=\"144\" height=\"36\">\n"); 
    
    print $info_file ("</font></th>\n");
    print $info_file ("<th height=\"$cHEAD_HEIGHT\"><font color=\"$cTEXT_COLOR\" face=\"Arial, Helvetica, sans-serif\" size=\"6\">\n");
    print $info_file ("$names{long}{$inType} Info\n");
    print $info_file ("</font></th>\n");
    print $info_file ("<th height=\"$cHEAD_HEIGHT\"><img src=\"../images/info.jpg\"\n");
    print $info_file ("width=\"$cHEAD_HEIGHT\" height=\"$cHEAD_HEIGHT\" align=\"right\"></th>\n");
    print $info_file ("</tr>\n");
    
#    print $info_file ("<tr bgcolor=\"$cBODY_BG_COLOR\">\n"); 
    print $info_file ("<tr bgcolor=\"$cINFO_BG_COLOR\">\n"); 

    print $info_file ("<td colspan=\"3\" height=\"150\" width=\"0\">\n"); 

}



#
# send_email - Thanks to Dennis Taylor <dennis@funkplanet.com> for this code...
#
# mail notification, before calling this, check for Mail::Send;
#
# send_email( 'zot@foo.com', <<NEEN );
# Your database escaped from the institution and burned your house down.
# NEEN
#
sub send_email ($$@) {
    my ($inRecipient, $inSubject, @inMessage) = @_;

    #
    # required only if you use this routine...
    # 
    require Mail::Send;
    my $msg = Mail::Send->new( To => $inRecipient,
                               Subject => $inSubject );
    my $fh = $msg->open( 'sendmail' );
    print $fh @inMessage;
    $fh->close;
}


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

    if (defined ($logfile)) {
	print $logfile ($message);
    }# else {
    # probably don't want to print an error a million times...
    #}
}

#
# log a message with a timestamp
#
# should this use $currTime??
#
sub log_message_wtime ($) {
    my ($inMessage) = @_;

    my $theTimeStr = getCurrTimeStr ();
    log_message ("$theTimeStr - $inMessage");
}

#
# normal kill signal handler
#
sub catch_term {
    log_message_wtime ("Received TERM signal, exiting karmad\n");
    exit_karma ();
}

#
# HUP signal means reread config file
#
sub catch_hup {
    log_message ("kill -HUP, rereading karma.conf file\n");

    # 
    # clean up the database handle structures
    #
    foreach $key (keys %db_info) {
	if (defined ($db_info{$key}[$cDB_HANDLE])) {
	    $db_info{$key}[$cDB_HANDLE]->disconnect;
	    undef $db_info{$key};
	}
    }

    #
    # clean up the config info data structures
    #
    foreach $key (keys %config_info) {
	if (not ($key =~ /^factory$/)) {
	    undef %{$config_info{$key}};
	}
    }
    
    #
    # clean up the config notify data structures
    #
    foreach $key (keys %config_notify) {
	undef %{$config_notify{$key}};
    }

    readConfig ($conf_file_name);

    #
    # do all checking as if karma just started
    #
    doDBChecks ();
    
    $currTime = time ();

}

#
# USR1 signal, return status via named pipe (fifo)
#
# status includes 
#   alert_notify services
#   warn_notify services
#   dbname: up/down
#
sub show_status {
    log_message ("kill -USR1, return status via named pipe\n");

    my $dbStatus = "";
    my $startTimeStr = getCurrTimeStr ();
    my $timeStr = "";
    my $statusStr = "";

    open (FIFO, ">$KARMA_FIFO_NAME");
    print FIFO ("karmad started at $startTimeStr, pid:$KARMA_PID\n");
    if ($USE_EMAIL_NOTIFICATION == 1) {
	print FIFO ("Using EMAIL for notification.\n");
    } else {
	print FIFO ("Using LOGFILE for notification.\n");
    }
    foreach $tnsKey (keys %db_info) {

	$thePrefGroup = $db_info{$tnsKey}[$cDB_PREFGROUP];
	$dbStatus = "DOWN";
	if (defined $db_info{$tnsKey}[$cDB_HANDLE]) {
	    $dbStatus = "UP";
	}
	print FIFO ("  DB:$tnsKey $dbStatus, Prefgroup:$thePrefGroup - services:\n");
	foreach $servKey (keys %{$config_info{$thePrefGroup}}) {
	    if (shouldShowService ($thePrefGroup, $servKey)) {
		$timeStr = getCurrTimeStr ($stats{$servKey}{$tnsKey}[1]);
		$statusStr = getStatusStr ($stats{$servKey}{$tnsKey}[0]);
		print FIFO ("    $timeStr $statusStr\t$servKey\n");

		
	    }
	}

    }

    close (FIFO);
}

#
# put all the clean exit stuff in here.
#
sub exit_karma () {

    #
    # close the message socket (named pipe)
    #
#    close (KARMAS_OCK);
#    close (FIFO);

    #
    # do we ever get here?
    #
    $logfile->close;
#    close (LFILE);

    #
    # not sure if we really ever get to here since we're looping
    # infinitely...  I wonder how we can terminate cleanly after
    # that?
    #
    foreach $key (keys %db_info) {
	if (defined ($db_info{$key}[$cDB_HANDLE])) {
	    $db_info{$key}[$cDB_HANDLE]->disconnect;
	}
    }

    exit;
}



#-----------------------------------------------------------------------
#
# sends email notification
# 
# one email gets sent out per database per email address...
#
# Want to add a check here to see if Mail::Send is installed, if
# not, log a message to the logfile, and return...
#
# Also, still need to add interval checking... don't mail unless the 
# specified interval has passed
#
#-----------------------------------------------------------------------
sub do_notification ($$) {
    my ($inTNS, $inMinutes) = @_;
    my $send_alert = 0;
    my $send_warn = 0;
    my $pref_group = $db_info{$inTNS}[$cDB_PREFGROUP];

    if (defined ($config_notify{$pref_group}{$cNOTIFY_ALRT}[0])) {

	#
	# send alert notification
	#
	
	my @alertServices = @{$config_notify{$pref_group}{$cNOTIFY_ALRT}[2]};
	my $alertSize = $config_notify{$pref_group}{$cNOTIFY_ALRT}[1];
	my @theServices = ();
	
	my $sent_message = 0;
	my $count = 0;
	
	if (defined (@alertServices)) {
	    $count = scalar @alertServices;
	    for ($i = 0; $i < $count; $i++) {
		
#	    print ("TNS:$inTNS AS:$alertServices[$i] ST:
#	print ("do_notify: P:$pref_group M:$inMinutes A:$config_notify{$pref_group}{$cNOTIFY_ALRT}[0]\n");
		if ((defined ($stats{$alertServices[$i]}{$inTNS}[0])) && 
		    (($inMinutes == 0) || ($inMinutes % $config_notify{$pref_group}{$cNOTIFY_ALRT}[0] == 0)) &&
		    ($stats{$alertServices[$i]}{$inTNS}[0] == $cALERT_STATUS)) {
		    $send_alert = 1;
		    push (@theServices, $alertServices[$i]);
		}
	    }
	    
	} else {
	    # should log message here
	}
	if ($send_alert == 1) {
	    send_notification ($cNOTIFY_ALRT, $alertSize, $inTNS, @theServices);
	}
    } 

    if (($send_alert == 0) && (defined ($config_notify{$pref_group}{$cNOTIFY_WARN}[0]))) {

	my @warnServices = @{$config_notify{$pref_group}{$cNOTIFY_WARN}[2]};
	my $warnSize = $config_notify{$pref_group}{$cNOTIFY_WARN}[1];
	my @theServices = ();
	my $count = 0;	    

	if (defined (@warnServices)) {
	    $count = scalar @warnServices;
	    for ($i = 0; $i < $count; $i++) {
		
#		print ("do_notify: P:$pref_group M:$inMinutes W:$config_notify{$pref_group}{$cNOTIFY_WARN}[0]\n");
		if ((defined ($stats{$warnServices[$i]}{$inTNS}[0])) && 
		    (($inMinutes == 0) || ($inMinutes % $config_notify{$pref_group}{$cNOTIFY_WARN}[0] == 0)) &&
		    ($stats{$warnServices[$i]}{$inTNS}[0] == $cWARNING_STATUS)) {
		    $send_warn = 1;
		    push (@theServices, $warnServices[$i]);
		}
	    }
	    
	    if ($send_warn == 1) {
		send_notification ($cNOTIFY_WARN, $warnSize, $inTNS, @theServices);
	    }
	} else {
	    # should log a message here...
	}

    }
}

#--------------------------------------------------------
#
# construct email message (full or short), and email it
# out to all the right people
#
#--------------------------------------------------------
sub send_notification ($$$@) {
    my ($inType, $inSize, $inTNS, @inServices) = @_;

    my $pref_group = $db_info{$inTNS}[$cDB_PREFGROUP];
    my $subject = "";
    my $email_count = 0;
    
    #
    # make sure there are email addresses defined
    # if not, we should at least log a message that they've enabled
    # notification without specifying who to email messages to
    #
    if (defined @{$config_email{$pref_group}}) {
	$email_count = scalar @{$config_email{$pref_group}};
    }

    if ($email_count > 0) {
	my $count = 0;
	my $message = "";
	my $i = 0;

	$message = "$inTNS:";
	if ($inType =~ /^$cNOTIFY_WARN$/i) {
	    $message .= "WARN:";
	} else {
	    $message .= "ALRT:";
	}
	$count = scalar @inServices;
	for ($i = 0; $i < $count; $i++) {
	    $message .= "$inServices[$i],";
	}

	if (not ($inSize =~ /^$cSHORT_EMAIL$/i)) {

	    $subject = $message;
	    $message = "$inTNS database ";
	    if ($inType =~ /^$cNOTIFY_WARN$/i) {
		$message .= "WARNING";
	    } else {
		$message .= "**ALERT**";
	    }
	    
	    $message .= " - The following services have problems:\n";
	    $count = scalar @inServices;
	    for ($i = 0; $i < $count; $i++) {
		$message .= "\t$inServices[$i]\n";
	    }
	}
	
	
	#
	# iterate on each email address in config_email hash of arrays
	#
#       show_config_email ();
	
	for ($i = 0; $i < $email_count; $i++ ) {
	    
	    $email = $config_email{$pref_group}[$i];
	    if ($USE_EMAIL_NOTIFICATION == 1) {
		send_email ($email, $subject, $message);
	    }
	    log_message ("EMAILING $email - $message\n");
	    
	}
}
}



#-----------------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------------
sub set_config_notify ($$$$$) {
    my ($inPrefGroup, $inType, $inInterval, $inSize, $inServices) = @_;

#    print ("SNC PG:$inPrefGroup, TY:$inType IN:$inInterval EM:$inEmail\n"); 

    my @services = split (",", $inServices);

    # current size of the array
#    my $i = scalar @{$config_notify{$inPrefGroup}{$inType}};

    #
    # array starts from zero sooo...
    #

#    push (@{$config_notify{$inPrefGroup}{$inType}}, [($inInterval, $inSize, $inEmail, [@services])]);

    #
    # if it's a duplicate, replaces existing one... last
    # entry in config file wins
    #
    $config_notify{$inPrefGroup}{$inType}[0] = $inInterval;
    $config_notify{$inPrefGroup}{$inType}[1] = $inSize;
    $config_notify{$inPrefGroup}{$inType}[2] = [@services];
   
}

#-----------------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------------
sub show_notify_config () {

    my $array_len = 0;
    my $interval = 0;
    my $email = 0;
    my $size = 0;
    my @services = ();
    my $j = 0;

    foreach $pref_key (keys %config_notify) {

	foreach $type_key (keys %{$config_notify{$pref_key}}) {
	    
	    $array_len = scalar @{$config_notify{$pref_key}{$type_key}};
#	    for ($i = 0; $i < $array_len; $i++ ) {
		
	    $interval = $config_notify{$pref_key}{$type_key}[0];
	    $size = $config_notify{$pref_key}{$type_key}[1];
#	    $email = $config_notify{$pref_key}{$type_key}[2];
	    @services = @{$config_notify{$pref_key}{$type_key}[2]};
	    print ("GP:$pref_key, TY:$type_key, SZ:$size IN:$interval EM:$email SERV:");
	    $j = scalar @services;
	    for ($k = 0; $k < $j; $k++) {
		print "$services[$k]|"
	    }
	    print ("\n");

#	    }

	}

    }


}

#-----------------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------------
sub check_service ($$) {
    my ($inPrefGroup, $inService, $inTNS) = @_;

}

#-----------------------------------------------------------------------
#
# test routine to print the stats hash
#
#-----------------------------------------------------------------------
sub show_stats () {
#    my ($inStats) = @_;


    foreach $key (keys %config_info) {
	print ("$key:$stats->{$key}, ");
    }
    print ("\n");

}

#-----------------------------------------------------------------------
#
# test routine to print out the pref groups hash
#
#-----------------------------------------------------------------------
sub show_pref_groups () {

    print ("show_pref_groups:");
    foreach $key (keys %pref_groups) {
	print ("\"$key,\"");
    }
    print ("\n");
}

#-----------------------------------------------------------------------
#
# test routine to print the config_email hash
#
#-----------------------------------------------------------------------
sub show_config_email () {

    my $count = 0;
    foreach $key (keys %config_email) {

	print ("GROUP: $key\n");
	$count = scalar @{$config_email{$key}};
	for ($i = 0; $i < $count ; $i++) {

	    print ("\t$config_email{$key}[$i]\n");
	}
	
    }


}

#-----------------------------------------------------------------------
#
# no config file was specified, but DBI_DSN, DBI_USER, and DBI_PASS 
# are set, so we'll try to connect to that db, and use all defaults
#
#-----------------------------------------------------------------------
sub setDefConfig () {

    my $theUser = $ENV{DBI_USER};
    my $thePass = $ENV{DBI_PASS};
    my $theTNS = $ENV{DBI_DSN};
    $theTNS =~ s/.*://;

    print ("setDefConfig: TNS:$theTNS\n");

#    print ("We're here...\n");

    # connect to the db
    $db_info{$theTNS}[$cDB_HANDLE] = DBI->connect 
	("DBI:Oracle:$theTNS", 
	 $theUser,
	 $thePass,
	 {RaiseError => 0,
	  PrintError => 0});

    # username
    $db_info{$theTNS}[$cDB_USER] = $theUser;
    
    # password
    $db_info{$theTNS}[$cDB_PASS] = $thePass;
    
    # karma refresh
    $db_info{$theTNS}[$cDB_REFRESH] = 1;

    # pref group
    $pref_groups{$cDEF_GRP_NAME} = 1;
    $db_info{$theTNS}[$cDB_PREFGROUP] = $cDEF_GRP_NAME;

    #
    # log any errors
    #
    if (defined ($DBI::errstr)) {
	log_message_wtime ("$DBI::errstr\n");
    }

    $config_info{default}{up}[0] = $config_info{factory}{up}[0];
    $config_info{default}{up}[1] = $config_info{factory}{up}[1];

#    print ("End of setDefConfig...\n");

}

#
#
#
sub shouldUpdateService ($$$) {
    my ($inPrefGroup, $inService, $inTime) = @_;


    #
    # using pref group prefs, if defined
    #
    if ((defined ($config_info{$inPrefGroup}{$inService}[1])) && 
	(($inTime == 0) ||
	 ($inTime % $config_info{$inPrefGroup}{$inService}[0] == 0))) {
#	print ("Will update S:$inService T:$inTime I:$config_info{$inPrefGroup}{$inService}[0]\n");
	return 1;
    }

    return 0;
}

sub getServiceWarn ($$) {
    my ($inPrefGroup, $inService, $inTime) = @_;

    #
    # using pref group prefs, if defined
    #
    if (defined ($config_info{$inPrefGroup}{$inService}[1])) {
	return $config_info{$inPrefGroup}{$inService}[1];
    #
    # using default prefs
    #
    } else {
	return $config_info{default}{$inService}[1];
    }

}

sub getServiceAlert ($$) {
    my ($inPrefGroup, $inService) = @_;

    #
    # using pref group prefs, if defined
    #
    if (defined ($config_info{$inPrefGroup}{$inService}[2])) {
	return $config_info{$inPrefGroup}{$inService}[2];
    #
    # using default prefs
    #
    } else {
	return $config_info{default}{$inService}[2];
    }
}

#
# call the appropriate status routine
#
sub getStatus ($$$) {
    my ($inPrefGroup, $inService, $inTNS) = @_;
    
    my $serviceWarn = getServiceWarn ($inPrefGroup, $inService);
    my $serviceAlert = getServiceAlert ($inPrefGroup, $inService);
    
    my $theStatus = $cNO_STATUS;
    
    if ($inService =~ /^redolog$/) {
	$theStatus = getRedologStatus ($serviceWarn, 
				       $serviceAlert, $inTNS);
    } elsif ($inService =~ /^rollback$/) {
	$theStatus = getRollbackStatus ($serviceWarn, 
					$serviceAlert, $inTNS);
    } elsif ($inService =~ /^fragmentation$/) {
	$theStatus = getFragmentationStatus ($serviceWarn,
					     $serviceAlert, $inTNS);
    } elsif ($inService =~ /^extents$/) {
	$theStatus = getExtentsStatus ($serviceWarn,
				       $serviceAlert, $inTNS);
    } elsif ($inService =~ /^slowsql$/) {
	$theStatus = getSlowsqlStatus ($serviceWarn,
				       $serviceAlert, $inTNS);
    } elsif ($inService =~ /^os$/) {
	$theStatus = getOSStatus ($serviceWarn,
				       $serviceAlert, $inTNS);
    } elsif ($inService =~ /^alertlog$/) {
	$theStatus = getAlertlogStatus ($serviceWarn,
					$serviceAlert, $inTNS);
    } elsif ($inService =~ /^mts$/) {
	$theStatus = getMTSStatus ($serviceWarn,
				   $serviceAlert, $inTNS);
    } elsif ($inService =~ /^tablespace$/) {
	$theStatus = getTablespaceStatus ($serviceWarn,
					  $serviceAlert, $inTNS);
    } elsif ($inService =~ /^latch$/) {
	$theStatus = getLatchStatus ($serviceWarn,
				     $serviceAlert, $inTNS);
    } elsif ($inService =~ /^hitratios$/) {
	$theStatus = getHitratiosStatus ($serviceWarn,
					 $serviceAlert, $inTNS);
    } elsif ($inService =~ /^db$/) {
	$theStatus = getDbStatus ($inTNS);
    } elsif ($inService =~ /^up$/) {
	$theStatus = getUpStatus ($inTNS);
    }

    return $theStatus;

}



#
#
#
#
#
sub getRefresh ($) {
    my ($inTNS) = @_;

    my $thePrefGroup = $db_info{$inTNS}[$cDB_PREFGROUP];
    
    return $config_info{$thePrefGroup}{up}[1];
}


#
#
#
#
#
sub shouldShowService ($$) {
    my ($inPrefGroup, $inService) = @_;

    if ((defined ($config_info{$inPrefGroup}{$inService}[0])) && 
	($config_info{$inPrefGroup}{$inService}[0] > 0)){

#	print ("SHOWING GP:$inPrefGroup SV:$inService\n");
	return 1;
    }

    return 0;

}

#
# The header should show if this service is displayed for any of the
# monitored databases
#
sub shouldShowServiceHeader ($) {
    my ($inService) = @_;

    my $retVal = 0;
    my $thePrefGroup = "";

    foreach $tnsKey (keys %db_info) {

#	print ("Checking TNS:$inTNS SV:$inService\n");
	$thePrefGroup = $db_info{$inTNS}[$cDB_PREFGROUP];

	
	if ((defined ($config_info{$thePrefGroup}{$inService}[0])) && 
	    ($config_info{$thePrefGroup}{$inService}[0] > 0)){
	    
#	    print ("HEADER SHOWING GP:$thePrefGroup SV:$inService\n");
	    $retVal = 1;
#	    print ("$inService - retval is: $retVal\n");
	}
    }
#    print ("$inService returning - $retVal\n");
    return $retVal;
}


#
#
#
sub isValidTNS ($) {
    my ($inTNS) = @_;

    my $i = 0;
    my @tns_names = DBI->data_sources ("dbi:Oracle:");

    while (defined ($tns_names[$i])) {
	@source = split (":", $tns_names[$i]);
	$aTNS = $source[2];
	if ($inTNS =~ /^$aTNS$/i) {
	    return 1;
	}
	$i++;
    }

    return 0;
}


#
#
#
sub doDBChecks () {

    #
    # iterate on each database
    #
    my $dayMinutes = int (($currTime - $startTime) / 60);
    foreach $tnsKey (keys %db_info) {
	getInfo ($tnsKey, $dayMinutes);

	#
	# handle notification here
	# 
	do_notification ($tnsKey, $dayMinutes);
    }
    
    
    #
    # need to open and close this file inside the doPage routine
    # and send the file handle as a param to the inside routines
    #  
    showIndexPage ($dayMinutes);
}

#
#
#
sub write_pid_file () {

    my $pid_file = new IO::File ">$PID_FILE_NAME";
#    my $curr_pid = $$;
    print $pid_file ($KARMA_PID);
    $pid_file->close ();

#    print ("Wrote to PID:$curr_pid to file:$PID_FILE_NAME\n");
}

sub getCurrTimeStr () {
    my ($inTime) = @_;

    if (defined $inTime) {
	return getTimeString ($inTime);
    } elsif (defined $startTime) {
	return getTimeString ($startTime);
    } else {
	return "";
    }
}

sub getStatusStr ($) {
    my ($inStatus) = @_;

    my $retStr = "--";
    if ($inStatus == $cALERT_STATUS) {
	$retStr = "ALRT";
    } elsif ($inStatus == $cWARNING_STATUS) {
	$retStr = "WARN";
    } elsif ($inStatus == $cOK_STATUS) {
	$retStr = "OK";
    }

    return $retStr;
}
