diff --git a/check_mssql/trunk/check_mssql_health b/check_mssql/trunk/check_mssql_health new file mode 100644 index 0000000..3f199f3 --- /dev/null +++ b/check_mssql/trunk/check_mssql_health @@ -0,0 +1,3118 @@ +#! /usr/bin/perl -w + +my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 ); +my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' ); +package DBD::MSSQL::Server::Memorypool; + +use strict; + +our @ISA = qw(DBD::MSSQL::Server); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + buffercache => undef, + procedurecache => undef, + locks => [], + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::memorypool::buffercache/) { + $self->{buffercache} = DBD::MSSQL::Server::Memorypool::BufferCache->new( + %params); + } elsif ($params{mode} =~ /server::memorypool::procedurecache/) { + $self->{procedurecache} = DBD::MSSQL::Server::Memorypool::ProcedureCache->new( + %params); + } elsif ($params{mode} =~ /server::memorypool::lock/) { + DBD::MSSQL::Server::Memorypool::Lock::init_locks(%params); + if (my @locks = DBD::MSSQL::Server::Memorypool::Lock::return_locks()) { + $self->{locks} = \@locks; + } else { + $self->add_nagios_critical("unable to aquire lock info"); + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if ($params{mode} =~ /server::memorypool::buffercache/) { + $self->{buffercache}->nagios(%params); + $self->merge_nagios($self->{buffercache}); + } elsif ($params{mode} =~ /^server::memorypool::lock::listlocks/) { + foreach (sort { $a->{name} cmp $b->{name}; } @{$self->{locks}}) { + printf "%s\n", $_->{name}; + } + $self->add_nagios_ok("have fun"); + } elsif ($params{mode} =~ /^server::memorypool::lock/) { + foreach (@{$self->{locks}}) { + $_->nagios(%params); + $self->merge_nagios($_); + } + } +} + + +package DBD::MSSQL::Server::Memorypool::BufferCache; + +use strict; + +our @ISA = qw(DBD::MSSQL::Server::Memorypool); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + hitratio => 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::memorypool::buffercache::hitratio/) { + # -- (a.cntr_value * 1.0 / b.cntr_value) * 100.0 [BufferCacheHitRatio] + $self->{cnt_hitratio} = $self->{handle}->get_perf_counter( + "SQLServer:Buffer Manager", "Buffer cache hit ratio"); + $self->{cnt_hitratio_base} = $self->{handle}->get_perf_counter( + "SQLServer:Buffer Manager", "Buffer cache hit ratio base"); + if (! defined $self->{cnt_hitratio}) { + $self->add_nagios_unknown("unable to aquire buffer cache data"); + } else { + # das kracht weil teilweise negativ + #$self->valdiff(\%params, qw(cnt_hitratio cnt_hitratio_base)); + $self->{hitratio} = ($self->{cnt_hitratio_base} == 0) ? + 100 : $self->{cnt_hitratio} / $self->{cnt_hitratio_base} * 100.0; + # soll vorkommen..... + $self->{hitratio} = 100 if ($self->{hitratio} > 100); + } + } elsif ($params{mode} =~ /server::memorypool::buffercache::lazywrites/) { + $self->{lazy_writes_s} = $self->{handle}->get_perf_counter( + "SQLServer:Buffer Manager", "Lazy writes/sec"); + if (! defined $self->{lazy_writes_s}) { + $self->add_nagios_unknown("unable to aquire buffer manager data"); + } else { + $self->valdiff(\%params, qw(lazy_writes_s)); + $self->{lazy_writes_per_sec} = $self->{delta_lazy_writes_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /server::memorypool::buffercache::pagelifeexpectancy/) { + $self->{pagelifeexpectancy} = $self->{handle}->get_perf_counter( + "SQLServer:Buffer Manager", "Page life expectancy"); + if (! defined $self->{pagelifeexpectancy}) { + $self->add_nagios_unknown("unable to aquire buffer manager data"); + } + } elsif ($params{mode} =~ /server::memorypool::buffercache::freeliststalls/) { + $self->{freeliststalls_s} = $self->{handle}->get_perf_counter( + "SQLServer:Buffer Manager", "Free list stalls/sec"); + if (! defined $self->{freeliststalls_s}) { + $self->add_nagios_unknown("unable to aquire buffer manager data"); + } else { + $self->valdiff(\%params, qw(freeliststalls_s)); + $self->{freeliststalls_per_sec} = $self->{delta_freeliststalls_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /server::memorypool::buffercache::checkpointpages/) { + $self->{checkpointpages_s} = $self->{handle}->get_perf_counter( + "SQLServer:Buffer Manager", "Checkpoint pages/sec"); + if (! defined $self->{checkpointpages_s}) { + $self->add_nagios_unknown("unable to aquire buffer manager data"); + } else { + $self->valdiff(\%params, qw(checkpointpages_s)); + $self->{checkpointpages_per_sec} = $self->{delta_checkpointpages_s} / $self->{delta_timestamp}; + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::memorypool::buffercache::hitratio/) { + $self->add_nagios( + $self->check_thresholds($self->{hitratio}, '90:', '80:'), + sprintf "buffer cache hit ratio is %.2f%%", $self->{hitratio}); + $self->add_perfdata(sprintf "buffer_cache_hit_ratio=%.2f%%;%s;%s", + $self->{hitratio}, + $self->{warningrange}, $self->{criticalrange}); + #$self->add_perfdata(sprintf "buffer_cache_hit_ratio_now=%.2f%%", + # $self->{hitratio_now}); + } elsif ($params{mode} =~ /server::memorypool::buffercache::lazywrites/) { + $self->add_nagios( + $self->check_thresholds($self->{lazy_writes_per_sec}, '20', '40'), + sprintf "%.2f lazy writes per second", $self->{lazy_writes_per_sec}); + $self->add_perfdata(sprintf "lazy_writes_per_sec=%.2f;%s;%s", + $self->{lazy_writes_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::memorypool::buffercache::pagelifeexpectancy/) { + $self->add_nagios( + $self->check_thresholds($self->{pagelifeexpectancy}, '300:', '180:'), + sprintf "page life expectancy is %d seconds", $self->{pagelifeexpectancy}); + $self->add_perfdata(sprintf "page_life_expectancy=%d;%s;%s", + $self->{pagelifeexpectancy}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::memorypool::buffercache::freeliststalls/) { + $self->add_nagios( + $self->check_thresholds($self->{freeliststalls_per_sec}, '4', '10'), + sprintf "%.2f free list stalls per second", $self->{freeliststalls_per_sec}); + $self->add_perfdata(sprintf "free_list_stalls_per_sec=%.2f;%s;%s", + $self->{freeliststalls_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::memorypool::buffercache::checkpointpages/) { + $self->add_nagios( + $self->check_thresholds($self->{checkpointpages_per_sec}, '100', '500'), + sprintf "%.2f pages flushed per second", $self->{checkpointpages_per_sec}); + $self->add_perfdata(sprintf "checkpoint_pages_per_sec=%.2f;%s;%s", + $self->{checkpointpages_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + + + + + +package DBD::MSSQL::Server::Memorypool::Lock; + +use strict; + +our @ISA = qw(DBD::MSSQL::Server::Memorypool); + + +{ + my @locks = (); + my $initerrors = undef; + + sub add_lock { + push(@locks, shift); + } + + sub return_locks { + return reverse + sort { $a->{name} cmp $b->{name} } @locks; + } + + sub init_locks { + my %params = @_; + my $num_locks = 0; + if (($params{mode} =~ /server::memorypool::lock::listlocks/) || + ($params{mode} =~ /server::memorypool::lock::waits/) || + ($params{mode} =~ /server::memorypool::lock::deadlocks/) || + ($params{mode} =~ /server::memorypool::lock::timeouts/)) { + my @lockresult = $params{handle}->get_instance_names( + 'SQLServer:Locks'); + foreach (@lockresult) { + my ($name) = @{$_}; + $name =~ s/\s*$//; + if ($params{regexp}) { + next if $params{selectname} && $name !~ /$params{selectname}/; + } else { + next if $params{selectname} && lc $params{selectname} ne lc $name; + } + my %thisparams = %params; + $thisparams{name} = $name; + my $lock = DBD::MSSQL::Server::Memorypool::Lock->new( + %thisparams); + add_lock($lock); + $num_locks++; + } + if (! $num_locks) { + $initerrors = 1; + return undef; + } + } + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + name => $params{name}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::memorypool::lock::listlocks/) { + # name reicht + } elsif ($params{mode} =~ /server::memorypool::lock::waits/) { + $self->{lock_waits_s} = $self->{handle}->get_perf_counter_instance( + "SQLServer:Locks", "Lock Waits/sec", $self->{name}); + if (! defined $self->{lock_waits_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(lock_waits_s)); + $self->{lock_waits_per_sec} = $self->{delta_lock_waits_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /^server::memorypool::lock::timeouts/) { + $self->{lock_timeouts_s} = $self->{handle}->get_perf_counter_instance( + "SQLServer:Locks", "Lock Timeouts/sec", $self->{name}); + if (! defined $self->{lock_timeouts_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(lock_timeouts_s)); + $self->{lock_timeouts_per_sec} = $self->{delta_lock_timeouts_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /^server::memorypool::lock::deadlocks/) { + $self->{lock_deadlocks_s} = $self->{handle}->get_perf_counter_instance( + "SQLServer:Locks", "Number of Deadlocks/sec", $self->{name}); + if (! defined $self->{lock_deadlocks_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(lock_deadlocks_s)); + $self->{lock_deadlocks_per_sec} = $self->{delta_lock_deadlocks_s} / $self->{delta_timestamp}; + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::memorypool::lock::waits/) { + $self->add_nagios( + $self->check_thresholds($self->{lock_waits_per_sec}, 100, 500), + sprintf "%.4f lock waits / sec for %s", + $self->{lock_waits_per_sec}, $self->{name}); + $self->add_perfdata(sprintf "%s_lock_waits_per_sec=%.4f;%s;%s", + $self->{name}, $self->{lock_waits_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::memorypool::lock::timeouts/) { + $self->add_nagios( + $self->check_thresholds($self->{lock_timeouts_per_sec}, 1, 5), + sprintf "%.4f lock timeouts / sec for %s", + $self->{lock_timeouts_per_sec}, $self->{name}); + $self->add_perfdata(sprintf "%s_lock_timeouts_per_sec=%.4f;%s;%s", + $self->{name}, $self->{lock_timeouts_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::memorypool::lock::deadlocks/) { + $self->add_nagios( + $self->check_thresholds($self->{lock_deadlocks_per_sec}, 1, 5), + sprintf "%.4f deadlocks / sec for %s", + $self->{lock_deadlocks_per_sec}, $self->{name}); + $self->add_perfdata(sprintf "%s_deadlocks_per_sec=%.4f;%s;%s", + $self->{name}, $self->{lock_deadlocks_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + + +package DBD::MSSQL::Server::Memorypool::SystemLevelDataStructures::LockTable; +package DBD::MSSQL::Server::Memorypool::ProcedureCache; +package DBD::MSSQL::Server::Memorypool::LogCache; +package DBD::MSSQL::Server::Memorypool::SystemLevelDataStructures; +package DBD::MSSQL::Server::Database::Datafile; + +use strict; +use File::Basename; + +our @ISA = qw(DBD::MSSQL::Server::Database); + + +{ + my @datafiles = (); + my $initerrors = undef; + + sub add_datafile { + push(@datafiles, shift); + } + + sub return_datafiles { + return reverse + sort { $a->{logicalfilename} cmp $b->{logicalfilename} } @datafiles; + } + + sub clear_datafiles { + @datafiles = (); + } + + sub init_datafiles { + my %params = @_; + my $num_datafiles = 0; + if ($params{mode} =~ /server::database::datafile::listdatafiles/) { + my @datafileresults = $params{handle}->fetchall_array(q{ +DECLARE @DBInfo TABLE +( ServerName VARCHAR(100), +DatabaseName VARCHAR(100), +FileSizeMB INT, +LogicalFileName sysname, +PhysicalFileName NVARCHAR(520), +Status sysname, +Updateability sysname, +RecoveryMode sysname, +FreeSpaceMB INT, +FreeSpacePct VARCHAR(7), +FreeSpacePages INT, +PollDate datetime) + +DECLARE @command VARCHAR(5000) + +SELECT @command = 'Use [' + '?' + '] SELECT +@@servername as ServerName, +' + '''' + '?' + '''' + ' AS DatabaseName, +CAST(sysfiles.size/128.0 AS int) AS FileSize, +sysfiles.name AS LogicalFileName, sysfiles.filename AS PhysicalFileName, +CONVERT(sysname,DatabasePropertyEx(''?'',''Status'')) AS Status, +CONVERT(sysname,DatabasePropertyEx(''?'',''Updateability'')) AS Updateability, +CONVERT(sysname,DatabasePropertyEx(''?'',''Recovery'')) AS RecoveryMode, +CAST(sysfiles.size/128.0 - CAST(FILEPROPERTY(sysfiles.name, ' + '''' + + 'SpaceUsed' + '''' + ' ) AS int)/128.0 AS int) AS FreeSpaceMB, +CAST(100 * (CAST (((sysfiles.size/128.0 -CAST(FILEPROPERTY(sysfiles.name, +' + '''' + 'SpaceUsed' + '''' + ' ) AS int)/128.0)/(sysfiles.size/128.0)) +AS decimal(4,2))) AS varchar(8)) + ' + '''' + '%' + '''' + ' AS FreeSpacePct, +GETDATE() as PollDate FROM dbo.sysfiles' +INSERT INTO @DBInfo + (ServerName, + DatabaseName, + FileSizeMB, + LogicalFileName, + PhysicalFileName, + Status, + Updateability, + RecoveryMode, + FreeSpaceMB, + FreeSpacePct, + PollDate) +EXEC sp_MSForEachDB @command + +SELECT + ServerName, + DatabaseName, + FileSizeMB, + LogicalFileName, + PhysicalFileName, + Status, + Updateability, + RecoveryMode, + FreeSpaceMB, + FreeSpacePct, + PollDate +FROM @DBInfo +ORDER BY + ServerName, + DatabaseName + }); + if (DBD::MSSQL::Server::return_first_server()->windows_server()) { + fileparse_set_fstype("MSWin32"); + } + foreach (@datafileresults) { + my ($servername, $databasename, $filesizemb, $logicalfilename, + $physicalfilename, $status, $updateability, $recoverymode, + $freespacemb, $freespacepct, $polldate) = @{$_}; + next if $databasename ne $params{database}; + if ($params{regexp}) { + #next if $params{selectname} && + # (($name !~ /$params{selectname}/) && + # (basename($name) !~ /$params{selectname}/)); + next if $params{selectname} && + ($logicalfilename !~ /$params{selectname}/); + } else { + #next if $params{selectname} && + # ((lc $params{selectname} ne lc $name) && + # (lc $params{selectname} ne lc basename($name))); + next if $params{selectname} && + (lc $params{selectname} ne lc $logicalfilename); + } + my %thisparams = %params; + $thisparams{servername} = $servername; + $thisparams{databasename} = $databasename; + $thisparams{filesizemb} = $filesizemb; + $thisparams{logicalfilename} = $logicalfilename; + $thisparams{servername} = $servername; + $thisparams{status} = $status; + $thisparams{updateability} = $updateability; + $thisparams{recoverymode} = $recoverymode; + $thisparams{freespacemb} = $freespacemb; + $thisparams{freespacepct} = $freespacepct; + $thisparams{polldate} = $polldate; + my $datafile = + DBD::MSSQL::Server::Database::Datafile->new( + %thisparams); + add_datafile($datafile); + $num_datafiles++; + } + } + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + databasename => $params{databasename}, + filesizemb => $params{filesizemb}, + logicalfilename => $params{logicalfilename}, + physicalfilename => $params{physicalfilename}, + status => $params{status}, + updateability => $params{updateability}, + recoverymode => $params{recoverymode}, + freespacemb => $params{freespacemb}, + freespacepct => $params{freespacepct}, + freespacepages => $params{freespacepages}, + polldate => $params{polldate}, + 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::database::iobalance/) { + if (! defined $self->{phyrds}) { + $self->add_nagios_critical(sprintf "unable to read datafile io %s", $@); + } else { + $params{differenciator} = $self->{path}; + $self->valdiff(\%params, qw(phyrds phywrts)); + $self->{io_total} = $self->{delta_phyrds} + $self->{delta_phywrts}; + } + } elsif ($params{mode} =~ /server::database::datafile::iotraffic/) { + if (! defined $self->{phyrds}) { + $self->add_nagios_critical(sprintf "unable to read datafile io %s", $@); + } else { + $params{differenciator} = $self->{path}; + $self->valdiff(\%params, qw(phyrds phywrts)); + $self->{io_total_per_sec} = ($self->{delta_phyrds} + $self->{delta_phywrts}) / + $self->{delta_timestamp}; + } + } +} + + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::database::datafile::iotraffic/) { + $self->add_nagios( + $self->check_thresholds($self->{io_total_per_sec}, "1000", "5000"), + sprintf ("%s: %.2f IO Operations per Second", + $self->{name}, $self->{io_total_per_sec})); + $self->add_perfdata(sprintf "'dbf_%s_io_total_per_sec'=%.2f;%d;%d", + $self->{name}, $self->{io_total_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } + } +} + +package DBD::MSSQL::Server::Database; + +use strict; + +our @ISA = qw(DBD::MSSQL::Server); + + +{ + my @databases = (); + my $initerrors = undef; + + sub add_database { + push(@databases, shift); + } + + sub return_databases { + return reverse + sort { $a->{name} cmp $b->{name} } @databases; + } + + sub init_databases { + my %params = @_; + my $num_databases = 0; + if (($params{mode} =~ /server::database::listdatabases/) || + ($params{mode} =~ /server::database::databasefree/) || + ($params{mode} =~ /server::database::lastbackup/) || + ($params{mode} =~ /server::database::transactions/) || + ($params{mode} =~ /server::database::datafile/)) { + my @databaseresult = (); + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + @databaseresult = $params{handle}->fetchall_array(q{ + SELECT name, database_id FROM master.sys.databases + }); + } else { + #@databaseresult = map { + # [ $_->[0], $_->[3] ] # only name, dbid + #} $params{handle}->fetchall_array(q{exec sp_helpdb}); + @databaseresult = $params{handle}->fetchall_array(q{ + SELECT name, dbid FROM master.dbo.sysdatabases + }); + } + if ($params{mode} =~ /server::database::transactions/) { + push(@databaseresult, [ '_Total', 0 ]); + } + foreach (@databaseresult) { + my ($name, $id) = @{$_}; + next if $params{database} && $name ne $params{database}; + if ($params{regexp}) { + next if $params{selectname} && $name !~ /$params{selectname}/; + } else { + next if $params{selectname} && lc $params{selectname} ne lc $name; + } + my %thisparams = %params; + $thisparams{name} = $name; + $thisparams{id} = $id; + my $database = DBD::MSSQL::Server::Database->new( + %thisparams); + add_database($database); + $num_databases++; + } + if (! $num_databases) { + $initerrors = 1; + return undef; + } + } elsif ($params{mode} =~ /server::database::backupage/) { + my @databaseresult = (); + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + @databaseresult = $params{handle}->fetchall_array(q{ + SELECT + a.name, + DATEDIFF(HH, MAX(b.backup_finish_date), GETDATE()), + DATEDIFF(MI, MAX(b.backup_start_date), MAX(b.backup_finish_date)) + FROM sys.sysdatabases a LEFT OUTER JOIN msdb.dbo.backupset b + ON b.database_name = a.name + GROUP BY a.name + ORDER BY a.name + }); + } else { + @databaseresult = $params{handle}->fetchall_array(q{ + SELECT + a.name, + DATEDIFF(HH, MAX(b.backup_finish_date), GETDATE()), + DATEDIFF(MI, MAX(b.backup_start_date), MAX(b.backup_finish_date)) + FROM master.dbo.sysdatabases a LEFT OUTER JOIN msdb.dbo.backupset b + ON b.database_name = a.name + GROUP BY a.name + ORDER BY a.name + }); + } + foreach (sort { + if (! defined $b->[1]) { + return 1; + } elsif (! defined $a->[1]) { + return -1; + } else { + return $a->[1] <=> $b->[1]; + } + } @databaseresult) { + my ($name, $age, $duration) = @{$_}; + next if $params{database} && $name ne $params{database}; + if ($params{regexp}) { + next if $params{selectname} && $name !~ /$params{selectname}/; + } else { + next if $params{selectname} && lc $params{selectname} ne lc $name; + } + my %thisparams = %params; + $thisparams{name} = $name; + $thisparams{backup_age} = $age; + $thisparams{backup_duration} = $duration; + my $database = DBD::MSSQL::Server::Database->new( + %thisparams); + add_database($database); + $num_databases++; + } + } + + } + +} + +sub new { + my $class = shift; + my %params = @_; + my $self = { + handle => $params{handle}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + name => $params{name}, + id => $params{id}, + datafiles => [], + backup_age => $params{backup_age}, + backup_duration => $params{backup_duration}, + }; + bless $self, $class; + $self->init(%params); + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $self->init_nagios(); + if ($params{mode} =~ /server::database::datafile/) { + $params{database} = $self->{name}; + DBD::MSSQL::Server::Database::Datafile::init_datafiles(%params); + if (my @datafiles = + DBD::MSSQL::Server::Database::Datafile::return_datafiles()) { + $self->{datafiles} = \@datafiles; + } else { + $self->add_nagios_critical("unable to aquire datafile info"); + } + } elsif ($params{mode} =~ /server::database::databasefree/) { + ################################################################################### + # fuer's museum + # 1> sp_spaceused + # 2> go + # database_name database_size unallocated space + # master 4.50 MB 1.32 MB + # reserved data index_size unused + # 2744 KB 1056 KB 1064 KB 624 KB + # (return status = 0) + #my($database_name, $database_size, $unallocated_space, + # $reserved, $data, $index_size, $unused) = + # $params{handle}->fetchrow_array( + # "USE ".$self->{name}."\nEXEC SP_SPACEUSED" + #); + # server mgmt studio sp_spaceused + # Currently Allocated Space database_size 641.94MB + # Available Free Space unallocated space 457.09MB + #$database_size =~ s/MB//g; + #$unallocated_space =~ s/MB//g; + #$self->{size} = $database_size * 1024 * 1024; + #$self->{free} = $unallocated_space * 1024 * 1024; + #$self->{percent_free} = $unallocated_space / $database_size * 100; + #$self->{used} = $self->{size} - $self->{free}; + #$self->{maxsize} = "99999999999999999"; + ################################################################################### + my $calc = {}; + $self->{handle}->execute(q{ + if object_id('tempdb..#FreeSpace') is null + create table #FreeSpace( + Drive varchar(10), + MB_Free bigint + ) + }); + $self->{handle}->execute(q{ + DELETE FROM #FreeSpace + }); + $self->{handle}->execute(q{ + INSERT INTO #FreeSpace exec master.dbo.xp_fixeddrives + }); + foreach($self->{handle}->fetchall_array(q{ + SELECT * FROM #FreeSpace + })) { + $calc->{drive_mb}->{lc $_->[0]} = $_->[1]; + } + #$self->{handle}->execute(q{ + # DROP TABLE #FreeSpace + #}); + # Page = 8KB + # sysfiles ist sv2000, noch als kompatibilitaetsview vorhanden + # dbo.sysfiles kann 2008 durch sys.database_files ersetzt werden? + # omeiomeiomei in 2005 ist ein sys.sysindexes compatibility view + # fuer 2000.dbo.sysindexes + # besser ist sys.allocation_units + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + my $sql = q{ + SELECT + SUM(CAST(used AS BIGINT)) / 128 + FROM + [?].sys.sysindexes + WHERE + indid IN (0,1,255) + }; + #$sql =~ s/\[\?\]/$self->{name}/g; + $sql =~ s/\?/$self->{name}/g; + $self->{used_mb} = $self->{handle}->fetchrow_array($sql); + } else { + my $sql = q{ + SELECT + SUM(CAST(used AS BIGINT)) / 128 + FROM + [?].dbo.sysindexes + WHERE + indid IN (0,1,255) + }; + #$sql =~ s/\[\?\]/$self->{name}/g; + $sql =~ s/\?/$self->{name}/g; + $self->{used_mb} = $self->{handle}->fetchrow_array($sql); + } + my @fileresult = (); + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + my $sql = q{ + SELECT + RTRIM(a.name), RTRIM(a.filename), CAST(a.size AS BIGINT), + CAST(a.maxsize AS BIGINT), a.growth + FROM + [?].sys.sysfiles a + JOIN + [?].sys.sysfilegroups b + ON + a.groupid = b.groupid + }; + #$sql =~ s/\[\?\]/$self->{name}/g; + $sql =~ s/\?/$self->{name}/g; + @fileresult = $self->{handle}->fetchall_array($sql); + } else { + my $sql = q{ + SELECT + RTRIM(a.name), RTRIM(a.filename), CAST(a.size AS BIGINT), + CAST(a.maxsize AS BIGINT), a.growth + FROM + [?].dbo.sysfiles a + JOIN + [?].dbo.sysfilegroups b + ON + a.groupid = b.groupid + }; + #$sql =~ s/\[\?\]/$self->{name}/g; + $sql =~ s/\?/$self->{name}/g; + @fileresult = $self->{handle}->fetchall_array($sql); + } + foreach(@fileresult) { + my($name, $filename, $size, $maxsize, $growth) = @{$_}; + my $drive = lc substr($filename, 0, 1); + $calc->{datafile}->{$name}->{allocsize} = $size / 128; + if ($growth == 0) { + $calc->{datafile}->{$name}->{maxsize} = $size / 128; + } else { + if ($maxsize == -1) { + $calc->{datafile}->{$name}->{maxsize} = + exists $calc->{drive_mb}->{$drive} ? + ($calc->{datafile}->{$name}->{allocsize} + + $calc->{drive_mb}->{$drive}) : 4 * 1024; + # falls die platte nicht gefunden wurde, dann nimm halt 4GB + } else { + $calc->{datafile}->{$name}->{maxsize} = $maxsize / 128; + } + } + $self->{allocated_mb} += $calc->{datafile}->{$name}->{allocsize}; + $self->{max_mb} += $calc->{datafile}->{$name}->{maxsize}; + } + $self->{allocated_mb} = $self->{allocated_mb}; + if ($self->{used_mb} > $self->{allocated_mb}) { + # obige used-berechnung liefert manchmal (wenns knapp hergeht) mehr als + # den maximal verfuegbaren platz. vermutlich muessen dann + # zwecks ermittlung des tatsaechlichen platzverbrauchs + # irgendwelche dbcc updateusage laufen. + # egal, wird schon irgendwie stimmen. + $self->{used_mb} = $self->{allocated_mb}; + $self->{estimated} = 1; + } else { + $self->{estimated} = 0; + } + $self->{free_mb} = $self->{max_mb} - $self->{used_mb}; + $self->{free_percent} = 100 * $self->{free_mb} / $self->{max_mb}; + $self->{allocated_percent} = 100 * $self->{allocated_mb} / $self->{max_mb}; + } elsif ($params{mode} =~ /^server::database::transactions/) { + $self->{transactions_s} = $self->{handle}->get_perf_counter_instance( + 'SQLServer:Databases', 'Transactions/sec', $self->{name}); + if (! defined $self->{transactions_s}) { + $self->add_nagios_unknown("unable to aquire counter data for $self->{name}"); + } else { + $self->valdiff(\%params, qw(transactions_s)); + $self->{transactions_per_sec} = $self->{delta_transactions_s} / $self->{delta_timestamp}; + } + } +} + +sub nagios { + my $self = shift; + my %params = @_; + if (! $self->{nagios_level}) { + if ($params{mode} =~ /server::database::datafile::listdatafiles/) { + foreach (sort { $a->{logicalfilename} cmp $b->{logicalfilename}; } @{$self->{datafiles}}) { + printf "%s\n", $_->{logicalfilename}; + } + $self->add_nagios_ok("have fun"); + } elsif ($params{mode} =~ /^server::database::transactions/) { + $self->add_nagios( + $self->check_thresholds($self->{transactions_per_sec}, 10000, 50000), + sprintf "%s has %.4f transactions / sec", + $self->{name}, $self->{transactions_per_sec}); + $self->add_perfdata(sprintf "%s_transactions_per_sec=%.4f;%s;%s", + $self->{name}, $self->{transactions_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /server::database::databasefree/) { + # ->percent_free + # ->free + # + # ausgabe + # perfdata db__free_pct + # perfdata db__free (real_bytes_max - bytes) + bytes_free (with units) + # perfdata db__alloc_free bytes_free (with units) + # + # umrechnen der thresholds + # ()/% + # MB + # GB + # KB + if (($self->{warningrange} && $self->{warningrange} !~ /^\d+[\.\d]*:/) || + ($self->{criticalrange} && $self->{criticalrange} !~ /^\d+[\.\d]*:/)) { + $self->add_nagios_unknown("you want an alert if free space is _above_ a threshold????"); + return; + } + if (! $params{units}) { + $params{units} = "%"; + } + $self->{warning_bytes} = 0; + $self->{critical_bytes} = 0; + if ($params{units} eq "%") { + $self->add_nagios( + $self->check_thresholds($self->{free_percent}, "5:", "2:"), + sprintf("database %s has %.2f%% free space left", + $self->{name}, $self->{free_percent}, + ($self->{estimated} ? " (estim.)" : "")) + ); + $self->{warningrange} =~ s/://g; + $self->{criticalrange} =~ s/://g; + $self->add_perfdata(sprintf "\'db_%s_free_pct\'=%.2f%%;%d:;%d:", + lc $self->{name}, + $self->{free_percent}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "\'db_%s_free\'=%dMB;%.2f:;%.2f:;0;%.2f", + lc $self->{name}, + $self->{free_mb}, + $self->{warningrange} * $self->{max_mb} / 100, + $self->{criticalrange} * $self->{max_mb} / 100, + $self->{max_mb}); + $self->add_perfdata(sprintf "\'db_%s_allocated_pct\'=%.2f%%", + lc $self->{name}, + $self->{allocated_percent}); + } else { + my $factor = 1; # default MB + if ($params{units} eq "GB") { + $factor = 1024; + } elsif ($params{units} eq "MB") { + $factor = 1; + } elsif ($params{units} eq "KB") { + $factor = 1 / 1024; + } + $self->{warningrange} ||= "5:"; + $self->{criticalrange} ||= "2:"; + my $saved_warningrange = $self->{warningrange}; + my $saved_criticalrange = $self->{criticalrange}; + # : entfernen weil gerechnet werden muss + $self->{warningrange} =~ s/://g; + $self->{criticalrange} =~ s/://g; + $self->{warningrange} = $self->{warningrange} ? + $self->{warningrange} * $factor : 5 * $factor; + $self->{criticalrange} = $self->{criticalrange} ? + $self->{criticalrange} * $factor : 2 * $factor; + $self->{percent_warning} = 100 * $self->{warningrange} / $self->{max_mb}; + $self->{percent_critical} = 100 * $self->{criticalrange} / $self->{max_mb}; + $self->{warningrange} .= ':'; + $self->{criticalrange} .= ':'; + $self->add_nagios( + $self->check_thresholds($self->{free_mb}, "5242880:", "1048576:"), + sprintf("database %s has %.2f%s free space left", $self->{name}, + $self->{free_mb} / $factor, $params{units}) + ); + $self->{warningrange} = $saved_warningrange; + $self->{criticalrange} = $saved_criticalrange; + $self->{warningrange} =~ s/://g; + $self->{criticalrange} =~ s/://g; + $self->add_perfdata(sprintf "\'db_%s_free_pct\'=%.2f%%;%.2f:;%.2f:", + lc $self->{name}, + $self->{free_percent}, $self->{percent_warning}, + $self->{percent_critical}); + $self->add_perfdata(sprintf "\'db_%s_free\'=%.2f%s;%.2f:;%.2f:;0;%.2f", + lc $self->{name}, + $self->{free_mb} / $factor, $params{units}, + $self->{warningrange}, + $self->{criticalrange}, + $self->{max_mb} / $factor); + $self->add_perfdata(sprintf "\'db_%s_allocated_pct\'=%.2f%%", + lc $self->{name}, + $self->{allocated_percent}); + } + } elsif ($params{mode} =~ /server::database::backupage/) { + if (! defined $self->{backup_age}) { + $self->add_nagios_critical(sprintf "%s was never backupped", + $self->{name}); + $self->{backup_age} = 0; + $self->{backup_duration} = 0; + $self->check_thresholds($self->{backup_age}, 48, 72); # init wg perfdata + } else { + $self->add_nagios( + $self->check_thresholds($self->{backup_age}, 48, 72), + sprintf "%s backupped %dh ago", $self->{name}, $self->{backup_age}); + } + $self->add_perfdata(sprintf "'%s_bck_age'=%d;%s;%s", + $self->{name}, $self->{backup_age}, + $self->{warningrange}, $self->{criticalrange}); + $self->add_perfdata(sprintf "'%s_bck_time'=%d", + $self->{name}, $self->{backup_duration}); + } + } +} + + + +package DBD::MSSQL::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 $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 = { + method => $params{method} || "dbi", + hostname => $params{hostname}, + username => $params{username}, + password => $params{password}, + port => $params{port} || 1433, + server => $params{server}, + timeout => $params{timeout}, + warningrange => $params{warningrange}, + criticalrange => $params{criticalrange}, + version => 'unknown', + os => 'unknown', + servicename => 'unknown', + instance => undef, + memorypool => undef, + databases => [], + handle => undef, + }; + bless $self, $class; + $self->init_nagios(); + if ($self->dbconnect(%params)) { + #$self->{version} = $self->{handle}->fetchrow_array( + # q{ SELECT SERVERPROPERTY('productversion') }); + map { + $self->{os} = $1 if /Windows (.*)/; + $self->{version} = $1 if /SQL Server.*\-\s*([\d\.]+)/; + } $self->{handle}->fetchrow_array( + q{ SELECT @@VERSION }); + $self->{dbuser} = $self->{handle}->fetchrow_array( + q{ SELECT SYSTEM_USER }); # maybe SELECT SUSER_SNAME() + $self->{servicename} = $self->{handle}->fetchrow_array( + q{ SELECT @@SERVICENAME }); + if (lc $self->{servicename} ne 'mssqlserver') { + # braucht man fuer abfragen von dm_os_performance_counters + # object_name ist entweder "SQLServer:Buffer Node" oder z.b. "MSSQL$OASH: Buffer Node" + $self->{servicename} = 'MSSQL$'.$self->{servicename}; + } else { + $self->{servicename} = 'SQLServer'; + } + DBD::MSSQL::Server::add_server($self); + $self->init(%params); + } + return $self; +} + +sub init { + my $self = shift; + my %params = @_; + $params{handle} = $self->{handle}; + if ($params{mode} =~ /^server::memorypool/) { + $self->{memorypool} = DBD::MSSQL::Server::Memorypool->new(%params); + } elsif ($params{mode} =~ /^server::database/) { + DBD::MSSQL::Server::Database::init_databases(%params); + if (my @databases = + DBD::MSSQL::Server::Database::return_databases()) { + $self->{databases} = \@databases; + } else { + $self->add_nagios_critical("unable to aquire database info"); + } + } elsif ($params{mode} =~ /^server::connectiontime/) { + $self->{connection_time} = $self->{tac} - $self->{tic}; + } elsif ($params{mode} =~ /^server::cpubusy/) { + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + ($self->{secs_busy}) = $self->{handle}->fetchrow_array(q{ + SELECT ((@@CPU_BUSY * CAST(@@TIMETICKS AS FLOAT)) / + (SELECT (CAST(CPU_COUNT AS FLOAT) / CAST(HYPERTHREAD_RATIO AS FLOAT)) FROM sys.dm_os_sys_info) / + 1000000) + }); + $self->valdiff(\%params, qw(secs_busy)); + if (defined $self->{secs_busy}) { + $self->{cpu_busy} = 100 * + $self->{delta_secs_busy} / $self->{delta_timestamp}; + } else { + $self->add_nagios_critical("got no cputime from dm_os_sys_info"); + } + } else { + #$self->requires_version('9'); + my @monitor = $params{handle}->exec_sp_1hash(q{exec sp_monitor}); + foreach (@monitor) { + if ($_->[0] eq 'cpu_busy') { + if ($_->[1] =~ /(\d+)%/) { + $self->{cpu_busy} = $1; + } + } + } + self->requires_version('9') unless defined $self->{cpu_busy}; + } + } elsif ($params{mode} =~ /^server::iobusy/) { + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + ($self->{secs_busy}) = $self->{handle}->fetchrow_array(q{ + SELECT ((@@IO_BUSY * CAST(@@TIMETICKS AS FLOAT)) / + (SELECT (CAST(CPU_COUNT AS FLOAT) / CAST(HYPERTHREAD_RATIO AS FLOAT)) FROM sys.dm_os_sys_info) / + 1000000) + }); + $self->valdiff(\%params, qw(secs_busy)); + if (defined $self->{secs_busy}) { + $self->{io_busy} = 100 * + $self->{delta_secs_busy} / $self->{delta_timestamp}; + } else { + $self->add_nagios_critical("got no iotime from dm_os_sys_info"); + } + } else { + #$self->requires_version('9'); + my @monitor = $params{handle}->exec_sp_1hash(q{exec sp_monitor}); + foreach (@monitor) { + if ($_->[0] eq 'io_busy') { + if ($_->[1] =~ /(\d+)%/) { + $self->{io_busy} = $1; + } + } + } + self->requires_version('9') unless defined $self->{io_busy}; + } + } elsif ($params{mode} =~ /^server::fullscans/) { + $self->{cnt_full_scans_s} = $self->{handle}->get_perf_counter( + 'SQLServer:Access Methods', 'Full Scans/sec'); + if (! defined $self->{cnt_full_scans_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(cnt_full_scans_s)); + $self->{full_scans_per_sec} = $self->{delta_cnt_full_scans_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /^server::latch::waittime/) { + $self->{latch_wait_time} = $self->{handle}->get_perf_counter( + "SQLServer:Latches", "Average Latch Wait Time (ms)"); + $self->{latch_wait_time_base} = $self->{handle}->get_perf_counter( + "SQLServer:Latches", "Average Latch Wait Time Base"); + if (! defined $self->{latch_wait_time}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } + $self->{latch_wait_time} = $self->{latch_wait_time} / $self->{latch_wait_time_base}; + } elsif ($params{mode} =~ /^server::latch::waits/) { + $self->{latch_waits_s} = $self->{handle}->get_perf_counter( + "SQLServer:Latches", "Latch Waits/sec"); + if (! defined $self->{latch_waits_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(latch_waits_s)); + $self->{latch_waits_per_sec} = $self->{delta_latch_waits_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /^server::sql::.*compilations/) { + $self->{recompilations_s} = $self->{handle}->get_perf_counter( + "SQLServer:SQL Statistics", "SQL Re-Compilations/sec"); + $self->{compilations_s} = $self->{handle}->get_perf_counter( + "SQLServer:SQL Statistics", "SQL Compilations/sec"); + if (! defined $self->{recompilations_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(recompilations_s compilations_s)); + # http://www.sqlmag.com/Articles/ArticleID/40925/pg/3/3.html + # http://www.grumpyolddba.co.uk/monitoring/Performance%20Counter%20Guidance%20-%20SQL%20Server.htm + $self->{delta_initial_compilations_s} = $self->{delta_compilations_s} - + $self->{delta_recompilations_s}; + $self->{initial_compilations_per_sec} = + $self->{delta_initial_compilations_s} / $self->{delta_timestamp}; + $self->{recompilations_per_sec} = + $self->{delta_recompilations_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /^server::batchrequests/) { + $self->{batch_requests_s} = $self->{handle}->get_perf_counter( + "SQLServer:SQL Statistics", "Batch requests/sec"); + if (! defined $self->{batch_requests_s}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } else { + $self->valdiff(\%params, qw(batch_requests_s)); + $self->{batch_requests_per_sec} = $self->{delta_batch_requests_s} / $self->{delta_timestamp}; + } + } elsif ($params{mode} =~ /^server::totalmemory/) { + $self->{total_memory} = $self->{handle}->get_perf_counter( + "SQLServer:Memory Manager", "Total Server Memory (KB)"); + if (! defined $self->{total_memory}) { + $self->add_nagios_unknown("unable to aquire counter data"); + } + } elsif ($params{mode} =~ /^server::connectedusers/) { + $self->{connectedusers} = $self->{handle}->fetchrow_array(q{ + SELECT + COUNT(*) + FROM + master..sysprocesses + WHERE + spid > ? + }, 51); + if (! defined $self->{connectedusers}) { + $self->add_nagios_unknown("unable to count connected users"); + } + } elsif ($params{mode} =~ /^server::sql/) { + @{$self->{genericsql}} = + $self->{handle}->fetchrow_array($params{selectname}); + if ((scalar(@{$self->{genericsql}}) == 0) || + (! (defined $self->{genericsql} && + (scalar(grep { /^\s*\d+\.{0,1}\d*\s*$/ } @{$self->{genericsql}})) == + scalar(@{$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} =~ /^my::([^:.]+)/) { + my $class = $1; + my $loaderror = undef; + substr($class, 0, 1) = uc substr($class, 0, 1); + foreach my $libpath (split(":", $DBD::MSSQL::Server::my_modules_dyn_dir)) { + foreach my $extmod (glob $libpath."/CheckMSSQLHealth*.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::MSSQL::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::MSSQL::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::listdatabases/) { + foreach (sort { $a->{name} cmp $b->{name}; } @{$self->{databases}}) { + printf "%s\n", $_->{name}; + } + $self->add_nagios_ok("have fun"); + } elsif ($params{mode} =~ /^server::database/) { + foreach (@{$self->{databases}}) { + $_->nagios(%params); + $self->merge_nagios($_); + } + } elsif ($params{mode} =~ /^server::database/) { + } elsif ($params{mode} =~ /^server::lock/) { + foreach (@{$self->{locks}}) { + $_->nagios(%params); + $self->merge_nagios($_); + } + } elsif ($params{mode} =~ /^server::memorypool/) { + $self->{memorypool}->nagios(%params); + $self->merge_nagios($self->{memorypool}); + } 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->{dbuser}); + $self->add_perfdata(sprintf "connection_time=%.2f;%d;%d", + $self->{connection_time}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::cpubusy/) { + $self->add_nagios( + $self->check_thresholds($self->{cpu_busy}, 80, 90), + sprintf "CPU busy %.2f%%", $self->{cpu_busy}); + $self->add_perfdata(sprintf "cpu_busy=%.2f;%s;%s", + $self->{cpu_busy}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::iobusy/) { + $self->add_nagios( + $self->check_thresholds($self->{io_busy}, 80, 90), + sprintf "IO busy %.2f%%", $self->{io_busy}); + $self->add_perfdata(sprintf "io_busy=%.2f;%s;%s", + $self->{io_busy}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::fullscans/) { + $self->add_nagios( + $self->check_thresholds($self->{full_scans_per_sec}, 100, 500), + sprintf "%.2f full table scans / sec", $self->{full_scans_per_sec}); + $self->add_perfdata(sprintf "full_scans_per_sec=%.2f;%s;%s", + $self->{full_scans_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::latch::waits/) { + $self->add_nagios( + $self->check_thresholds($self->{latch_waits_per_sec}, 10, 50), + sprintf "%.2f latches / sec have to wait", $self->{latch_waits_per_sec}); + $self->add_perfdata(sprintf "latch_waits_per_sec=%.2f;%s;%s", + $self->{latch_waits_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::latch::waittime/) { + $self->add_nagios( + $self->check_thresholds($self->{latch_wait_time}, 1, 5), + sprintf "latches have to wait %.2f ms avg", $self->{latch_wait_time}); + $self->add_perfdata(sprintf "latch_avg_wait_time=%.2fms;%s;%s", + $self->{latch_wait_time}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::sql::recompilations/) { + $self->add_nagios( + $self->check_thresholds($self->{recompilations_per_sec}, 1, 10), + sprintf "%.2f SQL recompilations / sec", $self->{recompilations_per_sec}); + $self->add_perfdata(sprintf "sql_recompilations_per_sec=%.2f;%s;%s", + $self->{recompilations_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::sql::initcompilations/) { + $self->add_nagios( + $self->check_thresholds($self->{initial_compilations_per_sec}, 100, 200), + sprintf "%.2f initial compilations / sec", $self->{initial_compilations_per_sec}); + $self->add_perfdata(sprintf "sql_initcompilations_per_sec=%.2f;%s;%s", + $self->{initial_compilations_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::batchrequests/) { + $self->add_nagios( + $self->check_thresholds($self->{batch_requests_per_sec}, 100, 200), + sprintf "%.2f batch requests / sec", $self->{batch_requests_per_sec}); + $self->add_perfdata(sprintf "batch_requests_per_sec=%.2f;%s;%s", + $self->{batch_requests_per_sec}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::totalmemory/) { + $self->add_nagios( + $self->check_thresholds($self->{total_memory}, 1000, 5000), + sprintf "total server memory %ld", $self->{total_memory}); + $self->add_perfdata(sprintf "total_server_memory=%ld;%s;%s", + $self->{total_memory}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::connectedusers/) { + $self->add_nagios( + $self->check_thresholds($self->{connectedusers}, 50, 80), + sprintf "%d connected users", $self->{connectedusers}); + $self->add_perfdata(sprintf "connected_users=%d;%s;%s", + $self->{connectedusers}, + $self->{warningrange}, $self->{criticalrange}); + } elsif ($params{mode} =~ /^server::sql/) { + $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} !~ /:/ && $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::MSSQL::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::MSSQL::Server::Connection->new(%params); + if ($self->{handle}->{errstr}) { + if ($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->{server} ? $self->{server} : + ($self->{hostname} ? $self->{hostname} : "unknown host")), + $self->{handle}->{errstr}); + $retval = undef; + } + } else { + $retval = $self->{handle}; + } + $self->{tac} = Time::HiRes::time(); + return $retval; +} + +sub trace { + my $self = shift; + my $format = shift; + if (! @_) { + # falls im sql-statement % vorkommen. sonst krachts im printf + $format =~ s/%/%%/g; + } + $self->{trace} = -f "/tmp/check_mssql_health.trace" ? 1 : 0; + if ($DBD::MSSQL::Server::verbose) { + printf("%s: ", scalar localtime); + printf($format, @_); + } + if ($self->{trace}) { + my $logfh = new IO::File; + $logfh->autoflush(1); + if ($logfh->open("/tmp/check_mssql_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::MSSQL::Server") { + } + $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2); + if (ref($self) eq "DBD::MSSQL::Server") { + #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{server}), $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $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{server}), $params{mode}; + $extension .= $params{differenciator} ? "_".$params{differenciator} : ""; + $extension .= $params{port} ? "_".$params{port} : ""; + $extension .= $params{database} ? "_".$params{database} : ""; + $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) { + $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'} = 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::MSSQL::Server::return_servers(); + my $instversion = $instances[0]->{version}; + if (! $self->version_is_minimum($version)) { + $self->add_nagios($ERRORS{UNKNOWN}, + sprintf "not implemented/possible for MSSQL 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::MSSQL::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_rac { + my $self = shift; + my @instances = DBD::MSSQL::Server::return_servers(); + return (lc $instances[0]->{parallel} eq "yes") ? 1 : 0; +} + +sub instance_thread { + my $self = shift; + my @instances = DBD::MSSQL::Server::return_servers(); + return $instances[0]->{thread}; +} + +sub windows_server { + my $self = shift; + my @instances = DBD::MSSQL::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_mssql_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::MSSQL::Server::Connection; + +use strict; + +our @ISA = qw(DBD::MSSQL::Server); + + +sub new { + my $class = shift; + my %params = @_; + my $self = { + mode => $params{mode}, + timeout => $params{timeout}, + method => $params{method} || "dbi", + hostname => $params{hostname}, + username => $params{username}, + password => $params{password}, + port => $params{port} || 1433, + server => $params{server}, + handle => undef, + }; + bless $self, $class; + if ($params{method} eq "dbi") { + bless $self, "DBD::MSSQL::Server::Connection::Dbi"; + } elsif ($params{method} eq "tsql") { + bless $self, "DBD::MSSQL::Server::Connection::Tsql"; + } elsif ($params{method} eq "sqlrelay") { + bless $self, "DBD::MSSQL::Server::Connection::Sqlrelay"; + } + $self->init(%params); + return $self; +} + +sub get_instance_names { + my $self = shift; + my $object_name = shift; + my $servicename = DBD::MSSQL::Server::return_first_server()->{servicename}; + if ($object_name =~ /SQLServer:(.*)/) { + $object_name = $servicename.':'.$1; + } + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + return $self->fetchall_array(q{ + SELECT + DISTINCT instance_name + FROM + sys.dm_os_performance_counters + WHERE + object_name = ? + }, $object_name); + } else { + return $self->fetchall_array(q{ + SELECT + DISTINCT instance_name + FROM + master.dbo.sysperfinfo + WHERE + object_name = ? + }, $object_name); + } +} + +sub get_perf_counter { + my $self = shift; + my $object_name = shift; + my $counter_name = shift; + my $servicename = DBD::MSSQL::Server::return_first_server()->{servicename}; + if ($object_name =~ /SQLServer:(.*)/) { + $object_name = $servicename.':'.$1; + } + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + return $self->fetchrow_array(q{ + SELECT + cntr_value + FROM + sys.dm_os_performance_counters + WHERE + counter_name = ? AND + object_name = ? + }, $counter_name, $object_name); + } else { + return $self->fetchrow_array(q{ + SELECT + cntr_value + FROM + master.dbo.sysperfinfo + WHERE + counter_name = ? AND + object_name = ? + }, $counter_name, $object_name); + } +} + +sub get_perf_counter_instance { + my $self = shift; + my $object_name = shift; + my $counter_name = shift; + my $instance_name = shift; + my $servicename = DBD::MSSQL::Server::return_first_server()->{servicename}; + if ($object_name =~ /SQLServer:(.*)/) { + $object_name = $servicename.':'.$1; + } + if (DBD::MSSQL::Server::return_first_server()->version_is_minimum("9.x")) { + return $self->fetchrow_array(q{ + SELECT + cntr_value + FROM + sys.dm_os_performance_counters + WHERE + counter_name = ? AND + object_name = ? AND + instance_name = ? + }, $counter_name, $object_name, $instance_name); + } else { + return $self->fetchrow_array(q{ + SELECT + cntr_value + FROM + master.dbo.sysperfinfo + WHERE + counter_name = ? AND + object_name = ? AND + instance_name = ? + }, $counter_name, $object_name, $instance_name); + } +} + +package DBD::MSSQL::Server::Connection::Dbi; + +use strict; +use Net::Ping; + +our @ISA = qw(DBD::MSSQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + if ($self->{mode} =~ /^server::tnsping/) { + # erstmal reserviert fuer irgendeinen tcp-connect + 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} && ! $self->{server}) || + ! $self->{username} || ! $self->{password}) { + $self->{errstr} = "Please specify hostname or server, username and password"; + return undef; + } + $self->{dsn} = "DBI:Sybase:"; + if ($self->{hostname}) { + $self->{dsn} .= sprintf ";host=%s", $self->{hostname}; + $self->{dsn} .= sprintf ";port=%s", $self->{port}; + } else { + $self->{dsn} .= sprintf ";server=%s", $self->{server}; + } + } + if (! exists $self->{errstr}) { + eval { + require DBI; + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + 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->{password}, + { RaiseError => 1, AutoCommit => 0, PrintError => 1 })) { + $retval = $self; + } else { + # doesnt seem to work $self->{errstr} = DBI::errstr(); + $self->{errstr} = "connect failed"; + return undef; + } + }; + if ($@) { + $self->{errstr} = $@; + $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 = (); + 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) || die DBI::errstr(); + } else { + $sth->execute() || die DBI::errstr(); + } + if (lc $sql =~ /^(exec |sp_)/) { + # flatten the result sets + do { + while (my $aref = $sth->fetchrow_arrayref()) { + push(@row, @{$aref}); + } + } while ($sth->{syb_more_results}); + } else { + @row = $sth->fetchrow_array(); + } + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper(\@row)); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + if (-f "/tmp/check_mssql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mssql_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_mssql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mssql_health_simulation/".$self->{mode}; <> }; + @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation); + } + return @{$rows}; +} + +sub exec_sp_1hash { + 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(); + } + do { + while (my $href = $sth->fetchrow_hashref()) { + foreach (keys %{$href}) { + push(@{$rows}, [ $_, $href->{$_} ]); + } + } + } while ($sth->{syb_more_results}); + $self->trace(sprintf "RESULT:\n%s\n", + Data::Dumper::Dumper($rows)); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } + return @{$rows}; +} + + +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::MSSQL::Server::Connection::Tsql; + +use strict; +use File::Temp qw/tempfile/; + +our @ISA = qw(DBD::MSSQL::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->{user} ||= time; # prefer an existing user + $self->{password} = time; + } + } else { + if ($self->{connect} && ! $self->{user} && ! $self->{password} && + $self->{connect} =~ /(\w+)\/(\w+)@(\w+)/) { + # --connect nagios/oradbmon@bba + $self->{connect} = $3; + $self->{user} = $1; + $self->{password} = $2; + $self->{sid} = $self->{connect}; + if ($self->{user} eq "sys") { + delete $ENV{TWO_TASK}; + $self->{loginstring} = "sys"; + } else { + $self->{loginstring} = "traditional"; + } + } elsif ($self->{connect} && ! $self->{user} && ! $self->{password} && + $self->{connect} =~ /sysdba@(\w+)/) { + # --connect sysdba@bba + $self->{connect} = $1; + $self->{user} = "/"; + $self->{sid} = $self->{connect}; + $self->{loginstring} = "sysdba"; + } elsif ($self->{connect} && ! $self->{user} && ! $self->{password} && + $self->{connect} =~ /(\w+)/) { + # --connect bba + $self->{connect} = $1; + # maybe this is a os authenticated user + delete $ENV{TWO_TASK}; + $self->{sid} = $self->{connect}; + if ($^O ne "hpux") { #hpux && 1.21 only accepts "DBI:MSSQL:SID" + $self->{connect} = ""; #linux 1.20 only accepts "DBI:MSSQL:" + MSSQL_SID + } + $self->{user} = '/'; + $self->{password} = ""; + $self->{loginstring} = "extauth"; + } elsif ($self->{user} && + $self->{user} =~ /^\/@(\w+)/) { + # --user /@ubba1 + $self->{user} = $1; + $self->{sid} = $self->{connect}; + $self->{loginstring} = "passwordstore"; + } elsif ($self->{connect} && $self->{user} && ! $self->{password} && + $self->{user} eq "sysdba") { + # --connect bba --user sysdba + $self->{connect} = $1; + $self->{user} = "/"; + $self->{sid} = $self->{connect}; + $self->{loginstring} = "sysdba"; + } elsif ($self->{connect} && $self->{user} && $self->{password}) { + # --connect bba --user nagios --password oradbmon + $self->{sid} = $self->{connect}; + $self->{loginstring} = "traditional"; + } else { + $self->{errstr} = "Please specify database, username and password"; + return undef; + } + } + if (! exists $self->{errstr}) { + eval { + $ENV{MSSQL_SID} = $self->{sid}; + $ENV{PATH} = $ENV{MSSQL_HOME}."/bin". + (defined $ENV{PATH} ? + ":".$ENV{PATH} : ""); + $ENV{LD_LIBRARY_PATH} = $ENV{MSSQL_HOME}."/lib". + (defined $ENV{LD_LIBRARY_PATH} ? + ":".$ENV{LD_LIBRARY_PATH} : ""); + # am 30.9.2008 hat perl das /bin/sqlplus in $ENV{MSSQL_HOME}.'/bin/sqlplus' + # eiskalt evaluiert und + # /u00/app/mssql/product/11.1.0/db_1/u00/app/mssql/product/11.1.0/db_1/bin/sqlplus + # draus gemacht. Leider nicht in Mini-Scripts reproduzierbar, sondern nur hier. + # Das ist der Grund fuer die vielen ' und . + my $sqlplus = $ENV{MSSQL_HOME}.'/'.'bin'.'/'.'sqlplus'; + if ((-x $ENV{MSSQL_HOME}.'/'.'sqlplus') && ( -f $ENV{MSSQL_HOME}.'/'.'sqlplus')) { + $sqlplus = $ENV{MSSQL_HOME}.'/'.'sqlplus'; + } + my $tnsping = $ENV{MSSQL_HOME}.'/'.'bin'.'/'.'tnsping'; + if (! -x $sqlplus) { + die "nosqlplus\n"; + } + if ($self->{mode} =~ /^server::tnsping/) { + if ($self->{loginstring} eq "traditional") { + $self->{sqlplus} = sprintf "%s -S %s/%s@%s < /dev/null", + $sqlplus, + $self->{user}, $self->{password}, $self->{sid}; + } elsif ($self->{loginstring} eq "extauth") { + $self->{sqlplus} = sprintf "%s -S / < /dev/null", + $sqlplus; + } elsif ($self->{loginstring} eq "passwordstore") { + $self->{sqlplus} = sprintf "%s -S /@%s < /dev/null", + $sqlplus, + $self->{user}; + } elsif ($self->{loginstring} eq "sysdba") { + $self->{sqlplus} = sprintf "%s -S / as sysdba < /dev/null", + $sqlplus; + } elsif ($self->{loginstring} eq "sys") { + $self->{sqlplus} = sprintf "%s -S %s/%s@%s as sysdba < /dev/null", + $sqlplus, + $self->{user}, $self->{password}, $self->{sid}; + } + } else { + if ($self->{loginstring} eq "traditional") { + $self->{sqlplus} = sprintf "%s -S %s/%s@%s < %s > %s", + $sqlplus, + $self->{user}, $self->{password}, $self->{sid}, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } elsif ($self->{loginstring} eq "extauth") { + $self->{sqlplus} = sprintf "%s -S / < %s > %s", + $sqlplus, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } elsif ($self->{loginstring} eq "passwordstore") { + $self->{sqlplus} = sprintf "%s -S /@%s < %s > %s", + $sqlplus, + $self->{user}, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } elsif ($self->{loginstring} eq "sysdba") { + $self->{sqlplus} = sprintf "%s -S / as sysdba < %s > %s", + $sqlplus, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } elsif ($self->{loginstring} eq "sys") { + $self->{sqlplus} = sprintf "%s -S %s/%s@%s as sysdba < %s > %s", + $sqlplus, + $self->{user}, $self->{password}, $self->{sid}, + $self->{sql_commandfile}, $self->{sql_resultfile}; + } + } + + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + 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->{mode} =~ /^server::tnsping/) { + if (-x $tnsping) { + my $exit_output = `$tnsping $self->{sid}`; + if ($?) { + # printf STDERR "tnsping exit bumm \n"; + # immer 1 bei misserfolg + } + if ($exit_output =~ /^OK \(\d+/m) { + die "ORA-01017"; # fake a successful connect with wrong password + } elsif ($exit_output =~ /^(TNS\-\d+)/m) { + die $1; + } else { + die "TNS-03505"; + } + } else { + my $exit_output = `$self->{sqlplus}`; + if ($?) { + printf STDERR "ping exit bumm \n"; + } + $exit_output =~ s/\n//g; + $exit_output =~ s/at $0//g; + chomp $exit_output; + die $exit_output; + } + } else { + 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 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->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 { + /(ORA\-\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(/\|/, (split(/\n/, $output))[0]); + } + 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->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 { + /(ORA\-\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 /\|/ + ] } grep { ! /^\d+ rows selected/ } + grep { ! /^Elapsed: / } + grep { ! /^\s*$/ } split(/\n/, $output); + $rows = \@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 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 "SET HEADING OFF\n"; + printf CMDCMD "SET PAGESIZE 0\n"; + printf CMDCMD "SET LINESIZE 32767\n"; + printf CMDCMD "SET COLSEP '|'\n"; + printf CMDCMD "SET TAB OFF\n"; + printf CMDCMD "SET TRIMSPOOL ON\n"; + printf CMDCMD "SET NUMFORMAT 9.999999EEEE\n"; + printf CMDCMD "SPOOL %s\n", $self->{sql_resultfile}; +# printf CMDCMD "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,';\n/\n"; + printf CMDCMD "%s\n/\n", $sql; + printf CMDCMD "EXIT\n"; + close CMDCMD; +} + +package DBD::MSSQL::Server::Connection::Sqlrelay; + +use strict; +use Net::Ping; + +our @ISA = qw(DBD::MSSQL::Server::Connection); + + +sub init { + my $self = shift; + my %params = @_; + my $retval = undef; + if ($self->{mode} =~ /^server::tnsping/) { + } else { + if (! $self->{hostname} || ! $self->{username} || ! $self->{password} || ! $self->{port}) { + $self->{errstr} = "Please specify database, username and password"; + return undef; + } + } + if (! exists $self->{errstr}) { + eval { + require DBI; + use POSIX ':signal_h'; + local $SIG{'ALRM'} = sub { + die "alarm\n"; + }; + 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", + sprintf("DBI:SQLRelay:host=%s;port=%d;", + $self->{hostname}, $self->{port}), + $self->{username}, + $self->{password}, + { RaiseError => 1, AutoCommit => 0, PrintError => 1 })) { + } 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 = (); + my $new_dbh = $self->{handle}->clone(); + $self->trace(sprintf "fetchrow_array: %s", $sql); + # + # does not work with bind variables + # + while ($sql =~ /\?/) { + my $param = shift @arguments; + if ($param !~ /^\d+$/) { + $param = $self->{handle}->quote($param); + } + $sql =~ s/\?/$param/; + } + $sql =~ s/^\s*//g; + $sql =~ s/\s*$//g; + eval { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + @row = $sth->fetchrow_array(); + $sth->finish(); + }; + if ($@) { + $self->debug(sprintf "bumm %s", $@); + } + # without this trick, there are error messages like + # "No server-side cursors were available to process the query" + # and the results are messed up. + $self->{handle}->disconnect(); + $self->{handle} = $new_dbh; + if (-f "/tmp/check_mssql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mssql_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; + my $new_dbh = $self->{handle}->clone(); + $self->trace(sprintf "fetchall_array: %s", $sql); + while ($sql =~ /\?/) { + my $param = shift @arguments; + if ($param !~ /^\d+$/) { + $param = $self->{handle}->quote($param); + } + $sql =~ s/\?/$param/; + } + eval { + $sth = $self->{handle}->prepare($sql); + if (scalar(@arguments)) { + $sth->execute(@arguments); + } else { + $sth->execute(); + } + $rows = $sth->fetchall_arrayref(); + my $asrows = $sth->fetchall_arrayref(); + $sth->finish(); + }; + if ($@) { + printf STDERR "bumm %s\n", $@; + } + $self->{handle}->disconnect(); + $self->{handle} = $new_dbh; + if (-f "/tmp/check_mssql_health_simulation/".$self->{mode}) { + my $simulation = do { local (@ARGV, $/) = + "/tmp/check_mssql_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 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_mssql_health"; +$REVISION = '$Revision: 1.5.3 $'; +$CONTACT = 'gerhard.lausser@consol.de'; +$TIMEOUT = 60; +$STATEFILESDIR = '/var/tmp/check_mssql_health'; +$needs_restart = 0; + +my @modes = ( + ['server::connectiontime', + 'connection-time', undef, + 'Time to connect to the server' ], + ['server::cpubusy', + 'cpu-busy', undef, + 'Cpu busy in percent' ], + ['server::iobusy', + 'io-busy', undef, + 'IO busy in percent' ], + ['server::fullscans', + 'full-scans', undef, + 'Full table scans per second' ], + ['server::connectedusers', + 'connected-users', undef, + 'Number of currently connected users' ], + ['server::database::transactions', + 'transactions', undef, + 'Transactions per second (per database)' ], + ['server::batchrequests', + 'batch-requests', undef, + 'Batch requests per second' ], + ['server::latch::waits', + 'latches-waits', undef, + 'Number of latch requests that could not be granted immediately' ], + ['server::latch::waittime', + 'latches-wait-time', undef, + 'Average time for a latch to wait before the request is met' ], + ['server::memorypool::lock::waits', + 'locks-waits', undef, + 'The number of locks per second that had to wait' ], + ['server::memorypool::lock::timeouts', + 'locks-timeouts', undef, + 'The number of locks per second that timed out' ], + ['server::memorypool::lock::deadlocks', + 'locks-deadlocks', undef, + 'The number of deadlocks per second' ], + ['server::sql::recompilations', + 'sql-recompilations', undef, + 'Re-Compilations per second' ], + ['server::sql::initcompilations', + 'sql-initcompilations', undef, + 'Initial compilations per second' ], + ['server::totalmemory', + 'total-server-memory', undef, + 'The amount of memory that SQL Server has allocated to it' ], + ['server::memorypool::buffercache::hitratio', + 'mem-pool-data-buffer-hit-ratio', ['buffer-cache-hit-ratio'], + 'Data Buffer Cache Hit Ratio' ], + ['server::memorypool::buffercache::hitratio', + 'mem-pool-data-buffer-hit-ratio', ['buffer-cache-hit-ratio'], + 'Data Buffer Cache Hit Ratio' ], + + + ['server::memorypool::buffercache::lazywrites', + 'lazy-writes', undef, + 'Lazy writes per second' ], + ['server::memorypool::buffercache::pagelifeexpectancy', + 'page-life-expectancy', undef, + 'Seconds a page is kept in memory before being flushed' ], + ['server::memorypool::buffercache::freeliststalls', + 'free-list-stalls', undef, + 'Requests per second that had to wait for a free page' ], + ['server::memorypool::buffercache::checkpointpages', + 'checkpoint-pages', undef, + 'Dirty pages flushed to disk per second. (usually by a checkpoint)' ], + + + ['server::database::databasefree', + 'database-free', undef, + 'Free space in database' ], + ['server::database::backupage', + 'backup-age', undef, + 'Elapsed time (in hours) since a database was last backupped' ], + ['server::sql', + 'sql', undef, + 'any sql command returning a single number' ], + ['server::database::listdatabases', + 'list-databases', undef, + 'convenience function which lists all databases' ], + ['server::database::datafile::listdatafiles', + 'list-datafiles', undef, + 'convenience function which lists all datafiles' ], + ['server::memorypool::lock::listlocks', + 'list-locks', undef, + 'convenience function which lists all locks' ], +); + +sub print_usage () { + print <] --hostname= + --username= --password= [--port ] + --mode= + $PROGNAME [-h | --help] + $PROGNAME [-V | --version] + + Options: + --hostname + the database server + --username + the mssql user + --password + the mssql user's password + --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 <; + 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::MSSQL::Server::verbose = exists $commandline{verbose}; +} + +if (exists $commandline{scream}) { +# $DBD::MSSQL::Server::hysterical = exists $commandline{scream}; +} + +$DBD::MSSQL::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::MSSQL::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 + MSSQL_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::MSSQL::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 && ($> == 0)) { + # this was not my idea. some people connect as root to their nagios clients. + exec "su", "-c", sprintf("%s %s", $0, join(" ", @newargv)), "-", $runas; + } elsif ($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{name}) { + # 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 = ""; +my $racmode = 0; +if ($commandline{mode} =~ /^rac-([^\-.]+)/) { + $racmode = 1; + $commandline{mode} =~ s/^rac\-//g; +} +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], + racmode => $racmode, + method => $commandline{method} || + $ENV{NAGIOS__SERVICEMSSQL_METH} || + $ENV{NAGIOS__HOSTMSSQL_METH} || 'dbi', + hostname => $commandline{hostname} || + $ENV{NAGIOS__SERVICEMSSQL_HOST} || + $ENV{NAGIOS__HOSTMSSQL_HOST}, + username => $commandline{username} || + $ENV{NAGIOS__SERVICEMSSQL_USER} || + $ENV{NAGIOS__HOSTMSSQL_USER}, + password => $commandline{password} || + $ENV{NAGIOS__SERVICEMSSQL_PASS} || + $ENV{NAGIOS__HOSTMSSQL_PASS}, + port => $commandline{port} || + $ENV{NAGIOS__SERVICEMSSQL_PORT} || + $ENV{NAGIOS__HOSTMSSQL_PORT}, + server => $commandline{server} || + $ENV{NAGIOS__SERVICEMSSQL_SERVER} || + $ENV{NAGIOS__HOSTMSSQL_SERVER}, + warningrange => $commandline{warning}, + criticalrange => $commandline{critical}, + absolute => $commandline{absolute}, + lookback => $commandline{lookback}, + tablespace => $commandline{tablespace}, + database => $commandline{database}, + datafile => $commandline{datafile}, + basis => $commandline{basis}, + selectname => $commandline{name} || $commandline{tablespace} || $commandline{datafile}, + regexp => $commandline{regexp}, + name => $commandline{name}, + name2 => $commandline{name2} || $commandline{name}, + units => $commandline{units}, + eyecandy => $commandline{eyecandy}, + statefilesdir => $STATEFILESDIR, +); + +my $server = undef; + +$server = DBD::MSSQL::Server->new(%params); +$server->nagios(%params); +$server->calculate_result(); +$nagios_message = $server->{nagios_message}; +$nagios_level = $server->{nagios_level}; +$perfdata = $server->{perfdata}; + +printf "%s - %s", $ERRORCODES{$nagios_level}, $nagios_message; +printf " | %s", $perfdata if $perfdata; +printf "\n"; +exit $nagios_level;