#! /usr/bin/perl -w # # check_mssql_monitor - Return cpu workload from sp_monitor procedure of a SQL Server # # This script requires the FreeTDS library and DBD::Sybase Perl # module. The SYBASE environment variable also needs to be defined. # Make sure FreeTDS is compiled with --with-tdsver=8.0 !!! # # It also requires File:::Basename, Nagios::Plugins and Time::HiRes. # # Copyright (c) 2008 Jean-Marc Amiaud # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # use strict; use warnings; use vars qw($PROGNAME $VERSION); use File::Basename qw(basename); use Nagios::Plugin; use Nagios::Plugin::Functions qw(max_state); use Time::HiRes qw(gettimeofday tv_interval); use DBI; $PROGNAME = basename($0); $VERSION = '0.9.0'; my $driver = 'Sybase'; my $database = 'master'; my $query = 'sp_monitor'; my $np = Nagios::Plugin->new( usage => "Usage: %s -H [ -p ] [ -t ]\n" . " -U -P [ -w ] [ -c ]\n" . " [ --cpuWarning ] [ --cpuCritical ]\n" . " [ --ioWarning ] [ --ioCritical ]\n" . ' [ -s ]', version => $VERSION, plugin => $PROGNAME, shortname => uc($PROGNAME), blurb => 'Retreive server workload values with query the sp_monitor procedure', extra => "\n\nCopyright (c) 2008 Hotplug SARL", timeout => 30, ); $np->add_arg( spec => 'hostname|H=s', help => "-H, --hostname=\n" . ' SQL Database hostname', required => 1, ); $np->add_arg( spec => 'port|p=i', help => "-p, --port=\n" . ' SQL TCP port (default: driver-dependent).', required => 0, ); $np->add_arg( spec => 'username|U=s', help => "-U, --username=\n" . ' Username to connect with.', required => 1, ); $np->add_arg( spec => 'password|P=s', help => "-P, --password=\n" . ' Password to use with the username.', required => 1, ); $np->add_arg( spec => 'warning|w=s', help => "-w, --warning=THRESHOLD\n" . " Warning threshold for the responce time. See\n" . " http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT\n" . ' for the threshold format.', required => 0, ); $np->add_arg( spec => 'critical|c=s', help => "-c, --critical=THRESHOLD\n" . " Critical threshold for the responce time. See\n" . " http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT\n" . ' for the threshold format.', required => 0, ); $np->add_arg( spec => 'ioWarning=s', help => "--ioWarning=THRESHOLD\n" . " Warning threshold for the io access value. Value must be numeric. See\n" . " http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT\n" . ' for the threshold format.', required => 0, ); $np->add_arg( spec => 'ioCritical=s', help => "--ioCritical=THRESHOLD\n" . " Critical threshold for the io access value. Value must be numeric. See\n" . " http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT\n" . ' for the threshold format.', required => 0, ); $np->add_arg( spec => 'cpuWarning=s', help => "--cpuWarning=THRESHOLD\n" . " Warning threshold for the cpu load value. Value must be numeric. See\n" . " http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT\n" . ' for the threshold format.', required => 0, ); $np->add_arg( spec => 'cpuCritical=s', help => "--cpuCritical=THRESHOLD\n" . " Critical threshold for the cpu load value. Value must be numeric. See\n" . " http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT\n" . ' for the threshold format.', required => 0, ); $np->add_arg( spec => 'show|s+', help => "-s, --show\n" . ' Show the result values in the status text.', required => 0, ); $np->getopts; # Assign, then check args my $hostname = $np->opts->hostname; my $port = $np->opts->port; my $username = $np->opts->username; my $password = $np->opts->password; my $warning = $np->opts->warning; my $critical = $np->opts->critical; my $show = $np->opts->show; my $verbose = $np->opts->verbose; my $cpuWarning = $np->opts->cpuWarning; my $cpuCritical = $np->opts->cpuCritical; my $ioWarning = $np->opts->ioWarning; my $ioCritical = $np->opts->ioCritical; # TODO: Should check if the DBI driver exists $np->nagios_exit('UNKNOWN', 'Hostname contains invalid characters.') if ($hostname =~ /\`|\~|\!|\$|\%|\^|\&|\*|\||\'|\"|\<|\>|\?|\,|\(|\)|\=/); $np->nagios_exit('UNKNOWN', 'Port must be an integer between 1 and 65535.') if ($port && ($port < 1 || $port > 65535)); $np->nagios_exit('UNKNOWN', 'Username is required.') if ($username eq ''); $np->nagios_exit('UNKNOWN', 'Password is required.') if ($password eq ''); # First set the cpu and io thresholds to validate them and get the threshold object. $np->set_thresholds( warning => $cpuWarning, critical => $cpuCritical, ); my $cpuThreshold = $np->threshold; $np->set_thresholds( warning => $ioWarning, critical => $ioCritical, ); my $ioThreshold = $np->threshold; # Then we can set the normal thresholds for validation and future use. $np->set_thresholds( warning => $warning, critical => $critical, ); # Note: There's no automated way to check if ranges makes sense, so you can # have a WARNING range within a CRITICAL range with no warning. I'm not going # to do N::P's job here so such thresholds are allowed for now. my $cs = "DBI:$driver:" . ($database ? "database=$database;" : '') . "server=$hostname" . ($port ? ";port=$port" : ''); warn("Trying to connect. Connect string: '$cs'\n") if ($verbose); warn("Using the following credentials: $username,$password\n") if ($verbose > 2); # Just in case of problems, let's not hang Nagios alarm $np->opts->timeout; my $timestart = [gettimeofday]; my $dbh = DBI->connect($cs,$username,$password,{PrintWarn=>($verbose ? 1 : 0),PrintError=>($verbose ? 1 : 0)}) or $np->nagios_exit('CRITICAL', $DBI::errstr); warn("Connected. Querying server with '$query'\n") if ($verbose > 1); my $result = ''; # selectrow_array behavior in scalar context is undefined (driver-dependent) # if multiple collumns are returned. Just get the first or only collumn: my $sth = $dbh->prepare($query) or $np->nagios_exit('CRITICAL', $DBI::errstr); $sth->execute() or die "Unable to execute ms sp_monitor:" . $dbh->errstr . "\n"; my $href; while($href = $sth->fetchrow_hashref or $sth->{syb_more_results}) { last if (defined $href->{cpu_busy}); } $sth->finish; for (keys %{$href}) { $href->{$_} =~ s/.*-(\d+)%/$1/; } my $cpu = $href->{'cpu_busy'}; my $io = $href->{'io_busy'}; my $idle = $href->{'idle'}; $dbh->disconnect; my $timeend = [gettimeofday]; #Turn off alarm alarm(0); my $elapsed = tv_interval($timestart, $timeend); warn("Request complete. Time elapsed: $elapsed\n") if ($verbose); warn("Server returned $result\n") if ($verbose > 1); # Add all performance data $np->add_perfdata( label => "time", value => $elapsed, uom => 's', threshold => $np->threshold, ); $np->add_perfdata( label => "cpu", value => $cpu, uom => '%', threshold => $cpuThreshold, ); $np->add_perfdata( label => "io", value => $io, uom => '%', threshold => $ioThreshold, ); $np->add_perfdata( label => "idle", value => $idle, uom => '%', ); my @results; push (@results, $np->check_threshold($elapsed)); push (@results, $np->check_threshold(check => $cpu, warning => $cpuWarning, critical => $cpuCritical)); push (@results, $np->check_threshold(check => $io, warning => $ioWarning, critical => $ioCritical)); push (@results, $idle); warn ('Thresholds results: time=' . $results[0] . ', cpu=' . $results[1] . ', io=' . $results[2] . ', idle=' . $results[3]) if ($verbose); my $status = max_state(@results); if ($show) { $np->nagios_exit($status, "SQL Server load : cpu=$cpu% io=$io% idle=$idle% ($elapsed seconds)"); } else { $np->nagios_exit($status, "SQL Server responded in $elapsed seconds"); }