File: //usr/share/perl5/DBMon/Daemon.pm
package DBMon::Daemon;
use POSIX qw(setsid);
use DBMon::Log qw(say);
use DBMon::DB;
use DBMon::Ini;
use strict;
use FindBin qw($Bin);
# настройки по-умолчанию
my $DEF_SETTINGS = {
INI_FILE => "dbmon.ini",
};
sub new
{
my $proto = shift;
my $setting = shift;
my $class = ref $proto || $proto;
my $self = $DEF_SETTINGS;
foreach(keys %$setting)
{
$self->{$_} = $setting->{$_};
}
$self->{INI} = new DBMon::Ini;
$self->{INI}->read( $self->{INI_FILE} );
$self->{PID_FILE} = '/var/run/'.$self->{INI}->{Settings}->{pid_file};
$self->{LOG_FILE} = $self->{INI}->{Settings}->{log_file};
$self->{WRITE_LOG_TO_FILE} = $self->{INI}->{Settings}->{write_log_to_file};
$self->{WRITE_LOG_TO_SYSLOG} = $self->{INI}->{Settings}->{write_log_to_syslog};
$self->{LOG_LEVEL} = $self->{INI}->{Settings}->{log_level};
$self->{DB} = [];
if (open (FD, $self->{PID_FILE})) {
$self->{PID} = <FD>;
close (FD);
}
DBMon::Log::init($self->{WRITE_LOG_TO_FILE}, $self->{LOG_FILE},
$self->{WRITE_LOG_TO_SYSLOG}, $self->{LOG_LEVEL});
$self = bless $self, $class;
return $self;
}
sub writepid
{
my $self = shift;
if (open (FD, '>'.$self->{PID_FILE})) {
print FD $$;
close FD;
chmod 0600, $self->{PID_FILE};
return 1;
} else {
say ("Can't write pid file: ".$!, "err");
return 0;
}
}
sub init
{
my $self = shift;
$self->{HOST_DEFAULT} = $self->{INI}->{'default:host'};
$self->{RULE_DEFAULT} = $self->{INI}->{'default:rule'};
$self->{CON_TIME} = $self->{INI}->{Settings}->{connection_interval};
$self->{MON_TIME} = $self->{INI}->{Settings}->{monitoring_interval};
my @list = keys %{$self->{INI}};
# считываем из конфига данные по все правилам
foreach(@list)
{
if(/^rule\:(.*)/)
{
my $rule_ini = $self->{INI}->{$_};
$rule_ini->{name} = $1;
my ($k, $v);
while(($k,$v) = each(%{$self->{RULE_DEFAULT}}))
{
unless(defined($rule_ini->{$k}))
{
$rule_ini->{$k} = $v;
}
}
$self->{RULE}->{$1} = $rule_ini;
}
}
# считываем из конфига данные по хостам
# и пробуем коннектиться
foreach(@list)
{
if(/^host\:(.*)/)
{
my $db_ini = $self->{INI}->{$_};
$db_ini->{name} = $1;
my ($k, $v);
while(($k,$v) = each(%{$self->{HOST_DEFAULT}}))
{
unless(defined($db_ini->{$k}))
{
$db_ini->{$k} = $v;
}
}
# расчитываем правила доступные на этом хосту
my %rules = ();
# подгружаем доступные правила
if(my $ar = $db_ini->{allow_rule})
{
if($ar eq '*')
{
%rules = %{$self->{RULE}};
}
else
{
foreach(split /;/, $ar)
{
$rules{$_} = $self->{RULE}->{$_};
}
}
}
# удаляем запрещенные правила
if(my $dr = $db_ini->{deny_rule})
{
if($dr eq '*')
{
%rules = ();
}
else
{
foreach(split /;/, $dr)
{
delete $rules{$_};
}
}
}
my $db = new DBMon::DB($db_ini);
$db->{RULE} = \%rules;
$db->connect;
push @{$self->{HOST}}, $db;
}
}
return 1;
}
sub run
{
my $self = shift;
if($self->{PID} && kill 0=>$self->{PID})
{
#say "DBMon is already running (pid ".$self->{PID}.")", "warn";
exit;
}
$self->{PID} = $$;
umask 0;
open STDIN, '/dev/null';
open STDOUT, '>/dev/null';
open STDERR, '>/dev/null';
my $pid = fork;
unless (defined($pid)) {
say "Can't fork: ".$!, "err";
exit;
}
exit if $pid;
setsid;
$|=1; # отключаем буферизацию
say 'DBMon started', 'notice';
$self->writepid || exit;
$self->init || exit;
my $last_mon_time = time;
my $last_con_time = time;
my $split = q/;/;
for(;;)
{
my $wait = 1;
# Проверяем process list'ы и убиваем то, что надо убить
if( time - $last_mon_time >= $self->{MON_TIME} )
{
# выполняем мониторинг
# say 'Checking queries on all servers', 'debug';
foreach my $db (@{$self->{HOST}})
{
next unless( $db->{conn} );
my $prlist = $db->monitor;
unless( $prlist )
{
$db->{conn} = 0;
next;
}
foreach my $one_proc(@{$prlist})
{
foreach(keys %{$db->{RULE}})
{
my $rule = $db->{RULE}->{$_};
next if($rule->{status} ne $one_proc->{State});
next if($rule->{killtime} > $one_proc->{Time});
say "$one_proc->{User}: killtime: $rule->{killtime} > $one_proc->{Time}", 'debug';
say "allow deny checks : Status:$rule->{status}, allow_to_kill:$rule->{allow_to_kill} , deny_to_kill:$rule->{deny_to_kill}", 'debug';
my $user_check = 0;
# проверка по allow
if(my $ak = $rule->{allow_to_kill})
{
if($ak eq '*')
{
$user_check = 1;
}
else
{
foreach(split /$split/,$ak)
{
chomp;
if($one_proc->{User}=~/$_/)
{
say "allow to kill: $one_proc->{User} --> $_ found, kill him", 'debug';
$user_check = 1;
last;
}
else
{
say "user $one_proc->{User} is not found in allow_t_kill rule ... ok", 'debug';
}
}
}
}
# проверка на deny
if(my $dk = $rule->{deny_to_kill})
{
if($dk eq '*')
{
$user_check = 0;
}
else
{
foreach(split /$split/,$dk)
{
chomp;
if($one_proc->{User}=~/$_/)
{
say "deny to kill: $one_proc->{User} --> $_", 'debug';
$user_check = 0;
last;
}
}
}
}
if ($user_check)
{
say "sorry I want to kill $one_proc->{User}", 'debug';
# убиваем
if ($db->killproc($one_proc->{Id}))
{
say "Killed query at \"$db->{name}\":".
"id $one_proc->{Id}, state: \"$one_proc->{State}\",".
"time: $one_proc->{Time} (user $one_proc->{User}, db $one_proc->{Db})",
'debug';
}
else
{
say "Can't kill thread $one_proc->{Id} at $db->{name}", 'warn';
}
last; #cut
}
}
}
}
$last_mon_time = time;
$wait = 0;
}
# Восстанавливаем соединения с серверами, с которыми его нет
if( time - $last_con_time >= $self->{CON_TIME} )
{
foreach my $db (@{$self->{HOST}})
{
unless( $db->{conn} )
{
$db->connect;
}
}
$last_con_time = time;
$wait = 0;
}
sleep 1 if $wait;
}
}
sub kill
{
my $self = shift;
if($self->{PID} && kill 0=>$self->{PID})
{
say 'DBMon stopped', 'notice';
kill HUP=>$self->{PID}; #proc killed
}
else
{
say 'Can\'t stop DBMon: daemon is not running', 'err';
}
unlink $self->{PID_FILE};
return 1;
}
1;