Created
March 23, 2017 15:09
-
-
Save alexymik/b5fcc080b421052211891ef120042301 to your computer and use it in GitHub Desktop.
check_mysql_health compiled nagios plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/perl -w | |
# nagios: -epn | |
my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 ); | |
my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' ); | |
package DBD::MySQL::Server::Instance::Innodb; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server::Instance); | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
handle => $params{handle}, | |
internals => undef, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless $self, $class; | |
$self->init(%params); | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
$self->init_nagios(); | |
if ($params{mode} =~ /server::instance::innodb/) { | |
$self->{internals} = | |
DBD::MySQL::Server::Instance::Innodb::Internals->new(%params); | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if ($params{mode} =~ /server::instance::innodb/) { | |
$self->{internals}->nagios(%params); | |
$self->merge_nagios($self->{internals}); | |
} | |
} | |
package DBD::MySQL::Server::Instance::Innodb::Internals; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server::Instance::Innodb); | |
our $internals; # singleton, nur ein einziges mal instantiierbar | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
unless ($internals) { | |
$internals = { | |
handle => $params{handle}, | |
bufferpool_hitrate => undef, | |
wait_free => undef, | |
log_waits => undef, | |
have_innodb => undef, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless($internals, $class); | |
$internals->init(%params); | |
} | |
return($internals); | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
my $dummy; | |
$self->debug("enter init"); | |
$self->init_nagios(); | |
if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1")) { | |
($dummy, $self->{have_innodb}) = $self->{handle}->fetchrow_array(q{ | |
SELECT ENGINE, SUPPORT FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE='InnoDB' | |
}); | |
} else { | |
($dummy, $self->{have_innodb}) = $self->{handle}->fetchrow_array(q{ | |
SHOW VARIABLES LIKE 'have_innodb' | |
}); | |
} | |
if ($self->{have_innodb} eq "NO") { | |
$self->add_nagios_critical("the innodb engine has a problem (have_innodb=no)"); | |
} elsif ($self->{have_innodb} eq "DISABLED") { | |
# add_nagios_ok later | |
} elsif ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) { | |
($dummy, $self->{bufferpool_reads}) | |
= $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_reads' | |
}); | |
($dummy, $self->{bufferpool_read_requests}) | |
= $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_read_requests' | |
}); | |
if (! defined $self->{bufferpool_reads}) { | |
$self->add_nagios_critical("no innodb buffer pool info available"); | |
} else { | |
$self->valdiff(\%params, qw(bufferpool_reads | |
bufferpool_read_requests)); | |
$self->{bufferpool_hitrate_now} = | |
$self->{delta_bufferpool_read_requests} > 0 ? | |
100 - (100 * $self->{delta_bufferpool_reads} / | |
$self->{delta_bufferpool_read_requests}) : 100; | |
$self->{bufferpool_hitrate} = | |
$self->{bufferpool_read_requests} > 0 ? | |
100 - (100 * $self->{bufferpool_reads} / | |
$self->{bufferpool_read_requests}) : 100; | |
} | |
} elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) { | |
($dummy, $self->{bufferpool_wait_free}) | |
= $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_wait_free' | |
}); | |
if (! defined $self->{bufferpool_wait_free}) { | |
$self->add_nagios_critical("no innodb buffer pool info available"); | |
} else { | |
$self->valdiff(\%params, qw(bufferpool_wait_free)); | |
$self->{bufferpool_wait_free_rate} = | |
$self->{delta_bufferpool_wait_free} / $self->{delta_timestamp}; | |
} | |
} elsif ($params{mode} =~ /server::instance::innodb::logwaits/) { | |
($dummy, $self->{log_waits}) | |
= $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Innodb_log_waits' | |
}); | |
if (! defined $self->{log_waits}) { | |
$self->add_nagios_critical("no innodb log info available"); | |
} else { | |
$self->valdiff(\%params, qw(log_waits)); | |
$self->{log_waits_rate} = | |
$self->{delta_log_waits} / $self->{delta_timestamp}; | |
} | |
} elsif ($params{mode} =~ /server::instance::innodb::needoptimize/) { | |
#fragmentation=$(($datafree * 100 / $datalength)) | |
#http://www.electrictoolbox.com/optimize-tables-mysql-php/ | |
my @result = $self->{handle}->fetchall_array(q{ | |
SHOW TABLE STATUS WHERE Data_free / Data_length > 0.1 AND Data_free > 102400 | |
}); | |
printf "%s\n", Data::Dumper::Dumper(\@result); | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
my $now = $params{lookback} ? '_now' : ''; | |
if ($self->{have_innodb} eq "DISABLED") { | |
$self->add_nagios_ok("the innodb engine has been disabled"); | |
} elsif (! $self->{nagios_level}) { | |
if ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) { | |
my $refkey = 'bufferpool_hitrate'.($params{lookback} ? '_now' : ''); | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "99:", "95:"), | |
sprintf "innodb buffer pool hitrate at %.2f%%", $self->{$refkey}); | |
$self->add_perfdata(sprintf "bufferpool_hitrate=%.2f%%;%s;%s;0;100", | |
$self->{bufferpool_hitrate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "bufferpool_hitrate_now=%.2f%%", | |
$self->{bufferpool_hitrate_now}); | |
} elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{bufferpool_wait_free_rate}, "1", "10"), | |
sprintf "%ld innodb buffer pool waits in %ld seconds (%.4f/sec)", | |
$self->{delta_bufferpool_wait_free}, $self->{delta_timestamp}, | |
$self->{bufferpool_wait_free_rate}); | |
$self->add_perfdata(sprintf "bufferpool_free_waits_rate=%.4f;%s;%s;0;100", | |
$self->{bufferpool_wait_free_rate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::innodb::logwaits/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{log_waits_rate}, "1", "10"), | |
sprintf "%ld innodb log waits in %ld seconds (%.4f/sec)", | |
$self->{delta_log_waits}, $self->{delta_timestamp}, | |
$self->{log_waits_rate}); | |
$self->add_perfdata(sprintf "innodb_log_waits_rate=%.4f;%s;%s;0;100", | |
$self->{log_waits_rate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} | |
} | |
} | |
package DBD::MySQL::Server::Instance::MyISAM; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server::Instance); | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
handle => $params{handle}, | |
internals => undef, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless $self, $class; | |
$self->init(%params); | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
$self->init_nagios(); | |
if ($params{mode} =~ /server::instance::myisam/) { | |
$self->{internals} = | |
DBD::MySQL::Server::Instance::MyISAM::Internals->new(%params); | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if ($params{mode} =~ /server::instance::myisam/) { | |
$self->{internals}->nagios(%params); | |
$self->merge_nagios($self->{internals}); | |
} | |
} | |
package DBD::MySQL::Server::Instance::MyISAM::Internals; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server::Instance::MyISAM); | |
our $internals; # singleton, nur ein einziges mal instantiierbar | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
unless ($internals) { | |
$internals = { | |
handle => $params{handle}, | |
keycache_hitrate => undef, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless($internals, $class); | |
$internals->init(%params); | |
} | |
return($internals); | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
my $dummy; | |
$self->debug("enter init"); | |
$self->init_nagios(); | |
if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) { | |
($dummy, $self->{key_reads}) | |
= $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Key_reads' | |
}); | |
($dummy, $self->{key_read_requests}) | |
= $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Key_read_requests' | |
}); | |
if (! defined $self->{key_read_requests}) { | |
$self->add_nagios_critical("no myisam keycache info available"); | |
} else { | |
$self->valdiff(\%params, qw(key_reads key_read_requests)); | |
$self->{keycache_hitrate} = | |
$self->{key_read_requests} > 0 ? | |
100 - (100 * $self->{key_reads} / | |
$self->{key_read_requests}) : 100; | |
$self->{keycache_hitrate_now} = | |
$self->{delta_key_read_requests} > 0 ? | |
100 - (100 * $self->{delta_key_reads} / | |
$self->{delta_key_read_requests}) : 100; | |
} | |
} elsif ($params{mode} =~ /server::instance::myisam::sonstnochwas/) { | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if (! $self->{nagios_level}) { | |
if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) { | |
my $refkey = 'keycache_hitrate'.($params{lookback} ? '_now' : ''); | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "99:", "95:"), | |
sprintf "myisam keycache hitrate at %.2f%%", $self->{$refkey}); | |
$self->add_perfdata(sprintf "keycache_hitrate=%.2f%%;%s;%s", | |
$self->{keycache_hitrate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "keycache_hitrate_now=%.2f%%;%s;%s", | |
$self->{keycache_hitrate_now}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} | |
} | |
} | |
package DBD::MySQL::Server::Instance::Replication; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server::Instance); | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
handle => $params{handle}, | |
internals => undef, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless $self, $class; | |
$self->init(%params); | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
$self->init_nagios(); | |
if ($params{mode} =~ /server::instance::replication/) { | |
$self->{internals} = | |
DBD::MySQL::Server::Instance::Replication::Internals->new(%params); | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if ($params{mode} =~ /server::instance::replication/) { | |
$self->{internals}->nagios(%params); | |
$self->merge_nagios($self->{internals}); | |
} | |
} | |
package DBD::MySQL::Server::Instance::Replication::Internals; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server::Instance::Replication); | |
our $internals; # singleton, nur ein einziges mal instantiierbar | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
unless ($internals) { | |
$internals = { | |
handle => $params{handle}, | |
seconds_behind_master => undef, | |
slave_io_running => undef, | |
slave_sql_running => undef, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless($internals, $class); | |
$internals->init(%params); | |
} | |
return($internals); | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
$self->debug("enter init"); | |
$self->init_nagios(); | |
if ($params{mode} =~ /server::instance::replication::slavelag/) { | |
# "show slave status", "Seconds_Behind_Master" | |
my $slavehash = $self->{handle}->selectrow_hashref(q{ | |
SHOW SLAVE STATUS | |
}); | |
if ((! defined $slavehash->{Seconds_Behind_Master}) && | |
(lc $slavehash->{Slave_IO_Running} eq 'no')) { | |
$self->add_nagios_critical( | |
"unable to get slave lag, because io thread is not running"); | |
} elsif (! defined $slavehash->{Seconds_Behind_Master}) { | |
$self->add_nagios_critical(sprintf "unable to get replication info%s", | |
$self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); | |
} else { | |
$self->{seconds_behind_master} = $slavehash->{Seconds_Behind_Master}; | |
} | |
} elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) { | |
# "show slave status", "Slave_IO_Running" | |
my $slavehash = $self->{handle}->selectrow_hashref(q{ | |
SHOW SLAVE STATUS | |
}); | |
if (! defined $slavehash->{Slave_IO_Running}) { | |
$self->add_nagios_critical(sprintf "unable to get replication info%s", | |
$self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); | |
} else { | |
$self->{slave_io_running} = $slavehash->{Slave_IO_Running}; | |
} | |
} elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) { | |
# "show slave status", "Slave_SQL_Running" | |
my $slavehash = $self->{handle}->selectrow_hashref(q{ | |
SHOW SLAVE STATUS | |
}); | |
if (! defined $slavehash->{Slave_SQL_Running}) { | |
$self->add_nagios_critical(sprintf "unable to get replication info%s", | |
$self->{handle}->{errstr} ? $self->{handle}->{errstr} : ""); | |
} else { | |
$self->{slave_sql_running} = $slavehash->{Slave_SQL_Running}; | |
} | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if (! $self->{nagios_level}) { | |
if ($params{mode} =~ /server::instance::replication::slavelag/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{seconds_behind_master}, "10", "20"), | |
sprintf "Slave is %d seconds behind master", | |
$self->{seconds_behind_master}); | |
$self->add_perfdata(sprintf "slave_lag=%d;%s;%s", | |
$self->{seconds_behind_master}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) { | |
if (lc $self->{slave_io_running} eq "yes") { | |
$self->add_nagios_ok("Slave io is running"); | |
} else { | |
$self->add_nagios_critical("Slave io is not running"); | |
} | |
} elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) { | |
if (lc $self->{slave_sql_running} eq "yes") { | |
$self->add_nagios_ok("Slave sql is running"); | |
} else { | |
$self->add_nagios_critical("Slave sql is not running"); | |
} | |
} | |
} | |
} | |
package DBD::MySQL::Server::Instance; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server); | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
handle => $params{handle}, | |
uptime => $params{uptime}, | |
replication_user => $params{replication_user}, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
threads_connected => undef, | |
threads_created => undef, | |
connections => undef, | |
threadcache_hitrate => undef, | |
querycache_hitrate => undef, | |
lowmem_prunes_per_sec => undef, | |
slow_queries_per_sec => undef, | |
longrunners => undef, | |
tablecache_hitrate => undef, | |
index_usage => undef, | |
engine_innodb => undef, | |
engine_myisam => undef, | |
replication => undef, | |
}; | |
bless $self, $class; | |
$self->init(%params); | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
my $dummy; | |
$self->init_nagios(); | |
if ($params{mode} =~ /server::instance::connectedthreads/) { | |
($dummy, $self->{threads_connected}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Threads_connected' | |
}); | |
} elsif ($params{mode} =~ /server::instance::createdthreads/) { | |
($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Threads_created' | |
}); | |
$self->valdiff(\%params, qw(threads_created)); | |
$self->{threads_created_per_sec} = $self->{delta_threads_created} / | |
$self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::runningthreads/) { | |
($dummy, $self->{threads_running}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Threads_running' | |
}); | |
} elsif ($params{mode} =~ /server::instance::cachedthreads/) { | |
($dummy, $self->{threads_cached}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Threads_cached' | |
}); | |
} elsif ($params{mode} =~ /server::instance::abortedconnects/) { | |
($dummy, $self->{connects_aborted}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Aborted_connects' | |
}); | |
$self->valdiff(\%params, qw(connects_aborted)); | |
$self->{connects_aborted_per_sec} = $self->{delta_connects_aborted} / | |
$self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::abortedclients/) { | |
($dummy, $self->{clients_aborted}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Aborted_clients' | |
}); | |
$self->valdiff(\%params, qw(clients_aborted)); | |
$self->{clients_aborted_per_sec} = $self->{delta_clients_aborted} / | |
$self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::threadcachehitrate/) { | |
($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Threads_created' | |
}); | |
($dummy, $self->{connections}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Connections' | |
}); | |
$self->valdiff(\%params, qw(threads_created connections)); | |
if ($self->{delta_connections} > 0) { | |
$self->{threadcache_hitrate_now} = | |
100 - ($self->{delta_threads_created} * 100.0 / | |
$self->{delta_connections}); | |
} else { | |
$self->{threadcache_hitrate_now} = 100; | |
} | |
$self->{threadcache_hitrate} = 100 - | |
($self->{threads_created} * 100.0 / $self->{connections}); | |
$self->{connections_per_sec} = $self->{delta_connections} / | |
$self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::querycachehitrate/) { | |
($dummy, $self->{qcache_inserts}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Qcache_inserts' | |
}); | |
($dummy, $self->{qcache_not_cached}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Qcache_not_cached' | |
}); | |
($dummy, $self->{com_select}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Com_select' | |
}); | |
($dummy, $self->{qcache_hits}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Qcache_hits' | |
}); | |
# SHOW VARIABLES WHERE Variable_name = 'have_query_cache' for 5.x, but LIKE is compatible | |
($dummy, $self->{have_query_cache}) = $self->{handle}->fetchrow_array(q{ | |
SHOW VARIABLES LIKE 'have_query_cache' | |
}); | |
# SHOW VARIABLES WHERE Variable_name = 'query_cache_size' | |
($dummy, $self->{query_cache_size}) = $self->{handle}->fetchrow_array(q{ | |
SHOW VARIABLES LIKE 'query_cache_size' | |
}); | |
$self->valdiff(\%params, qw(com_select qcache_hits)); | |
$self->{querycache_hitrate_now} = | |
($self->{delta_com_select} + $self->{delta_qcache_hits}) > 0 ? | |
100 * $self->{delta_qcache_hits} / | |
($self->{delta_com_select} + $self->{delta_qcache_hits}) : | |
0; | |
$self->{querycache_hitrate} = | |
($self->{qcache_not_cached} + $self->{qcache_inserts} + $self->{qcache_hits}) > 0 ? | |
100 * $self->{qcache_hits} / | |
($self->{qcache_not_cached} + $self->{qcache_inserts} + $self->{qcache_hits}) : | |
0; | |
$self->{selects_per_sec} = | |
$self->{delta_com_select} / $self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) { | |
($dummy, $self->{lowmem_prunes}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Qcache_lowmem_prunes' | |
}); | |
$self->valdiff(\%params, qw(lowmem_prunes)); | |
$self->{lowmem_prunes_per_sec} = $self->{delta_lowmem_prunes} / | |
$self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::slowqueries/) { | |
($dummy, $self->{slow_queries}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Slow_queries' | |
}); | |
$self->valdiff(\%params, qw(slow_queries)); | |
$self->{slow_queries_per_sec} = $self->{delta_slow_queries} / | |
$self->{delta_timestamp}; | |
} elsif ($params{mode} =~ /server::instance::longprocs/) { | |
if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1")) { | |
($self->{longrunners}) = $self->{handle}->fetchrow_array(qq( | |
SELECT | |
COUNT(*) | |
FROM | |
information_schema.processlist | |
WHERE user <> ? | |
AND id <> CONNECTION_ID() | |
AND time > 60 | |
AND command <> 'Sleep' | |
), $self->{replication_user}); | |
} else { | |
$self->{longrunners} = 0 if ! defined $self->{longrunners}; | |
foreach ($self->{handle}->fetchall_array(q{ | |
SHOW PROCESSLIST | |
})) { | |
my($id, $user, $host, $db, $command, $tme, $state, $info) = @{$_}; | |
if (($user ne $self->{replication_user}) && | |
($tme > 60) && | |
($command ne 'Sleep')) { | |
$self->{longrunners}++; | |
} | |
} | |
} | |
} elsif ($params{mode} =~ /server::instance::tablecachehitrate/) { | |
($dummy, $self->{open_tables}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Open_tables' | |
}); | |
($dummy, $self->{opened_tables}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Opened_tables' | |
}); | |
if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1.3")) { | |
# SHOW VARIABLES WHERE Variable_name = 'table_open_cache' | |
($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{ | |
SHOW VARIABLES LIKE 'table_open_cache' | |
}); | |
} else { | |
# SHOW VARIABLES WHERE Variable_name = 'table_cache' | |
($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{ | |
SHOW VARIABLES LIKE 'table_cache' | |
}); | |
} | |
$self->{table_cache} ||= 0; | |
#$self->valdiff(\%params, qw(open_tables opened_tables table_cache)); | |
# _now ist hier sinnlos, da opened_tables waechst, aber open_tables wieder | |
# schrumpfen kann weil tabellen geschlossen werden. | |
if ($self->{opened_tables} != 0 && $self->{table_cache} != 0) { | |
$self->{tablecache_hitrate} = | |
100 * $self->{open_tables} / $self->{opened_tables}; | |
$self->{tablecache_fillrate} = | |
100 * $self->{open_tables} / $self->{table_cache}; | |
} elsif ($self->{opened_tables} == 0 && $self->{table_cache} != 0) { | |
$self->{tablecache_hitrate} = 100; | |
$self->{tablecache_fillrate} = | |
100 * $self->{open_tables} / $self->{table_cache}; | |
} else { | |
$self->{tablecache_hitrate} = 0; | |
$self->{tablecache_fillrate} = 0; | |
$self->add_nagios_critical("no table cache"); | |
} | |
} elsif ($params{mode} =~ /server::instance::tablelockcontention/) { | |
($dummy, $self->{table_locks_waited}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Table_locks_waited' | |
}); | |
($dummy, $self->{table_locks_immediate}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Table_locks_immediate' | |
}); | |
$self->valdiff(\%params, qw(table_locks_waited table_locks_immediate)); | |
$self->{table_lock_contention} = | |
($self->{table_locks_waited} + $self->{table_locks_immediate}) > 0 ? | |
100 * $self->{table_locks_waited} / | |
($self->{table_locks_waited} + $self->{table_locks_immediate}) : | |
100; | |
$self->{table_lock_contention_now} = | |
($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) > 0 ? | |
100 * $self->{delta_table_locks_waited} / | |
($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) : | |
100; | |
} elsif ($params{mode} =~ /server::instance::tableindexusage/) { | |
# http://johnjacobm.wordpress.com/2007/06/ | |
# formula for calculating the percentage of full table scans | |
($dummy, $self->{handler_read_first}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Handler_read_first' | |
}); | |
($dummy, $self->{handler_read_key}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Handler_read_key' | |
}); | |
($dummy, $self->{handler_read_next}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Handler_read_next' | |
}); | |
($dummy, $self->{handler_read_prev}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Handler_read_prev' | |
}); | |
($dummy, $self->{handler_read_rnd}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd' | |
}); | |
($dummy, $self->{handler_read_rnd_next}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd_next' | |
}); | |
$self->valdiff(\%params, qw(handler_read_first handler_read_key | |
handler_read_next handler_read_prev handler_read_rnd | |
handler_read_rnd_next)); | |
my $delta_reads = $self->{delta_handler_read_first} + | |
$self->{delta_handler_read_key} + | |
$self->{delta_handler_read_next} + | |
$self->{delta_handler_read_prev} + | |
$self->{delta_handler_read_rnd} + | |
$self->{delta_handler_read_rnd_next}; | |
my $reads = $self->{handler_read_first} + | |
$self->{handler_read_key} + | |
$self->{handler_read_next} + | |
$self->{handler_read_prev} + | |
$self->{handler_read_rnd} + | |
$self->{handler_read_rnd_next}; | |
$self->{index_usage_now} = ($delta_reads == 0) ? 0 : | |
100 - (100.0 * ($self->{delta_handler_read_rnd} + | |
$self->{delta_handler_read_rnd_next}) / | |
$delta_reads); | |
$self->{index_usage} = ($reads == 0) ? 0 : | |
100 - (100.0 * ($self->{handler_read_rnd} + | |
$self->{handler_read_rnd_next}) / | |
$reads); | |
} elsif ($params{mode} =~ /server::instance::tabletmpondisk/) { | |
($dummy, $self->{created_tmp_tables}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_tables' | |
}); | |
($dummy, $self->{created_tmp_disk_tables}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_disk_tables' | |
}); | |
$self->valdiff(\%params, qw(created_tmp_tables created_tmp_disk_tables)); | |
$self->{pct_tmp_on_disk} = $self->{created_tmp_tables} > 0 ? | |
100 * $self->{created_tmp_disk_tables} / $self->{created_tmp_tables} : | |
100; | |
$self->{pct_tmp_on_disk_now} = $self->{delta_created_tmp_tables} > 0 ? | |
100 * $self->{delta_created_tmp_disk_tables} / $self->{delta_created_tmp_tables} : | |
100; | |
} elsif ($params{mode} =~ /server::instance::openfiles/) { | |
($dummy, $self->{open_files_limit}) = $self->{handle}->fetchrow_array(q{ | |
SHOW VARIABLES LIKE 'open_files_limit' | |
}); | |
($dummy, $self->{open_files}) = $self->{handle}->fetchrow_array(q{ | |
SHOW /*!50000 global */ STATUS LIKE 'Open_files' | |
}); | |
$self->{pct_open_files} = 100 * $self->{open_files} / $self->{open_files_limit}; | |
} elsif ($params{mode} =~ /server::instance::needoptimize/) { | |
$self->{fragmented} = []; | |
#http://www.electrictoolbox.com/optimize-tables-mysql-php/ | |
my @result = $self->{handle}->fetchall_array(q{ | |
SHOW TABLE STATUS | |
}); | |
foreach (@result) { | |
my ($name, $engine, $data_length, $data_free) = | |
($_->[0], $_->[1], $_->[6 ], $_->[9]); | |
next if ($params{name} && $params{name} ne $name); | |
my $fragmentation = $data_length ? $data_free * 100 / $data_length : 0; | |
push(@{$self->{fragmented}}, | |
[$name, $fragmentation, $data_length, $data_free]); | |
} | |
} elsif ($params{mode} =~ /server::instance::myisam/) { | |
$self->{engine_myisam} = DBD::MySQL::Server::Instance::MyISAM->new( | |
%params | |
); | |
} elsif ($params{mode} =~ /server::instance::innodb/) { | |
$self->{engine_innodb} = DBD::MySQL::Server::Instance::Innodb->new( | |
%params | |
); | |
} elsif ($params{mode} =~ /server::instance::replication/) { | |
$self->{replication} = DBD::MySQL::Server::Instance::Replication->new( | |
%params | |
); | |
} | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if (! $self->{nagios_level}) { | |
if ($params{mode} =~ /server::instance::connectedthreads/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{threads_connected}, 10, 20), | |
sprintf "%d client connection threads", $self->{threads_connected}); | |
$self->add_perfdata(sprintf "threads_connected=%d;%d;%d", | |
$self->{threads_connected}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::createdthreads/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{threads_created_per_sec}, 10, 20), | |
sprintf "%.2f threads created/sec", $self->{threads_created_per_sec}); | |
$self->add_perfdata(sprintf "threads_created_per_sec=%.2f;%.2f;%.2f", | |
$self->{threads_created_per_sec}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::runningthreads/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{threads_running}, 10, 20), | |
sprintf "%d running threads", $self->{threads_running}); | |
$self->add_perfdata(sprintf "threads_running=%d;%d;%d", | |
$self->{threads_running}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::cachedthreads/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{threads_cached}, 10, 20), | |
sprintf "%d cached threads", $self->{threads_cached}); | |
$self->add_perfdata(sprintf "threads_cached=%d;%d;%d", | |
$self->{threads_cached}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::abortedconnects/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{connects_aborted_per_sec}, 1, 5), | |
sprintf "%.2f aborted connections/sec", $self->{connects_aborted_per_sec}); | |
$self->add_perfdata(sprintf "connects_aborted_per_sec=%.2f;%.2f;%.2f", | |
$self->{connects_aborted_per_sec}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::abortedclients/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{clients_aborted_per_sec}, 1, 5), | |
sprintf "%.2f aborted (client died) connections/sec", $self->{clients_aborted_per_sec}); | |
$self->add_perfdata(sprintf "clients_aborted_per_sec=%.2f;%.2f;%.2f", | |
$self->{clients_aborted_per_sec}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::threadcachehitrate/) { | |
my $refkey = 'threadcache_hitrate'.($params{lookback} ? '_now' : ''); | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "90:", "80:"), | |
sprintf "thread cache hitrate %.2f%%", $self->{$refkey}); | |
$self->add_perfdata(sprintf "thread_cache_hitrate=%.2f%%;%s;%s", | |
$self->{threadcache_hitrate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "thread_cache_hitrate_now=%.2f%%", | |
$self->{threadcache_hitrate_now}); | |
$self->add_perfdata(sprintf "connections_per_sec=%.2f", | |
$self->{connections_per_sec}); | |
} elsif ($params{mode} =~ /server::instance::querycachehitrate/) { | |
my $refkey = 'querycache_hitrate'.($params{lookback} ? '_now' : ''); | |
if ((lc $self->{have_query_cache} eq 'yes') && ($self->{query_cache_size})) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "90:", "80:"), | |
sprintf "query cache hitrate %.2f%%", $self->{$refkey}); | |
} else { | |
$self->check_thresholds($self->{$refkey}, "90:", "80:"); | |
$self->add_nagios_ok( | |
sprintf "query cache hitrate %.2f%% (because it's turned off)", | |
$self->{querycache_hitrate}); | |
} | |
$self->add_perfdata(sprintf "qcache_hitrate=%.2f%%;%s;%s", | |
$self->{querycache_hitrate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "qcache_hitrate_now=%.2f%%", | |
$self->{querycache_hitrate_now}); | |
$self->add_perfdata(sprintf "selects_per_sec=%.2f", | |
$self->{selects_per_sec}); | |
} elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{lowmem_prunes_per_sec}, "1", "10"), | |
sprintf "%d query cache lowmem prunes in %d seconds (%.2f/sec)", | |
$self->{delta_lowmem_prunes}, $self->{delta_timestamp}, | |
$self->{lowmem_prunes_per_sec}); | |
$self->add_perfdata(sprintf "qcache_lowmem_prunes_rate=%.2f;%s;%s", | |
$self->{lowmem_prunes_per_sec}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::slowqueries/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{slow_queries_per_sec}, "0.1", "1"), | |
sprintf "%d slow queries in %d seconds (%.2f/sec)", | |
$self->{delta_slow_queries}, $self->{delta_timestamp}, | |
$self->{slow_queries_per_sec}); | |
$self->add_perfdata(sprintf "slow_queries_rate=%.2f%%;%s;%s", | |
$self->{slow_queries_per_sec}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::longprocs/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{longrunners}, 10, 20), | |
sprintf "%d long running processes", $self->{longrunners}); | |
$self->add_perfdata(sprintf "long_running_procs=%d;%d;%d", | |
$self->{longrunners}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /server::instance::tablecachehitrate/) { | |
if ($self->{tablecache_fillrate} < 95) { | |
$self->add_nagios_ok( | |
sprintf "table cache hitrate %.2f%%, %.2f%% filled", | |
$self->{tablecache_hitrate}, | |
$self->{tablecache_fillrate}); | |
$self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"); | |
} else { | |
$self->add_nagios( | |
$self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"), | |
sprintf "table cache hitrate %.2f%%", $self->{tablecache_hitrate}); | |
} | |
$self->add_perfdata(sprintf "tablecache_hitrate=%.2f%%;%s;%s", | |
$self->{tablecache_hitrate}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "tablecache_fillrate=%.2f%%", | |
$self->{tablecache_fillrate}); | |
} elsif ($params{mode} =~ /server::instance::tablelockcontention/) { | |
my $refkey = 'table_lock_contention'.($params{lookback} ? '_now' : ''); | |
if ($self->{uptime} > 10800) { # MySQL Bug #30599 | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "1", "2"), | |
sprintf "table lock contention %.2f%%", $self->{$refkey}); | |
} else { | |
$self->check_thresholds($self->{$refkey}, "1", "2"); | |
$self->add_nagios_ok( | |
sprintf "table lock contention %.2f%% (uptime < 10800)", | |
$self->{$refkey}); | |
} | |
$self->add_perfdata(sprintf "tablelock_contention=%.2f%%;%s;%s", | |
$self->{table_lock_contention}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "tablelock_contention_now=%.2f%%", | |
$self->{table_lock_contention_now}); | |
} elsif ($params{mode} =~ /server::instance::tableindexusage/) { | |
my $refkey = 'index_usage'.($params{lookback} ? '_now' : ''); | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "90:", "80:"), | |
sprintf "index usage %.2f%%", $self->{$refkey}); | |
$self->add_perfdata(sprintf "index_usage=%.2f%%;%s;%s", | |
$self->{index_usage}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "index_usage_now=%.2f%%", | |
$self->{index_usage_now}); | |
} elsif ($params{mode} =~ /server::instance::tabletmpondisk/) { | |
my $refkey = 'pct_tmp_on_disk'.($params{lookback} ? '_now' : ''); | |
$self->add_nagios( | |
$self->check_thresholds($self->{$refkey}, "25", "50"), | |
sprintf "%.2f%% of %d tables were created on disk", | |
$self->{$refkey}, $self->{delta_created_tmp_tables}); | |
$self->add_perfdata(sprintf "pct_tmp_table_on_disk=%.2f%%;%s;%s", | |
$self->{pct_tmp_on_disk}, | |
$self->{warningrange}, $self->{criticalrange}); | |
$self->add_perfdata(sprintf "pct_tmp_table_on_disk_now=%.2f%%", | |
$self->{pct_tmp_on_disk_now}); | |
} elsif ($params{mode} =~ /server::instance::openfiles/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{pct_open_files}, 80, 95), | |
sprintf "%.2f%% of the open files limit reached (%d of max. %d)", | |
$self->{pct_open_files}, | |
$self->{open_files}, $self->{open_files_limit}); | |
$self->add_perfdata(sprintf "pct_open_files=%.3f%%;%.3f;%.3f", | |
$self->{pct_open_files}, | |
$self->{warningrange}, | |
$self->{criticalrange}); | |
$self->add_perfdata(sprintf "open_files=%d;%d;%d", | |
$self->{open_files}, | |
$self->{open_files_limit} * $self->{warningrange} / 100, | |
$self->{open_files_limit} * $self->{criticalrange} / 100); | |
} elsif ($params{mode} =~ /server::instance::needoptimize/) { | |
foreach (@{$self->{fragmented}}) { | |
$self->add_nagios( | |
$self->check_thresholds($_->[1], 10, 25), | |
sprintf "table %s is %.2f%% fragmented", $_->[0], $_->[1]); | |
if ($params{name}) { | |
$self->add_perfdata(sprintf "'%s_frag'=%.2f%%;%d;%d", | |
$_->[0], $_->[1], $self->{warningrange}, $self->{criticalrange}); | |
} | |
} | |
} elsif ($params{mode} =~ /server::instance::myisam/) { | |
$self->{engine_myisam}->nagios(%params); | |
$self->merge_nagios($self->{engine_myisam}); | |
} elsif ($params{mode} =~ /server::instance::innodb/) { | |
$self->{engine_innodb}->nagios(%params); | |
$self->merge_nagios($self->{engine_innodb}); | |
} elsif ($params{mode} =~ /server::instance::replication/) { | |
$self->{replication}->nagios(%params); | |
$self->merge_nagios($self->{replication}); | |
} | |
} | |
} | |
package DBD::MySQL::Server; | |
use strict; | |
use Time::HiRes; | |
use IO::File; | |
use File::Copy 'cp'; | |
use Data::Dumper; | |
{ | |
our $verbose = 0; | |
our $scream = 0; # scream if something is not implemented | |
our $access = "dbi"; # how do we access the database. | |
our $my_modules_dyn_dir = ""; # where we look for self-written extensions | |
my @servers = (); | |
my $initerrors = undef; | |
sub add_server { | |
push(@servers, shift); | |
} | |
sub return_servers { | |
return @servers; | |
} | |
sub return_first_server() { | |
return $servers[0]; | |
} | |
} | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
mode => $params{mode}, | |
access => $params{method} || 'dbi', | |
hostname => $params{hostname}, | |
database => $params{database} || 'information_schema', | |
port => $params{port}, | |
socket => $params{socket}, | |
username => $params{username}, | |
password => $params{password}, | |
replication_user => $params{replication_user}, | |
mycnf => $params{mycnf}, | |
mycnfgroup => $params{mycnfgroup}, | |
timeout => $params{timeout}, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
verbose => $params{verbose}, | |
report => $params{report}, | |
negate => $params{negate}, | |
labelformat => $params{labelformat}, | |
version => 'unknown', | |
instance => undef, | |
handle => undef, | |
}; | |
bless $self, $class; | |
$self->init_nagios(); | |
if ($self->dbconnect(%params)) { | |
($self->{dummy}, $self->{version}) = $self->{handle}->fetchrow_array( | |
#q{ SHOW VARIABLES WHERE Variable_name = 'version' } | |
q{ SHOW VARIABLES LIKE 'version' } | |
); | |
$self->{version} = (split "-", $self->{version})[0]; | |
($self->{dummy}, $self->{uptime}) = $self->{handle}->fetchrow_array( | |
q{ SHOW STATUS LIKE 'Uptime' } | |
); | |
DBD::MySQL::Server::add_server($self); | |
$self->init(%params); | |
} | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
$params{handle} = $self->{handle}; | |
$params{uptime} = $self->{uptime}; | |
$self->set_global_db_thresholds(\%params); | |
if ($params{mode} =~ /^server::instance/) { | |
$self->{instance} = DBD::MySQL::Server::Instance->new(%params); | |
} elsif ($params{mode} =~ /^server::sql/) { | |
$self->set_local_db_thresholds(%params); | |
if ($params{regexp}) { | |
# sql output is treated as text | |
if ($params{name2} eq $params{name}) { | |
$self->add_nagios_unknown(sprintf "where's the regexp????"); | |
} else { | |
$self->{genericsql} = | |
$self->{handle}->fetchrow_array($params{selectname}); | |
if (! defined $self->{genericsql}) { | |
$self->add_nagios_unknown(sprintf "got no valid response for %s", | |
$params{selectname}); | |
} | |
} | |
} else { | |
# sql output must be a number (or array of numbers) | |
@{$self->{genericsql}} = | |
$self->{handle}->fetchrow_array($params{selectname}); | |
if ($self->{handle}->{errstr}) { | |
$self->add_nagios_unknown(sprintf "got no valid response for %s: %s", | |
$params{selectname}, $self->{handle}->{errstr}); | |
} elsif (! (defined $self->{genericsql} && | |
(scalar(grep { | |
/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/ | |
} @{$self->{genericsql}})) == | |
scalar(@{$self->{genericsql}}))) { | |
$self->add_nagios_unknown(sprintf "got no valid response for %s", | |
$params{selectname}); | |
} elsif (! defined $self->{genericsql}) { | |
$self->add_nagios_unknown(sprintf "got no valid response for %s", | |
$params{selectname}); | |
} else { | |
# name2 in array | |
# units in array | |
} | |
} | |
} elsif ($params{mode} =~ /^server::uptime/) { | |
# already set with the connection. but use minutes here | |
} elsif ($params{mode} =~ /^server::connectiontime/) { | |
$self->{connection_time} = $self->{tac} - $self->{tic}; | |
} elsif ($params{mode} =~ /^my::([^:.]+)/) { | |
my $class = $1; | |
my $loaderror = undef; | |
substr($class, 0, 1) = uc substr($class, 0, 1); | |
foreach my $libpath (split(":", $DBD::MySQL::Server::my_modules_dyn_dir)) { | |
foreach my $extmod (glob $libpath."/CheckMySQLHealth*.pm") { | |
eval { | |
$self->trace(sprintf "loading module %s", $extmod); | |
require $extmod; | |
}; | |
if ($@) { | |
$loaderror = $extmod; | |
$self->trace(sprintf "failed loading module %s: %s", $extmod, $@); | |
} | |
} | |
} | |
my $obj = { | |
handle => $params{handle}, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
}; | |
bless $obj, "My$class"; | |
$self->{my} = $obj; | |
if ($self->{my}->isa("DBD::MySQL::Server")) { | |
my $dos_init = $self->can("init"); | |
my $dos_nagios = $self->can("nagios"); | |
my $my_init = $self->{my}->can("init"); | |
my $my_nagios = $self->{my}->can("nagios"); | |
if ($my_init == $dos_init) { | |
$self->add_nagios_unknown( | |
sprintf "Class %s needs an init() method", ref($self->{my})); | |
} elsif ($my_nagios == $dos_nagios) { | |
$self->add_nagios_unknown( | |
sprintf "Class %s needs a nagios() method", ref($self->{my})); | |
} else { | |
$self->{my}->init_nagios(%params); | |
$self->{my}->init(%params); | |
} | |
} else { | |
$self->add_nagios_unknown( | |
sprintf "Class %s is not a subclass of DBD::MySQL::Server%s", | |
ref($self->{my}), | |
$loaderror ? sprintf " (syntax error in %s?)", $loaderror : "" ); | |
} | |
} else { | |
printf "broken mode %s\n", $params{mode}; | |
} | |
} | |
sub dump { | |
my $self = shift; | |
my $message = shift || ""; | |
printf "%s %s\n", $message, Data::Dumper::Dumper($self); | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
if (! $self->{nagios_level}) { | |
if ($params{mode} =~ /^server::instance/) { | |
$self->{instance}->nagios(%params); | |
$self->merge_nagios($self->{instance}); | |
} elsif ($params{mode} =~ /^server::database/) { | |
$self->{database}->nagios(%params); | |
$self->merge_nagios($self->{database}); | |
} elsif ($params{mode} =~ /^server::uptime/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{uptime} / 60, "10:", "5:"), | |
sprintf "database is up since %d minutes", $self->{uptime} / 60); | |
$self->add_perfdata(sprintf "uptime=%ds", | |
$self->{uptime}); | |
} elsif ($params{mode} =~ /^server::connectiontime/) { | |
$self->add_nagios( | |
$self->check_thresholds($self->{connection_time}, 1, 5), | |
sprintf "%.2f seconds to connect as %s", | |
$self->{connection_time}, ($self->{username} || getpwuid($<))); | |
$self->add_perfdata(sprintf "connection_time=%.4fs;%d;%d", | |
$self->{connection_time}, | |
$self->{warningrange}, $self->{criticalrange}); | |
} elsif ($params{mode} =~ /^server::sql/) { | |
if ($params{regexp}) { | |
if (substr($params{name2}, 0, 1) eq '!') { | |
$params{name2} =~ s/^!//; | |
if ($self->{genericsql} !~ /$params{name2}/) { | |
$self->add_nagios_ok( | |
sprintf "output %s does not match pattern %s", | |
$self->{genericsql}, $params{name2}); | |
} else { | |
$self->add_nagios_critical( | |
sprintf "output %s matches pattern %s", | |
$self->{genericsql}, $params{name2}); | |
} | |
} else { | |
if ($self->{genericsql} =~ /$params{name2}/) { | |
$self->add_nagios_ok( | |
sprintf "output %s matches pattern %s", | |
$self->{genericsql}, $params{name2}); | |
} else { | |
$self->add_nagios_critical( | |
sprintf "output %s does not match pattern %s", | |
$self->{genericsql}, $params{name2}); | |
} | |
} | |
} else { | |
$self->add_nagios( | |
# the first item in the list will trigger the threshold values | |
$self->check_thresholds($self->{genericsql}[0], 1, 5), | |
sprintf "%s: %s%s", | |
$params{name2} ? lc $params{name2} : lc $params{selectname}, | |
# float as float, integers as integers | |
join(" ", map { | |
(sprintf("%d", $_) eq $_) ? $_ : sprintf("%f", $_) | |
} @{$self->{genericsql}}), | |
$params{units} ? $params{units} : ""); | |
my $i = 0; | |
# workaround... getting the column names from the database would be nicer | |
my @names2_arr = split(/\s+/, $params{name2}); | |
foreach my $t (@{$self->{genericsql}}) { | |
$self->add_perfdata(sprintf "\'%s\'=%s%s;%s;%s", | |
$names2_arr[$i] ? lc $names2_arr[$i] : lc $params{selectname}, | |
# float as float, integers as integers | |
(sprintf("%d", $t) eq $t) ? $t : sprintf("%f", $t), | |
$params{units} ? $params{units} : "", | |
($i == 0) ? $self->{warningrange} : "", | |
($i == 0) ? $self->{criticalrange} : "" | |
); | |
$i++; | |
} | |
} | |
} elsif ($params{mode} =~ /^my::([^:.]+)/) { | |
$self->{my}->nagios(%params); | |
$self->merge_nagios($self->{my}); | |
} | |
} | |
} | |
sub init_nagios { | |
my $self = shift; | |
no strict 'refs'; | |
if (! ref($self)) { | |
my $nagiosvar = $self."::nagios"; | |
my $nagioslevelvar = $self."::nagios_level"; | |
$$nagiosvar = { | |
messages => { | |
0 => [], | |
1 => [], | |
2 => [], | |
3 => [], | |
}, | |
perfdata => [], | |
}; | |
$$nagioslevelvar = $ERRORS{OK}, | |
} else { | |
$self->{nagios} = { | |
messages => { | |
0 => [], | |
1 => [], | |
2 => [], | |
3 => [], | |
}, | |
perfdata => [], | |
}; | |
$self->{nagios_level} = $ERRORS{OK}, | |
} | |
} | |
sub check_thresholds { | |
my $self = shift; | |
my $value = shift; | |
my $defaultwarningrange = shift; | |
my $defaultcriticalrange = shift; | |
my $level = $ERRORS{OK}; | |
$self->{warningrange} = defined $self->{warningrange} ? | |
$self->{warningrange} : $defaultwarningrange; | |
$self->{criticalrange} = defined $self->{criticalrange} ? | |
$self->{criticalrange} : $defaultcriticalrange; | |
if ($self->{warningrange} =~ /^([-+]?[0-9]*\.?[0-9]+)$/) { | |
# warning = 10, warn if > 10 or < 0 | |
$level = $ERRORS{WARNING} | |
if ($value > $1 || $value < 0); | |
} elsif ($self->{warningrange} =~ /^([-+]?[0-9]*\.?[0-9]+):$/) { | |
# warning = 10:, warn if < 10 | |
$level = $ERRORS{WARNING} | |
if ($value < $1); | |
} elsif ($self->{warningrange} =~ /^~:([-+]?[0-9]*\.?[0-9]+)$/) { | |
# warning = ~:10, warn if > 10 | |
$level = $ERRORS{WARNING} | |
if ($value > $1); | |
} elsif ($self->{warningrange} =~ /^([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { | |
# warning = 10:20, warn if < 10 or > 20 | |
$level = $ERRORS{WARNING} | |
if ($value < $1 || $value > $2); | |
} elsif ($self->{warningrange} =~ /^@([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { | |
# warning = @10:20, warn if >= 10 and <= 20 | |
$level = $ERRORS{WARNING} | |
if ($value >= $1 && $value <= $2); | |
} | |
if ($self->{criticalrange} =~ /^([-+]?[0-9]*\.?[0-9]+)$/) { | |
# critical = 10, crit if > 10 or < 0 | |
$level = $ERRORS{CRITICAL} | |
if ($value > $1 || $value < 0); | |
} elsif ($self->{criticalrange} =~ /^([-+]?[0-9]*\.?[0-9]+):$/) { | |
# critical = 10:, crit if < 10 | |
$level = $ERRORS{CRITICAL} | |
if ($value < $1); | |
} elsif ($self->{criticalrange} =~ /^~:([-+]?[0-9]*\.?[0-9]+)$/) { | |
# critical = ~:10, crit if > 10 | |
$level = $ERRORS{CRITICAL} | |
if ($value > $1); | |
} elsif ($self->{criticalrange} =~ /^([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { | |
# critical = 10:20, crit if < 10 or > 20 | |
$level = $ERRORS{CRITICAL} | |
if ($value < $1 || $value > $2); | |
} elsif ($self->{criticalrange} =~ /^@([-+]?[0-9]*\.?[0-9]+):([-+]?[0-9]*\.?[0-9]+)$/) { | |
# critical = @10:20, crit if >= 10 and <= 20 | |
$level = $ERRORS{CRITICAL} | |
if ($value >= $1 && $value <= $2); | |
} | |
return $level; | |
# | |
# syntax error must be reported with returncode -1 | |
# | |
} | |
sub add_nagios { | |
my $self = shift; | |
my $level = shift; | |
my $message = shift; | |
push(@{$self->{nagios}->{messages}->{$level}}, $message); | |
# recalc current level | |
foreach my $llevel (qw(CRITICAL WARNING UNKNOWN OK)) { | |
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) { | |
$self->{nagios_level} = $ERRORS{$llevel}; | |
} | |
} | |
} | |
sub add_nagios_ok { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{OK}, $message); | |
} | |
sub add_nagios_warning { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{WARNING}, $message); | |
} | |
sub add_nagios_critical { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{CRITICAL}, $message); | |
} | |
sub add_nagios_unknown { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{UNKNOWN}, $message); | |
} | |
sub add_perfdata { | |
my $self = shift; | |
my $data = shift; | |
push(@{$self->{nagios}->{perfdata}}, $data); | |
} | |
sub merge_nagios { | |
my $self = shift; | |
my $child = shift; | |
foreach my $level (0..3) { | |
foreach (@{$child->{nagios}->{messages}->{$level}}) { | |
$self->add_nagios($level, $_); | |
} | |
#push(@{$self->{nagios}->{messages}->{$level}}, | |
# @{$child->{nagios}->{messages}->{$level}}); | |
} | |
push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}}); | |
} | |
sub calculate_result { | |
my $self = shift; | |
my $labels = shift || {}; | |
my $multiline = 0; | |
map { | |
$self->{nagios_level} = $ERRORS{$_} if | |
(scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}})); | |
} ("OK", "UNKNOWN", "WARNING", "CRITICAL"); | |
if ($ENV{NRPE_MULTILINESUPPORT} && | |
length join(" ", @{$self->{nagios}->{perfdata}}) > 200) { | |
$multiline = 1; | |
} | |
my $all_messages = join(($multiline ? "\n" : ", "), map { | |
join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} grep { | |
scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} ("CRITICAL", "WARNING", "UNKNOWN", "OK")); | |
my $bad_messages = join(($multiline ? "\n" : ", "), map { | |
join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} grep { | |
scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} ("CRITICAL", "WARNING", "UNKNOWN")); | |
my $good_messages = join(($multiline ? "\n" : ", "), map { | |
join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} grep { | |
scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} ("OK")); | |
my $all_messages_short = $bad_messages ? $bad_messages : 'no problems'; | |
# if mode = my-.... | |
# and there are some ok-messages | |
# output them instead of "no problems" | |
if ($self->{mode} =~ /^my\:\:/ && $good_messages) { | |
$all_messages_short = $bad_messages ? $bad_messages : $good_messages; | |
} | |
my $all_messages_html = "<table style=\"border-collapse: collapse;\">". | |
join("", map { | |
my $level = $_; | |
join("", map { | |
sprintf "<tr valign=\"top\"><td class=\"service%s\">%s</td></tr>", | |
$level, $_; | |
} @{$self->{nagios}->{messages}->{$ERRORS{$_}}}); | |
} grep { | |
scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}) | |
} ("CRITICAL", "WARNING", "UNKNOWN", "OK")). | |
"</table>"; | |
if (exists $self->{identstring}) { | |
$self->{nagios_message} .= $self->{identstring}; | |
} | |
if ($self->{report} eq "long") { | |
$self->{nagios_message} .= $all_messages; | |
} elsif ($self->{report} eq "short") { | |
$self->{nagios_message} .= $all_messages_short; | |
} elsif ($self->{report} eq "html") { | |
$self->{nagios_message} .= $all_messages_short."\n".$all_messages_html; | |
} | |
foreach my $from (keys %{$self->{negate}}) { | |
if ((uc $from) =~ /^(OK|WARNING|CRITICAL|UNKNOWN)$/ && | |
(uc $self->{negate}->{$from}) =~ /^(OK|WARNING|CRITICAL|UNKNOWN)$/) { | |
if ($self->{nagios_level} == $ERRORS{uc $from}) { | |
$self->{nagios_level} = $ERRORS{uc $self->{negate}->{$from}}; | |
} | |
} | |
} | |
if ($self->{labelformat} eq "pnp4nagios") { | |
$self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}}); | |
} else { | |
$self->{perfdata} = join(" ", map { | |
my $perfdata = $_; | |
if ($perfdata =~ /^(.*?)=(.*)/) { | |
my $label = $1; | |
my $data = $2; | |
if (exists $labels->{$label} && | |
exists $labels->{$label}->{$self->{labelformat}}) { | |
$labels->{$label}->{$self->{labelformat}}."=".$data; | |
} else { | |
$perfdata; | |
} | |
} else { | |
$perfdata; | |
} | |
} @{$self->{nagios}->{perfdata}}); | |
} | |
} | |
sub set_global_db_thresholds { | |
my $self = shift; | |
my $params = shift; | |
my $warning = undef; | |
my $critical = undef; | |
return unless defined $params->{dbthresholds}; | |
$params->{name0} = $params->{dbthresholds}; | |
# :pluginmode :name :warning :critical | |
# mode empty | |
# | |
eval { | |
if ($self->{handle}->fetchrow_array(q{ | |
SELECT table_name FROM information_schema.tables | |
WHERE table_schema = ? | |
AND table_name = 'CHECK_MYSQL_HEALTH_THRESHOLDS'; | |
}, $self->{database})) { # either --database... or information_schema | |
my @dbthresholds = $self->{handle}->fetchall_array(q{ | |
SELECT * FROM check_mysql_health_thresholds | |
}); | |
$params->{dbthresholds} = \@dbthresholds; | |
foreach (@dbthresholds) { | |
if (($_->[0] eq $params->{cmdlinemode}) && | |
(! defined $_->[1] || ! $_->[1])) { | |
($warning, $critical) = ($_->[2], $_->[3]); | |
} | |
} | |
} | |
}; | |
if (! $@) { | |
if ($warning) { | |
$params->{warningrange} = $warning; | |
$self->trace("read warningthreshold %s from database", $warning); | |
} | |
if ($critical) { | |
$params->{criticalrange} = $critical; | |
$self->trace("read criticalthreshold %s from database", $critical); | |
} | |
} | |
} | |
sub set_local_db_thresholds { | |
my $self = shift; | |
my %params = @_; | |
my $warning = undef; | |
my $critical = undef; | |
# :pluginmode :name :warning :critical | |
# mode name0 | |
# mode name2 | |
# mode name | |
# | |
# first: argument of --dbthresholds, it it exists | |
# second: --name2 | |
# third: --name | |
if (ref($params{dbthresholds}) eq 'ARRAY') { | |
my $marker; | |
foreach (@{$params{dbthresholds}}) { | |
if ($_->[0] eq $params{cmdlinemode}) { | |
if (defined $_->[1] && $params{name0} && $_->[1] eq $params{name0}) { | |
($warning, $critical) = ($_->[2], $_->[3]); | |
$marker = $params{name0}; | |
last; | |
} elsif (defined $_->[1] && $params{name2} && $_->[1] eq $params{name2}) { | |
($warning, $critical) = ($_->[2], $_->[3]); | |
$marker = $params{name2}; | |
last; | |
} elsif (defined $_->[1] && $params{name} && $_->[1] eq $params{name}) { | |
($warning, $critical) = ($_->[2], $_->[3]); | |
$marker = $params{name}; | |
last; | |
} | |
} | |
} | |
if ($warning) { | |
$self->{warningrange} = $warning; | |
$self->trace("read warningthreshold %s for %s from database", | |
$marker, $warning); | |
} | |
if ($critical) { | |
$self->{criticalrange} = $critical; | |
$self->trace("read criticalthreshold %s for %s from database", | |
$marker, $critical); | |
} | |
} | |
} | |
sub debug { | |
my $self = shift; | |
my $msg = shift; | |
if ($DBD::MySQL::Server::verbose) { | |
printf "%s %s\n", $msg, ref($self); | |
} | |
} | |
sub dbconnect { | |
my $self = shift; | |
my %params = @_; | |
my $retval = undef; | |
$self->{tic} = Time::HiRes::time(); | |
$self->{handle} = DBD::MySQL::Server::Connection->new(%params); | |
if ($self->{handle}->{errstr}) { | |
if ($params{mode} =~ /^server::tnsping/ && | |
$self->{handle}->{errstr} =~ /ORA-01017/) { | |
$self->add_nagios($ERRORS{OK}, | |
sprintf "connection established to %s.", $self->{connect}); | |
$retval = undef; | |
} elsif ($self->{handle}->{errstr} eq "alarm\n") { | |
$self->add_nagios($ERRORS{CRITICAL}, | |
sprintf "connection could not be established within %d seconds", | |
$self->{timeout}); | |
} else { | |
$self->add_nagios($ERRORS{CRITICAL}, | |
sprintf "cannot connect to %s. %s", | |
$self->{database}, $self->{handle}->{errstr}); | |
$retval = undef; | |
} | |
} else { | |
$retval = $self->{handle}; | |
} | |
$self->{tac} = Time::HiRes::time(); | |
return $retval; | |
} | |
sub trace { | |
my $self = shift; | |
my $format = shift; | |
$self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0; | |
if ($self->{verbose}) { | |
printf("%s: ", scalar localtime); | |
printf($format, @_); | |
} | |
if ($self->{trace}) { | |
my $logfh = new IO::File; | |
$logfh->autoflush(1); | |
if ($logfh->open("/tmp/check_mysql_health.trace", "a")) { | |
$logfh->printf("%s: ", scalar localtime); | |
$logfh->printf($format, @_); | |
$logfh->printf("\n"); | |
$logfh->close(); | |
} | |
} | |
} | |
sub DESTROY { | |
my $self = shift; | |
my $handle1 = "null"; | |
my $handle2 = "null"; | |
if (defined $self->{handle}) { | |
$handle1 = ref($self->{handle}); | |
if (defined $self->{handle}->{handle}) { | |
$handle2 = ref($self->{handle}->{handle}); | |
} | |
} | |
$self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2); | |
if (ref($self) eq "DBD::MySQL::Server") { | |
} | |
$self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); | |
if (ref($self) eq "DBD::MySQL::Server") { | |
#printf "humpftata\n"; | |
} | |
} | |
sub save_state { | |
my $self = shift; | |
my %params = @_; | |
my $extension = ""; | |
my $mode = $params{mode}; | |
if ($params{connect} && $params{connect} =~ /(\w+)\/(\w+)@(\w+)/) { | |
$params{connect} = $3; | |
} elsif ($params{connect}) { | |
# just to be sure | |
$params{connect} =~ s/\//_/g; | |
} | |
if ($^O =~ /MSWin/) { | |
$mode =~ s/::/_/g; | |
$params{statefilesdir} = $self->system_vartmpdir(); | |
} | |
if (! -d $params{statefilesdir}) { | |
eval { | |
use File::Path; | |
mkpath $params{statefilesdir}; | |
}; | |
} | |
if ($@ || ! -w $params{statefilesdir}) { | |
$self->add_nagios($ERRORS{CRITICAL}, | |
sprintf "statefilesdir %s does not exist or is not writable\n", | |
$params{statefilesdir}); | |
return; | |
} | |
my $statefile = sprintf "%s_%s", $params{hostname}, $mode; | |
$extension .= $params{differenciator} ? "_".$params{differenciator} : ""; | |
$extension .= $params{socket} ? "_".$params{socket} : ""; | |
$extension .= $params{port} ? "_".$params{port} : ""; | |
$extension .= $params{database} ? "_".$params{database} : ""; | |
$extension .= $params{tablespace} ? "_".$params{tablespace} : ""; | |
$extension .= $params{datafile} ? "_".$params{datafile} : ""; | |
$extension .= $params{name} ? "_".$params{name} : ""; | |
$extension =~ s/\//_/g; | |
$extension =~ s/\(/_/g; | |
$extension =~ s/\)/_/g; | |
$extension =~ s/\*/_/g; | |
$extension =~ s/\s/_/g; | |
$statefile .= $extension; | |
$statefile = lc $statefile; | |
$statefile = sprintf "%s/%s", $params{statefilesdir}, $statefile; | |
if (open(STATE, ">$statefile")) { | |
if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) { | |
$params{save}->{localtime} = scalar localtime $params{save}->{timestamp}; | |
} | |
printf STATE Data::Dumper::Dumper($params{save}); | |
close STATE; | |
} else { | |
$self->add_nagios($ERRORS{CRITICAL}, | |
sprintf "statefile %s is not writable", $statefile); | |
} | |
$self->debug(sprintf "saved %s to %s", | |
Data::Dumper::Dumper($params{save}), $statefile); | |
} | |
sub load_state { | |
my $self = shift; | |
my %params = @_; | |
my $extension = ""; | |
my $mode = $params{mode}; | |
if ($params{connect} && $params{connect} =~ /(\w+)\/(\w+)@(\w+)/) { | |
$params{connect} = $3; | |
} elsif ($params{connect}) { | |
# just to be sure | |
$params{connect} =~ s/\//_/g; | |
} | |
if ($^O =~ /MSWin/) { | |
$mode =~ s/::/_/g; | |
$params{statefilesdir} = $self->system_vartmpdir(); | |
} | |
my $statefile = sprintf "%s_%s", $params{hostname}, $mode; | |
$extension .= $params{differenciator} ? "_".$params{differenciator} : ""; | |
$extension .= $params{socket} ? "_".$params{socket} : ""; | |
$extension .= $params{port} ? "_".$params{port} : ""; | |
$extension .= $params{database} ? "_".$params{database} : ""; | |
$extension .= $params{tablespace} ? "_".$params{tablespace} : ""; | |
$extension .= $params{datafile} ? "_".$params{datafile} : ""; | |
$extension .= $params{name} ? "_".$params{name} : ""; | |
$extension =~ s/\//_/g; | |
$extension =~ s/\(/_/g; | |
$extension =~ s/\)/_/g; | |
$extension =~ s/\*/_/g; | |
$extension =~ s/\s/_/g; | |
$statefile .= $extension; | |
$statefile = lc $statefile; | |
$statefile = sprintf "%s/%s", $params{statefilesdir}, $statefile; | |
if ( -f $statefile) { | |
our $VAR1; | |
eval { | |
require $statefile; | |
}; | |
if($@) { | |
$self->add_nagios($ERRORS{CRITICAL}, | |
sprintf "statefile %s is corrupt", $statefile); | |
} | |
$self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); | |
return $VAR1; | |
} else { | |
return undef; | |
} | |
} | |
sub valdiff { | |
my $self = shift; | |
my $pparams = shift; | |
my %params = %{$pparams}; | |
my @keys = @_; | |
my $now = time; | |
my $last_values = $self->load_state(%params) || eval { | |
my $empty_events = {}; | |
foreach (@keys) { | |
$empty_events->{$_} = 0; | |
} | |
$empty_events->{timestamp} = 0; | |
if ($params{lookback}) { | |
$empty_events->{lookback_history} = {}; | |
} | |
$empty_events; | |
}; | |
foreach (@keys) { | |
if ($params{lookback}) { | |
# find a last_value in the history which fits lookback best | |
# and overwrite $last_values->{$_} with historic data | |
if (exists $last_values->{lookback_history}->{$_}) { | |
foreach my $date (sort {$a <=> $b} keys %{$last_values->{lookback_history}->{$_}}) { | |
if ($date >= ($now - $params{lookback})) { | |
$last_values->{$_} = $last_values->{lookback_history}->{$_}->{$date}; | |
$last_values->{timestamp} = $date; | |
last; | |
} else { | |
delete $last_values->{lookback_history}->{$_}->{$date}; | |
} | |
} | |
} | |
} | |
$last_values->{$_} = 0 if ! exists $last_values->{$_}; | |
if ($self->{$_} >= $last_values->{$_}) { | |
$self->{'delta_'.$_} = $self->{$_} - $last_values->{$_}; | |
} else { | |
# vermutlich db restart und zaehler alle auf null | |
$self->{'delta_'.$_} = $self->{$_}; | |
} | |
$self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_}); | |
} | |
$self->{'delta_timestamp'} = $now - $last_values->{timestamp}; | |
$params{save} = eval { | |
my $empty_events = {}; | |
foreach (@keys) { | |
$empty_events->{$_} = $self->{$_}; | |
} | |
$empty_events->{timestamp} = $now; | |
if ($params{lookback}) { | |
$empty_events->{lookback_history} = $last_values->{lookback_history}; | |
foreach (@keys) { | |
$empty_events->{lookback_history}->{$_}->{$now} = $self->{$_}; | |
} | |
} | |
$empty_events; | |
}; | |
$self->save_state(%params); | |
} | |
sub requires_version { | |
my $self = shift; | |
my $version = shift; | |
my @instances = DBD::MySQL::Server::return_servers(); | |
my $instversion = $instances[0]->{version}; | |
if (! $self->version_is_minimum($version)) { | |
$self->add_nagios($ERRORS{UNKNOWN}, | |
sprintf "not implemented/possible for MySQL release %s", $instversion); | |
} | |
} | |
sub version_is_minimum { | |
# the current version is newer or equal | |
my $self = shift; | |
my $version = shift; | |
my $newer = 1; | |
my @instances = DBD::MySQL::Server::return_servers(); | |
my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version); | |
my @v2 = split(/\./, $instances[0]->{version}); | |
if (scalar(@v1) > scalar(@v2)) { | |
push(@v2, (0) x (scalar(@v1) - scalar(@v2))); | |
} elsif (scalar(@v2) > scalar(@v1)) { | |
push(@v1, (0) x (scalar(@v2) - scalar(@v1))); | |
} | |
foreach my $pos (0..$#v1) { | |
if ($v2[$pos] > $v1[$pos]) { | |
$newer = 1; | |
last; | |
} elsif ($v2[$pos] < $v1[$pos]) { | |
$newer = 0; | |
last; | |
} | |
} | |
#printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1); | |
return $newer; | |
} | |
sub instance_thread { | |
my $self = shift; | |
my @instances = DBD::MySQL::Server::return_servers(); | |
return $instances[0]->{thread}; | |
} | |
sub windows_server { | |
my $self = shift; | |
my @instances = DBD::MySQL::Server::return_servers(); | |
if ($instances[0]->{os} =~ /Win/i) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
sub system_vartmpdir { | |
my $self = shift; | |
if ($^O =~ /MSWin/) { | |
return $self->system_tmpdir(); | |
} else { | |
return "/var/tmp/check_mysql_health"; | |
} | |
} | |
sub system_oldvartmpdir { | |
my $self = shift; | |
return "/tmp"; | |
} | |
sub system_tmpdir { | |
my $self = shift; | |
if ($^O =~ /MSWin/) { | |
return $ENV{TEMP} if defined $ENV{TEMP}; | |
return $ENV{TMP} if defined $ENV{TMP}; | |
return File::Spec->catfile($ENV{windir}, 'Temp') | |
if defined $ENV{windir}; | |
return 'C:\Temp'; | |
} else { | |
return "/tmp"; | |
} | |
} | |
sub decode_password { | |
my $self = shift; | |
my $password = shift; | |
if ($password && $password =~ /^rfc3986:\/\/(.*)/) { | |
$password = $1; | |
$password =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; | |
} | |
return $password; | |
} | |
package DBD::MySQL::Server::Connection; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Server); | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
mode => $params{mode}, | |
timeout => $params{timeout}, | |
access => $params{method} || "dbi", | |
hostname => $params{hostname}, | |
database => $params{database} || "information_schema", | |
port => $params{port}, | |
socket => $params{socket}, | |
username => $params{username}, | |
password => $params{password}, | |
mycnf => $params{mycnf}, | |
mycnfgroup => $params{mycnfgroup}, | |
handle => undef, | |
}; | |
bless $self, $class; | |
if ($params{method} eq "dbi") { | |
bless $self, "DBD::MySQL::Server::Connection::Dbi"; | |
} elsif ($params{method} eq "mysql") { | |
bless $self, "DBD::MySQL::Server::Connection::Mysql"; | |
} elsif ($params{method} eq "sqlrelay") { | |
bless $self, "DBD::MySQL::Server::Connection::Sqlrelay"; | |
} | |
$self->init(%params); | |
return $self; | |
} | |
package DBD::MySQL::Server::Connection::Dbi; | |
use strict; | |
use Net::Ping; | |
our @ISA = qw(DBD::MySQL::Server::Connection); | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
my $retval = undef; | |
if ($self->{mode} =~ /^server::tnsping/) { | |
if (! $self->{connect}) { | |
$self->{errstr} = "Please specify a database"; | |
} else { | |
$self->{sid} = $self->{connect}; | |
$self->{username} ||= time; # prefer an existing user | |
$self->{password} = time; | |
} | |
} else { | |
if ( | |
($self->{hostname} ne 'localhost' && (! $self->{username} || ! $self->{password})) && | |
(! $self->{mycnf}) ) { | |
$self->{errstr} = "Please specify hostname, username and password or a .cnf file"; | |
return undef; | |
} | |
$self->{dsn} = "DBI:mysql:"; | |
$self->{dsn} .= sprintf "database=%s", $self->{database}; | |
if ($self->{mycnf}) { | |
$self->{dsn} .= sprintf ";mysql_read_default_file=%s", $self->{mycnf}; | |
if ($self->{mycnfgroup}) { | |
$self->{dsn} .= sprintf ";mysql_read_default_group=%s", $self->{mycnfgroup}; | |
} | |
} else { | |
$self->{dsn} .= sprintf ";host=%s", $self->{hostname}; | |
$self->{dsn} .= sprintf ";port=%s", $self->{port} | |
unless $self->{socket} || $self->{hostname} eq 'localhost'; | |
$self->{dsn} .= sprintf ";mysql_socket=%s", $self->{socket} | |
if $self->{socket}; | |
} | |
} | |
if (! exists $self->{errstr}) { | |
eval { | |
require DBI; | |
use POSIX ':signal_h'; | |
if ($^O =~ /MSWin/) { | |
local $SIG{'ALRM'} = sub { | |
die "alarm\n"; | |
}; | |
} else { | |
my $mask = POSIX::SigSet->new( SIGALRM ); | |
my $action = POSIX::SigAction->new( | |
sub { die "alarm\n" ; }, $mask); | |
my $oldaction = POSIX::SigAction->new(); | |
sigaction(SIGALRM ,$action ,$oldaction ); | |
} | |
alarm($self->{timeout} - 1); # 1 second before the global unknown timeout | |
if ($self->{handle} = DBI->connect( | |
$self->{dsn}, | |
$self->{username}, | |
$self->decode_password($self->{password}), | |
{ RaiseError => 0, AutoCommit => 0, PrintError => 1 })) { | |
# $self->{handle}->do(q{ | |
# ALTER SESSION SET NLS_NUMERIC_CHARACTERS=".," }); | |
$retval = $self; | |
} else { | |
$self->{errstr} = DBI::errstr(); | |
} | |
}; | |
if ($@) { | |
$self->{errstr} = $@; | |
$retval = undef; | |
} | |
} | |
$self->{tac} = Time::HiRes::time(); | |
return $retval; | |
} | |
sub selectrow_hashref { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my $hashref = undef; | |
eval { | |
$self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", | |
$sql, Data::Dumper::Dumper(\@arguments)); | |
# helm auf! jetzt wirds dreckig. | |
if ($sql =~ /^\s*SHOW/) { | |
$hashref = $self->{handle}->selectrow_hashref($sql); | |
} else { | |
$sth = $self->{handle}->prepare($sql); | |
if (scalar(@arguments)) { | |
$sth->execute(@arguments); | |
} else { | |
$sth->execute(); | |
} | |
$hashref = $sth->selectrow_hashref(); | |
} | |
$self->trace(sprintf "RESULT:\n%s\n", | |
Data::Dumper::Dumper($hashref)); | |
}; | |
if ($@) { | |
$self->debug(sprintf "bumm %s", $@); | |
} | |
if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { | |
my $simulation = do { local (@ARGV, $/) = | |
"/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; | |
# keine lust auf den scheiss | |
} | |
return $hashref; | |
} | |
sub fetchrow_array { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my @row = (); | |
my $stderrvar; | |
*SAVEERR = *STDERR; | |
open ERR ,'>',\$stderrvar; | |
*STDERR = *ERR; | |
eval { | |
$self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", | |
$sql, Data::Dumper::Dumper(\@arguments)); | |
$sth = $self->{handle}->prepare($sql); | |
if (scalar(@arguments)) { | |
$sth->execute(@arguments); | |
} else { | |
$sth->execute(); | |
} | |
@row = $sth->fetchrow_array(); | |
$self->trace(sprintf "RESULT:\n%s\n", | |
Data::Dumper::Dumper(\@row)); | |
}; | |
*STDERR = *SAVEERR; | |
if ($@) { | |
$self->debug(sprintf "bumm %s", $@); | |
$self->{errstr} = $@; | |
return (undef); | |
} elsif ($stderrvar) { | |
$self->{errstr} = $stderrvar; | |
return (undef); | |
} elsif ($sth->errstr()) { | |
$self->{errstr} = $sth->errstr(); | |
return (undef); | |
} | |
if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { | |
my $simulation = do { local (@ARGV, $/) = | |
"/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; | |
@row = split(/\s+/, (split(/\n/, $simulation))[0]); | |
} | |
return $row[0] unless wantarray; | |
return @row; | |
} | |
sub fetchall_array { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my $rows = undef; | |
eval { | |
$self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n", | |
$sql, Data::Dumper::Dumper(\@arguments)); | |
$sth = $self->{handle}->prepare($sql); | |
if (scalar(@arguments)) { | |
$sth->execute(@arguments); | |
} else { | |
$sth->execute(); | |
} | |
$rows = $sth->fetchall_arrayref(); | |
$self->trace(sprintf "RESULT:\n%s\n", | |
Data::Dumper::Dumper($rows)); | |
}; | |
if ($@) { | |
printf STDERR "bumm %s\n", $@; | |
} | |
if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { | |
my $simulation = do { local (@ARGV, $/) = | |
"/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; | |
@{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); | |
} | |
return @{$rows}; | |
} | |
sub func { | |
my $self = shift; | |
$self->{handle}->func(@_); | |
} | |
sub execute { | |
my $self = shift; | |
my $sql = shift; | |
eval { | |
my $sth = $self->{handle}->prepare($sql); | |
$sth->execute(); | |
}; | |
if ($@) { | |
printf STDERR "bumm %s\n", $@; | |
} | |
} | |
sub errstr { | |
my $self = shift; | |
return $self->{errstr}; | |
} | |
sub DESTROY { | |
my $self = shift; | |
$self->trace(sprintf "disconnecting DBD %s", | |
$self->{handle} ? "with handle" : "without handle"); | |
$self->{handle}->disconnect() if $self->{handle}; | |
} | |
package DBD::MySQL::Server::Connection::Mysql; | |
use strict; | |
use File::Temp qw/tempfile/; | |
our @ISA = qw(DBD::MySQL::Server::Connection); | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
my $retval = undef; | |
$self->{loginstring} = "traditional"; | |
($self->{sql_commandfile_handle}, $self->{sql_commandfile}) = | |
tempfile($self->{mode}."XXXXX", SUFFIX => ".sql", | |
DIR => $self->system_tmpdir() ); | |
close $self->{sql_commandfile_handle}; | |
($self->{sql_resultfile_handle}, $self->{sql_resultfile}) = | |
tempfile($self->{mode}."XXXXX", SUFFIX => ".out", | |
DIR => $self->system_tmpdir() ); | |
close $self->{sql_resultfile_handle}; | |
if ($self->{mode} =~ /^server::tnsping/) { | |
if (! $self->{connect}) { | |
$self->{errstr} = "Please specify a database"; | |
} else { | |
$self->{sid} = $self->{connect}; | |
$self->{username} ||= time; # prefer an existing user | |
$self->{password} = time; | |
} | |
} else { | |
if (! $self->{username} || ! $self->{password}) { | |
$self->{errstr} = "Please specify database, username and password"; | |
return undef; | |
} elsif (! (($self->{hostname} && $self->{port}) || $self->{socket})) { | |
$self->{errstr} = "Please specify hostname and port or socket"; | |
return undef; | |
} | |
} | |
if (! exists $self->{errstr}) { | |
$self->{password} = $self->decode_password($self->{password}); | |
eval { | |
my $mysql = '/'.'usr'.'/'.'bin'.'/'.'mysql'; | |
if (! -x $mysql) { | |
die "nomysql\n"; | |
} | |
if ($self->{loginstring} eq "traditional") { | |
$self->{sqlplus} = sprintf "%s ", $mysql; | |
$self->{sqlplus} .= sprintf "--batch --raw --skip-column-names "; | |
$self->{sqlplus} .= sprintf "--database=%s ", $self->{database}; | |
$self->{sqlplus} .= sprintf "--host=%s ", $self->{hostname}; | |
$self->{sqlplus} .= sprintf "--port=%s ", $self->{port} | |
unless $self->{socket} || $self->{hostname} eq "localhost"; | |
$self->{sqlplus} .= sprintf "--socket=%s ", $self->{socket} | |
if $self->{socket}; | |
$self->{sqlplus} .= sprintf "--user=%s --password='%s' < %s > %s", | |
$self->{username}, $self->{password}, | |
$self->{sql_commandfile}, $self->{sql_resultfile}; | |
} | |
use POSIX ':signal_h'; | |
if ($^O =~ /MSWin/) { | |
local $SIG{'ALRM'} = sub { | |
die "alarm\n"; | |
}; | |
} else { | |
my $mask = POSIX::SigSet->new( SIGALRM ); | |
my $action = POSIX::SigAction->new( | |
sub { die "alarm\n" ; }, $mask); | |
my $oldaction = POSIX::SigAction->new(); | |
sigaction(SIGALRM ,$action ,$oldaction ); | |
} | |
alarm($self->{timeout} - 1); # 1 second before the global unknown timeout | |
my $answer = $self->fetchrow_array( | |
q{ SELECT 42 FROM dual}); | |
die unless defined $answer and $answer == 42; | |
$retval = $self; | |
}; | |
if ($@) { | |
$self->{errstr} = $@; | |
$self->{errstr} =~ s/at $0 .*//g; | |
chomp $self->{errstr}; | |
$retval = undef; | |
} | |
} | |
$self->{tac} = Time::HiRes::time(); | |
return $retval; | |
} | |
sub selectrow_hashref { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my $hashref = undef; | |
foreach (@arguments) { | |
# replace the ? by the parameters | |
if (/^\d+$/) { | |
$sql =~ s/\?/$_/; | |
} else { | |
$sql =~ s/\?/'$_'/; | |
} | |
} | |
if ($sql =~ /^\s*SHOW/) { | |
$sql .= '\G'; # http://dev.mysql.com/doc/refman/5.1/de/show-slave-status.html | |
} | |
$self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", | |
$sql, Data::Dumper::Dumper(\@arguments)); | |
$self->create_commandfile($sql); | |
my $exit_output = `$self->{sqlplus}`; | |
if ($?) { | |
printf STDERR "fetchrow_array exit bumm \n"; | |
my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; | |
my @oerrs = map { | |
/((ERROR \d+).*)/ ? $1 : (); | |
} split(/\n/, $output); | |
$self->{errstr} = join(" ", @oerrs); | |
} else { | |
my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; | |
if ($sql =~ /^\s*SHOW/) { | |
map { | |
if (/^\s*([\w_]+):\s*(.*)/) { | |
$hashref->{$1} = $2; | |
} | |
} split(/\n/, $output); | |
} else { | |
# i dont mess around here and you shouldn't either | |
} | |
$self->trace(sprintf "RESULT:\n%s\n", | |
Data::Dumper::Dumper($hashref)); | |
} | |
unlink $self->{sql_commandfile}; | |
unlink $self->{sql_resultfile}; | |
return $hashref; | |
} | |
sub fetchrow_array { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my @row = (); | |
foreach (@arguments) { | |
# replace the ? by the parameters | |
if (/^\d+$/) { | |
$sql =~ s/\?/$_/; | |
} else { | |
$sql =~ s/\?/'$_'/; | |
} | |
} | |
$self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", | |
$sql, Data::Dumper::Dumper(\@arguments)); | |
$self->create_commandfile($sql); | |
my $exit_output = `$self->{sqlplus}`; | |
if ($?) { | |
printf STDERR "fetchrow_array exit bumm \n"; | |
my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; | |
my @oerrs = map { | |
/((ERROR \d+).*)/ ? $1 : (); | |
} split(/\n/, $output); | |
$self->{errstr} = join(" ", @oerrs); | |
} else { | |
my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; | |
@row = map { convert($_) } | |
map { s/^\s+([\.\d]+)$/$1/g; $_ } # strip leading space from numbers | |
map { s/\s+$//g; $_ } # strip trailing space | |
split(/\t/, (split(/\n/, $output))[0]); | |
$self->trace(sprintf "RESULT:\n%s\n", | |
Data::Dumper::Dumper(\@row)); | |
} | |
if ($@) { | |
$self->debug(sprintf "bumm %s", $@); | |
} | |
unlink $self->{sql_commandfile}; | |
unlink $self->{sql_resultfile}; | |
return $row[0] unless wantarray; | |
return @row; | |
} | |
sub fetchall_array { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my $rows = undef; | |
foreach (@arguments) { | |
# replace the ? by the parameters | |
if (/^\d+$/) { | |
$sql =~ s/\?/$_/; | |
} else { | |
$sql =~ s/\?/'$_'/; | |
} | |
} | |
$self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n", | |
$sql, Data::Dumper::Dumper(\@arguments)); | |
$self->create_commandfile($sql); | |
my $exit_output = `$self->{sqlplus}`; | |
if ($?) { | |
printf STDERR "fetchrow_array exit bumm %s\n", $exit_output; | |
my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; | |
my @oerrs = map { | |
/((ERROR \d+).*)/ ? $1 : (); | |
} split(/\n/, $output); | |
$self->{errstr} = join(" ", @oerrs); | |
} else { | |
my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> }; | |
my @rows = map { [ | |
map { convert($_) } | |
map { s/^\s+([\.\d]+)$/$1/g; $_ } | |
map { s/\s+$//g; $_ } | |
split /\t/ | |
] } grep { ! /^\d+ rows selected/ } | |
grep { ! /^Elapsed: / } | |
grep { ! /^\s*$/ } split(/\n/, $output); | |
$rows = \@rows; | |
$self->trace(sprintf "RESULT:\n%s\n", | |
Data::Dumper::Dumper($rows)); | |
} | |
if ($@) { | |
$self->debug(sprintf "bumm %s", $@); | |
} | |
unlink $self->{sql_commandfile}; | |
unlink $self->{sql_resultfile}; | |
return @{$rows}; | |
} | |
sub func { | |
my $self = shift; | |
my $function = shift; | |
$self->{handle}->func(@_); | |
} | |
sub convert { | |
my $n = shift; | |
# mostly used to convert numbers in scientific notation | |
if ($n =~ /^\s*\d+\s*$/) { | |
return $n; | |
} elsif ($n =~ /^\s*([-+]?)(\d*[\.,]*\d*)[eE]{1}([-+]?)(\d+)\s*$/) { | |
my ($vor, $num, $sign, $exp) = ($1, $2, $3, $4); | |
$n =~ s/E/e/g; | |
$n =~ s/,/\./g; | |
$num =~ s/,/\./g; | |
my $sig = $sign eq '-' ? "." . ($exp - 1 + length $num) : ''; | |
my $dec = sprintf "%${sig}f", $n; | |
$dec =~ s/\.[0]+$//g; | |
return $dec; | |
} elsif ($n =~ /^\s*([-+]?)(\d+)[\.,]*(\d*)\s*$/) { | |
return $1.$2.".".$3; | |
} elsif ($n =~ /^\s*(.*?)\s*$/) { | |
return $1; | |
} else { | |
return $n; | |
} | |
} | |
sub execute { | |
my $self = shift; | |
my $sql = shift; | |
eval { | |
my $sth = $self->{handle}->prepare($sql); | |
$sth->execute(); | |
}; | |
if ($@) { | |
printf STDERR "bumm %s\n", $@; | |
} | |
} | |
sub errstr { | |
my $self = shift; | |
return $self->{errstr}; | |
} | |
sub DESTROY { | |
my $self = shift; | |
$self->trace("try to clean up command and result files"); | |
unlink $self->{sql_commandfile} if -f $self->{sql_commandfile}; | |
unlink $self->{sql_resultfile} if -f $self->{sql_resultfile}; | |
} | |
sub create_commandfile { | |
my $self = shift; | |
my $sql = shift; | |
open CMDCMD, "> $self->{sql_commandfile}"; | |
printf CMDCMD "%s\n", $sql; | |
close CMDCMD; | |
} | |
sub decode_password { | |
my $self = shift; | |
my $password = shift; | |
$password = $self->SUPER::decode_password($password); | |
# we call '...%s/%s@...' inside backticks where the second %s is the password | |
# abc'xcv -> ''abc'\''xcv'' | |
# abc'`xcv -> ''abc'\''\`xcv'' | |
if ($password && $password =~ /'/) { | |
$password = "'".join("\\'", map { "'".$_."'"; } split("'", $password))."'"; | |
} | |
return $password; | |
} | |
package DBD::MySQL::Server::Connection::Sqlrelay; | |
use strict; | |
use Net::Ping; | |
our @ISA = qw(DBD::MySQL::Server::Connection); | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
my $retval = undef; | |
if ($self->{mode} =~ /^server::tnsping/) { | |
if (! $self->{connect}) { | |
$self->{errstr} = "Please specify a database"; | |
} else { | |
if ($self->{connect} =~ /([\.\w]+):(\d+)/) { | |
$self->{host} = $1; | |
$self->{port} = $2; | |
$self->{socket} = ""; | |
} elsif ($self->{connect} =~ /([\.\w]+):([\w\/]+)/) { | |
$self->{host} = $1; | |
$self->{socket} = $2; | |
$self->{port} = ""; | |
} | |
} | |
} else { | |
if (! $self->{hostname} || ! $self->{username} || ! $self->{password}) { | |
if ($self->{hostname} && $self->{hostname} =~ /(\w+?)\/(.+)@([\.\w]+):(\d+)/) { | |
$self->{username} = $1; | |
$self->{password} = $2; | |
$self->{hostname} = $3; | |
$self->{port} = $4; | |
$self->{socket} = ""; | |
} elsif ($self->{hostname} && $self->{hostname} =~ /(\w+?)\/(.+)@([\.\w]+):([\w\/]+)/) { | |
$self->{username} = $1; | |
$self->{password} = $2; | |
$self->{hostname} = $3; | |
$self->{socket} = $4; | |
$self->{port} = ""; | |
} else { | |
$self->{errstr} = "Please specify database, username and password"; | |
return undef; | |
} | |
} else { | |
if ($self->{hostname} =~ /([\.\w]+):(\d+)/) { | |
$self->{hostname} = $1; | |
$self->{port} = $2; | |
$self->{socket} = ""; | |
} elsif ($self->{hostname} =~ /([\.\w]+):([\w\/]+)/) { | |
$self->{hostname} = $1; | |
$self->{socket} = $2; | |
$self->{port} = ""; | |
} else { | |
$self->{errstr} = "Please specify hostname, username, password and port/socket"; | |
return undef; | |
} | |
} | |
} | |
if (! exists $self->{errstr}) { | |
eval { | |
require DBI; | |
use POSIX ':signal_h'; | |
if ($^O =~ /MSWin/) { | |
local $SIG{'ALRM'} = sub { | |
die "alarm\n"; | |
}; | |
} else { | |
my $mask = POSIX::SigSet->new( SIGALRM ); | |
my $action = POSIX::SigAction->new( | |
sub { die "alarm\n" ; }, $mask); | |
my $oldaction = POSIX::SigAction->new(); | |
sigaction(SIGALRM ,$action ,$oldaction ); | |
} | |
alarm($self->{timeout} - 1); # 1 second before the global unknown timeout | |
if ($self->{handle} = DBI->connect( | |
sprintf("DBI:SQLRelay:host=%s;port=%d;socket=%s", | |
$self->{hostname}, $self->{port}, $self->{socket}), | |
$self->{username}, | |
$self->decode_password($self->{password}), | |
{ RaiseError => 1, AutoCommit => 0, PrintError => 1 })) { | |
$retval = $self; | |
if ($self->{mode} =~ /^server::tnsping/ && $self->{handle}->ping()) { | |
# database connected. fake a "unknown user" | |
$self->{errstr} = "ORA-01017"; | |
} | |
} else { | |
$self->{errstr} = DBI::errstr(); | |
} | |
}; | |
if ($@) { | |
$self->{errstr} = $@; | |
$self->{errstr} =~ s/at [\w\/\.]+ line \d+.*//g; | |
$retval = undef; | |
} | |
} | |
$self->{tac} = Time::HiRes::time(); | |
return $retval; | |
} | |
sub fetchrow_array { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my @row = (); | |
$self->trace(sprintf "fetchrow_array: %s", $sql); | |
eval { | |
$sth = $self->{handle}->prepare($sql); | |
if (scalar(@arguments)) { | |
$sth->execute(@arguments); | |
} else { | |
$sth->execute(); | |
} | |
@row = $sth->fetchrow_array(); | |
}; | |
if ($@) { | |
$self->debug(sprintf "bumm %s", $@); | |
} | |
if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { | |
my $simulation = do { local (@ARGV, $/) = | |
"/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; | |
@row = split(/\s+/, (split(/\n/, $simulation))[0]); | |
} | |
return $row[0] unless wantarray; | |
return @row; | |
} | |
sub fetchall_array { | |
my $self = shift; | |
my $sql = shift; | |
my @arguments = @_; | |
my $sth = undef; | |
my $rows = undef; | |
$self->trace(sprintf "fetchall_array: %s", $sql); | |
eval { | |
$sth = $self->{handle}->prepare($sql); | |
if (scalar(@arguments)) { | |
$sth->execute(@arguments); | |
} else { | |
$sth->execute(); | |
} | |
$rows = $sth->fetchall_arrayref(); | |
}; | |
if ($@) { | |
printf STDERR "bumm %s\n", $@; | |
} | |
if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) { | |
my $simulation = do { local (@ARGV, $/) = | |
"/tmp/check_mysql_health_simulation/".$self->{mode}; <> }; | |
@{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); | |
} | |
return @{$rows}; | |
} | |
sub func { | |
my $self = shift; | |
$self->{handle}->func(@_); | |
} | |
sub execute { | |
my $self = shift; | |
my $sql = shift; | |
eval { | |
my $sth = $self->{handle}->prepare($sql); | |
$sth->execute(); | |
}; | |
if ($@) { | |
printf STDERR "bumm %s\n", $@; | |
} | |
} | |
sub DESTROY { | |
my $self = shift; | |
#$self->trace(sprintf "disconnecting DBD %s", | |
# $self->{handle} ? "with handle" : "without handle"); | |
#$self->{handle}->disconnect() if $self->{handle}; | |
} | |
package DBD::MySQL::Cluster; | |
use strict; | |
use Time::HiRes; | |
use IO::File; | |
use Data::Dumper; | |
{ | |
our $verbose = 0; | |
our $scream = 0; # scream if something is not implemented | |
our $access = "dbi"; # how do we access the database. | |
our $my_modules_dyn_dir = ""; # where we look for self-written extensions | |
my @clusters = (); | |
my $initerrors = undef; | |
sub add_cluster { | |
push(@clusters, shift); | |
} | |
sub return_clusters { | |
return @clusters; | |
} | |
sub return_first_cluster() { | |
return $clusters[0]; | |
} | |
} | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
hostname => $params{hostname}, | |
port => $params{port}, | |
username => $params{username}, | |
password => $params{password}, | |
timeout => $params{timeout}, | |
warningrange => $params{warningrange}, | |
criticalrange => $params{criticalrange}, | |
version => 'unknown', | |
nodes => [], | |
ndbd_nodes => 0, | |
ndb_mgmd_nodes => 0, | |
mysqld_nodes => 0, | |
}; | |
bless $self, $class; | |
$self->init_nagios(); | |
if ($self->connect(%params)) { | |
DBD::MySQL::Cluster::add_cluster($self); | |
$self->init(%params); | |
} | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
if ($self->{show}) { | |
my $type = undef; | |
foreach (split /\n/, $self->{show}) { | |
if (/\[(\w+)\((\w+)\)\]\s+(\d+) node/) { | |
$type = uc $2; | |
} elsif (/id=(\d+)(.*)/) { | |
push(@{$self->{nodes}}, DBD::MySQL::Cluster::Node->new( | |
type => $type, | |
id => $1, | |
status => $2, | |
)); | |
} | |
} | |
} else { | |
} | |
if ($params{mode} =~ /^cluster::ndbdrunning/) { | |
foreach my $node (@{$self->{nodes}}) { | |
$node->{type} eq "NDB" && $node->{status} eq "running" && $self->{ndbd_nodes}++; | |
$node->{type} eq "MGM" && $node->{status} eq "running" && $self->{ndb_mgmd_nodes}++; | |
$node->{type} eq "API" && $node->{status} eq "running" && $self->{mysqld_nodes}++; | |
} | |
} else { | |
printf "broken mode %s\n", $params{mode}; | |
} | |
} | |
sub dump { | |
my $self = shift; | |
my $message = shift || ""; | |
printf "%s %s\n", $message, Data::Dumper::Dumper($self); | |
} | |
sub nagios { | |
my $self = shift; | |
my %params = @_; | |
my $dead_ndb = 0; | |
my $dead_api = 0; | |
if (! $self->{nagios_level}) { | |
if ($params{mode} =~ /^cluster::ndbdrunning/) { | |
foreach my $node (grep { $_->{type} eq "NDB"} @{$self->{nodes}}) { | |
next if $params{selectname} && $params{selectname} ne $_->{id}; | |
if (! $node->{connected}) { | |
$self->add_nagios_critical( | |
sprintf "ndb node %d is not connected", $node->{id}); | |
$dead_ndb++; | |
} | |
} | |
foreach my $node (grep { $_->{type} eq "API"} @{$self->{nodes}}) { | |
next if $params{selectname} && $params{selectname} ne $_->{id}; | |
if (! $node->{connected}) { | |
$self->add_nagios_critical( | |
sprintf "api node %d is not connected", $node->{id}); | |
$dead_api++; | |
} | |
} | |
if (! $dead_ndb) { | |
$self->add_nagios_ok("all ndb nodes are connected"); | |
} | |
if (! $dead_api) { | |
$self->add_nagios_ok("all api nodes are connected"); | |
} | |
} | |
} | |
$self->add_perfdata(sprintf "ndbd_nodes=%d ndb_mgmd_nodes=%d mysqld_nodes=%d", | |
$self->{ndbd_nodes}, $self->{ndb_mgmd_nodes}, $self->{mysqld_nodes}); | |
} | |
sub init_nagios { | |
my $self = shift; | |
no strict 'refs'; | |
if (! ref($self)) { | |
my $nagiosvar = $self."::nagios"; | |
my $nagioslevelvar = $self."::nagios_level"; | |
$$nagiosvar = { | |
messages => { | |
0 => [], | |
1 => [], | |
2 => [], | |
3 => [], | |
}, | |
perfdata => [], | |
}; | |
$$nagioslevelvar = $ERRORS{OK}, | |
} else { | |
$self->{nagios} = { | |
messages => { | |
0 => [], | |
1 => [], | |
2 => [], | |
3 => [], | |
}, | |
perfdata => [], | |
}; | |
$self->{nagios_level} = $ERRORS{OK}, | |
} | |
} | |
sub check_thresholds { | |
my $self = shift; | |
my $value = shift; | |
my $defaultwarningrange = shift; | |
my $defaultcriticalrange = shift; | |
my $level = $ERRORS{OK}; | |
$self->{warningrange} = $self->{warningrange} ? | |
$self->{warningrange} : $defaultwarningrange; | |
$self->{criticalrange} = $self->{criticalrange} ? | |
$self->{criticalrange} : $defaultcriticalrange; | |
if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) { | |
# warning = 10, critical = 20, warn if > 10, crit if > 20 | |
$level = $ERRORS{WARNING} if $value > $self->{warningrange}; | |
$level = $ERRORS{CRITICAL} if $value > $self->{criticalrange}; | |
} elsif ($self->{warningrange} =~ /([\d\.]+):/ && | |
$self->{criticalrange} =~ /([\d\.]+):/) { | |
# warning = 98:, critical = 95:, warn if < 98, crit if < 95 | |
$self->{warningrange} =~ /([\d\.]+):/; | |
$level = $ERRORS{WARNING} if $value < $1; | |
$self->{criticalrange} =~ /([\d\.]+):/; | |
$level = $ERRORS{CRITICAL} if $value < $1; | |
} | |
return $level; | |
# | |
# syntax error must be reported with returncode -1 | |
# | |
} | |
sub add_nagios { | |
my $self = shift; | |
my $level = shift; | |
my $message = shift; | |
push(@{$self->{nagios}->{messages}->{$level}}, $message); | |
# recalc current level | |
foreach my $llevel (qw(CRITICAL WARNING UNKNOWN OK)) { | |
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) { | |
$self->{nagios_level} = $ERRORS{$llevel}; | |
} | |
} | |
} | |
sub add_nagios_ok { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{OK}, $message); | |
} | |
sub add_nagios_warning { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{WARNING}, $message); | |
} | |
sub add_nagios_critical { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{CRITICAL}, $message); | |
} | |
sub add_nagios_unknown { | |
my $self = shift; | |
my $message = shift; | |
$self->add_nagios($ERRORS{UNKNOWN}, $message); | |
} | |
sub add_perfdata { | |
my $self = shift; | |
my $data = shift; | |
push(@{$self->{nagios}->{perfdata}}, $data); | |
} | |
sub merge_nagios { | |
my $self = shift; | |
my $child = shift; | |
foreach my $level (0..3) { | |
foreach (@{$child->{nagios}->{messages}->{$level}}) { | |
$self->add_nagios($level, $_); | |
} | |
#push(@{$self->{nagios}->{messages}->{$level}}, | |
# @{$child->{nagios}->{messages}->{$level}}); | |
} | |
push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}}); | |
} | |
sub calculate_result { | |
my $self = shift; | |
if ($ENV{NRPE_MULTILINESUPPORT} && | |
length join(" ", @{$self->{nagios}->{perfdata}}) > 200) { | |
foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { | |
# first the bad news | |
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { | |
$self->{nagios_message} .= | |
"\n".join("\n", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}); | |
} | |
} | |
$self->{nagios_message} =~ s/^\n//g; | |
$self->{perfdata} = join("\n", @{$self->{nagios}->{perfdata}}); | |
} else { | |
foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") { | |
# first the bad news | |
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { | |
$self->{nagios_message} .= | |
join(", ", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}).", "; | |
} | |
} | |
$self->{nagios_message} =~ s/, $//g; | |
$self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}}); | |
} | |
foreach my $level ("OK", "UNKNOWN", "WARNING", "CRITICAL") { | |
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) { | |
$self->{nagios_level} = $ERRORS{$level}; | |
} | |
} | |
} | |
sub debug { | |
my $self = shift; | |
my $msg = shift; | |
if ($DBD::MySQL::Cluster::verbose) { | |
printf "%s %s\n", $msg, ref($self); | |
} | |
} | |
sub connect { | |
my $self = shift; | |
my %params = @_; | |
my $retval = undef; | |
$self->{tic} = Time::HiRes::time(); | |
eval { | |
use POSIX ':signal_h'; | |
local $SIG{'ALRM'} = sub { | |
die "alarm\n"; | |
}; | |
my $mask = POSIX::SigSet->new( SIGALRM ); | |
my $action = POSIX::SigAction->new( | |
sub { die "connection timeout\n" ; }, $mask); | |
my $oldaction = POSIX::SigAction->new(); | |
sigaction(SIGALRM ,$action ,$oldaction ); | |
alarm($self->{timeout} - 1); # 1 second before the global unknown timeout | |
my $ndb_mgm = "ndb_mgm"; | |
$params{hostname} = "127.0.0.1" if ! $params{hostname}; | |
$ndb_mgm .= sprintf " --ndb-connectstring=%s", $params{hostname} | |
if $params{hostname}; | |
$ndb_mgm .= sprintf ":%d", $params{port} | |
if $params{port}; | |
$self->{show} = `$ndb_mgm -e show 2>&1`; | |
if ($? == -1) { | |
$self->add_nagios_critical("ndb_mgm failed to execute $!"); | |
} elsif ($? & 127) { | |
$self->add_nagios_critical("ndb_mgm failed to execute $!"); | |
} elsif ($? >> 8 != 0) { | |
$self->add_nagios_critical("ndb_mgm unable to connect"); | |
} else { | |
if ($self->{show} !~ /Cluster Configuration/) { | |
$self->add_nagios_critical("got no cluster configuration"); | |
} else { | |
$retval = 1; | |
} | |
} | |
}; | |
if ($@) { | |
$self->{errstr} = $@; | |
$self->{errstr} =~ s/at $0 .*//g; | |
chomp $self->{errstr}; | |
$self->add_nagios_critical($self->{errstr}); | |
$retval = undef; | |
} | |
$self->{tac} = Time::HiRes::time(); | |
return $retval; | |
} | |
sub trace { | |
my $self = shift; | |
my $format = shift; | |
$self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0; | |
if ($self->{verbose}) { | |
printf("%s: ", scalar localtime); | |
printf($format, @_); | |
} | |
if ($self->{trace}) { | |
my $logfh = new IO::File; | |
$logfh->autoflush(1); | |
if ($logfh->open("/tmp/check_mysql_health.trace", "a")) { | |
$logfh->printf("%s: ", scalar localtime); | |
$logfh->printf($format, @_); | |
$logfh->printf("\n"); | |
$logfh->close(); | |
} | |
} | |
} | |
sub DESTROY { | |
my $self = shift; | |
my $handle1 = "null"; | |
my $handle2 = "null"; | |
if (defined $self->{handle}) { | |
$handle1 = ref($self->{handle}); | |
if (defined $self->{handle}->{handle}) { | |
$handle2 = ref($self->{handle}->{handle}); | |
} | |
} | |
$self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2); | |
if (ref($self) eq "DBD::MySQL::Cluster") { | |
} | |
$self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); | |
if (ref($self) eq "DBD::MySQL::Cluster") { | |
#printf "humpftata\n"; | |
} | |
} | |
sub save_state { | |
my $self = shift; | |
my %params = @_; | |
my $extension = ""; | |
mkdir $params{statefilesdir} unless -d $params{statefilesdir}; | |
my $statefile = sprintf "%s/%s_%s", | |
$params{statefilesdir}, $params{hostname}, $params{mode}; | |
$extension .= $params{differenciator} ? "_".$params{differenciator} : ""; | |
$extension .= $params{socket} ? "_".$params{socket} : ""; | |
$extension .= $params{port} ? "_".$params{port} : ""; | |
$extension .= $params{database} ? "_".$params{database} : ""; | |
$extension .= $params{tablespace} ? "_".$params{tablespace} : ""; | |
$extension .= $params{datafile} ? "_".$params{datafile} : ""; | |
$extension .= $params{name} ? "_".$params{name} : ""; | |
$extension =~ s/\//_/g; | |
$extension =~ s/\(/_/g; | |
$extension =~ s/\)/_/g; | |
$extension =~ s/\*/_/g; | |
$extension =~ s/\s/_/g; | |
$statefile .= $extension; | |
$statefile = lc $statefile; | |
open(STATE, ">$statefile"); | |
if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) { | |
$params{save}->{localtime} = scalar localtime $params{save}->{timestamp}; | |
} | |
printf STATE Data::Dumper::Dumper($params{save}); | |
close STATE; | |
$self->debug(sprintf "saved %s to %s", | |
Data::Dumper::Dumper($params{save}), $statefile); | |
} | |
sub load_state { | |
my $self = shift; | |
my %params = @_; | |
my $extension = ""; | |
my $statefile = sprintf "%s/%s_%s", | |
$params{statefilesdir}, $params{hostname}, $params{mode}; | |
$extension .= $params{differenciator} ? "_".$params{differenciator} : ""; | |
$extension .= $params{socket} ? "_".$params{socket} : ""; | |
$extension .= $params{port} ? "_".$params{port} : ""; | |
$extension .= $params{database} ? "_".$params{database} : ""; | |
$extension .= $params{tablespace} ? "_".$params{tablespace} : ""; | |
$extension .= $params{datafile} ? "_".$params{datafile} : ""; | |
$extension .= $params{name} ? "_".$params{name} : ""; | |
$extension =~ s/\//_/g; | |
$extension =~ s/\(/_/g; | |
$extension =~ s/\)/_/g; | |
$extension =~ s/\*/_/g; | |
$extension =~ s/\s/_/g; | |
$statefile .= $extension; | |
$statefile = lc $statefile; | |
if ( -f $statefile) { | |
our $VAR1; | |
eval { | |
require $statefile; | |
}; | |
if($@) { | |
printf "rumms\n"; | |
} | |
$self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); | |
return $VAR1; | |
} else { | |
return undef; | |
} | |
} | |
sub valdiff { | |
my $self = shift; | |
my $pparams = shift; | |
my %params = %{$pparams}; | |
my @keys = @_; | |
my $last_values = $self->load_state(%params) || eval { | |
my $empty_events = {}; | |
foreach (@keys) { | |
$empty_events->{$_} = 0; | |
} | |
$empty_events->{timestamp} = 0; | |
$empty_events; | |
}; | |
foreach (@keys) { | |
$self->{'delta_'.$_} = $self->{$_} - $last_values->{$_}; | |
$self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_}); | |
} | |
$self->{'delta_timestamp'} = time - $last_values->{timestamp}; | |
$params{save} = eval { | |
my $empty_events = {}; | |
foreach (@keys) { | |
$empty_events->{$_} = $self->{$_}; | |
} | |
$empty_events->{timestamp} = time; | |
$empty_events; | |
}; | |
$self->save_state(%params); | |
} | |
sub requires_version { | |
my $self = shift; | |
my $version = shift; | |
my @instances = DBD::MySQL::Cluster::return_clusters(); | |
my $instversion = $instances[0]->{version}; | |
if (! $self->version_is_minimum($version)) { | |
$self->add_nagios($ERRORS{UNKNOWN}, | |
sprintf "not implemented/possible for MySQL release %s", $instversion); | |
} | |
} | |
sub version_is_minimum { | |
# the current version is newer or equal | |
my $self = shift; | |
my $version = shift; | |
my $newer = 1; | |
my @instances = DBD::MySQL::Cluster::return_clusters(); | |
my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version); | |
my @v2 = split(/\./, $instances[0]->{version}); | |
if (scalar(@v1) > scalar(@v2)) { | |
push(@v2, (0) x (scalar(@v1) - scalar(@v2))); | |
} elsif (scalar(@v2) > scalar(@v1)) { | |
push(@v1, (0) x (scalar(@v2) - scalar(@v1))); | |
} | |
foreach my $pos (0..$#v1) { | |
if ($v2[$pos] > $v1[$pos]) { | |
$newer = 1; | |
last; | |
} elsif ($v2[$pos] < $v1[$pos]) { | |
$newer = 0; | |
last; | |
} | |
} | |
#printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1); | |
return $newer; | |
} | |
sub instance_rac { | |
my $self = shift; | |
my @instances = DBD::MySQL::Cluster::return_clusters(); | |
return (lc $instances[0]->{parallel} eq "yes") ? 1 : 0; | |
} | |
sub instance_thread { | |
my $self = shift; | |
my @instances = DBD::MySQL::Cluster::return_clusters(); | |
return $instances[0]->{thread}; | |
} | |
sub windows_cluster { | |
my $self = shift; | |
my @instances = DBD::MySQL::Cluster::return_clusters(); | |
if ($instances[0]->{os} =~ /Win/i) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
sub system_vartmpdir { | |
my $self = shift; | |
if ($^O =~ /MSWin/) { | |
return $self->system_tmpdir(); | |
} else { | |
return "/var/tmp/check_mysql_health"; | |
} | |
} | |
sub system_oldvartmpdir { | |
my $self = shift; | |
return "/tmp"; | |
} | |
sub system_tmpdir { | |
my $self = shift; | |
if ($^O =~ /MSWin/) { | |
return $ENV{TEMP} if defined $ENV{TEMP}; | |
return $ENV{TMP} if defined $ENV{TMP}; | |
return File::Spec->catfile($ENV{windir}, 'Temp') | |
if defined $ENV{windir}; | |
return 'C:\Temp'; | |
} else { | |
return "/tmp"; | |
} | |
} | |
package DBD::MySQL::Cluster::Node; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Cluster); | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
mode => $params{mode}, | |
timeout => $params{timeout}, | |
type => $params{type}, | |
id => $params{id}, | |
status => $params{status}, | |
}; | |
bless $self, $class; | |
$self->init(%params); | |
if ($params{type} eq "NDB") { | |
bless $self, "DBD::MySQL::Cluster::Node::NDB"; | |
$self->init(%params); | |
} | |
return $self; | |
} | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
if ($self->{status} =~ /@(\d+\.\d+\.\d+\.\d+)\s/) { | |
$self->{addr} = $1; | |
$self->{connected} = 1; | |
} elsif ($self->{status} =~ /accepting connect from (\d+\.\d+\.\d+\.\d+)/) { | |
$self->{addr} = $1; | |
$self->{connected} = 0; | |
} | |
if ($self->{status} =~ /starting,/) { | |
$self->{status} = "starting"; | |
} elsif ($self->{status} =~ /shutting,/) { | |
$self->{status} = "shutting"; | |
} else { | |
$self->{status} = $self->{connected} ? "running" : "dead"; | |
} | |
} | |
package DBD::MySQL::Cluster::Node::NDB; | |
use strict; | |
our @ISA = qw(DBD::MySQL::Cluster::Node); | |
sub init { | |
my $self = shift; | |
my %params = @_; | |
if ($self->{status} =~ /Nodegroup:\s*(\d+)/) { | |
$self->{nodegroup} = $1; | |
} | |
$self->{master} = ($self->{status} =~ /Master\)/) ? 1 : 0; | |
} | |
package Extraopts; | |
use strict; | |
use File::Basename; | |
use Data::Dumper; | |
sub new { | |
my $class = shift; | |
my %params = @_; | |
my $self = { | |
file => $params{file}, | |
commandline => $params{commandline}, | |
config => {}, | |
section => 'default_no_section', | |
}; | |
bless $self, $class; | |
$self->prepare_file_and_section(); | |
$self->init(); | |
return $self; | |
} | |
sub prepare_file_and_section { | |
my $self = shift; | |
if (! defined $self->{file}) { | |
# ./check_stuff --extra-opts | |
$self->{section} = basename($0); | |
$self->{file} = $self->get_default_file(); | |
} elsif ($self->{file} =~ /^[^@]+$/) { | |
# ./check_stuff --extra-opts=special_opts | |
$self->{section} = $self->{file}; | |
$self->{file} = $self->get_default_file(); | |
} elsif ($self->{file} =~ /^@(.*)/) { | |
# ./check_stuff --extra-opts=@/etc/myconfig.ini | |
$self->{section} = basename($0); | |
$self->{file} = $1; | |
} elsif ($self->{file} =~ /^(.*?)@(.*)/) { | |
# ./check_stuff --extra-opts=special_opts@/etc/myconfig.ini | |
$self->{section} = $1; | |
$self->{file} = $2; | |
} | |
} | |
sub get_default_file { | |
my $self = shift; | |
foreach my $default (qw(/etc/nagios/plugins.ini | |
/usr/local/nagios/etc/plugins.ini | |
/usr/local/etc/nagios/plugins.ini | |
/etc/opt/nagios/plugins.ini | |
/etc/nagios-plugins.ini | |
/usr/local/etc/nagios-plugins.ini | |
/etc/opt/nagios-plugins.ini)) { | |
if (-f $default) { | |
return $default; | |
} | |
} | |
return undef; | |
} | |
sub init { | |
my $self = shift; | |
if (! defined $self->{file}) { | |
$self->{errors} = sprintf 'no extra-opts file specified and no default file found'; | |
} elsif (! -f $self->{file}) { | |
$self->{errors} = sprintf 'could not open %s', $self->{file}; | |
} else { | |
my $data = do { local (@ARGV, $/) = $self->{file}; <> }; | |
my $in_section = 'default_no_section'; | |
foreach my $line (split(/\n/, $data)) { | |
if ($line =~ /\[(.*)\]/) { | |
$in_section = $1; | |
} elsif ($line =~ /(.*?)\s*=\s*(.*)/) { | |
$self->{config}->{$in_section}->{$1} = $2; | |
} | |
} | |
} | |
} | |
sub is_valid { | |
my $self = shift; | |
return ! exists $self->{errors}; | |
} | |
sub overwrite { | |
my $self = shift; | |
my %commandline = (); | |
if (scalar(keys %{$self->{config}->{default_no_section}}) > 0) { | |
foreach (keys %{$self->{config}->{default_no_section}}) { | |
$commandline{$_} = $self->{config}->{default_no_section}->{$_}; | |
} | |
} | |
if (exists $self->{config}->{$self->{section}}) { | |
foreach (keys %{$self->{config}->{$self->{section}}}) { | |
$commandline{$_} = $self->{config}->{$self->{section}}->{$_}; | |
} | |
} | |
foreach (keys %commandline) { | |
if (! exists $self->{commandline}->{$_}) { | |
$self->{commandline}->{$_} = $commandline{$_}; | |
} | |
} | |
} | |
package main; | |
use strict; | |
use Getopt::Long qw(:config no_ignore_case); | |
use File::Basename; | |
use lib dirname($0); | |
use vars qw ($PROGNAME $REVISION $CONTACT $TIMEOUT $STATEFILESDIR $needs_restart %commandline); | |
$PROGNAME = "check_mysql_health"; | |
$REVISION = '$Revision: 2.2.2 $'; | |
$CONTACT = '[email protected]'; | |
$TIMEOUT = 60; | |
$STATEFILESDIR = '/var/tmp/check_mysql_health'; | |
$needs_restart = 0; | |
my @modes = ( | |
['server::connectiontime', | |
'connection-time', undef, | |
'Time to connect to the server' ], | |
['server::uptime', | |
'uptime', undef, | |
'Time the server is running' ], | |
['server::instance::connectedthreads', | |
'threads-connected', undef, | |
'Number of currently open connections' ], | |
['server::instance::threadcachehitrate', | |
'threadcache-hitrate', undef, | |
'Hit rate of the thread-cache' ], | |
['server::instance::createdthreads', | |
'threads-created', undef, | |
'Number of threads created per sec' ], | |
['server::instance::runningthreads', | |
'threads-running', undef, | |
'Number of currently running threads' ], | |
['server::instance::cachedthreads', | |
'threads-cached', undef, | |
'Number of currently cached threads' ], | |
['server::instance::abortedconnects', | |
'connects-aborted', undef, | |
'Number of aborted connections per sec' ], | |
['server::instance::abortedclients', | |
'clients-aborted', undef, | |
'Number of aborted connections (because the client died) per sec' ], | |
['server::instance::replication::slavelag', | |
'slave-lag', ['replication-slave-lag'], | |
'Seconds behind master' ], | |
['server::instance::replication::slaveiorunning', | |
'slave-io-running', ['replication-slave-io-running'], | |
'Slave io running: Yes' ], | |
['server::instance::replication::slavesqlrunning', | |
'slave-sql-running', ['replication-slave-sql-running'], | |
'Slave sql running: Yes' ], | |
['server::instance::querycachehitrate', | |
'qcache-hitrate', ['querycache-hitrate'], | |
'Query cache hitrate' ], | |
['server::instance::querycachelowmemprunes', | |
'qcache-lowmem-prunes', ['querycache-lowmem-prunes'], | |
'Query cache entries pruned because of low memory' ], | |
['server::instance::myisam::keycache::hitrate', | |
'keycache-hitrate', ['myisam-keycache-hitrate'], | |
'MyISAM key cache hitrate' ], | |
['server::instance::innodb::bufferpool::hitrate', | |
'bufferpool-hitrate', ['innodb-bufferpool-hitrate'], | |
'InnoDB buffer pool hitrate' ], | |
['server::instance::innodb::bufferpool::waitfree', | |
'bufferpool-wait-free', ['innodb-bufferpool-wait-free'], | |
'InnoDB buffer pool waits for clean page available' ], | |
['server::instance::innodb::logwaits', | |
'log-waits', ['innodb-log-waits'], | |
'InnoDB log waits because of a too small log buffer' ], | |
['server::instance::tablecachehitrate', | |
'tablecache-hitrate', undef, | |
'Table cache hitrate' ], | |
['server::instance::tablelockcontention', | |
'table-lock-contention', undef, | |
'Table lock contention' ], | |
['server::instance::tableindexusage', | |
'index-usage', undef, | |
'Usage of indices' ], | |
['server::instance::tabletmpondisk', | |
'tmp-disk-tables', undef, | |
'Percent of temp tables created on disk' ], | |
['server::instance::needoptimize', | |
'table-fragmentation', undef, | |
'Show tables which should be optimized' ], | |
['server::instance::openfiles', | |
'open-files', undef, | |
'Percent of opened files' ], | |
['server::instance::slowqueries', | |
'slow-queries', undef, | |
'Slow queries' ], | |
['server::instance::longprocs', | |
'long-running-procs', undef, | |
'long running processes' ], | |
['cluster::ndbdrunning', | |
'cluster-ndbd-running', undef, | |
'ndnd nodes are up and running' ], | |
['server::sql', | |
'sql', undef, | |
'any sql command returning a single number' ], | |
); | |
# rrd data store names are limited to 19 characters | |
my %labels = ( | |
bufferpool_hitrate => { | |
groundwork => 'bp_hitrate', | |
}, | |
bufferpool_hitrate_now => { | |
groundwork => 'bp_hitrate_now', | |
}, | |
bufferpool_free_waits_rate => { | |
groundwork => 'bp_freewaits', | |
}, | |
innodb_log_waits_rate => { | |
groundwork => 'inno_log_waits', | |
}, | |
keycache_hitrate => { | |
groundwork => 'kc_hitrate', | |
}, | |
keycache_hitrate_now => { | |
groundwork => 'kc_hitrate_now', | |
}, | |
threads_created_per_sec => { | |
groundwork => 'thrds_creat_per_s', | |
}, | |
connects_aborted_per_sec => { | |
groundwork => 'conn_abrt_per_s', | |
}, | |
clients_aborted_per_sec => { | |
groundwork => 'clnt_abrt_per_s', | |
}, | |
thread_cache_hitrate => { | |
groundwork => 'tc_hitrate', | |
}, | |
thread_cache_hitrate_now => { | |
groundwork => 'tc_hitrate_now', | |
}, | |
qcache_lowmem_prunes_rate => { | |
groundwork => 'qc_lowm_prnsrate', | |
}, | |
slow_queries_rate => { | |
groundwork => 'slow_q_rate', | |
}, | |
tablecache_hitrate => { | |
groundwork => 'tac_hitrate', | |
}, | |
tablecache_fillrate => { | |
groundwork => 'tac_fillrate', | |
}, | |
tablelock_contention => { | |
groundwork => 'tl_contention', | |
}, | |
tablelock_contention_now => { | |
groundwork => 'tl_contention_now', | |
}, | |
pct_tmp_table_on_disk => { | |
groundwork => 'tmptab_on_disk', | |
}, | |
pct_tmp_table_on_disk_now => { | |
groundwork => 'tmptab_on_disk_now', | |
}, | |
); | |
sub print_usage () { | |
print <<EOUS; | |
Usage: | |
$PROGNAME [-v] [-t <timeout>] [[--hostname <hostname>] | |
[--port <port> | --socket <socket>] | |
--username <username> --password <password>] --mode <mode> | |
[--method mysql] | |
$PROGNAME [-h | --help] | |
$PROGNAME [-V | --version] | |
Options: | |
--hostname | |
the database server's hostname | |
--port | |
the database's port. (default: 3306) | |
--socket | |
the database's unix socket. | |
--username | |
the mysql db user | |
--password | |
the mysql db user's password | |
--database | |
the database's name. (default: information_schema) | |
--replication-user | |
the database's replication user name (default: replication) | |
--warning | |
the warning range | |
--critical | |
the critical range | |
--mode | |
the mode of the plugin. select one of the following keywords: | |
EOUS | |
my $longest = length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0]); | |
my $format = " %-". | |
(length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0])). | |
"s\t(%s)\n"; | |
foreach (@modes) { | |
printf $format, $_->[1], $_->[3]; | |
} | |
printf "\n"; | |
print <<EOUS; | |
--name | |
the name of something that needs to be further specified, | |
currently only used for sql statements | |
--name2 | |
if name is a sql statement, this statement would appear in | |
the output and the performance data. This can be ugly, so | |
name2 can be used to appear instead. | |
--regexp | |
if this parameter is used, name will be interpreted as a | |
regular expression. | |
--units | |
one of %, KB, MB, GB. This is used for a better output of mode=sql | |
and for specifying thresholds for mode=tablespace-free | |
--labelformat | |
one of pnp4nagios (which is the default) or groundwork. | |
It is used to shorten performance data labels to 19 characters. | |
In mode sql you can url-encode the statement so you will not have to mess | |
around with special characters in your Nagios service definitions. | |
Instead of | |
--name="select count(*) from v\$session where status = 'ACTIVE'" | |
you can say | |
--name=select%20count%28%2A%29%20from%20v%24session%20where%20status%20%3D%20%27ACTIVE%27 | |
For your convenience you can call check_mysql_health with the --mode encode | |
option and it will encode the standard input. | |
You can find the full documentation at | |
https://labs.consol.de/nagios/check_mysql_health/ | |
EOUS | |
} | |
sub print_help () { | |
print "Copyright (c) 2009 Gerhard Lausser\n\n"; | |
print "\n"; | |
print " Check various parameters of MySQL databases \n"; | |
print "\n"; | |
print_usage(); | |
support(); | |
} | |
sub print_revision ($$) { | |
my $commandName = shift; | |
my $pluginRevision = shift; | |
$pluginRevision =~ s/^\$Revision: //; | |
$pluginRevision =~ s/ \$\s*$//; | |
print "$commandName ($pluginRevision)\n"; | |
print "This nagios plugin comes with ABSOLUTELY NO WARRANTY. You may redistribute\ncopies of this plugin under the terms of the GNU General Public License.\n"; | |
} | |
sub support () { | |
my $support='Send email to [email protected] if you have questions\nregarding use of this software. \nPlease include version information with all correspondence (when possible,\nuse output from the --version option of the plugin itself).\n'; | |
$support =~ s/@/\@/g; | |
$support =~ s/\\n/\n/g; | |
print $support; | |
} | |
sub contact_author ($$) { | |
my $item = shift; | |
my $strangepattern = shift; | |
if ($commandline{verbose}) { | |
printf STDERR | |
"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n". | |
"You found a line which is not recognized by %s\n". | |
"This means, certain components of your system cannot be checked.\n". | |
"Please contact the author %s and\nsend him the following output:\n\n". | |
"%s /%s/\n\nThank you!\n". | |
"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n", | |
$PROGNAME, $CONTACT, $item, $strangepattern; | |
} | |
} | |
%commandline = (); | |
my @params = ( | |
"timeout|t=i", | |
"version|V", | |
"help|h", | |
"verbose|v", | |
"debug|d", | |
"hostname|H=s", | |
"database=s", | |
"port|P=s", | |
"socket|S=s", | |
"username|u=s", | |
"password|p=s", | |
"replication-user=s", | |
"mycnf=s", | |
"mycnfgroup=s", | |
"mode|m=s", | |
"name=s", | |
"name2=s", | |
"regexp", | |
"perfdata", | |
"warning=s", | |
"critical=s", | |
"dbthresholds:s", | |
"absolute|a", | |
"environment|e=s%", | |
"negate=s%", | |
"method=s", | |
"runas|r=s", | |
"scream", | |
"shell", | |
"eyecandy", | |
"encode", | |
"units=s", | |
"lookback=i", | |
"3", | |
"statefilesdir=s", | |
"with-mymodules-dyn-dir=s", | |
"report=s", | |
"labelformat=s", | |
"extra-opts:s"); | |
if (! GetOptions(\%commandline, @params)) { | |
print_help(); | |
exit $ERRORS{UNKNOWN}; | |
} | |
if (exists $commandline{'extra-opts'}) { | |
# read the extra file and overwrite other parameters | |
my $extras = Extraopts->new(file => $commandline{'extra-opts'}, commandline => | |
\%commandline); | |
if (! $extras->is_valid()) { | |
printf "extra-opts are not valid: %s\n", $extras->{errors}; | |
exit $ERRORS{UNKNOWN}; | |
} else { | |
$extras->overwrite(); | |
} | |
} | |
if (exists $commandline{version}) { | |
print_revision($PROGNAME, $REVISION); | |
exit $ERRORS{OK}; | |
} | |
if (exists $commandline{help}) { | |
print_help(); | |
exit $ERRORS{OK}; | |
} elsif (! exists $commandline{mode}) { | |
printf "Please select a mode\n"; | |
print_help(); | |
exit $ERRORS{OK}; | |
} | |
if ($commandline{mode} eq "encode") { | |
my $input = <>; | |
chomp $input; | |
$input =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; | |
printf "%s\n", $input; | |
exit $ERRORS{OK}; | |
} | |
if (exists $commandline{3}) { | |
$ENV{NRPE_MULTILINESUPPORT} = 1; | |
} | |
if (exists $commandline{timeout}) { | |
$TIMEOUT = $commandline{timeout}; | |
} | |
if (exists $commandline{verbose}) { | |
$DBD::MySQL::Server::verbose = exists $commandline{verbose}; | |
} | |
if (exists $commandline{scream}) { | |
# $DBD::MySQL::Server::hysterical = exists $commandline{scream}; | |
} | |
if (exists $commandline{method}) { | |
# snmp or mysql cmdline | |
} else { | |
$commandline{method} = "dbi"; | |
} | |
if (exists $commandline{report}) { | |
# short, long, html | |
} else { | |
$commandline{report} = "long"; | |
} | |
if (exists $commandline{labelformat}) { | |
# groundwork | |
} else { | |
$commandline{labelformat} = "pnp4nagios"; | |
} | |
if (exists $commandline{'with-mymodules-dyn-dir'}) { | |
$DBD::MySQL::Server::my_modules_dyn_dir = $commandline{'with-mymodules-dyn-dir'}; | |
} else { | |
$DBD::MySQL::Server::my_modules_dyn_dir = '/usr/local/nagios/libexec'; | |
} | |
if (exists $commandline{environment}) { | |
# if the desired environment variable values are different from | |
# the environment of this running script, then a restart is necessary. | |
# because setting $ENV does _not_ change the environment of the running script. | |
foreach (keys %{$commandline{environment}}) { | |
if ((! $ENV{$_}) || ($ENV{$_} ne $commandline{environment}->{$_})) { | |
$needs_restart = 1; | |
$ENV{$_} = $commandline{environment}->{$_}; | |
printf STDERR "new %s=%s forces restart\n", $_, $ENV{$_} | |
if $DBD::MySQL::Server::verbose; | |
} | |
} | |
# e.g. called with --runas dbnagio. shlib_path environment variable is stripped | |
# during the sudo. | |
# so the perl interpreter starts without a shlib_path. but --runas cares for | |
# a --environment shlib_path=... | |
# so setting the environment variable in the code above and restarting the | |
# perl interpreter will help it find shared libs | |
} | |
if (exists $commandline{runas}) { | |
# remove the runas parameter | |
# exec sudo $0 ... the remaining parameters | |
$needs_restart = 1; | |
# if the calling script has a path for shared libs and there is no --environment | |
# parameter then the called script surely needs the variable too. | |
foreach my $important_env (qw(LD_LIBRARY_PATH SHLIB_PATH | |
ORACLE_HOME TNS_ADMIN ORA_NLS ORA_NLS33 ORA_NLS10)) { | |
if ($ENV{$important_env} && ! scalar(grep { /^$important_env=/ } | |
keys %{$commandline{environment}})) { | |
$commandline{environment}->{$important_env} = $ENV{$important_env}; | |
printf STDERR "add important --environment %s=%s\n", | |
$important_env, $ENV{$important_env} if $DBD::MySQL::Server::verbose; | |
} | |
} | |
} | |
if ($needs_restart) { | |
my @newargv = (); | |
my $runas = undef; | |
if (exists $commandline{runas}) { | |
$runas = $commandline{runas}; | |
delete $commandline{runas}; | |
} | |
foreach my $option (keys %commandline) { | |
if (grep { /^$option/ && /=/ } @params) { | |
if (ref ($commandline{$option}) eq "HASH") { | |
foreach (keys %{$commandline{$option}}) { | |
push(@newargv, sprintf "--%s", $option); | |
push(@newargv, sprintf "%s=%s", $_, $commandline{$option}->{$_}); | |
} | |
} else { | |
push(@newargv, sprintf "--%s", $option); | |
push(@newargv, sprintf "%s", $commandline{$option}); | |
} | |
} else { | |
push(@newargv, sprintf "--%s", $option); | |
} | |
} | |
if ($runas) { | |
exec "sudo", "-S", "-u", $runas, $0, @newargv; | |
} else { | |
exec $0, @newargv; | |
# this makes sure that even a SHLIB or LD_LIBRARY_PATH are set correctly | |
# when the perl interpreter starts. Setting them during runtime does not | |
# help loading e.g. libclntsh.so | |
} | |
exit; | |
} | |
if (exists $commandline{shell}) { | |
# forget what you see here. | |
system("/bin/sh"); | |
} | |
if (! exists $commandline{statefilesdir}) { | |
if (exists $ENV{OMD_ROOT}) { | |
$commandline{statefilesdir} = $ENV{OMD_ROOT}."/var/tmp/check_mysql_health"; | |
} else { | |
$commandline{statefilesdir} = $STATEFILESDIR; | |
} | |
} | |
if (exists $commandline{name}) { | |
if ($^O =~ /MSWin/ && $commandline{name} =~ /^'(.*)'$/) { | |
# putting arguments in single ticks under Windows CMD leaves the ' intact | |
# we remove them | |
$commandline{name} = $1; | |
} | |
# objects can be encoded like an url | |
# with s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; | |
if (($commandline{mode} ne "sql") || | |
(($commandline{mode} eq "sql") && | |
($commandline{name} =~ /select%20/i))) { # protect ... like '%cac%' ... from decoding | |
$commandline{name} =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg; | |
} | |
if ($commandline{name} =~ /^0$/) { | |
# without this, $params{selectname} would be treated like undef | |
$commandline{name} = "00"; | |
} | |
} | |
$SIG{'ALRM'} = sub { | |
printf "UNKNOWN - %s timed out after %d seconds\n", $PROGNAME, $TIMEOUT; | |
exit $ERRORS{UNKNOWN}; | |
}; | |
alarm($TIMEOUT); | |
my $nagios_level = $ERRORS{UNKNOWN}; | |
my $nagios_message = ""; | |
my $perfdata = ""; | |
if ($commandline{mode} =~ /^my-([^\-.]+)/) { | |
my $param = $commandline{mode}; | |
$param =~ s/\-/::/g; | |
push(@modes, [$param, $commandline{mode}, undef, 'my extension']); | |
} elsif ((! grep { $commandline{mode} eq $_ } map { $_->[1] } @modes) && | |
(! grep { $commandline{mode} eq $_ } map { defined $_->[2] ? @{$_->[2]} : () } @modes)) { | |
printf "UNKNOWN - mode %s\n", $commandline{mode}; | |
print_usage(); | |
exit 3; | |
} | |
my %params = ( | |
timeout => $TIMEOUT, | |
mode => ( | |
map { $_->[0] } | |
grep { | |
($commandline{mode} eq $_->[1]) || | |
( defined $_->[2] && grep { $commandline{mode} eq $_ } @{$_->[2]}) | |
} @modes | |
)[0], | |
cmdlinemode => $commandline{mode}, | |
method => $commandline{method} || | |
$ENV{NAGIOS__SERVICEMYSQL_METH} || | |
$ENV{NAGIOS__HOSTMYSQL_METH} || 'dbi', | |
hostname => $commandline{hostname} || | |
$ENV{NAGIOS__SERVICEMYSQL_HOST} || | |
$ENV{NAGIOS__HOSTMYSQL_HOST} || 'localhost', | |
database => $commandline{database} || | |
$ENV{NAGIOS__SERVICEMYSQL_DATABASE} || | |
$ENV{NAGIOS__HOSTMYSQL_DATABASE} || 'information_schema', | |
port => $commandline{port} || (($commandline{mode} =~ /^cluster/) ? | |
($ENV{NAGIOS__SERVICENDBMGM_PORT} || $ENV{NAGIOS__HOSTNDBMGM_PORT} || 1186) : | |
($ENV{NAGIOS__SERVICEMYSQL_PORT} || $ENV{NAGIOS__HOSTMYSQL_PORT} || 3306)), | |
socket => $commandline{socket} || | |
$ENV{NAGIOS__SERVICEMYSQL_SOCKET} || | |
$ENV{NAGIOS__HOSTMYSQL_SOCKET}, | |
username => $commandline{username} || | |
$ENV{NAGIOS__SERVICEMYSQL_USER} || | |
$ENV{NAGIOS__HOSTMYSQL_USER}, | |
password => $commandline{password} || | |
$ENV{NAGIOS__SERVICEMYSQL_PASS} || | |
$ENV{NAGIOS__HOSTMYSQL_PASS}, | |
replication_user => $commandline{'replication-user'} || 'replication', | |
mycnf => $commandline{mycnf} || | |
$ENV{NAGIOS__SERVICEMYSQL_MYCNF} || | |
$ENV{NAGIOS__HOSTMYSQL_MYCNF}, | |
mycnfgroup => $commandline{mycnfgroup} || | |
$ENV{NAGIOS__SERVICEMYSQL_MYCNFGROUP} || | |
$ENV{NAGIOS__HOSTMYSQL_MYCNFGROUP}, | |
warningrange => $commandline{warning}, | |
criticalrange => $commandline{critical}, | |
dbthresholds => $commandline{dbthresholds}, | |
absolute => $commandline{absolute}, | |
lookback => $commandline{lookback}, | |
selectname => $commandline{name} || $commandline{tablespace} || $commandline{datafile}, | |
regexp => $commandline{regexp}, | |
name => $commandline{name}, | |
name2 => $commandline{name2} || $commandline{name}, | |
units => $commandline{units}, | |
lookback => $commandline{lookback} || 0, | |
eyecandy => $commandline{eyecandy}, | |
statefilesdir => $commandline{statefilesdir}, | |
verbose => $commandline{verbose}, | |
report => $commandline{report}, | |
labelformat => $commandline{labelformat}, | |
negate => $commandline{negate}, | |
); | |
my $server = undef; | |
my $cluster = undef; | |
if ($params{mode} =~ /^(server|my)/) { | |
$server = DBD::MySQL::Server->new(%params); | |
$server->nagios(%params); | |
$server->calculate_result(\%labels); | |
$nagios_message = $server->{nagios_message}; | |
$nagios_level = $server->{nagios_level}; | |
$perfdata = $server->{perfdata}; | |
} elsif ($params{mode} =~ /^cluster/) { | |
$cluster = DBD::MySQL::Cluster->new(%params); | |
$cluster->nagios(%params); | |
$cluster->calculate_result(\%labels); | |
$nagios_message = $cluster->{nagios_message}; | |
$nagios_level = $cluster->{nagios_level}; | |
$perfdata = $cluster->{perfdata}; | |
} | |
printf "%s - %s", $ERRORCODES{$nagios_level}, $nagios_message; | |
printf " | %s", $perfdata if $perfdata; | |
printf "\n"; | |
exit $nagios_level; | |
__END__ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment