#!/usr/bin/env perl

# $Id$

=head1 NAME

	mysql-simple-check-tables - мониторинг работоспособности таблиц mysql

=head1 DESCRIPTION

	Читает конфиги из /etc/mysql-monitor.d/*, после идет в mysql, получает из information_schema.TABLES 
	список таблиц, проверяет их работоспособность. В ответе выводит список побитых таблиц. Результат работы 
    записывает в /tmp/mysql-simple-check-tables-$config.status. 
    Если добавить флаг -v или --verbose, то выводит список всех проверенных таблиц, с их статусом. 
    При флаге -m или --monrun дополнительно проверяет наличие файла /tmp/mysql-simple-check-tables-$config.status. 
    При его отсутствии или если старше одного дня - запускает проверку таблиц, в противном случае не делает ничего.

=cut

use strict;
use Getopt::Long;

use YAML;
use DBI;
use File::Slurp;
use Time::Local;
use Pid::File::Flock;

use utf8;
use open ':std' => ':utf8';

my $CONF_DIR = '/etc/mysql-monitor.d';
my $DEBUG = 0;
my $MONRUN = 0; 
my $CONFIG;
my $MONAGE = 1;

GetOptions(
    "help" => sub {system("podselect -section NAME -section DESCRIPTION $0 | pod2text-utf8 >&2"); exit 0;},
    "conf-dir=s" => \$CONF_DIR,
    "config=s" => \$CONFIG,
    "monrun" => \$MONRUN,
    "debug" => \$DEBUG,
    ) || die "Error occured";


die '--monrun and --debug' if $DEBUG && $MONRUN;

my $conf;
my $dbh;
my $mon_file;
my $status;

my $flock = Pid::File::Flock->new(ext => ".$CONFIG.pid", quiet => 1, raise => 0);

# Если $CONFIG не задан, то идем в дирректорию $CONF_DIR и получаем все конфиги и
# по каждому проверяем.

if (!defined $CONFIG) {
    opendir (DIR, $CONF_DIR) or die $!;
    while (my $config = readdir(DIR)) {
        next if ($config =~ m/^\./);
        run_check($config);
    }
    closedir(DIR);
} else {
    run_check($CONFIG);
}

exit 0;

sub run_check {
    my ($file) = @_;
    $conf = read_config("$CONF_DIR/$file");
    $mon_file = "/tmp/mysql-simple-check-tables-$file.status";
    $status = "0; OK\n";
    $dbh = DBI->connect("DBI:mysql:;mysql_socket=$conf->{socket};mysql_enable_utf8=1", "root", "", { RaiseError=>0} ) 
           or return 1;

    if (($MONRUN) && (-f $mon_file) && (-M $mon_file < $MONAGE)) {
        # Ничего не делаем, если мониторинговый файл существует и не старый.
    } else {
        run_request();
    }
    return 0;
}

sub run_request { 

    $dbh->{RaiseError} = 0; # Остальные ошибки не должны приводить к raise. 
    $dbh->{PrintError} = 0; # Отключаем exception и лишние сообщения.

    my $sql = qq{select TABLE_SCHEMA, TABLE_NAME from information_schema.TABLES 
                 where TABLE_SCHEMA NOT in ("information_schema", "performance_schema", "sys")
	         and ENGINE in ('InnoDB', 'MyISAM')};
    my $sth = $dbh->prepare($sql);
    $sth->execute();
    
    while (my $row = $sth->fetchrow_hashref()) {
       my $table = $row->{TABLE_SCHEMA} . "." . $row->{TABLE_NAME};
       my $fail = check_table("$table");
       if ($fail) { 
           $status = "1; Found broken tables. Use --debug.\n";
           last if $MONRUN;
       }
    }
    write_monrun_status($status);
}

sub write_monrun_status {
    open(my $fh, ">", $mon_file) or die "cannot open $!";
    print $fh @_;
}

sub read_monrun_status {
    open(my $fh, '<', $mon_file) or die "cannot open $!";
    my $row = <$fh>;
    print $row;
}

# 0 -- OK, 1 -- fail
sub check_table { 
    my ($table) = @_;

    my $sql = "SELECT 1 FROM $table LIMIT 1";
    my $sth = $dbh->prepare($sql);
    $sth->execute();

    if ($sth->err) {
        print "[FAIL] Broken table $table\n" if !$MONRUN;
        warn sprintf("sth->execute failed with sth->err: %s\n", $sth->err) if $DEBUG;
        return 1;
    } else {
        print "[OK] Good table $table\n" if $DEBUG;
    }
    return 0;
}

sub read_config {
    my ($file) = @_;
    my $conf = YAML::LoadFile($file);
    if (!exists $conf->{socket}) {
        die "Error in $file: socket is not defined";
    }
    return $conf;
}
