#!/usr/bin/perl # check_sybase # A nagios plugin that connects to a Sybase database and checks free space. # # Copyright 2004-2005 Simon Bellwood, NetMan Network Management and IT # Services GmbH # Portions Copyright 2001 Michael Peppler. # License: GPL # # Bugs and feedback to simon.bellwood@NOSPAM.net-man.at # Latest version available from: # http://www.net-man.at/software/check_sybase-LATEST.zip # # Revision history: # 0.1 01-OCT-2004 Initial version. # 0.2 08-NOV-2004 Initial release. # 0.3 13-JAN-2005 Fixed lib path, improved timeouts. # 0.4 26-JAN-2005 Added loginTimeout. # 0.5 04-FEB-2005 Fixed dates in history. Oops. # 0.6 29-MAR-2005 Added --explain option. # 0.7 08-APR-2005 Added initial performance data support. my $VERSION = "0.7"; #use warnings; use strict; use DBI; use Getopt::Long; use lib qw( /usr/lib/nagios/plugins/ /usr/local/nagios/libexec/ ); use utils qw(%ERRORS &print_revision &support &usage $TIMEOUT); my $PROGNAME = "check_sybase"; my $DEFAULT_CHECKTYPE = "FREESPACE"; my $DEFAULT_WARNING = "25"; my $DEFAULT_CRITICAL = "10"; my $DEFAULT_TIMEOUT = "30"; my ($user, $pass, $dbsvr, $dbname, $config, $checktype, $explain, $warn, $crit, $timeout, $help, $version); my $options_okay = GetOptions( "U|user=s" => \$user, "P|pass:s" => \$pass, # ":" means optional "S|dbsvr=s" => \$dbsvr, "D|dbname=s" => \$dbname, "config=s" => \$config, "checktype=s" => \$checktype, "explain" => \$explain, "w|warning=i" => \$warn, "c|critical=i" => \$crit, "t|timeout=i" => \$timeout, "h|help" => \$help, "V|version" => \$version ); if (! $options_okay) # Bad option passed { &help; &nunk("Bad command line option passed!"); } # Use defaults, if needed $warn = $warn || $DEFAULT_WARNING; $crit = $crit || $DEFAULT_CRITICAL; $checktype = $checktype || $DEFAULT_CHECKTYPE; $timeout = $timeout || $TIMEOUT || $DEFAULT_TIMEOUT; if ($help) { &help; &nok; } if ($version) { print_revision($PROGNAME,"\$Revision: 1.3 $VERSION \$"); &nok; } if ($config) # Read any of "user", "pass", "dbsvr", "dbname" from config file { &read_config; } # Some more descriptive syntax checks my $syntax_error; $syntax_error .= "No dbsvr given! " unless $dbsvr; $syntax_error .= "No dbname given! " unless $dbname; $syntax_error .= "No user given! " unless $user; $syntax_error .= "Bad checktype given!" unless $checktype =~ m/^CONNECT|FREESPACE$/; &nunk($syntax_error) if $syntax_error; # Just in case of problems, let's not hang Nagios $SIG{'ALRM'} = sub { &nunk("Timeout: no response from dbsvr $dbsvr within $timeout seconds"); }; alarm($timeout); # Decide on what we are checking if ($checktype eq "CONNECT") { &connect; } elsif ($checktype eq "FREESPACE") { &check_space; } my $dbh; my $is_connected; sub connect { $dbh = DBI->connect("dbi:Sybase:server=$dbsvr;database=$dbname;". "timeout=$timeout,loginTimeout=$timeout", $user, $pass) or &ncrit("Could not connect to '$dbname' on '$dbsvr'"); # Report success for a check of type CONNECT &nok("Connect okay") if $checktype ne "FREESPACE"; } sub disconnect { $dbh->disconnect if $is_connected; $is_connected = 0; } sub check_space { &connect; # Most of this sub based on Michael Peppler's check-space.pl # For debugging purposes, more values are collected than needed. $dbh->{syb_do_proc_status} = 1; my $dbinfo; # First check space in the database my $sth = $dbh->prepare("sp_spaceused") or &nunk("Failed to call sp_spaceused on '$dbsvr'"); $sth->execute or &nunk("Failed to call sp_spaceused on '$dbsvr'"); do { while(my $d = $sth->fetch) { if($d->[0] =~ /$dbname/) { # Grab "database_size" $d->[1] =~ s/[^\d.]//g; $dbinfo->{size} = $d->[1]; } else { # Reserved, data, index, unused foreach (@$d) { s/\D//g; } # Grab "reserved", "data", "index" $dbinfo->{reserved} = $d->[0] / 1024; $dbinfo->{data} = $d->[1] / 1024; $dbinfo->{index} = $d->[2] / 1024; $dbinfo->{unused} = $d->[3] / 1024; } } } while($sth->{syb_more_results}); &explain("db size: ".$dbinfo->{size}); &explain("reserved: ".$dbinfo->{reserved}); &explain(" data: ".$dbinfo->{data}); &explain(" index: ".$dbinfo->{index}); &explain(" unused: ".$dbinfo->{unused}); # Get the actual device usage from sp_helpdb to get the free log space $sth = $dbh->prepare("sp_helpdb $dbname") or &nunk("Failed to call sp_helpdb $dbname on '$dbsvr'"); $sth->execute or &nunk("Failed to call sp_helpdb $dbname on '$dbsvr'"); do { while(my $d = $sth->fetch) { # Look for "usage" column with value "log only" if($d->[2] && $d->[2] =~ /log only/) { # Grab "size", add it to our log size $d->[1] =~ s/[^\d\.]//g; $dbinfo->{log} += $d->[1]; } # Look for "device fragments" column with "log only" # followed by a number. if($d->[0] =~ /log only .* (\d+)/) { $dbinfo->{logfree} = $1 / 1024; } } } while($sth->{syb_more_results}); &explain("log: ".$dbinfo->{log}); &explain("logfree: ".$dbinfo->{logfree}); # Subtract the log size from the database size $dbinfo->{realsize} = $dbinfo->{size} - $dbinfo->{log}; &explain("realsize (i.e. size - log) = ".$dbinfo->{realsize}); # The "reserved" space is free for use by the table that freed it, so # it is not truly free space. To be safe, our calculation ignores it. my $free = ($dbinfo->{realsize} - $dbinfo->{reserved})/$dbinfo->{realsize}; $free = sprintf("%.2f", $free*100); &explain("(realsize-reserved)/realsize = $free%"); &explain("For safety, this calculation assumes no log space reuse. ". "Because of this, you may get negative values."); if ($free < $crit) { &ncrit("Free space is $free%! (critical threshold is $crit%)". "|free_space=$free%"); } if ($free < $warn) { &nwarn("Free space is $free%! (warning threshold is $warn%)". "|free_space=$free%"); } &nok("Free space within thresholds ($free% free)". "|free_space=$free%"); } sub read_config { open (CONFIG, "<$config") or &nunk("Failed to open config file '$config': $!"); while () { chomp; next if m/^#/; # skip comments next if m/^$/; # skip blanks # Each case-insensitive argument can be followed by an optional # colon, then must be followed by whitespace and the value. # Options in the config file override those given on the # command line, but don't rely on this! if (m/USER:?\s+(\S+)/i) { $user = $1; } elsif (m/PASS:?\s+(\S+)/i) { $pass = $1; } elsif (m/DBSVR:?\s+(\S+)/i) { $dbsvr = $1; } elsif (m/DBNAME:?\s+(\S+)/i) { $dbname = $1; } else { &nunk("Invalid line $. in config file '$config'"); } } close (CONFIG); } sub help { print <<_HELP_; Usage: $PROGNAME OPTIONS A nagios plugin that connects to a Sybase database and checks free space. Mandatory arguments to long options are mandatory for short options too. -U, --user Username to connect to database. -P, --pass Password to connect to database. -S, --dbsvr Database server (as in the interfaces file). -D, --dbname Database name to check. --config=FILE Config file (see SECURITY below) --checktype=TYPE Type of check to run (see TYPEs below) --explain Explains how we calculated the free space. -w, --warning Warning threshold, in percent (default 25) -c, --critical Critical threshold, in percent (default 10) -t, --timeout Timeout value, in seconds (default 30) -h, --help This help message -V, --version Version information ($VERSION) Examples: $PROGNAME -U sa -P secret -S bigbox -D orders $PROGNAME --config=/secure/nagios-sybase.cfg --checktype=CONNECT TYPEs There are two types of checks you can run: --checktype=CONNECT Checks just the connection to the database. --checktype=FREESPACE (Default) Checks both the connection to the database and the free space. SECURITY - Using a config file Since a "ps ax" will reveal your database username and password, you can instead specify them in a config file. Pass the config file with --config. The format of the file is: USER value PASS value You can also specify a DBSVR and DBNAME in the file. Comments (#) and blank lines are ignored. Use whitespace to separate argument and value. _HELP_ } sub explain { return unless $explain; my $msg = shift; print "$msg\n"; } # Some wrappers.. # Returns code 0, OK sub nok { my $msg = shift; print "OK: $msg\n" if $msg; &disconnect; exit $ERRORS{OK}; } # Returns code 1, Warning sub nwarn { my $msg = shift; print "WARNING: $msg\n"; &disconnect; exit $ERRORS{WARNING}; } # Returns code 2, Critical sub ncrit { my $msg = shift; print "CRITICAL: $msg\n"; &disconnect; exit $ERRORS{CRITICAL}; } # Returns code 3, Unknown sub nunk { my $msg = shift; print "ERROR: $msg\n"; &disconnect; exit $ERRORS{UNKNOWN}; }