#!/usr/local/bin/perl
#
# Check log rates
#
# $Header: /opt/home/doke/work/nagios/RCS/check_ipf,v 1.13 2010/01/08 17:55:18 doke Exp $



use strict;
use warnings;
use Fcntl;
use NDBM_File;
#use Math::BigInt;
use Getopt::Long;
#use Data::Dumper;

use vars qw( $log_file $crit_rate $warn_rate $pattern $timeout $history_db $verbose
    $help @crits @warns @unknowns @oks @ignores %stats $stat );


$log_file = "/var/adm/messages";
$crit_rate = 10;
$warn_rate = 2;
$pattern = '.';
$timeout = 240;
$history_db = "/usr/local/nagios/var/check_log_rate";  
$verbose = 0;
$help = 0;


#########################

sub usage {
    my( $rc ) = @_;
    print "Usage: $0 [-vh]
    -l    log file [$log_file]
    -c    critical rate per minute [$crit_rate]
    -w    warning rate per minute [$warn_rate]
    -p    pattern to match in logs [none]
    -t    timeout in seconds [$timeout]
    --db  history database file [$history_db]
    -v    verbose
    -h    help
";
    exit $rc;
    }

Getopt::Long::Configure ("bundling");
GetOptions(
    "l=s" => \$log_file,
    "c=i" => \$crit_rate,
    "w=i" => \$warn_rate,
    "p=s" => \$pattern,
    "t=i" => \$timeout,
    "db=s" => \$history_db,
    'v+' => \$verbose,
    'h' => \$help,
    );
&usage( 0 ) if ( $help );

# Just in case of problems, let's not hang Nagios
$SIG{'ALRM'} = sub {
        print ("Unknown: log scan timed out\n");
        exit -1;
    };
alarm( $timeout );

&check_log_rate();

my $rc = 0;
my $sep = '';
if ( $#crits >= 0 ) {
    $rc = 2;
    print "CRITICAL ", join( ", ", @crits );
    $sep = '; ';
    }
if ( $#warns >= 0 ) {
    $rc = 1 if ( $rc == 0 );
    print $sep, "Warning ", join( ", ", @warns );
    $sep = '; ';
    }
if ( $#unknowns >= 0 ) {
    $rc = -1 if ( $rc == 0 );
    print $sep, "Unknown ", join( ", ", @unknowns );
    $sep = '; ';
    }
if ( $rc == 0 ) {
    print "Ok ", join( ", ", @oks );
    $sep = '; ';
    }
if ( $#ignores >= 0 ) {
    print $sep, "Ignoring ", join( ", ", @ignores );
    }

print " |";
$sep = '';
foreach $stat ( keys %stats ) {
    if ( defined $stats{ $stat } ) { 
	printf "%s %s=%s", $sep, lc $stat, $stats{ $stat };
	$sep = ',';
	}
    }

print "\n";
exit $rc;


##################




sub check_log_rate { 
    my( $db_key, $line, $old_time, $old_size, $old_mtime, $old_offset,
	$dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime,
	$mtime, $ctime, $blksize, $blocks, %hist, $now, $offset, $lines, $rate );

    $lines = 0; 
    $rate = 0; 

    # get history
    $db_key = "$log_file;$pattern";
    $verbose && print "db_key $db_key\n";

    # 02 = O_RDWR 
    # 0644 = permissions on dbm files
    if ( ! -e "$history_db.dir" || ! -e "$history_db.pag" ) { 
	open( fH, ">$history_db.dir" ); 
	close fH;
	open( fH, ">$history_db.pag" ); 
	close fH;
	}
    if ( ! tie( %hist, 'NDBM_File', $history_db, O_RDWR | O_CREAT, 0644 ) )  { 
	print "internal plugin error, can't tie dbm: $!\n"; 
	exit 2;  # 0 ok, 1 warn, 2 crit, -1 unknown
	}
    $line = $hist{ $db_key };
    untie( %hist );   # don't keep the database locked
    if ( $line ) {
	( $old_time, $old_size, $old_mtime, $old_offset ) = split( ";", $line );
	}
    else { 
	$verbose && print "no history data\n";
	$old_time = $old_size = $old_mtime = $old_offset = 0;
	}
    $verbose && print "old_time $old_time, old_size $old_size, old_mtime $old_mtime, old_offset $old_offset\n";


    # check the log file
    ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,  $atime, $mtime, $ctime, 
	$blksize, $blocks ) = stat( $log_file );
    $verbose && print "log file size $size, mtime $mtime\n";

    if ( $size == $old_size && $mtime == $old_mtime ) { 
	# no change, don't bother reading it
	push @oks, "0 new log lines";

	# 02 = O_RDWR 
	# 0644 = permissions on dbm files
	if ( ! tie( %hist, 'NDBM_File', $history_db, O_RDWR | O_CREAT, 0644 ) )  { 
	    print "internal plugin error, can't tie dbm: $!\n"; 
	    exit 2;  # 0 ok, 1 warn, 2 crit, -1 unknown
	    }
	$now = time();
	$hist{ $db_key } = "$now;$size;$mtime;$old_offset";
	untie( %hist );

	return;
	}

    if ( $size < $old_size ) { 
	# log reset/rotated, start over
	# This will introduce errors, since there may have been uncounted lines 
	# writen to the old log after our last check.  Not much we can do about that.
	$verbose && print "log rotated\n";
	$old_offset = 0;
	}


    # open the log file
    if ( ! open( lH, $log_file ) ) { 
	push @crits, "can't open $log_file: $!";
	return;
	}
    $offset = int( $old_offset ); 
    $verbose && print "seeking to $offset\n";
    if ( ! seek( lH, 0, $offset ) ) { 
	push @crits, "can't seek to $old_offset in $log_file: $!";
	return;
	}

    $lines = 0;
    $offset = tell( lH );
    while ( <lH> )  { 
	last if ( ! m/\n$/ );  # don't do the final partial line
	if ( $verbose && $. % 1000 == 0 ) { 
	    printf "reading log line $.\n";
	    }

	$offset = tell( lH );  # keep the offset of the end of the last full line

	next if ( defined $pattern && $pattern ne '.' && m/$pattern/oi );
	$lines++;
	}
    close lH;

    $now = time();

    if ( $lines ) { 
	}

    $verbose && print "old_time $old_time\n";
    if ( $old_time )  { 
	my( $elapsed );
	$elapsed = $now - $old_time;  # seconds
	$verbose && print "elapsed $elapsed\n";
	if ( $elapsed > 0 ) { 
	    my( $rate );
	    $rate = $stats{ lines } * 60.0 / $elapsed;  # lines per minute
	    $verbose && print "rate $rate\n";
	    $stats{ 'rate' } = $rate;
	    $stats{ 'lines' } = $lines;

	    if ( $rate >= $crit_rate ) {  
		push @crits, sprintf( "rate %0.2f lines/min", $rate );
		}
	    elsif ( $rate >= $warn_rate ) { 
		push @warns, sprintf( "rate %0.2f lines/min", $rate );
		}
	    else { 
		push @oks, sprintf( "rate %0.2f lines/min", $rate );
		}
	    }
	else { 
	    # uh, no time elapsed, or negative time???
	    push @unknowns, "no time elapsed since last check? rate unknown";
	    }
	}
    else { 
	# tracking a new log file?
	# should only happen once per file and pattern
	# Could try figuring it out from timestamps in log, 
	# but every log formats them differently.
	# punt and say unknown
	push @ignores, "new log file/pattern, no history, rate unknown";
	}

    # 02 = O_RDWR 
    # 0644 = permissions on dbm files
    if ( ! tie( %hist, 'NDBM_File', $history_db, O_RDWR | O_CREAT, 0644 ) )  { 
	print "internal plugin error, can't tie dbm: $!\n"; 
	exit 2;  # 0 ok, 1 warn, 2 crit, -1 unknown
	}
    $verbose && print "writing to database: now $now, size $size, mtime $mtime, offset $offset\n";
    $hist{ $db_key } = "$now;$size;$mtime;$offset";
    untie( %hist );

    }
    





	
	     






