#!/usr/bin/perl -w
use strict;
use DBI;
use Symbol;
use IPC::Open3;
use IO::Select;
use POSIX qw(:sys_wait_h);
use Fcntl qw(:flock F_GETFL F_SETFL O_NONBLOCK);
use Time::HiRes qw(usleep);

my $args = parseArgs();

my $root_my_cnf = "/root/.my.cnf";
if (-e $root_my_cnf && -r $root_my_cnf) {
    $$args{cmd_extra} = "--defaults-extra-file=$root_my_cnf";
}

my $msg = "showhelp"; my $state = "OK";

if ($$args{ping}) {
    my @cmd = ();
    push @cmd, "mysqladmin";
    push @cmd, $$args{cmd_extra} if defined $$args{cmd_extra};
    push @cmd, $$args{mysqlargs} if defined $$args{mysqlargs};
    push @cmd, "ping";
    my ($rval, $output) = safeExec(@cmd);
    if ($output =~ m#mysqld is alive#i) {
        $msg .= " - mysqld is running";
    } else {
        $msg .= " - mysqld is NOT running";
        $state = "CRITICAL";
    } 
}

if ($$args{uptime}) {
    my @cmd = ();
    push @cmd, "mysql";
    push @cmd, $$args{cmd_extra} if defined $$args{cmd_extra};
    push @cmd, $$args{mysqlargs} if defined $$args{mysqlargs};
    push @cmd, ("-e", "SHOW STATUS WHERE Variable_name='Uptime';");
    my ($rval, $output) = safeExec(@cmd);
    if ($output =~ m#Uptime\s+(\d+)#ms) {
        my $uptime = $1;
        if ($uptime > 600) {
            $msg .= " - uptime $uptime"
        } else {
            $msg .= " - mysql restarted with uptime $uptime";
            $state = "WARNING";
        }
    } else {
        $msg .= " - uptime could not be parsed ($output)";
        $state = "WARNING";
    }
}


if ($$args{slavesql} or $$args{slaveio} or $$args{slavebehind}) {
    my @cmd = ();
    push @cmd, "mysql";
    push @cmd, $$args{cmd_extra} if defined $$args{cmd_extra};
    push @cmd, $$args{mysqlargs} if defined $$args{mysqlargs};
    push @cmd, ("-e", "SHOW SLAVE STATUS\\G");
    my ($rval, $output) = safeExec(@cmd);

    my ($io_running)   = $output =~ m#Slave_IO_Running:\s+(Yes|No)#ms;   $io_running ||= 'No'; 
    my ($sql_running)  = $output =~ m#Slave_SQL_Running:\s+(Yes|No)#ms;  $sql_running ||= 'No';
    my ($slave_behind) = $output =~ m#Seconds_Behind_Master:\s+(\d+)#ms; 
    my ($slave_status) = $output =~ m#Slave_SQL_Running_State:\s+(\w+)#ms;  $slave_status ||= "error";

    if (defined $$args{slavesql} and $$args{slavesql} == 1) {
        if ($sql_running ne 'Yes') {
            $msg .= " - slave SQL not running";
            $state = "CRITICAL";
        } else {
            $msg .= " - slave SQL running";
        }
    }
    
    if (defined $$args{slaveio} and $$args{slaveio} == 1) {
        if ($io_running ne 'Yes') {
            $msg .= " - slave IO not running";
            $state = "CRITICAL";
        } else {
            $msg .= " - slave IO running";
        }
    }

    if (defined $$args{slavebehind} and $$args{slavebehind} == 1) {
        if (defined $slave_behind and $slave_behind > $$args{slavebehindmax}) {
            $msg .= " - slave lags behind outside margin ($slave_behind > $$args{slavebehindmax})";
            $state = "CRITICAL";

            if (not defined $$args{slavestatusimportant} or $$args{slavestatusimportant} != 1) {
                if ($slave_status eq 'updating') {
                    $msg .=" (busy updating)";
                    $state = "OK";
                }
            }

        } elsif (not defined $slave_behind) {
            $msg .= " - slave seconds behind did not parse";
            $state = "WARNING";
        } else {
            $msg .= " - slave lag is within margins ($slave_behind)";
        }
    }
}

showHelp() if $msg eq 'showhelp';
$msg =~ s/showhelp//g;
print "${state}${msg}\n";
exit 0 if $state eq "OK";
exit 1 if $state eq "WARNING";
exit 2;

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

sub safeExec {
    my (@cmd) = @_;

    my $io_select = IO::Select->new();
    my ($stdin, $stdout, $stderr) = (gensym, gensym, gensym);
    
    my $pid = open3($stdin, $stdout, $stderr, @cmd);
    $io_select->add($stdout, $stderr);
    
    # Read data from child, but we may not block on this.
    my $process_output = "";
    while (kill 0, $pid) {
        foreach my $handle ($io_select->can_read(1)) {
            my $flags = fcntl($handle, F_GETFL, 0);
            fcntl($handle, F_SETFL, $flags | O_NONBLOCK);
            my $rsize = read($handle, my $buf, 10485760);
            $process_output .= $buf if ($rsize);
        }   
        waitpid($pid, WNOHANG);
        usleep(50000); # grace in this loop
    }   

    my ($exitval, $signal, $core) = ( ($? >> 8), ($? & 127), ($? & 128) );

    # get residual output from process
    foreach my $handle ($io_select->can_read(1)) {
        my $flags = fcntl($handle, F_GETFL, 0);
        fcntl($handle, F_SETFL, $flags | O_NONBLOCK);
        my $rsize = read($handle, my $buf, 10485760);
        $process_output .= $buf if ($rsize);
    }   
   
    return ($exitval, $process_output); 
}


sub parseArgs {
    my $ret = {};

    while (@ARGV) {
        my $arg = shift @ARGV;

        if ($arg eq "-a") {
            $$ret{mysqlargs} = shift @ARGV;

        } elsif ($arg eq "-u") {
            $$ret{uptime} = 1;

        } elsif ($arg eq "-p") {
            $$ret{ping} = 1;

        } elsif ($arg eq "-r") {
            $$ret{slavesql} = 1;

        } elsif ($arg eq "-i") {
            $$ret{slaveio} = 1;

        } elsif ($arg eq "-I") {
            $$ret{slavestatusimportant} = 1;

        } elsif ($arg eq "-b") {
            $$ret{slavebehind} = 1;
            $$ret{slavebehindmax} = shift @ARGV;
            die "Argument to -b is not numeric.\n" if (not defined $$ret{slavebehindmax} or $$ret{slavebehindmax} !~ m#^\d+$#);

        } elsif ($arg eq "-h") {
            showHelp();

        } else {
            die "Unknown argument '$arg' - try -h\n";
        }
    }

    return $ret;
}

sub showHelp {
    print "Usage: $0 [-hupriI] [-b seconds]\n";
    print "          -u checks uptime of mysql to be higher then 10 minutes\n";
    print "          -p ping the SQL Server process to see if its alive\n";
    print "          -r checks for the SLAVE_RUNNING option to be YES\n";
    print "          -i checks for the SLAVE_IO option to be YES\n";
    print "          -I do not ignore '-b' error states if SLAVE_SQL_RUNNING_STATE is 'updating'\n";
    print "          -b checks for the SECONDS_BEHIND_MASTER not to be higher\n";
    print "             than given number of seconds\n";
    print "          -a mysql arguments (like -u root -p secret)\n";
    print "          -h shows this help\n";
    exit 0;
}

__END__
