From 0659e124a79962e1658f2f187bab4d3866f72548 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 5 Jun 2014 12:20:32 +0200 Subject: [PATCH] Adding check_redis --- check_redis/Makefile | 3 + check_redis/check_redis | 2910 +++++++++++++++++++++++++++++++++++ check_redis/check_redis.cfg | 5 + check_redis/control | 6 + check_redis/copyright | 7 + 5 files changed, 2931 insertions(+) create mode 100644 check_redis/Makefile create mode 100644 check_redis/check_redis create mode 100644 check_redis/check_redis.cfg create mode 100644 check_redis/control create mode 100644 check_redis/copyright diff --git a/check_redis/Makefile b/check_redis/Makefile new file mode 100644 index 0000000..cf9673d --- /dev/null +++ b/check_redis/Makefile @@ -0,0 +1,3 @@ +#/usr/bin/make -f + +include ../common.mk diff --git a/check_redis/check_redis b/check_redis/check_redis new file mode 100644 index 0000000..5d19f03 --- /dev/null +++ b/check_redis/check_redis @@ -0,0 +1,2910 @@ +#!/usr/bin/perl -w +# +# ============================== SUMMARY ===================================== +# +# Program : check_redis.pl +# Version : 0.72 +# Date : Oct 05, 2012 +# Author : William Leibzon - william@leibzon.org +# Licence : GPL - summary below, full text at http://www.fsf.org/licenses/gpl.txt +# +# =========================== PROGRAM LICENSE ================================= +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# ===================== INFORMATION ABOUT THIS PLUGIN ========================= +# +# This is Redis Server Check plugin. It gets stats variables and allows to set +# thresholds on their value or their rate of change. It can measure response time, +# hitrate, memory utilization, check replication sync and more. It can also test +# data in a specified key (if necessary doing average or sum on range). +# +# Plugin returns stats variables as perfomance data for further nagios 2.0 +# post-processing, you can find graph templates for PNP4Nagios at: +# http://william.leibzon.org/nagios/ +# +# This program is written and maintained by: +# William Leibzon - william(at)leibzon.org +# +# ============================= SETUP NOTES ==================================== +# +# Make sure to install Redis perl library from CPAN first. +# +# Next for help and to see what parameters this plugin accepts do: +# ./check_redis.pl --help +# +# This plugin checks Redis NoSQL database status varialbes, measures its response +# time and if specified allows to set thresholds on one or more key data. You can +# set thresholds for data in stats varialbles and some of them are also conviently +# available as long options with special threshold syntax. Plugin also calculates +# statistics such as Hitrate (calculated as rate of change of hits/misses) and +# memory use and can check replication delay. +# +# All variables can be returned as performance data for graphing and pnp4nagios +# template should be available with this plugin on the site you downloaded it from. + +# 1. Connection Parameters +# +# The connection parameters are "-H hostname", "-p port", "-D database" and +# "-C password_file" or "-x password". Specifying hostname is required, if you +# run locally specify it as -H 127.0.0.1. Everything else is optional and rarely +# needed. Default port is 6337. Database name (usually a numeric id) is probably +# only needed if you use --query option. Password can be passed on a command +# line with -x but its safer to read read it from a file or change in the code +# itself if you do use authentication. +# +# 2. Response Time, HitRate, Memory Utilization, Replication Delay +# +# To get response time you use "-T" or "--response_time=" option. By itself +# it will cause output of respose time at the status line. You can also use +# it as "-T warn,crit" to specify warning and critical thresholds. +# +# To get hitrate the option is "-R" or "--hitrate=". If previous performance +# data is not feed to plugin (-P option, see below) the plugin calculates +# it as total hitrate over life of redis process. If -P is specified and +# previous performance data is feed back, the data is based on real hitrate +# (which can show spikes and downs) with lifelong info also given in paramphesis +# The data is based on keyspace_hits and keyspace_misses stats variables. +# As with -T you can specify -R by itself or with thresholds as -R warn,crit +# +# Memory utilization is percent of real memory used by Redis out of total +# memory on the system. To be able to calculate it plugin needs to known +# amount of memory your system has which you specify with "-M" or "--memory=" +# option. Memory utilization option itself is lower "-m" or "--memory_utilization=" +# and you can specify threshold for it as "-m warn,crit" +# +# Replication delay threshold option "-R" or "--replication_delay=" is used +# to check replication with data from "master_last_io_seconds_ago" stats and +# valid only on slave servers. Other variables maybe checked for this later +# with more complex funcationality, so it was chosen to do this as separate +# option rather than drecting people to check that variable. +# +# 3. Checks on Redis Status Variables +# +# All status variables from redis can be checked with the plugin. For some +# status variables separate long option is provided to specify threshold. +# i.e. --connected_clients= +# +# This is a new alternative to specifying all variables together with -a +# (--variables) option. For example: +# -a connected_clients,blocked_clients +# When you do above results are included in status output line and you +# are required to specify thresholds with -w or --warn and -c or --crit +# with exactly number of thresholds as a number of variables specified +# in -a. If you simply want variable values on status line without specifying +# any threshold, use ~ in place of threshold value or skip value but specify +# all apropriate commas. For example: +# -a connected_clients,blocked_clients -w ~,~ -c ~,~ +# OR -a connected_clients,blocked_clients -w , -c , +# +# If you use new syntax with a long option for specific stats variables, you +# can specify list of one or more trhreshold specifiers which can be any of: +# NAME: - Overrides name for this variable for use in status and PERF output +# PATTERN: - Regular Expression that allows to match multiple data results +# WARN:threshold - warning alert threshold +# CRIT:threshold - critical alert threshold +# Threshold is a value (usually numeric) which may have the following prefix: +# > - warn if data is above this value (default for numeric values) +# < - warn if data is below this value (must be followed by number) +# = - warn if data is equal to this value (default for non-numeric values) +# ! - warn if data is not equal to this value +# Threshold can also be specified as a range in two forms: +# num1:num2 - warn if data is outside range i.e. if datanum2 +# \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 +# ABSENT:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if data is absent +# ZERO:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if result is 0 +# DISPLAY:YES|NO - Specifies if data should be included in nagios status line output +# PERF:YES|NO - Output in performance data or not (always YES if -F option is used) +# UOM: - Unit Of Measurement symbol to add to perf data - 'c','%','s','B' +# This is used by prorams that graph perf data such as PNP +# +# These can be specified in any order separated by ",". For example: +# --connected_clients=CRIT:>100,WARN:>50,ABSENT:CRITICAL,ZERO:OK,DISPLAY:YES,PERF:YES +# +# Variables that are not known to plugin and don't have specific long option (or even if +# they do) can be specified using general long option --check or --option or -o +# (all are aliases for same option): +# --check=NAME:connected_clients,CRIT:>100,WARN:>50,ABSENT:CRITICAL,DISPLAY:YES,PERF:YES +# +# Then NAME is used to specify what to match and multiple data vars maybe matched +# with PATTERN regex option (and please only use PATTERN with --check and not confuse +# plugin by using it in a named long option). Either NAME or PATTERN are required. +# +# 4. Calculating and using Rate of Change for Variables +# +# If you want to check rate of change rather than actual value you can do this +# by specifying it as '&variable' such as "&total_connections_received" or +# as "variable_rate" which is "total_connections_received_rate" and is similar +# to 'connected_clients' variable. By default it would be reported in the output +# as 'variable_rate' though '&variable' is a format used internally by plugin. +# +# As an alternative you can specify how to label these with --rate_label +# option where you can specify prefix and/or suffix. For example '--rate_label=dt_' +# would have the output being "dt_total_connections_received' where as +# '--rate_label=,_rate' is plugin default giving 'total_connections_received_rate'. +# You can use these names with -a and -A such as: +# --rate_label=,_rate -a total_connections_received_rate -w 1000 -c ~ +# Note that --rate_label will not work with new variable-named options, the +# only way to change default if you use that is to modify code and change +# $o_rprefix and $o_rsuffix variables default values. +# +# Now in order to be able to calculate rate of change, the plugin needs to +# know values of the variables from when it was run the last time. This +# is done by feeding it previous performance data with a -P option. +# In commands.cfg this would be specified as: +# -P "$SERVICEPERFDATA$" +# And don't forget the quotes, in this case they are not just for documentation. +# +# 5. Threshold Specification +# +# The plugin fully supports Nagios plug-in specification for specifying thresholds: +# http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT +# +# And it supports an easier format with the following one-letter prefix modifiers: +# >value : issue alert if data is above this value (default for numeric value) +# and < are interpreted by shell you may need to specify this in quotes) +# There are also two specifications of range formats as with other nagios plugins: +# number1:number2 issue alert if data is OUTSIDE of range [number1..number2] +# i.e. alert if data<$number1 or data>$number2 +# @number1:number2 issue alert if data is WITHIN range [number1..number2] +# i.e. alert if data>=$number and $data<=$number2 +# +# The plugin will attempt to check that WARNING value is less than CRITICAL +# (or greater for <). A special prefix modifier '^' can be used to disable these +# checks. A quick example of such special use is '--warn=^<100 --crit=>200' which +# means warning alert if value is < 100 and critical alert if its greater than 200. +# +# 6. Performance Data +# +# Using '-f' option causes values of all variables you specified in -a as +# well as response time from -T (response time), from -R (hitrate), from -m +# and other checks to go out as performance data for Nagios graphing programs. +# +# You may also directly specify which variables are to be return as performance data +# with '-A' option. If you use '-A' by itself and not specify any variables or use +# special special value of '*' (as in '-A *') the plugin will output all variables +# which is very useful for finding what data you can chck with this plugin. +# +# The plugin will output threshold values as part of performance data as specified at +# http://nagiosplug.sourceforge.net/developer-guidelines.html#AEN201 +# And don't worry about using non-standard >,<,=,~ prefixes, all of that would get +# converted into nagios threshold format for performance output +# +# The plugin is smart enough to add 'c' suffix for known COUNTER variables to +# values in performance data. Known variables are specifed in an array you can +# find at the top of the code (further below) and plugin author does not claim +# to have identified all variables correctly. Please email if you find an error +# or want to add more variables. +# +# As noted above performance data is also used to calcualte rate of change +# by feeding it back with -P option. In that regard even if you did not specify +# -f or -A but you have specified &variable, its actual data would be sent out +# in performance output. Additionally last time plugin was run is also in +# performance data as special _ptime variable. +# +# 7. Query Option and setting thresholds for data in Redis Database +# +# With -q (--query) option the plugin can retrieve data from Redis database +# which become new variables you can then check thresholds on. Currently it +# supports getting single key values with GET and getting range or values (or +# everything in list) with LRANGE and finding their Average or Min or Max or Sum. +# The option maybe repeated more than once. The format for this option is: +# +# -q, --query=query_type,key[:varname]<,list of threshold specifiers> +# +# query_type is one of: +# GET - get one string value +# LLEN - returns number of items in a list +# LRANGE:AVG:start:end - retrieve list and average results +# LRANGE:SUM:start:end - retrieve list and sum results +# LRANGE:MIN:start:end - retrieve list and return minimum +# LRANGE:MAX:start:end - retrieve list and return maximum +# HLEN - returns number of items in a hash [TODO] +# HGET:name - get specific hash key 'name' [TODO] +# HEXISTS:name - returns 0 or 1 depending on if specified hash key 'name' exists [TODO] +# SLEN - returns number of items in a set [TODO, SCARD redis opp] +# SEXISTS:name - returns 0 or 1 depending on if set member 'name' exists [SISMEMBER, TODO] +# ZLEN - returns number of items in a sorted set [TODO, ZCARD redis opp] +# ZCOUNT:min:max - counts number of items in sorted set with scores within the given values +# ZRANGE:AVG:min:max - retrieve sorted set members from min to max and average results +# ZRANGE:SUM:min:max - retrieve sorted set members from min to max and sum results +# ZRANGE:MIN:min:max - retrieve sorted set members from min to max list and return minimum +# ZRANGE:MAX:min:max- retrieve sorted set memers from min to max and return maximum +# For LRANGE if you do not specify start and end, then start will be 0 and end +# is last value in the list pointed to by this key (found by using llen). +# +# Key is the Redis key name to be retrieved and optionally you can add ":varname" +# after it which spcecifies what to name plugin variable based on this data - +# based on what you specify here is how it will be displayed in the status +# line and perormance data, default is same as Redis key name. +# +# After these key name you specify list of thresholds in the same format as +# variable-based long options described in section 3. Again the list of the +# possible specifiers are: +# WARN:threshold +# CRIT:threshold +# ABSENT:OK|WARNING|CRITICAL|UNKNOWN - what to do if data is not available +# ZERO:OK|WARNING|CRIICAL|UNKNOWN - what do do if data is 0 (rarely needed) +# DISPLAY:YES|NO - display on status line or not (default YES) +# PERF:YES|NO - output in perf data or not +# +# You can also optionally use -a, -w and -c to theck data from the query instead +# of specifying thresholds as part of query option itself And remember that you if +# you need to check multiple keys you just repeat --query option more than once. +# +# 8. Example of Nagios Config Definitions +# +# Sample command and service definitions are below: +# +# define command { +# command_name check_redis_new +# command_line $USER1$/check_redis.pl -H $HOSTADDRESS$ -p $ARG1$ -T $ARG2$ -R -A -M $_HOSTSYSTEM_MEMORY$ -m $ARG3$ -a $ARG4$ -w $ARG5$ -c $ARG6$ -f -P "$SERVICEPERFDATA$" +# } +# +# Arguments and thresholds are: +# $ARG1 : Port +# $ARG2 : response time thresholds +# $ARG3 : memory utilization thresholds +# $ARG4 : additional variables to be checked +# $ARG5 : warning thresholds for those variables +# $ARG6 : critical thresholds for those variables +# +# define service { +# use prod-service +# hostgroups redishosts +# service_description Redis +# check_command check_redis_new!6379!"1,2"!"80,90"!blocked_clients,connected_clients!50,~!100,~ +# } +# +# define host { +# use prod-server +# host_name redis.mynetwork +# address redis.mynetwork +# alias Redis Stat Server +# hostgroups linux,redishosts +# _SYSTEM_MEMORY '8G' +# } +# +# Example of command-line use: +# /usr/lib/nagios/plugins/check_redis.pl -H localhost -a 'connected_clients,blocked_clients' -w ~,~ -c ~,~ -m -M 4G -A -R -T -f -v +# +# In above the -v option means "verbose" and with it plugin will output some debugging information +# about what it is doing. The option is not intended to be used when plugin is called from nagios itself. +# +# Example of using query and varialbe-based long options with debug enabled as well (-v): +# +# ./check_redis.pl -H localhost -p 6379 -D 1 --query LRANGE:AVG:0:,MyColumn1:Q1,ABSENT:WARNING,WARN:300,CRIT:500,DISPLAY:YES,PERF:NO +# --query GET,MyKey:K1,ABSENT:CRITICAL "--connected_clients=WARN:<2,CRIT:>100,ZERO:OK,ABSENT:WARNING,DISPLAY:YES,PERF:YES" +# +# ======================= VERSION HISTORY and TODO ================================ +# +# The plugins is written by reusing code my check_memcached.pl which itself is based +# on check_mysqld.pl. check_mysqld.pl has history going back to 2004. +# +# [0.4 - Mar 2012] First version of the code based on check_mysqld.pl 0.93 +# and check_memcached.pl 0.6. Internal work, not released. +# Version 0.4 because its based on a well developed code base +# [0.41 - Apr 15, 2012] Added list of variables array and perf_ok regex. +# Still testing internally and not released yet. +# [0.42 - Apr 28, 2012] Added total_keys, total_expires, nice uptime_info +# and memory utilization +# [0.43 - May 31, 2012] Release candidate. More documentation added +# replacing check_memcached examples. Bugs fixed. +# Made "_rate" as default rate variables suffix in +# place of &delta. Changed -D option to -r. +# +# [0.5 - Jun 01, 2012] First official release will start with version 0.5 +# Documentation changes, but no code updates. +# [0.51 - Jun 16, 2012] Added support to specify filename to '-v' option +# for debug output and '--debug' as alias to '--verbose' +# [0.52 - Jul 10, 2012] Patch by Jon Schulz to support credentials with -C +# (credentials file) and addition by me to support +# password as command argument. +# [0.53 - Jul 15, 2012] Adding special option to do query on one redis key and +# and do threshold checking of results if its numeric +# +# [0.6 - Jul 17, 2012] Rewrote parts of thresholds checking code and moved code +# that checks and parses thresholds from main into separate +# functions that are to become part of plugin library. +# Added support for variable thresholds specified as: +# option=WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL,ZERO:.. +# which are to be used for stats-variable based long options such as +# --connected_clients=WARN:threshold,CRIT:threshold +# and added DISPLAY:YES|NO and PERF specifiers for above too. +# Added -D option to specify database needed for --query +# [0.61 - Aug 03, 2012] Added more types of key query for lists, sets, hashes +# and options to find number of elements in a list/set/hash. +# New options added are: +# LLEN,HLEN,SLEN,ZLEN,HGET,HEXISTS,SEXISTS,ZRANGE +# +# [0.7 - Aug 28, 2012] A lot of internal rewrites in the library. Its now not just a +# a set of functions, but a proper object library with internal +# variables hidden from outside. Support has also been added for +# regex matching with PATTERN specifier and for generalized +# --check option that can be used where specific long option is +# not available. For use with that option also added UOM specifier. +# Also added checkin 'master_last_io_seconds_ago' (when link is down) +# for when replication_delay info is requested. +# [0.71 - Sep 03, 2012] Fixed bug in a new library related to when data is missing +# [0.72 - Oct 05, 2012] Fixed bug reported by Matt McMillan in specified memory size +# when KB are used. Fixed bugs in adding performance data that +# results in keyspace_hits, keyspace_misses, memory_utilization +# having double 'c' or '%' in perfdata. Added contributors section. +# +# TODO or consider for future: +# +# 1. Library Enhancements (will apply to multiple plugins that share common code) +# (a) Add '--extra-opts' to allow to read options from a file as specified +# at http://nagiosplugins.org/extra-opts. This is TODO for all my plugins +# (b) [DONE] +# In plans are to allow long options to specify thresholds for known variables. +# These would mean you specify '--connected_clients' in similar way to '--hitrate' +# Internally these would be convered into -A, -w, -c as appropriate an used +# together with these options. So in practice it will now allow to get any data +# just a different way to specify options for this plugin. +# (c) Allow regex when selecting variable name(s) with -a, this will be enabled with +# a special option and not be default +# [DONE] +# +# 2. REDIS Specific +# (a) Add option to check from master that slave is connected and working. +# (b) Look into replication delay from master and how it can be done. Look +# for into on replication_delay from slave as well +# (c) How to better calculate memory utilization and get max memory available +# without directly specifying it +# (d) Maybe special options to measure cpu use and set thresholds +# +# Others are welcome recommand a new feature to be added here. If so please email to +# william@leibzon.org. +# And don't worry, I'm not a company with some hidden agenda to use your idea +# but an actual person who you can easily get hold of by email, find on forums +# and on Nagios conferences. More info on my nagios work is at: +# http://william.leibzon.org/nagios/ +# Above site should also have PNP4Nagios template for this and other plugins. +# +# ============================ LIST OF CONTRIBUTORS =============================== +# +# The following individuals have contributed code, patches, bug fixes and ideas to +# this plugin (listed in last-name alphabetical order): +# +# William Leibzon +# Matthew Litwin +# Matt McMillan +# Jon Schulz +# M Spiegle +# +# ============================ START OF PROGRAM CODE ============================= + +use strict; +use IO::Socket; +use Time::HiRes; +use Text::ParseWords; +use Getopt::Long qw(:config no_ignore_case); +use Redis; + +# default hostname, port, database, user and password, see NOTES above +my $HOSTNAME= 'localhost'; +my $PORT= 6379; +my $PASSWORD= undef; +my $DATABASE= undef; + +# Add path to additional libraries if necessary +use lib '/usr/lib/nagios/plugins'; +our $TIMEOUT; +our %ERRORS; +eval 'use utils qw(%ERRORS $TIMEOUT)'; +if ($@) { + $TIMEOUT = 20; + %ERRORS = ('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4); +} + +my $Version='0.72'; + +# This is a list of known stat and info variables including variables added by plugin, +# used in order to designate COUNTER variables with 'c' in perfout for graphing programs +# The format is: +# VAR_NAME => [ TYPE, PerfSuffix, DESCRIPTION] +# If option has description, the variable will also become available as a long option so for example +# you can specify "--connected_clients=WARN,CRIT" instead of specifying "-a connected_clients -w WARN -c CRIT' +my %KNOWN_STATUS_VARS = ( + 'memory_utilization' => [ 'status', 'GAUGE', '%' ], # calculated by plugin + 'redis_version' => [ 'status', 'VERSION', '' ], # version string variable + 'response_time' => [ 'status','GAUGE', 's' ], # measured by plugin + 'hitrate' => [ 'status', 'GAUGE', '%' ], # calculated by plugin + 'total_keys' => [ 'status','GAUGE', '', 'Total Number of Keys on the Server' ], + 'total_expires' => [ 'status','GAUGE', '', 'Number of Expired Keys for All DBs' ], + 'last_save_time' => [ 'status', 'GAUGE', 's' ], + 'bgsave_in_progress' => [ 'status', 'BOOLEAN', '' ], + 'vm_enabled' => [ 'status', 'BOOLEAN', '' ], + 'uptime_in_seconds' => [ 'status', 'COUNTER', 'c' ], + 'total_connections_received' => [ 'status', 'COUNTER', 'c', 'Total Connections Received' ], + 'used_memory_rss' => [ 'status', 'GAUGE', 'B', 'Resident Set Size, Used Memory in Bytes' ], # RSS - Resident Set Size + 'used_cpu_sys' => [ 'status', 'GAUGE', '', 'Main Process Used System CPU' ], + 'redis_git_dirty' => [ 'status', 'BOOLEAN', '', 'Git Dirty Set Bit' ], + 'loading' => [ 'status', 'BOOLEAN', '' ], + 'latest_fork_usec' => [ 'status', 'GAUGE', '' ], + 'connected_clients' => [ 'status', 'GAUGE', '', 'Total Number of Connected Clients' ], + 'used_memory_peak_human' => [ 'status', 'GAUGE', '' ], + 'mem_allocator' => [ 'status', 'TEXTINFO', '' ], + 'uptime_in_days' => [ 'status', 'COUNTER', 'c', 'Total Uptime in Days' ], + 'keyspace_hits' => [ 'status', 'COUNTER', 'c', 'Total Keyspace Hits' ], + 'client_biggest_input_buf' => [ 'status', 'GAUGE', '' ], + 'gcc_version' => [ 'status', 'TEXTINFO', '' ], + 'changes_since_last_save' => [ 'status', 'COUNTER', 'c' ], + 'arch_bits' => [ 'status', 'TEXTINFO', '' ], + 'lru_clock' => [ 'status', 'GAUGE', '' ], # LRU is page replacement algorithm (least recently used), I'm unsure what this represents though + 'role' => [ 'status', 'SETTING', '' ], + 'multiplexing_api' => [ 'status', 'SETTING' , '' ], + 'slave' => [ 'status', 'TEXTDATA', '' ], + 'pubsub_channels' => [ 'status', 'GAUGE', '', 'Number of Pubsub Channels' ], + 'redis_git_sha1' => [ 'status', 'TEXTDATA', '' ], + 'used_cpu_user_children' => [ 'status', 'GAUGE', '', 'Child Processes Used User CPU' ], + 'process_id' => [ 'status', 'GAUGE', '' ], + 'used_memory_human' => [ 'status', 'GAUGE', '' ], + 'keyspace_misses' => [ 'status', 'COUNTER', 'c', 'Keyspace Misses' ], + 'used_cpu_user' => [ 'status', 'GAUGE', '', 'Main Process Used User CPU' ], + 'total_commands_processed' => [ 'status', 'COUNTER', 'c', 'Total Number of Commands Processed from Start' ], + 'mem_fragmentation_ratio' => [ 'status', 'GAUGE', '', 'Memory Fragmentation Ratio' ], + 'client_longest_output_list' => [ 'status', 'GAUGE', '' ], + 'blocked_clients' => [ 'status', 'GAUGE', '', 'Number of Currently Blocked Clients' ], + 'aof_enabled' => [ 'status', 'BOOLEAN', '' ], + 'evicted_keys' => [ 'status', 'COUNTER', 'c', 'Total Number of Evicted Keys' ], + 'bgrewriteaof_in_progress' => [ 'status','BOOLEAN', '' ], + 'expired_keys' => [ 'status', 'COUNTER', 'c', 'Total Number of Expired Keys' ], + 'used_memory_peak' => [ 'status', 'GAUGE', 'B' ], + 'connected_slaves' => [ 'status', 'GAUGE', '', 'Number of Connected Slaves' ], + 'used_cpu_sys_children' => [ 'status', 'GAUGE', '', 'Child Processed Used System CPU' ], + 'master_host' => [ 'status', 'TEXTINFO', '' ], + 'master_port' => [ 'status', 'TEXTINFO', '' ], + 'master_link_status' => [ 'status', 'TEXTINFO', '' ], + 'slave0' => [ 'status', 'TEXTINFO', '' ], + 'slave1' => [ 'status', 'TEXTINFO', '' ], + 'slave2' => [ 'status', 'TEXTINFO', '' ], + 'slave3' => [ 'status', 'TEXTINFO', '' ], + ); + +# Here you can also specify which variables should go into perf data, +# For right now it is 'GAUGE', 'COUNTER', 'DATA' (but not 'TEXTDATA'), and 'BOOLEAN' +# you may want to remove BOOLEAN if you don't want too much data +my $PERF_OK_STATUS_REGEX = 'GAUGE|COUNTER|^DATA$|BOOLEAN'; + +# ============= MAIN PROGRAM CODE - DO NOT MODIFY BELOW THIS LINE ============== + +my $o_host= undef; # hostname +my $o_port= undef; # port +my $o_pwfile= undef; # password file +my $o_password= undef; # password as parameter +my $o_database= undef; # database name (usually a number) +my $o_help= undef; # help option +my $o_verb= undef; # verbose mode +my $o_version= undef; # version info option +my $o_variables=undef; # list of variables for warn and critical +my $o_perfvars= undef; # list of variables to include in perfomance data +my $o_warn= undef; # warning level option +my $o_crit= undef; # Critical level option +my $o_perf= undef; # Performance data option +my @o_check= (); # General check option that maybe repeated more than once +my $o_timeout= undef; # Timeout to use - note that normally timeout is from nagios +my $o_timecheck=undef; # threshold spec for connection time +my $o_memutilization=undef; # threshold spec for memory utilization% +my $o_totalmemory=undef; # total memory on a system +my $o_hitrate= undef; # threshold spec for hitrate% +my $o_repdelay=undef; # replication delay time +my @o_querykey=(); # query this key, this option maybe repeated so its an array +my $o_prevperf= undef; # performance data given with $SERVICEPERFDATA$ macro +my $o_prevtime= undef; # previous time plugin was run $LASTSERVICECHECK$ macro +my $o_ratelabel=undef; # prefix and suffix for creating rate variables +my $o_rsuffix='_rate'; # default suffix +my $o_rprefix=''; + +## Additional global variables +my $redis= undef; # DB connection object +my @query=(); # array of queries with each entry being keyed hash of processedoption data on howto query + + +sub p_version { print "check_redis.pl version : $Version\n"; } + +sub print_usage_line { + print "Usage: $0 [-v [debugfilename]] -H [-p ] [-x password | -C credentials_file] [-D ] [-a -w -c ] [-A ] [-T [conntime_warn,conntime_crit]] [-R [hitrate_warn,hitrate_crit]] [-m [mem_utilization_warn,mem_utilization_crit] [-M [B|K|M|G]]] [-r replication_delay_time_warn,replication_delay_time_crit] [-f] [-T ] [-V] [-P ] [-q (GET|LLEN|HLEN|SLEN|ZLEN|HGET:name|HEXISTS:name|SEXISTS:name|LRANGE:(AVG|SUM|MIN|MAX):start:end|ZRANGE:(AVG|SUM|MIN|MAX):start:end),query_type,query_key_name[:data_name][,ABSENT:WARNING|CRITICAL][,WARN:threshold,CRIT:threshold]] [-o ]\n"; +} + +sub print_usage { + print_usage_line(); + print "For more details on options do: $0 --help\n"; +} + +sub help { + my $nlib = shift; + + print "Redis Check for Nagios version ",$Version,"\n"; + print " by William Leibzon - william(at)leibzon.org\n\n"; + print "This is redis monitoring plugin to check its stats variables, replication, response time\n"; + print "hitrate, memory utilization and other info. The plugin can also query and test key data\n"; + print "against specified thresholds. All data is available as performance output for graphing.\n\n"; + print_usage_line(); + print "\n"; + print < - warn if data is above this value (default for numeric values) + < - warn if data is below this value (must be followed by number) + = - warn if data is equal to this value (default for non-numeric values) + ! - warn if data is not equal to this value + ~ - do not check this data (must not be followed by number or ':') + ^ - for numeric values this disables check that warning < critical + Threshold values can also be specified as range in two forms: + num1:num2 - warn if data is outside range i.e. if datanum2 + \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 + -c, --crit=STR[,STR[,STR[..]]] + This option can only be used if '--variables' (or '-a') option above + is used and number of values listed here must exactly match number of + variables specified with '-a'. The values specify critical threshold + for when Nagios should send CRITICAL alert. The format is exactly same + as with -w option except no '^' prefix. + +Performance Data Processing Options: + -f, --perfparse + This should only be used with '-a' and causes variable data not only as part of + main status line but also as perfparse compatible output (for graphing, etc). + -A, --perfvars=[STRING[,STRING[,STRING...]]] + This allows to list variables which values will go only into perfparse + output (and not for threshold checking). The option by itself (emply value) + is same as a special value '*' and specify to output all variables. + -P, --prev_perfdata + Previous performance data (normally put '-P \$SERVICEPERFDATA\$' in nagios + command definition). This is used to calculate rate of change for counter + statistics variables and for proper calculation of hitrate. + --rate_label=[PREFIX_STRING[,SUFFIX_STRING]] + Prefix or Suffix label used to create a new variable which has rate of change + of another base variable. You can specify PREFIX or SUFFIX or both. Default + if not specified is suffix '_rate' i.e. --rate_label=,_rate + +Key Data Query Option (maybe repeated more than once): + -q, --query=query_type,key[:varname][,ABSENT:OK|WARNING|CRITICAL,WARN:threshold,CRIT:threshold] + query_type is one of: + GET - get one data value + LLEN - number of items in a list + LRANGE:AVG:start:end - retrieve list and average results + LRANGE:SUM:start:end - retrieve list and sum results + LRANGE:MIN:start:end - retrieve list and return minimum + LRANGE:MAX:start:end - retrieve list and return maximum + HLEN - returns number of items in a hash + HGET:name - get specific hash key 'name' + HEXISTS:name - returns 0 or 1 depending on if specified hash key 'name' exists + SLEN - returns number of items in a set + SEXISTS:name - returns 0 or 1 depending on if set member 'name' exists + ZLEN - returns number of items in a sorted set + ZCOUNT:min:max - counts items in sorted set with scores within the given values + ZRANGE:AVG:min:max - retrieve sorted set members from min to max and average results + ZRANGE:SUM:min:max - retrieve sorted set members from min to max and sum results + ZRANGE:MIN:min:max - retrieve sorted set members from min to max list and return minimum + ZRANGE:MAX:min:max - retrieve sorted set memers from min to max and return maximum + + Option specifies key to query and optional variable name to assign the results to after : + (if not specified it would be same as key). If key is not available the plugin can issue + either warning or critical alert depending on what you specified after ABSENT. + Numeric results are calculated for ranges and can be checked with specified thresholds + or you can do it together with standard with redis stats variables and -a option. + +General Check Option (all 3 forms equivalent, can be repated more than once): + -o , --option=, --check= + where specifiers are separated by , and must include NAME or PATTERN: + NAME: - Default name for this variable as you'd have specified with -v + PATTERN: - Regular Expression that allows to match multiple data results + WARN:threshold - warning alert threshold + CRIT:threshold - critical alert threshold + Threshold is a value (usually numeric) which may have the following prefix: + > - warn if data is above this value (default for numeric values) + < - warn if data is below this value (must be followed by number) + = - warn if data is equal to this value (default for non-numeric values) + ! - warn if data is not equal to this value + Threshold can also be specified as a range in two forms: + num1:num2 - warn if data is outside range i.e. if datanum2 + \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 + ABSENT:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if data is absent + ZERO:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if result is 0 + DISPLAY:YES|NO - Specifies if data should be included in nagios status line output + PERF:YES|NO - Output results as performance data or not (always YES if asked for rate) + UOM: - Unit Of Measurement symbol to add to perf data - 'c','%','s','B' + +Measured/Calculated Data: + -T, --response_time=[WARN,CRIT] + If this is used as just -T the plugin will measure and output connection + response time in seconds. With -f this would also be provided on perf variables. + You can also specify values for this parameter, these are interprted as + WARNING and CRITICAL thresholds (separated by ','). + -R, --hitrate=[WARN,CRIT] + Calculates Hitrate %: cache_miss/(cache_hits+cache_miss). If this is used + as just -R then this info just goes to output line. With '-R -f' these + go as performance data. You can also specify values for this parameter, + these are interprted as WARNING and CRITICAL thresholds (separated by ','). + The format for WARN and CRIT is same as what you would use in -w and -c. + -m, --memory_utilization=[WARN,CRIT] + This calculates percent of total memory on system used by redis, which is + utilization=redis_memory_rss/total_memory*100. + Total_memory on server must be specified with -M since Redis does not report + it and can use maximum memory unless you enabled virtual memory and set a limit + (I plan to test this case and see if it gets reported then). + If you specify -m by itself, the plugin will just output this info, + with '-f' it will also include this in performance data. You can also specify + parameter values which are interpreted as WARNING and CRITICAL thresholds. + -M, --memory=NUM[B|K|M|G] + Amount of memory on a system for memory utilization calculations above. + If it does not end with K,M,G then its assumed to be B (bytes) + -r, --replication_delay=WARN,CRIT + Allows to set threshold on replication delay info. Only valid if this is a slave! + The threshold value is in seconds and fractions are acceptable. + +EOT + + if (defined($nlib) && $nlib->{'enable_long_options'} == 1) { + my $long_opt_help = $nlib->additional_options_help(); + if ($long_opt_help) { + print "Stats Variable Options (this is alternative to specifying them as list with -a):\n"; + print $long_opt_help; + print "\n"; + } + } +} + +############################ START OF THE LIBRARY FUNCTIONS ##################################### +# +# THIS IS WORK IN PROGRESS, THE LIBRARY HAS NOT BEEN RELEASED YET AND INTERFACES MAY CHANGE +# +# ====================================== SUMMARY ================================================ +# +# Name : Naglio Perl Library For Developing Nagios Plugins +# Version : 0.2 +# Date : Aug 28, 2012 +# Author : William Leibzon - william@leibzon.org +# Licence : LGPL - full text at http://www.fsf.org/licenses/lgpl.txt +# +# ============================= LIBRARY HISTORY AND VERSIONS ==================================== +# +# Note: you may safely skip this section if you're looking at documentation about this library or plugin +# +# [2006-2008] The history of this library goes back to plugins such as check_snmp_temperature.pl, +# check_mysqld,pl and others released as early as 2006 with common functions to +# support prefixes "<,>,=,!" for specifying thresholds and checking data against +# these thresholds. Several of my plugins had common architecture supporting multiple +# variables or attributes to be checked using -a/--attributes/--variables option and +# --warn and --crit options with list of thresholds for these attributes and --perfvars +# specifying variables whose data would only go as PERFOUT for graphing. +# +# [2008-2011] Threshold parsing and check code had been rewritten and support added for specifying +# range per plugin guidelines: http://nagiosplug.sourceforge.net/developer-guidelines.html +# Internal structures had been changing and becoming more complex to various cases. +# In 2010-2012 plugins started to get support for ;warn;crit output of thresholds in perf, +# as specified in the guidelines. +# +# [Early 2012] Code from check_memcached had been used as a base for check_memcached and then +# check_redis plugins with some of the latest threshold code from check_netstat +# with more updates. Starting with check_redis the code from check_options() and +# from main part of plugin that was very similar across my plugins were separated +# into their own functions. KNOWN_STATS_VARS array was introduced as well to be +# able to properly add UOM symbol ('c', '%', 's', 'ms', 'B', 'KB') to perfout. +# check_memcached and check_redis also included support for calculating rate of +# variables in a similar way to how its been done in check_snmp_netint +# +# [0.1 - July 17, 2012] In 0.6 release of check_redis.pl support had been added for long options +# with special threshold line syntax: +# --option=WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,DISPLAY:YES|NO,PERF:YES|NO +# This was extension from just doing --option=WARN,CRIT to have a more universal +# and extendable way to specify and alike parameters for checking. check_redis 0.6 +# also introduced support automatically adding long options with above syntax based +# on description in KNOWN_STATS_VARS. The functions for the library were all separated +# into their own section of the code. When inported to check_memcached global variables +# were added to that section and accessor functions written for some of them. +# This is considered 0.1 version of the library +# +# [0.2 - Aug 28, 2012] In August the library code in check_memcached had been re-written from +# just functions to object-oriented perl interface. All variables were hidden from +# direct access with accessor functions written. Documentation header had been added +# to each library function and the header for the library itself. This was major work +# taking over a week to do although functions and mainly sllllame as in 0.1. They are +# not stabilized and so library is only to be included within plugins. Support was +# also added for regex matching with PATTERN option spec. Also added NAME spec. +# License changed to LGPL from GPL for this code. +# [0.21 - Sep 3, 2012] Fix bug in handling absent data +# +# ================================== LIBRARY TODO ================================================= +# +# (a) Add library function to support '--extra-opts' to read plugin options from a file +# This is being to be compatible with http://nagiosplugins.org/extra-opts +# (b) Support regex matching and allowing multiple data for same threshold definition. +# [DONE] +# (c) Support for expressions in places of numeric values for thresholds. The idea is to allow +# to refer to another variable or to special macro. I know at least one person has extended +# my check_mysqld to support using mysql variables (not same as status data) for thresholds. +# I also previouslyhad planned such support with experimental check_snmp_attributes plugin +# library/base. The idea was also floated around on nagios-devel list. +# (d) Support specifying variables as expressions. This is straight out of check_snmp_atributes +# and maybe part of it can be reused for this +# (e) Add common SNMP functions into library as so many of my plugins use it# +# (f) Add more functions to make this library easier to use and stabilize its interfaces. +# Port my plugins to this library. +# (f) Add support for functions in Nagios-Plugins perl library. While its interfaces are +# different, I believe, it'd be possible to add "shim" code to support them too. +# (h) Write proper Perl-style documentation as well as web documentation (much of above maybe +# moved to web documentation) and move library to separate GITHUB project. Release it. +# (i) Port this library to Python and write one or two example plugins +# +# ================================================================================================ +{ +package Naglio; +use fields qw(); +use Text::ParseWords; + +my %ERRORS = ('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4); +my $DEFAULT_PERF_OK_STATUS_REGEX = 'GAUGE|COUNTER|^DATA$|BOOLEAN'; + +# @DESCRIPTION : Library object constructor +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : Hash array of named config settings. All parameters are optiona. Currently supported are: +# plugin_name => string - short name of the plugin +# plugin_description => string - plugin longer description +# plugin_authors => string - list of plugin authors +# knownStatsVars => reference to hash - hash array defining known variables, what type they are, their description +# usage_function => &ref - function that would display helpful text in case of error with options for this plugin +# verbose => 1 or "" or "filename" - set to 1 or "" if verbose/debug or to filename to send data to (may not be called "0" or "1") +# output_comparison_symbols => 0 or 1 - 1 means library output in case threshold is met can use "<", ">", "=" +# 0 means output is something like "less than or equal", "more than", etc. +# all_variables_perf => 0 or 1 - 1 means data for all variables would go to PERF. This is what '-A *' or just -A do +# enable_long_options => 0 or 1 - 1 enables long options generated based on knownStatsVars. This is automatically enabled (from 0 +# to 1) when plugin references additional_options_list() unless this is set to -1 at library init +# enable_rate_of_change => 0 or 1 - enables support for calculating rate of change based on previously saved data, default is 1 +# enable_regex_match => 0 or 1 - when set to 1 each threshold-specified var name is treated as regex and can match +# to multiple collected data. this can also be enabled per-variable with PATTERN spec +# @RETURNS : Reference representing object instance of this library +# @PRIVACY & USE : PUBLIC, To be used when initializing the library +sub lib_init { + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my %other_args = @_; + + # These used to be global variables, now these are object local variables in self with accessor + my @allVars = (); # all variables after options processing + my @perfVars = (); # performance variables list [renamed from @o_perfVarsL in earlier code] + my %thresholds=(); # hash array of thresholds for above variables, [this replaced @o_warnL and @o_critL in earlier code] + my %dataresults= (); # This is where data is loaded. It is a hash with variable names as keys and array array for value: + # $dataresults{$var}[0] - undef of value of this variable + # $dataresults{$var}[1] - 0 if variable not printed out to status line yet, 1 or more otherwise + # $dataresults{$var}[2] - 0 if variable data not yet put into PERF output, -1 if PERF output is preset, 1 after output + # $dataresults{$var}[3] - string, '' to start with, holds ready performance data output for this variable + # $dataresults{$var}[4] - only for regex matches. name of match var (which should be key in thresholds), otherwise undef + my %dataVars = (); # keys are variables from allVars and perfVars, values is array of data that matched i.e. keys in dataresults + my @ar_warnLv = (); # used during options processing + my @ar_critLv = (); # used during options processing + my @ar_varsL= (); # used during options processing + my @prev_time= (); # timestamps if more then one set of previois performance data + + my $self = { # library and nagios versions + _NaglioLibraryVersion => 0.2, # this library's version + _NagiosVersion => 3, # assume nagios core 3.x unless known otherwise + # library internal data structures + _allVars => \@allVars, + _perfVars => \@perfVars, + _thresholds => \%thresholds, + _dataresults => \%dataresults, + _datavars => \%dataVars, + _ar_warnLv => \@ar_warnLv, + _ar_critLv => \@ar_critLv, + _ar_varsL => \@ar_varsL, + _prevTime => \@prev_time, + _prevPerf => {}, # array that is populated with previous performance data + _checkTime => undef, # time when data was last checked + _statuscode => "OK", # final status code + _statusinfo => "", # if there is an error, this has human info about what it is + _statusdata => "", # if there is no error but we want some data in status line, this var gets it + _perfdata => "", # this variable collects performance data line + _saveddata => "", # collects saved data (for next plugin re-run, not implimented yet) + _init_args => \%other_args, + # copy of data from plugin option variables + o_variables => undef, # List of variables for warn and critical checks + o_crit => undef, # Comma-separated list of critical thresholds for each checked variable + o_warn => undef, # Comma-separated list of warning thresholds for each checked variable + o_perf => undef, # defined or undef. perf option means all data from variables also goes as PERFDATA + o_perfvars => undef, # List of variables only for PERFDATA + o_prevperf => undef, # previously saved performance data coming from $SERVICEPERFDATA$ macro + # library special input variables (similar to options) + o_rprefix => '', # prefix used to distinguish rate variables + o_rsuffix => '_rate', # suffix used to distinguish rate variables + knownStatusVars => {}, # Special HASH ARRAY with names and description of known variables + perfOKStatusRegex => $DEFAULT_PERF_OK_STATUS_REGEX, + verbose => 0, # verbose, same as debug, same as o_verb + plugin_name => '', # next 3 parameters are variables are currently not used + plugin_description => '', # but its still better if these are provided + plugin_authors => '', # in the future these maybe used for help & usage functions + # library setting variables + debug_file => "", # instead of setting file name in verbose, can also set it here + output_comparison_symbols => 1, # should plugin output >,<.=,! for threshold match + # if 0, it will say it in human form, i.e. "less" + all_variables_perf => 0, # should we all variables go to PERF (even those not listed in o_variables and o_perfvars) + # this is the option set to 1 when --perfvars '*' is used + enable_long_options => 0, # enable support for long options generated based on knownStatusVars description + enable_rate_of_change => 1, # enables support for calculatin rate of chane and for rate of change long options + enable_regex_match => 0, # 0 is not enabled, 1 means variables in o_variables and o_perfvars are considered regex to match actual data + # a value of 2 means its enabled, but for options with PATTERN specifier (this is not configurale value) + }; + + # bless to create an object + bless $self, $class; + + # deal with arguments that maybe passed to library when initalizing + if (exists($other_args{'KNOWN_STATUS_VARS'})) { + $self->{'knownStatusVars'} = $other_args{'KNOWN_STATUS_VARS'}; + } + $self->{'plugin_name'} = $other_args{'plugin_name'} if exists($other_args{'plugin_name'}); + $self->{'plugin_description'} = $other_args{'plugin_description'} if exists($other_args{'plugin_description'}); + $self->{'plugin_authors'} = $other_args{'plugin_authors'} if exists($other_args{'plugin_authors'}); + $self->{'usage_function'} = $other_args{'usage_gunction'} if exists($other_args{'usage_function'}); + $self->configure(%other_args); + + # return self object + return $self; +} + +# This is just an alias for object constructor lib_init function +sub new { + return lib_init(@_); +} + +# @DESCRIPTION : Allows to confiure some settings after initialization (all these can also be done as part of lib_init) +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : Hash array of named config settings. All parameters are optiona. Currently supported are: +# verbose => 1 or "" or "filename" - set to 1 or "" if verbose/debug or to filename to send data to (may not be called "0" or "1") +# output_comparison_symbols => 0 or 1 - 1 means library output in case threshold is met can use "<", ">", "=" +# 0 means output is something like "less than or equal", "more than", etc. +# all_variables_perf => 0 or 1 - 1 means data for all variables would go to PERF. This is what '-A *' or just -A do +# enable_long_options => 0 or 1 - 1 enables long options generated based on knownStatsVars. This is automatically enabled (from 0 +# to 1) when plugin references additional_options_list() unless this is set to -1 at library init +# enable_rate_of_change => 0 or 1 - enables support for calculating rate of change based on previously saved data, default is 1 +# enable_regex_match => 0 or 1 - when set to 1 each threshold-specified var name is treated as regex and can match +# to multiple collected data. this can also be enabled per-variable with PATTERN spec +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function. +sub configure { + my $self = shift; + my %args = @_; + + if (exists($args{'verbose'}) || exists($args{'debug'})) { + $self->{'verbose'} = 1; + if (exists($args{'verbose'}) && $args{'verbose'}) { + $self->{'debug_file'} = $args{'verbose'}; + } + if (exists($args{'debug_log_filename'})) { + $self->{'debug_file'} = $args{'debug_log_filename'}; + } + } + $self->{'all_variables_perf'} = $args{'all_variables_perf'} if exists($args{'all_variables_perf'}); + $self->{'enable_long_options'} = $args{'enable_long_options'} if exists($args{'enable_long_options'}); + $self->{'enable_rate_of_change'} = $args{'enable_rate_of_change'} if exists($args{'enable_rate_of_change'}); + $self->{'enable_regex_match'} = 1 if exists($args{'enable_regex_match'}) && $args{'enable_regex_match'}!=0; + $self->{'output_comparison_symbols'} = $args{'output_comparison_symbols'} if exists($args{'output_comparison_symbols'}); +} + +# @DESCRIPTION : Allows functions to take be used both directly and as object referenced functions +# In the 2nd case they get $self as 1st argument, in 1st they don't. this just adds +# $self if its if its not there so their argument list is known. +# Functions that allow both should still check if $self is defined +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : arbitrary list of arguments +# @RETURNS : arbitrary list of arguments with 1st being object hash or undef +# @PRIVACY & USE : PRIVATE +sub _self_args { + return @_ if ref($_[0]) && exists($_[0]->{'_NaglioLibraryVersion'}); + unshift @_,undef; + return @_; +} + +# @DESCRIPTION : Sets function to be called to display help text on using plugin in case of error +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : reference to usage function +# @RETURNS : nothing +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function : +sub set_usage_function { + my ($self, $usage_function) = @_; + $self->{'usage_function'} = $usage_function; +} + +# @DESCRIPTION : Usage function. For right now it just calls usage function given as a parameter +# In the future if it is not available, it'll print something standard. +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : nothing +# @PRIVACY & USE : PUBLIC, But primary for internal use. Must be used as an object instance function. +sub usage { + my $self = shift; + if (defined($self) && defined($self->{'usage_function'})) { &{$self->{'usage_function'}}(); } +} + +# @DESCRIPTION : This function converts uptime in seconds to nice & short output format +# @LAST_CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - uptime in seconds +# @RETURNS : string of uptime for human consumption +# @PRIVACY & USE : PUBLIC, Maybe used directly or as object instance function : +sub uptime_info { + my ($self,$uptime_seconds) = _self_args(@_); + my $upinfo = ""; + my ($secs,$mins,$hrs,$days) = (undef,undef,undef,undef); + + sub div_mod { return int( $_[0]/$_[1]) , ($_[0] % $_[1]); } + + ($mins,$secs) = div_mod($uptime_seconds,60); + ($hrs,$mins) = div_mod($mins,60); + ($days,$hrs) = div_mod($hrs,24); + $upinfo .= "$days days" if $days>0; + $upinfo .= (($upinfo ne '')?' ':'').$hrs." hours" if $hrs>0; + $upinfo .= (($upinfo ne '')?' ':'').$mins." minutes" if $mins>0 && ($days==0 || $hrs==0); + $upinfo .= (($upinfo ne '')?' ':'').$secs." seconds" if $secs>0 && $days==0 && $hrs==0; + return $upinfo; +} + +# @DESCRIPTION : If debug / verbose option is set, function prints its input out or to debug file +# @LAST_CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - string of debug text +# @RETURNS : nothing +# @PRIVACY & USE : PUBLIC, Maybe used directly or as object instance function +sub verb { + my ($self,$in) = _self_args(@_); + my $debug_file_name = ""; + + if (defined($o_verb) || (defined($self) && defined($self->{'verbose'}) && $self->{'verbose'} ne 0)) { + $debug_file_name = $self->{'debug_file'} if defined($self) && $self->{'debug_file'} ne ""; + $debug_file_name = $self->{'verbose'} if $debug_file_name ne "" && defined($self) && + ($self->{'verbose'} ne 0 && $self->{'verbose'} ne 1 && $self->{'verbose'} ne ''); + $debug_file_name = $o_verb if $debug_file_name ne "" && defined($o_verb) && $o_verb ne ""; + if ($debug_file_name ne "") { + if (!open (DEBUGFILE, ">>$debug_file_name")) { + print $in, "\n"; + } + else { + print DEBUGFILE $in,"\n"; + close DEBUGFILE; + } + } + else { + print $in, "\n"; + } + } +} + +# @DESCRIPTION : Check of string is a a number supporting integers, negative, decimal floats +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - string of text to be checked +# @RETURNS : 1 if its a number, 0 if its not a number +# @PRIVACY & USE : PUBLIC, To be used statically and not as an object instance reference +sub isnum { + my $num = shift; + if (defined($num) && $num =~ /^[-|+]?((\d+\.?\d*)|(^\.\d+))$/ ) { return 1 ;} + return 0; +} + +# @DESCRIPTION : Check of string is a a number supporting integers, negative, decimal floats +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - string of text to be checked +# @RETURNS : 1 if its a number, 0 if its not a number +# @PRIVACY & USE : PUBLIC, To be used statically and not as an object instance function +sub trim { + my $string = shift; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} + +# @DESCRIPTION : Takes as input string from PERF or SAVED data from previous plugin invocation +# which should contain space-separated list of var=data pairs. The string is +# parsed and it returns back hash array of var=>data pairs. +# - Function written in 2007 for check_snmp_netint, first release 06/01/07 +# - Modified to use quotewords as suggested by Nicholas Scott, release of 05/20/12 +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - string of text passed from SERVICEPERFDATA OR SERVICESAVEDDATA MACRO +# @RETURNS : hash array (see description) +# @PRIVACY & USE : PUBLIC, Maybe used directly or as object instance function +# TODO: double-check this works when there are no single quotes as check_snmp_netint always did quotes +sub process_perf { + my ($self,$in) = _self_args(@_); + my %pdh; + my ($nm,$dt); + use Text::ParseWords; + foreach (quotewords('\s+',1,$in)) { + if (/(.*)=(.*)/) { + ($nm,$dt)=($1,$2); + if (defined($self)) { $self->verb("prev_perf: $nm = $dt"); } + else { verb("prev_perf: $nm = $dt"); } + # in some of my plugins time_ is to profile execution time for part of plugin + # $pdh{$nm}=$dt if $nm !~ /^time_/; + $pdh{$nm}=$dt; + $pdh{$nm}=$1 if $dt =~ /(\d+)[csB%]/; # 'c' or 's' or B or % maybe have been added + # support for more than one set of previously cached performance data + # push @prev_time,$1 if $nm =~ /.*\.(\d+)/ && (!defined($prev_time[0]) || $prev_time[0] ne $1); + } + } + return %pdh; +} + +# @DESCRIPTION : Converts variables with white-spaces with per-name enclosed with '' +# @LAST CHANGED : 08-24-12 by WL +# @INPUT : ARG1 - varible name +# @RETURNS : name for perf-out output +# @PRIVACY & USE : PUBLIC, but its use should be limited. To be used statically and not as an object instance function +sub perf_name { + my $in = shift; + my $out = $in; + $out =~ s/'\/\(\)/_/g; #' get rid of special characters in performance description name + if ($in !~ /\s/ && $in eq $out) { + return $in; + } + return "'".$out."'"; +} + +# @DESCRIPTION : Determines appropriate output name (for STATUS and PERF) taking into account +# rate variales prefix/suffix and 'NAME' override in long thresholds line specification +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name (variable as found in dataresults) +# @RETURNS : name for output +# @PRIVACY & USE : PUBLIC, but its use should be limited. To be as an object instance function, +sub out_name { + my ($self,$dname) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self-> {'_dataresults'}; + my $vr = $self->data2varname($dname,1); + my $name_out; + + if (defined($vr) && exists($thresholds->{$vr}{'NAME'})) { + if (exists($thresholds->{$vr}{'PATTERN'}) || $self->{'enable_regex_match'} == 1) { + $thresholds->{$vr}{'NAMES_INDEX'} = {} if !exists($thresholds->{$vr}{'NAMES_INDEX'}); + if (!exists($thresholds->{$vr}{'NAMES_INDEX'}{$dname})) { + my $ncount = scalar(keys %{$thresholds->{$vr}{'NAMES_INDEX'}}); + $ncount++; + $thresholds->{$vr}{'NAMES_INDEX'}{$dname} = $ncount; + } + $name_out = $thresholds->{$vr}{'NAME'} .'_'. $thresholds->{$vr}{'NAMES_INDEX'}{$dname}; + } + else { + $name_out = $thresholds->{$vr}{'NAME'}; + } + } + else { + # this is for output of rate variables which name internally start with & + if ($dname =~ /^&(.*)/) { + $name_out = $self->{'o_rprefix'}.$1.$self->{'o_rsuffix'}; + } + else { + $name_out = $dname; + } + } + return $name_out; +} + +# @DESCRIPTION : Builds statusline. Adds info on error conditions that would preceed status data. +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - string argument for status info +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, but its direct use is discouraged. Must be used as an object instance function +sub addto_statusinfo_output { + my ($self, $var, $sline) = @_; + $self->{'_statusinfo'} .= ", " if $self->{'_statusinfo'}; + $self->{'_statusinfo'} .= trim($sline); + $self->{'_dataresults'}{$var}[1]++; +} + +# @DESCRIPTION : Accessor function for statusinfo +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : statusinfo (error conditions and messages) string +# @PRIVACY & USE : PUBLIC. Must be used as an object instance function +sub statusinfo { + my $self = shift; + if (defined($self) && defined($self->{'_statusinfo'})) { + return $self->{'_statusinfo'}; + } + return undef; +} + +# @DESCRIPTION : Builds Statuline. Adds variable data for status line output in non-error condition. +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - formatted for human consumption text of collected data for this variable +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, but its direct use is discouraged. Must be used as an object instance function +sub addto_statusdata_output { + my ($self,$dvar,$data) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self -> {'_dataresults'}; + my $avar = $self->data2varname($dvar,1); + + # $self->verb("debug: addto_statusdata_output - dvar is $dvar and avar is $avar"); + if ((!exists($thresholds->{$avar}{'DISPLAY'}) || $thresholds->{$avar}{'DISPLAY'} eq 'YES') && + (!exists($dataresults->{$dvar}[1]) || $dataresults->{$dvar}[1] == 0)) { + $self->{'_statusdata'} .= ", " if $self->{'_statusdata'}; + if (defined($data)) { + $self->{'_statusdata'} .= trim($data); + } + elsif (exists($dataresults->{$dvar}[0])) { + $self->{'_statusdata'} .= $self->out_name($dvar) ." is ".$dataresults->{$dvar}[0]; + } + $dataresults->{$dvar}[1]++; + } +} + +# @DESCRIPTION : Accessor function for statusdata +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : statusdata string (non-error data from some variables) +# @PRIVACY & USE : PUBLIC. Must be used as an object instance function +sub statusdata { + my $self = shift; + if (defined($self) && defined($self->{'_statusdata'})) { + return $self->{'_statusdata'}; + } + return undef; +} + +# @DESCRIPTION : This function sets text or data for data variable PERFORMANCE output +# (;warn;crit would be added to it later if thresholds were set for this variable) +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - either "var=data" text or just "data" (in which case var= is prepended to it) +# ARG3 - UOM symol ('c' for continous, '%' for percent, 's' for seconds) to added after data +# if undef then it is looked up in known variables and if one is present there, its used +# ARG4 - one of: "REPLACE" - if existing preset perfdata is present, it would be replaced with ARG2 +# "ADD" - if existing preset perfdata is there, ARG2 string would be added to it (DEFAULT) +# "IFNOTSET - only set perfdata to ARG2 if it is empty, otherwise keep existing +# @RETURNS : nothing (future: 0 on success, -1 on error) +# @PRIVACY & USE : PUBLIC, but its use should be limited to custom variables added by plugins to data +# Must be used as an object instance function +sub set_perfdata { + my ($self,$avar,$adata,$unit,$opt) = @_; + my $dataresults = $self->{'_dataresults'}; + my $thresholds = $self->{'_thresholds'}; + my $known_vars = $self->{'knownStatusVars'}; + my $bdata = $adata; + my $vr = undef; + + # default operation is ADD + if (!defined($opt)) { + $opt = "ADD"; + } + else { + $opt = uc $opt; + } + if (defined($adata)) { + # if only data wthout "var=" create proper perf line + $bdata = perf_name($self->out_name($avar)).'='.$adata if $adata !~ /=/; + if (defined($unit)) { + $bdata .= $unit; + } + else { + # appending UOM is done here + $vr = $self->data2varname($avar,1); + if (defined($vr)) { + if (exists($thresholds->{$vr}{'UOM'})) { + $bdata .= $thresholds->{$vr}{'UOM'}; + } + elsif (exists($known_vars->{$vr}[2])) { + $bdata .= $known_vars->{$vr}[2]; + } + } + } + # preset perfdata in dataresults array + $dataresults->{$avar}=[undef,0,0,''] if !defined($dataresults->{$avar}); + $dataresults->{$avar}[2]=-1; + if ($opt eq "REPLACE" || !exists($dataresults->{$avar}[3]) || $dataresults->{$avar}[3] eq '') { + $dataresults->{$avar}[3]=$bdata; + } + elsif (exists($dataresults->{$avar}[3]) && $dataresults->{$avar}[3] ne '' && $opt eq "ADD") { + $dataresults->{$avar}[3].=$adata; + } + } +} + +# @DESCRIPTION : This function is used when building performance output +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - optional data argument, if not present variable's dataresults are used +# ARG3 - one of: "REPLACE" - if existing preset perfdata is present, it would be replaced with ARG2 +# "ADD" - if existing preset perfdata is there, ARG2 string would be added to it +# "IFNOTSET - only set perfdata to ARG2 if it is empty, otherwise keep existing (DEFAULT) +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, but its direct use is discouraged. Must be used as an object instance function +sub addto_perfdata_output { + my ($self,$avar,$adata, $opt) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self-> {'_dataresults'}; + my $vr = undef; + + if (!defined($opt)) { + $opt = "IFNOTSET"; + } + else { + $opt = uc $opt; + } + $vr = $self->data2varname($avar,1); + if (defined($avar) && defined($vr) && + (!exists($thresholds->{$vr}{'PERF'}) || $thresholds->{$vr}{'PERF'} eq 'YES') && + (!defined($dataresults->{$avar}[2]) || $dataresults->{$avar}[2] < 1)) { + my $bdata = ''; + if (defined($adata)) { + $bdata .= trim($adata); + } + # this is how most perfdata gets added + elsif (defined($dataresults->{$avar}[0])) { + $bdata .= perf_name($self->out_name($avar)) .'='. $dataresults->{$avar}[0]; + } + # this would use existing preset data now if it was present due to default + # setting UOM from KNOWN_STATUS_VARS array is now in set_perfdata if 3rd arg is undef + $self->set_perfdata($avar,$bdata,undef,$opt); + # now we actually add to perfdata from [3] of dataresults + if (exists($dataresults->{$avar}[3]) && $dataresults->{$avar}[3] ne '') { + $bdata = trim($dataresults->{$avar}[3]); + $self->{'_perfdata'} .= " " if $self->{'_perfdata'}; + $self->{'_perfdata'} .= $bdata; + $dataresults->{$avar}[2]=0 if $dataresults->{$avar}[2] < 0; + $dataresults->{$avar}[2]++; + } + } +} + +# @DESCRIPTION : Accessor function for map from data collected to variable names specified in options and thresholds +# @LAST CHANGED : 08-22-13 by WL +# @INPUT : ARG1 - data variable name +# ARG2 - if undef or 0 return undef if no match for ARG1 found, if 1 return ARG1 +# @RETURNS : string of variable name as was specified with --variables or --thresholds +# @PRIVACY & USE : PUBLIC. Must be used as an object instance function +sub data2varname { + my ($self,$dname,$ropt) = @_; + my $dataresults = $self->{'_dataresults'}; + + return $dataresults->{$dname}[4] if defined($self) && defined($dataresults->{$dname}[4]); + return $dname if defined($ropt) && $ropt eq 1; + return undef; +} + +# @DESCRIPTION : Sets list and info on known variables and regex for acceptable data types. +# This function maybe called more than once. If called again, new vars in subsequent +# calls are added to existing ones and existing vars are replaced if they are there again. +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : ARG1 - ref to hash array of known vars. Keys are variable names. Data is an array. Example is: +# 'version' => [ 'misc', 'VERSION', '' ], +# 'utilization' => [ 'misc', 'GAUGE', '%' ], +# 'cmd_get' => [ 'misc', 'COUNTER', 'c', "Total Number of Get Commands from Start" ], +# The array elements are: +# 1st - string of source for this variable. not used by the library at all, but maybe used by code getting the data +# 2nd - type of data in a variable. May be "GAUGE", "VERSION", "COUNTER", "BOOLEAN", "TEXTINFO", "TEXTDATA", "SETTING" +# 3rd - either empty or one-character UOM to be added to perforance data - 'c' for continous, '%' percent, 's' seconds +# 4th - either empty or a description of this variable. If not empty, the variable becomes long-option and this is help text +# ARG2 - regex of acceptable types of data for performance output. Anything else is ignored (i.e. no no output to perf), but +# is still available for threshold checks. if this is undef, then default of 'GAUGE|COUNTER|^DATA$|BOOLEAN' is used +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as object instance function +sub set_knownvars { + my ($self, $known_vars_in, $vartypes_regex_in) = @_; + my $known_vars = $self->{'knownStatusVars'}; + + if (defined($known_vars_in)) { + foreach (keys %{$known_vars_in}) { + $known_vars->{$_} = $known_vars_in->{$_}; + } + } + if (defined($vartypes_regex_in)) { + $self->{'perfOKStatusRegex'} = $vartypes_regex_in; + } + else { + $self->{'perfOKStatusRegex'} = $DEFAULT_PERF_OK_STATUS_REGEX; + } +} + +# @DESCRIPTION : Adds known variables definition one at a time +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : ARG1 - variable name +# ARG2 - string of source for this variable. not used by the library at all, but maybe used by code getting the data +# ARG3 - type of data in a variable. May be "GAUGE", "VERSION", "COUNTER", "BOOLEAN", "TEXTINFO", "TEXTDATA", "SETTING" +# ARG4 - either empty or one-character UOM symbol to be added to perforance data - 'c' for continous, '%' percent, 's' seconds +# ARG5 - either empty or a description of this variable. If not empty, the variable becomes long-option and this is help text +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as object instance function +sub add_knownvar { + my ($self, $varname, $source, $type, $unit, $description) = @_; + my $temp = { $varname => [ $source, $type, $unit, $description] }; + $self->set_knownvars($temp,undef); +} + +# @DESCRIPTION : This function is used for checking data values against critical and warning thresholds +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - variable name (used for text output in case it falls within threshold) +# ARG2 - data to be checked +# ARG3 - threshold to be checked, internal structure returned by parse_threshold() +# @RETURNS : Returns "" (empty string) if data is not within threshold range +# and text message for status line out about how data is within range otherwise +# @PRIVACY & USE : PUBLIC. Maybe used directly or as an object instance function +sub check_threshold { + my ($self,$attrib,$data,$th_array) = _self_args(@_); + my $mod = $th_array->[0]; + my $lv1 = $th_array->[1]; + my $lv2 = $th_array->[2]; + my $issymb = 1; + $issymb = 0 if defined($self) && $self->{'output_comparison_symbols'} eq 0; + + # verb("debug check_threshold: $mod : ".(defined($lv1)?$lv1:'')." : ".(defined($lv2)?$lv2:'')); + return "" if !defined($lv1) || ($mod eq '' && $lv1 eq ''); + return " " . $attrib . " is " . $data . ( ($issymb==1)?' = ':' equal to ' ). $lv1 if $mod eq '=' && $data eq $lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' != ':' not equal to ' ). $lv1 if $mod eq '!' && $data ne $lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' > ':' more than ' ) . $lv1 if $mod eq '>' && $data>$lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' > ':' more than ' ) . $lv2 if $mod eq ':' && $data>$lv2; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' >= ':' more than or equal to ' ) . $lv1 if $mod eq '>=' && $data>=$lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' < ':' less than ' ). $lv1 if ($mod eq '<' || $mod eq ':') && $data<$lv1; + return " " . $attrib . " is " . $data . ( ($issymb==1)?' <= ':' less than or equal to ' ) . $lv1 if $mod eq '<=' && $data<=$lv1; + return " " . $attrib . " is " . $data . " in range $lv1..$lv2" if $mod eq '@' && $data>=$lv1 && $data<=$lv2; + return ""; +} + +# @DESCRIPTION : This function is called to parse threshold string +# @LAST CHANGED : 08-20-12 by WL +# (the code in this function can be traced back to late 2006. It has not much changed from 2008) +# @INPUT : ARG1 - String for one variable WARN or CRIT threshold which can be as follows: +# data - warn if data is above this value if numeric data, or equal for non-numeric +# >data - warn if data is above this value (default for numeric values) +# num2 +# \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 +# @RETURNS : Returns reference to a hash array, this library's structure for holding processed threshold spec +# @PRIVACY & USE : PUBLIC. Maybe used directly or as an object instance function +sub parse_threshold { + my ($self,$thin) = _self_args(@_); + + # link to an array that holds processed threshold data + # array: 1st is type of check, 2nd is threshold value or value1 in range, 3rd is value2 in range, + # 4th is extra options such as ^, 5th is nagios spec string representation for perf out + my $th_array = [ '', undef, undef, '', '' ]; + my $th = $thin; + my $at = ''; + + $at = $1 if $th =~ s/^(\^?[@|>|<|=|!]?~?)//; # check mostly for my own threshold format + $th_array->[3]='^' if $at =~ s/\^//; # deal with ^ option + $at =~ s/~//; # ignore ~ if it was entered + if ($th =~ /^\:([-|+]?\d+\.?\d*)/) { # :number format per nagios spec + $th_array->[1]=$1; + $th_array->[0]=($at !~ /@/)?'>':'<='; + $th_array->[5]=($at != /@/)?('~:'.$th_array->[1]):($th_array->[1].':'); + } + elsif ($th =~ /([-|+]?\d+\.?\d*)\:$/) { # number: format per nagios spec + $th_array->[1]=$1; + $th_array->[0]=($at !~ /@/)?'<':'>='; + $th_array->[5]=($at != /@/)?'':'@'; + $th_array->[5].=$th_array->[1].':'; + } + elsif ($th =~ /([-|+]?\d+\.?\d*)\:([-|+]?\d+\.?\d*)/) { # nagios range format + $th_array->[1]=$1; + $th_array->[2]=$2; + if ($th_array->[1] > $th_array->[2]) { + print "Incorrect format in '$thin' - in range specification first number must be smaller then 2nd\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + $th_array->[0]=($at !~ /@/)?':':'@'; + $th_array->[5]=($at != /@/)?'':'@'; + $th_array->[5].=$th_array->[1].':'.$th_array->[2]; + } + if (!defined($th_array->[1])) { # my own format (<,>,=,!) + $th_array->[0] = ($at eq '@')?'<=':$at; + $th_array->[1] = $th; + $th_array->[5] = '~:'.$th_array->[1] if ($th_array->[0] eq '>' || $th_array->[0] eq '>='); + $th_array->[5] = $th_array->[1].':' if ($th_array->[0] eq '<' || $th_array->[0] eq '<='); + $th_array->[5] = '@'.$th_array->[1].':'.$th_array->[1] if $th_array->[0] eq '='; + $th_array->[5] = $th_array->[1].':'.$th_array->[1] if $th_array->[0] eq '!'; + } + if ($th_array->[0] =~ /[>|<]/ && !isnum($th_array->[1])) { + print "Numeric value required when '>' or '<' are used !\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + # verb("debug parse_threshold: $th_array->[0] and $th_array->[1]"); + $th_array->[0] = '=' if !$th_array->[0] && !isnum($th_array->[1]) && $th_array->[1] ne ''; + if (!$th_array->[0] && isnum($th_array->[1])) { # this is just the number by itself, becomes 0:number check per nagios guidelines + $th_array->[2]=$th_array->[1]; + $th_array->[1]=0; + $th_array->[0]=':'; + $th_array->[5]=$th_array->[2]; + } + return $th_array; +} + +# @DESCRIPTION : this function checks that for numeric data warn threshold is within range of critical +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - warhing threshold structure (reference to hash array) +# ARG2 - critical threshold structure (reference to hash array) +# @RETURNS : Returns 1 if warning does not fall within critical (there is an error) +# Returns 0 if everything is ok and warning is within critical +# @PRIVACY & USE : PUBLIC, but its use is discouraged. Maybe used directly or as an object instance function. +sub threshold_specok { + my ($self, $warn_thar,$crit_thar) = _self_args(@_); + + return 1 if defined($warn_thar) && defined($warn_thar->[1]) && + defined($crit_thar) && defined($crit_thar->[1]) && + isnum($warn_thar->[1]) && isnum($crit_thar->[1]) && + $warn_thar->[0] eq $crit_thar->[0] && + (!defined($warn_thar->[3]) || $warn_thar->[3] !~ /\^/) && + (!defined($crit_thar->[3]) || $crit_thar->[3] !~ /\^/) && + (($warn_thar->[1]>$crit_thar->[1] && ($warn_thar->[0] =~ />/ || $warn_thar->[0] eq '@')) || + ($warn_thar->[1]<$crit_thar->[1] && ($warn_thar->[0] =~ /[0] eq ':')) || + ($warn_thar->[0] eq ':' && $warn_thar->[2]>=$crit_thar->[2]) || + ($warn_thar->[0] eq '@' && $warn_thar->[2]<=$crit_thar->[2])); + return 0; # return with 0 means specs check out and are ok +} + +# @DESCRIPTION : this compares var names from data to names given as plugin options treating them regex +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : ARG1 - the name to search for +# @RETURNS : Keyname for what first one that matched from _thresholds +# Undef if nothing matched +# @PRIVACY & USE : PUBLIC, but its direct use should be rare. Must be used as an object instance function. +sub var_pattern_match { + my ($self, $name) = @_; + my $thresholds = $self->{'_thresholds'}; + my $allvars = $self->{'_allVars'}; + my $is_regex_match = $self->{'enable_regex_match'}; + my $v; + my $pattern; + + foreach $v (@{$allvars}) { + $pattern=''; + if ($is_regex_match eq 1 && !defined($thresholds->{$v}{'PATTERN'})) { + $pattern=$v; + } + elsif ($is_regex_match ne 0 && defined($thresholds->{$v}{'PATTERN'})) { + $pattern = $thresholds->{$v}{'PATTERN'}; + } + if ($pattern ne '' && $name =~ /$pattern/) { + $self->verb("Data name '".$name."' matches pattern '".$pattern."'"); + return $v; + } + } + return undef; +} + +# @DESCRIPTION : This function adds data results +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - name of data variable +# ARG2 - data for this variable +# ARG3 - name of checked variable/parameter corresponding to this data variable +# default undef, assumed to be same as ARG1 +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub add_data { + my ($self, $dnam, $dval, $anam) = @_; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self-> {'_dataresults'}; + my $datavars = $self -> {'_datavars'}; + my $perfVars = $self->{'_perfVars'}; + + # determine what plugin options-specified var & threshold this data corresponds to + if (!defined($anam)) { + if ($self->{'enable_regex_match'} == 0) { + $anam = $dnam; + } + else { + $anam = $self->var_pattern_match($dnam); + $anam = $dnam if !defined($anam); + } + } + # set dataresults + if (exists($dataresults->{$dnam})) { + $dataresults->{$dnam}[0] = $dval; + $dataresults->{$dnam}[4] = $anam if defined($anam); + } + else { + $dataresults->{$dnam} = [$dval, 0, 0, '', $anam]; + } + # reverse map array + $datavars->{$anam} = [] if !exists($datavars->{$anam}); + push @{$datavars->{$anam}}, $dnam; + # setperf if all variables go to perf + if ($self->{'all_variables_perf'} == 1) { + $thresholds->{$anam}={} if !exists($thresholds->{$anam}); + $thresholds->{$anam}{'PERF_DATALIST'} = [] if !exists($thresholds->{$anam}{'PERF_DATALIST'}); + push @{$thresholds->{$anam}{'PERF_DATALIST'}}, $dnam; + if (!defined($thresholds->{$anam}{'PERF'})) { + push @{$perfVars}, $anam; + $thresholds->{$anam}{'PERF'} = 'YES'; + } + } +} + +# @DESCRIPTION : Accessor function that gets variable data +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - name of data variable +# @RETURNS : undef if variable does not exist and data otherwise +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub vardata { + my ($self,$dnam) = @_; + my $dataresults = $self->{'_dataresults'}; + return undef if !exists($dataresults->{$dnam}); + return $dataresults->{$dnam}[0]; +} + +# @DESCRIPTION : This function parses "WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN" combined threshold string +# Parsing of actual threshold i.e. what is after WARN, CRIT is done by parse_threshold() function +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - String containing threshold line like "WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN" +# Acceptable comma-separated parts threshold specifiers are: +# WARN: - warning threshold +# CRIT: - critical threshold +# ABSENT:OK|WARNING|CRITICAL|UNKNOWN - nagios exit code if data for this variable is not found +# ZERO:OK|WARNING|CRITICAL|UNKNOWN - nagios exit code if data is 0 +# DISPLAY:YES|NO - output data in plugin status line +# PERF:YES|NO - output data as plugin performance data +# SAVED:YES|NO - put results in saved data (this really should not be set manually) +# PATTERN: - enables regex match allowing more than one real data name to match this threshold +# NAME: - overrides output status and perf name for this variable +# UOM: - unit of measurement symbol to add to perf +# @RETURNS : Returns reference to a hash array, a library's structure for holding processed MULTI-THRESHOLD spec +# Note that this is MULTI-THRESHOLD hash structure, it itself contains threshold hashes returned by parse_threshold() +# @PRIVACY & USE : PUBLIC, but its use is discouraged. Maybe used directly or as an object instance function. +sub parse_thresholds_list { + my ($self,$in) = _self_args(@_); + my $thres = {}; + my @tin = undef; + my $t = undef; + my $t2 = undef; + + @tin = split(',', $in); + $t = uc $tin[0] if exists($tin[0]); + # old format with =warn,crit thresolds without specifying which one + if (defined($t) && $t !~ /^WARN/ && $t !~ /^CRIT/ && $t !~ /^ABSENT/ && $t !~ /^ZERO/ && + $t !~ /^DISPLAY/ && $t !~ /^PERF/ && $t !~ /^SAVED/ && + $t !~ /^PATTERN/ && $t !~ /^NAME/ && $t !~ /^UOM/) { + if (scalar(@tin)==2) { + if (defined($self)) { + $thres->{'WARN'} = $self->parse_threshold($tin[0]); + $thres->{'CRIT'} = $self->parse_threshold($tin[1]); + } + else { + $thres->{'WARN'} = parse_threshold($tin[0]); + $thres->{'CRIT'} = parse_threshold($tin[1]); + } + } + else { + print "Can not parse. Unknown threshold specification: $in\n"; + print "Threshold line should be either both warning and critical thresholds separated by ',' or \n"; + print "new format of: WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN\n"; + print "which allows to specify all 3 (CRIT,WARN,ABSENT) or any one of them in any order\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + # new format with prefix specifying if its WARN or CRIT and support of ABSENT + else { + foreach $t (@tin) { + $t2 = uc $t; + if ($t2 =~ /^WARN\:(.*)/) { + if (defined($self)) { + $thres->{'WARN'} = $self->parse_threshold($1); + } + else { + $thres->{'WARN'} = parse_threshold($1); + } + } + elsif ($t2 =~ /^CRIT\:(.*)/) { + if (defined($self)) { + $thres->{'CRIT'} = $self->parse_threshold($1); + } + else { + $thres->{'CRIT'} = parse_threshold($1); + } + } + elsif ($t2 =~ /^ABSENT\:(.*)/) { + my $val = $1; + if (defined($ERRORS{$val})) { + $thres->{'ABSENT'} = $val; + } + else { + print "Invalid value $val after ABSENT. Acceptable values are: OK, WARNING, CRITICAL, UNKNOWN\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t2 =~ /^ZERO\:(.*)/) { + my $val = $1; + if (exists($ERRORS{$val})) { + $thres->{'ZERO'} = $val; + } + else { + print "Invalid value $val after ZERO. Acceptable values are: OK, WARNING, CRITICAL, UNKNOWN\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t2 =~ /^DISPLAY\:(.*)/) { + if ($1 eq 'YES' || $1 eq 'NO') { + $thres->{'DISPLAY'} = $1; + } + else { + print "Invalid value $1 after DISPLAY. Specify this as YES or NO.\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t2 =~ /^PERF\:(.*)/) { + if ($1 eq 'YES' || $1 eq 'NO') { + $thres->{'PERF'} = $1; + } + else { + print "Invalid value $1 after PERF. Specify this as YES or NO.\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($t =~ /^PATTERN\:(.*)/i) { + $thres->{'PATTERN'} = $1; + $self->{'enable_regex_match'} = 2 if defined($self) && $self->{'enable_regex_match'} eq 0; + } + elsif ($t =~ /^NAME\:(.*)/i) { + $thres->{'NAME'} = $1; + } + elsif ($t =~ /^UOM\:(.*)/i) { + $thres->{'UOM'} = $1; + } + else { + print "Can not parse. Unknown threshold specification: $_\n"; + print "Threshold line should be WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,ZERO:OK|WARNING|CRITICAL|UNKNOWN\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + } + if (exists($thres->{'WARN'}) && exists($thres->{'CRIT'})) { + my $check_warncrit = 0; + if (defined($self)) { + $check_warncrit = $self->threshold_specok($thres->{'WARN'},$thres->{'CRIT'}); + } + else { + $check_warncrit = threshold_specok($thres->{'WARN'},$thres->{'CRIT'}); + } + if ($check_warncrit) { + print "All numeric warning values must be less then critical (or greater then when '<' is used)\n"; + print "Note: to override this check prefix warning value with ^\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + return $thres; +} + +# @DESCRIPTION : Adds variable to those whose thresholds would be checked +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : ARG1 - name of the data variable +# ARG2 - either: +# 1) ref to combined thresholds hash array i.e. { 'WARN' => threshold array, 'CRIT' => threshold array, ABSENT => ... } +# such hash array is returned by by parse_thresholds_list function +# -- OR -- +# 2) a tet string with a list of thresholds in the format +# WARN:threshold,CRIT:thresholod,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,ZERO:WARNING|CRITICAL|UNKNOWN,PATTERN:pattern,NAME:name +# which would get parsed y parse_thresholds_list function into ref array +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, Recommend function for adding thresholds. Must be used as an object instance function +sub add_thresholds { + my ($self,$var,$th_in) = @_; + my $th; + if (ref($th_in) && (exists($th_in->{'WARN'}) || exists($th_in->{'CRIT'}) || exists($th_in->{'DISPLAY'}) || + exists($th_in->{'PERF'}) || exists($th_in->{'SAVED'}) || exists($th_in->{'ABSENT'}) || + exists($th_in->{'ZERO'}) || exists($th_in->{'PATTERN'}))) { + $th = $th_in; + } + else { + $th = $self->parse_thresholds_list($th_in); + } + if (!defined($var)) { + if (defined($th->{'NAME'})) { + $var = $th->{'NAME'}; + } + elsif (defined($th->{'PATTERN'})) { + $var = $th->{'PATTERN'}; + } + else { + print "Can not parse. No name or pattern in threshold: $th_in\n"; + print "Specify threshold line as: NAME:name,PATTERN:regex,WARN:threshold,CRIT:threshold,ABSENT:OK|WARNING|CRITICAL|UNKNOWN,ZERO:OK|WARNING|CRITICAL|UNKNOWN\n"; + $self->usage(); + exit $ERRORS{"UNKNOWN"}; + } + } + push @{$self->{'_allVars'}}, $var if !exists($self->{'_thresholds'}{$var}); + $self->{'_thresholds'}{$var}=$th; +} + +# @DESCRIPTION : Accessor function for thresholds and related variable settings on what and how to check +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - name of data variable +# ARG2 - name of the threshold or related data setting to return +# This can be: "WARN", "CRIT", "ABSENT", "ZERO", "DISPLAY", "PERF" +# @RETURNS : undef if variable does not exist +# if variable exists and "WARN" or "CRIT" thresholds are requested, it returns asociated +# threshold hash array structure for named threshold of the type returned by parse_threshold() +# for ABSENT, ZERO, DISPLAY, PERF and other, it returns a string for this check setting +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub get_threshold { + my ($self,$var,$thname) = @_; + return undef if !exists($self->{'_thresholds'}{$var}) || !exists($self->{'_thresholds'}{$var}{$thname}); + return $self->{'_thresholds'}{$var}{$thname}; +} + +# @DESCRIPTION : Modifier function for thresholds and related variable settings on how to check and display results +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - name of data variable +# ARG2 - type of the threshold or related data setting +# This can be: "WARN", "CRIT", "ABSENT", "ZERO", "DISPLAY", "PERF" +# ARG3 - what to set this to, for "WARN" and "CRIT" this must be hash array returned by parse_threshold() +# @RETURNS : 0 if type you want to set is not one of "WARN", "CRIT", "ZERO" or other acceptable settings +# 1 on success +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub set_threshold { + my ($self,$var,$thname,$thdata) = @_; + if ($thname ne 'WARN' && $thname ne 'CRIT' && $thname ne 'ZERO' && $thname ne 'PATTERN' && $thname ne 'NAME' && + $thname ne 'ABSENT' && $thname ne 'PERF' && $thname ne 'DISPLAY' && $thname ne 'SAVED' && $thname ne 'UOM') { + return 0; + } + $self->{'_thresholds'}{$var}={} if !exists($self->{'_thresholds'}{$var}); + $self->{'_thresholds'}{$var}{$thname}=$thdata; + return 1; +} + +# @DESCRIPTION : Returns list variables for GetOptions(..) that are long-options based on known/defined variable +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : none +# @RETURNS : Array of additional options based on KNOWN_STATS_VARS +# @PRIVACY & USE : PUBLIC, Special use case with GetOpt::Long. Must be used as an object instance function +sub additional_options_list { + my $self = shift; + + my $known_vars = $self->{'knownStatusVars'}; + my ($o_rprefix, $o_rsuffix, $v, $v2) = ('','','',''); + $o_rprefix = $self->{'o_rprefix'} if defined($self->{'o_rprefix'}); + $o_rsuffix = $self->{'o_rsuffix'} if defined($self->{'o_rsuffix'}); + my @VarOptions = (); + + if ($self->{'enable_long_options'} != -1) { + if (defined($self) && defined($known_vars)) { + foreach $v (keys %{$known_vars}) { + if (exists($known_vars->{$v}[3]) && $known_vars->{$v}[3] ne '') { + push @VarOptions,$v."=s"; + if ($self->{'enable_rate_of_change'} eq 1 && $known_vars->{$v}[1] eq 'COUNTER' && ($o_rprefix ne '' || $o_rsuffix ne '')) { + $v2 = $o_rprefix.$v.$o_rsuffix; + push @VarOptions,$v2."=s" + } + } + } + } + } + if (scalar(@VarOptions)>0) { + $self->{'enable_long_options'} = 1; + } + return @VarOptions; +} + +# @DESCRIPTION : Prints out help for generated long options +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : none +# @RETURNS : a string of text for help output +# @PRIVACY & USE : PUBLIC, Special use case with GetOpt::Long. Must be used as an object instance function +sub additional_options_help { + my $self = shift; + my $vname; + my $vname2; + my $counter = 0; + my $known_vars = $self->{'knownStatusVars'}; + + if ($self->{'enable_long_options'} != 1) { return ''; } + + my $out=" These options are all --long_name= + where specifiers are one or more of: + WARN:threshold - warning alert threshold + CRIT:threshold - critical alert threshold + Threshold is a value (usually numeric) which may have the following prefix: + > - warn if data is above this value (default for numeric values) + < - warn if data is below this value (must be followed by number) + = - warn if data is equal to this value (default for non-numeric values) + ! - warn if data is not equal to this value + Threshold can also be specified as a range in two forms: + num1:num2 - warn if data is outside range i.e. if datanum2 + \@num1:num2 - warn if data is in range i.e. data>=num1 && data<=num2 + ABSENT:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if data is absent + ZERO:OK|WARNING|CRITICAL|UNKNOWN - Nagios alert (or lock of thereof) if result is 0 + DISPLAY:YES|NO - Specifies if data should be included in nagios status line output + PERF:YES|NO - Output results as performance data or not (always YES if asked for rate) + NAME: - Change the name to in status and PERF output\n\n"; + + # add more options based on KNOWN_STATUS_VARS array + foreach $vname (keys(%{$known_vars})) { + if (exists($known_vars->{$vname}[3])) { + $counter++; + $out .= ' --'.$vname."=WARN:threshold,CRIT:threshold,\n"; + $out .= " ".$known_vars->{$vname}[3]."\n"; + if ($known_vars->{$vname}[1] eq 'COUNTER' && $self->{'enable_rate_of_change'} eq 1) { + $vname2=$o_rprefix.$vname.$o_rsuffix; + $out .= ' --'.$vname2."=WARN:threshold,CRIT:threshold,\n"; + $out .= " Rate of Change of ".$known_vars->{$vname}[3]."\n"; + } + } + } + if ($counter>0) { return $out; } + return ""; +} + +# @DESCRIPTION : Processes standard options parsing out of them variables to be checked +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : ARG1 - Options data hash from GetOpt::Long +# ARG2 - option --verbose or -v or --debug : undef normally and "" or filename if debug enabled +# ARG3 - option --variables or -a in WL's plugins : comma-separated list of variables to check +# ARG4 - option --warn or -w : comma-separated warning thresholds for variables in ARG3 +# ARG5 - option --crit or -c : comma-separated critical thresholds for variables in ARG3 +# ARG6 - option --perf or -f in WL's plugin: all regular variables should also go to perf data +# ARG7 - option --perfvars or -A in WL's plugins: command-separated list of variables whose data goes to PERF output +# ARG8 - prefix to distinguish rate variables, maybe "" but usually this is "rate_" +# ARG9 - suffix to distinguish rate variables, only if ARG7 is "", otherwise optional and absent +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be used shortly after GetOptions. Must be used as an object instance function +sub options_startprocessing { + my ($self, $Options, $o_verb, $o_variables, $o_warn, $o_crit, $o_perf, $o_perfvars, $o_rprefix, $o_rsuffix) = @_; + + # Copy input parameters to object hash array, set them if not present + $o_rprefix="" if !defined($o_rprefix); + $o_rsuffix="" if !defined($o_rsuffix); + $o_crit="" if !defined($o_crit); + $o_warn="" if !defined($o_warn); + $o_variables="" if !defined($o_variables); + $self->{'o_variables'} = $o_variables; + $self->{'o_perfvars'} = $o_perfvars; + $self->{'o_crit'} = $o_crit; + $self->{'o_warn'} = $o_warn; + $self->{'o_perf'} = $o_perf; + $self->{'o_rprefix'} = $o_rprefix; + $self->{'o_rsuffix'} = $o_rsuffix; + $self->{'verbose'} = $o_verb if defined($o_verb); + # start processing + my $perfVars = $self->{'_perfVars'}; + my $ar_varsL = $self->{'_ar_varsL'}; + my $ar_critLv = $self->{'_ar_critLv'}; + my $ar_warnLv = $self->{'_ar_warnLv'}; + my $known_vars = $self->{'knownStatusVars'}; + $o_rprefix = lc $o_rprefix; + $o_rsuffix = lc $o_rsuffix; + # process o_perfvars option + if (defined($o_perfvars)) { + @{$perfVars} = split( /,/ , lc $o_perfvars ); + if (scalar(@{$perfVars})==0) { + $o_perfvars='*'; + $self->{'o_perfvars'}='*'; + } + if ($o_perfvars eq '*') { + $self->{'all_variables_perf'} = 1; + } + else { + # below loop converts rate variables to internal representation + for (my $i=0; $i[$i] = '&'.$1 if $perfVars->[$i] =~ /^$o_rprefix(.*)$o_rsuffix$/; + } + } + } + if (defined($o_warn) || defined($o_crit) || defined($o_variables)) { + if (defined($o_variables)) { + @{$ar_varsL}=split( /,/ , lc $o_variables ); + if (defined($o_warn)) { + $o_warn.="~" if $o_warn =~ /,$/; + @{$ar_warnLv}=split( /,/ , lc $o_warn ); + } + if (defined($o_crit)) { + $o_crit.="~" if $o_crit =~ /,$/; + @{$ar_critLv}=split( /,/ , lc $o_crit ); + } + } + else { + print "Specifying warning or critical thresholds requires specifying list of variables to be checked\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + # this is a special loop to check stats-variables options such as "connected_clients=WARN:warning,CRIT:critical" + # which are specified as long options (new extended threshold line spec introduced in check_redis and check_memcached) + my ($vname,$vname2) = (undef,undef); + foreach $vname (keys(%{$known_vars})) { + $vname2=$o_rprefix.$vname.$o_rsuffix; + if (exists($known_vars->{$vname}[3])) { + if (exists($Options->{$vname})) { + $self->verb("Option $vname found with spec parameter: ".$Options->{$vname}); + $self->add_thresholds($vname,$Options->{$vname}); + } + if (exists($Options->{$vname2})) { + $self->verb("Rate option $vname2 found with spec parameter: ".$Options->{$vname2}); + $self->add_thresholds('&'.$vname,$Options->{$vname2}); + } + } + } + $self->{'_called_options_startprocessing'}=1; +} + +# @DESCRIPTION : Internal function. Parses and sets thresholds for given list of variables after all options have been processed +# @LAST CHANGED : 08-20-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PRIVATE, Must be used as an object instance function +sub _options_setthresholds { + my $self = shift; + + my $perfVars = $self->{'_perfVars'}; + my $ar_varsL = $self->{'_ar_varsL'}; + my $ar_critLv = $self->{'_ar_critLv'}; + my $ar_warnLv = $self->{'_ar_warnLv'}; + my $known_vars = $self->{'knownStatusVars'}; + my $thresholds = $self->{'_thresholds'}; + my ($o_rprefix, $o_rsuffix) = ("", ""); + $o_rprefix = $self->{'o_rprefix'} if exists($self->{'o_rprefix'}); + $o_rsuffix = $self->{'o_rsuffix'} if exists($self->{'o_rsuffix'}); + + if (scalar(@{$ar_warnLv})!=scalar(@{$ar_varsL}) || scalar(@{$ar_critLv})!=scalar(@{$ar_varsL})) { + printf "Number of specified warning levels (%d) and critical levels (%d) must be equal to the number of attributes specified at '-a' (%d). If you need to ignore some attribute do it as ',,'\n", scalar(@{$ar_warnLv}), scalar(@{$ar_critLv}), scalar(@{$ar_varsL}); + $self->verb("Warning Levels: ".join(",",@{$ar_warnLv})); + $self->verb("Critical Levels: ".join(",",@{$ar_critLv})); + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + for (my $i=0; $i[$i] = '&'.$1 if $ar_varsL->[$i] =~ /^$o_rprefix(.*)$o_rsuffix$/; + if ($ar_varsL->[$i] =~ /^&(.*)/) { + if (!defined($self->{'o_prevperf'})) { + print "Calculating rate variable such as ".$ar_varsL->[$i]." requires previous performance data. Please add '-P \$SERVICEPERFDATA\$' to your nagios command line.\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + if (defined($known_vars->{$1}) && $known_vars->{$1}[0] ne 'COUNTER') { + print "$1 is not a COUNTER variable for which rate of change should be calculated\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + if (!exists($thresholds->{$ar_varsL->[$i]})) { + my $warn = $self->parse_threshold($ar_warnLv->[$i]); + my $crit = $self->parse_threshold($ar_critLv->[$i]); + if ($self->threshold_specok($warn,$crit)) { + print "All numeric warning values must be less then critical (or greater then when '<' is used)\n"; + print "Note: to override this check prefix warning value with ^\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + $self->add_thresholds($ar_varsL->[$i], {'WARN'=>$warn,'CRIT'=>$crit} ); + } + } +} + +# @DESCRIPTION : Internal helper function. Finds time when previous performance data was calculated/saved at +# @DEVNOTE : Right now this library and function only supports one previous performance data set, +# but check_snmp_netint plugin supports multiple sets and there the code is more complex, +# As this function originated there, that code is commented out right now. +# @LAST CHANGED : 08-21-12 by WL +# @INPUT : ARG1 - reference to previous performance data hash array. It looks for _ptime variable there. +# ARG2 - string with previous performance time in unix seconds. This may come from separate plugin option. +# @RETURNS : Time in unix seconds frm 1970 or undef if it was not located +# @PRIVACY & USE : PRIVATE, Maybe used directly or as an object instance function. +sub _set_prevtime { + my ($self,$prevperf,$o_prevtime) = _self_args(@_); + my $perfcheck_time; + + if (defined($o_prevtime)) { + # push @prev_time, $o_prevtime; + # $prev_perf{ptime}=$o_prevtime; + $perfcheck_time=$o_prevtime; + } + elsif (defined($prevperf) && defined($prevperf->{'_ptime'})) { + # push @prev_time, $prev_perf{ptime}; + $perfcheck_time=$prevperf->{'_ptime'}; + } + else { + # @prev_time=(); + $perfcheck_time=undef; + } + # numeric sort for timestamp array (this is from lowest time to highiest, i.e. to latest) + # my %ptimes=(); + # $ptimes{$_}=$_ foreach @prev_time; + # @prev_time = sort { $a <=> $b } keys(%ptimes); + return $perfcheck_time; +} + +# @DESCRIPTION : Processes standard options, setting up thresholds based on options that are to be checked +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after plugin finished processing its own custom options. Must be used as an object instance function +sub options_finishprocessing { + my $self = shift; + + if (!exists($self->{'_called_options_finishprocessing'})) { + # process previous performance data + my $prevperf = $self->{'_prevPerf'}; + if (defined($self->{'o_prevperf'})) { + if (defined($self->{'o_perf'}) || defined($self->{'o_perfvars'})) { + %{$prevperf}=$self->process_perf($self->{'o_prevperf'}); + $self->{'_perfcheck_time'} = $self->_set_prevtime($prevperf,$self->{'o_prevtime'}); + } + else { + print "--prevperf can only be used with --perf or --perfvars options\n"; + if (defined($self)) { $self->usage(); } + exit $ERRORS{"UNKNOWN"}; + } + } + # set thresholds + $self->_options_setthresholds(); + # prepare data results arrays + my $dataresults = $self->{'_dataresults'}; + my $thresholds = $self->{'_thresholds'}; + $dataresults->{$_} = [undef, 0, 0] foreach(@{$self->{'_allVars'}}); + if (defined($self->{'_perfVars'})) { + foreach(@{$self->{'_perfVars'}}) { + $dataresults->{$_} = [undef, 0, 0] if !exists($dataresults->{$_}); + $thresholds->{$_} = {} if !exists($thresholds->{$_}); + $thresholds->{$_}{'PERF'} = 'YES'; + } + } + # mark as having finished + $self->{'_called_options_finishprocessing'}=1; + } +} + +# @DESCRIPTION : Accessor function for previously saved perfdata +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : ARG1 - varname +# @RETURNS : value of that variable on previous plugin run, undef if not known +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub prev_perf { + my ($self,$var) = @_; + if (defined($self) && defined($self->{'_prevPerf'}{$var})) { + return $self->{'_prevPerf'}{$var}; + } + return undef; +} + +# @DESCRIPTION : Accessor function for exit status code +# @LAST CHANGED : 08-21-12 by WL +# @INPUT : none +# @RETURNS : current expected exit status code +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub statuscode { + my $self = shift; + return $self->{'_statuscode'}; +} + +# @DESCRIPTION : Sets plugin exist status +# @LAST CHANGED : 08-21-12 by WL +# @INPUT : status code string - one of "WARNING", "CRITICAL", "UNKNOWN". +# @RETURNS : 0 on success, 1 if this status code is below level that plugin would exit with and as such it was not set +# @PRIVACY & USE : PUBLIC, Must be used as an object instance function +sub set_statuscode { + my ($self,$newcode) = @_; + + if ($newcode eq 'UNKNOWN') { + $self->{'_statuscode'} = 'UNKNOWN'; + return 0; + } + if ($self->{'_statuscode'} eq 'UNKNOWN') { return 1; } + elsif ($self->{'_statuscode'} eq 'CRITICAL') { + if ($newcode eq 'CRITICAL') { return 0;} + else { return 1; } + } + elsif ($self->{'_statuscode'} eq 'WARNING') { + if ($newcode eq 'CRITICAL') { + $self->{'_statuscode'} ='CRITICAL'; + return 0; + } + elsif ($newcode eq 'WARNING') { return 0; } + else { return 1; } + } + elsif ($self->{'_statuscode'} eq 'OK') { + if ($newcode eq 'CRITICAL' || $newcode eq 'WARNING') { + $self->{'_statuscode'} = $newcode; + return 0; + } + else { return 1; } + } + else { + printf "SYSTEM ERROR: status code $newcode not supported"; + exit $ERRORS{'UNKNOWN'}; + } + return 1; # should never get here +} + +# @DESCRIPTION : This function is called closer to end of the code after plugin retrieved data and +# assigned values to variables. This function checks variables against all thresholds. +# It prepares statusdata and statusinfo and exitcode. +# @LAST CHANGED : 09-03-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after variables have values. Must be used as an object instance function +sub main_checkvars { + my $self = shift; + + $self->options_finishprocessing() if !exists($self->{'_called_options_finshprocessing'}); + if (exists($self->{'_called_main_checkvars'})) { return; } + + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self->{'_dataresults'}; + my $allVars = $self->{'_allVars'}; + my $datavars = $self->{'_datavars'}; + + my ($dvar,$avar,$aname,$perf_str,$chk)=(undef,undef,undef,undef,undef); + + # main loop to check for warning & critical thresholds + for (my $i=0;$i[$i]; + if (!defined($datavars->{$avar}) || scalar(@{$datavars->{$avar}})==0) { + if (defined($thresholds->{$avar}{'ABSENT'})) { + $self->set_statuscode($thresholds->{$avar}{'ABSENT'}); + } + else { + $self->set_statuscode("CRITICAL"); + } + $aname = $self->out_name($avar); + $self->addto_statusinfo_output($avar, "$aname data is missing"); + } + foreach $dvar (@{$datavars->{$avar}}) { + $aname = $self->out_name($dvar); + if (defined($dataresults->{$dvar}[0])) { + # main check + if (defined($avar)) { + if ($dataresults->{$dvar}[0] eq 0 && exists($thresholds->{$avar}{'ZERO'})) { + $self->set_statuscode($thresholds->{$avar}{'ZERO'}); + $self->addto_statusinfo_output($dvar, "$aname is zero") if $self->statuscode() ne 'OK'; + } + else { + $chk=undef; + if (exists($thresholds->{$avar}{'CRIT'})) { + $chk = $self->check_threshold($aname,lc $dataresults->{$dvar}[0], $thresholds->{$avar}{'CRIT'}); + if ($chk) { + $self->set_statuscode("CRITICAL"); + $self->addto_statusinfo_output($dvar,$chk); + } + } + if (exists($thresholds->{$avar}{'WARN'}) && (!defined($chk) || !$chk)) { + $chk = $self->check_threshold($aname,lc $dataresults->{$dvar}[0], $thresholds->{$avar}{'WARN'}); + if ($chk) { + $self->set_statuscode("WARNING"); + $self->addto_statusinfo_output($dvar,$chk); + } + } + } + } + # if we did not output to status line yet, do so + $self->addto_statusdata_output($dvar,$aname." is ".$dataresults->{$dvar}[0]); + + # if we were asked to output performance, prepare it but do not output until later + if ((defined($self->{'o_perf'}) && defined($avar) && !exists($thresholds->{$avar}{'PERF'})) || + (exists($thresholds->{$avar}{'PERF'}) && $thresholds->{$avar}{'PERF'} eq 'YES')) { + $perf_str = perf_name($aname).'='.$dataresults->{$dvar}[0]; + $self->set_perfdata($dvar, $perf_str, undef, "IFNOTSET"); # with undef UOM would get added + $dataresults->{$dvar}[2]=0; # this would clear -1 from preset perf data, making it ready for output + # below is where threshold info gets added to perfdata + if ((exists($thresholds->{$avar}{'WARN'}[5]) && $thresholds->{$avar}{'WARN'}[5] ne '') || + (exists($thresholds->{$avar}{'CRIT'}[5]) && $thresholds->{$avar}{'CRIT'}[5] ne '')) { + $perf_str = ';'; + $perf_str .= $thresholds->{$avar}{'WARN'}[5] if exists($thresholds->{$avar}{'WARN'}[5]) && $thresholds->{$avar}{'WARN'}[5] ne ''; + $perf_str .= ';'.$thresholds->{$avar}{'CRIT'}[5] if exists($thresholds->{$avar}{'CRIT'}[5]) && $thresholds->{$avar}{'CRIT'}[5] ne ''; + $self->set_perfdata($dvar, $perf_str, '', "ADD"); + } + } + } + } + } + $self->{'_called_main_checkvars'}=1; + # $statusinfo=trim($statusinfo); + # $statusdata=trim($statusdata); +} + +# @DESCRIPTION : This function is at the end. It prepares PERFOUT for output collecting all perf variables data +# @LAST CHANGED : 08-26-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after variables have values. Must be used as an object instance function +# Calling this function direcly is optional, its automatically called on 1st call to perfdata() +sub main_perfvars { + my $self = shift; + + my $dataresults = $self->{'_dataresults'}; + my $PERF_OK_STATUS_REGEX = $self->{'perfOKStatusRegex'}; + my $perfVars = $self->{'_perfVars'}; + my $known_vars = $self->{'knownStatusVars'}; + my $datavars = $self->{'_datavars'}; + my $avar; + my $dvar; + + $self->main_checkvars() if !exists($self->{'_called_main_checkvars'}); + if (exists($self->{'_called_main_perfvars'})) { return; } + + for (my $i=0;$i[$i]; + if (!defined($datavars->{$avar}) || scalar(@{$datavars->{$avar}})==0) { + $self->verb("Perfvar: $avar selected for PERFOUT but data not available"); + } + else { + foreach $dvar (@{$datavars->{$avar}}) { + if (defined($dataresults->{$dvar}[0])) { + $self->verb("Perfvar: $dvar ($avar) = ".$dataresults->{$dvar}[0]); + if (!defined($known_vars->{$avar}[1]) || $known_vars->{$avar}[1] =~ /$PERF_OK_STATUS_REGEX/ ) { + $self->addto_perfdata_output($dvar); + } + else { + $self->verb(" -- not adding to perfdata because of it is '".$known_vars->{$avar}[1]."' type variable --"); + } + } + else { + $self->verb("Perfvar: $avar selected for PERFOUT but data not defined"); + } + } + } + } + if (defined($self->{'o_prevperf'})) { + $self->addto_perfdata_output('_ptime', "_ptime=".time(), "REPLACE"); + } + foreach $dvar (keys %{$dataresults}) { + if (defined($dataresults->{$dvar}[3]) && $dataresults->{$dvar}[3] ne '') { + $self->verb("Perfvar (Dataresults Loop): $dvar => ".$dataresults->{$dvar}[3]); + $self->addto_perfdata_output($dvar); + } + } + + $self->{'_called_main_perfvars'}=1; + # $perfdata = trim($perfdata); +} + +# @DESCRIPTION : This function should be called at the very very end, it returns perf data output +# @LAST CHANGED : 08-22-12 by WL +# @INPUT : none +# @RETURNS : string of perfdata starting with "|" +# @PRIVACY & USE : PUBLIC, To be called during plugin output. Must be used as an object instance function +sub perfdata { + my $self=shift; + + $self->main_perfvars() if !exists($self->{'_called_main_perfvars'}); + my $perfdata = trim($self->{'_perfdata'}); + if ($perfdata ne '') { + return " | " . $perfdata; + } + return ""; +} + +# @DESCRIPTION : This function is called after data is available and calculates rate variables +# based on current and previous (saved in perfdata) values. +# @LAST CHANGED : 08-27-12 by WL +# @INPUT : none +# @RETURNS : nothing (future: 1 on success, 0 on error) +# @PRIVACY & USE : PUBLIC, To be called after variables have values. Must be used as an object instance function +sub calculate_ratevars { + my $self = shift; + + my $prev_perf = $self->{'_prevPerf'}; + my $ptime = $self->{'_perfcheck_time'}; + my $thresholds = $self->{'_thresholds'}; + my $dataresults = $self->{'_dataresults'}; + my $datavars = $self->{'_datavars'}; + my $allVars = $self->{'_allVars'}; + + my ($avar,$dvar,$nvar) = (undef,undef,undef); + my $timenow=time(); + if (defined($self->{'o_prevperf'}) && (defined($self->{'o_perf'}) || defined($self->{'o_perfvars'}))) { + for (my $i=0;$i[$i] =~ /^&(.*)/) { + $avar = $1; + if (defined($datavars->{$avar}) && scalar(@{$datavars->{$avar}})>0) { + foreach $dvar (@{$datavars->{$avar}}) { + $nvar = '&'.$dvar; + # this forces perfdata output if it was not already + if (defined($dataresults->{$dvar}) && $dataresults->{$dvar}[2]<1 && + (!defined($dataresults->{$dvar}[3]) || $dataresults->{$dvar}[3] eq '')) { + $self->set_perfdata($dvar, perf_name($self->out_name($dvar)).'='.$dataresults->{$dvar}[0], undef, "IFNOTSET"); + $self->set_threshold($dvar,'PERF','YES'); + $self->set_threshold($dvar,'SAVED','YES'); # will replace PERF in the future + } + if (defined($prev_perf->{$dvar}) && defined($ptime)) { + $self->add_data($nvar, + sprintf("%.2f",($dataresults->{$dvar}[0]-$prev_perf->{$dvar})/($timenow-$ptime))); + $self->verb("Calculating Rate of Change for $dvar ($avar) : ".$nvar."=". $self->vardata($nvar)); + } + } + } + } + } + } +} + +} +##################################### END OF THE LIBRARY FUNCTIONS ######################################### + +# process --query options (which maybe repeated, that's why loop) +sub option_query { + my $nlib = shift; + + for(my $i=0;$iverb("Processing query key option: $o_querykey[$i]"); + my @ar=split(/,/, $o_querykey[$i]); + # how to query + my @key_querytype = split(':', uc shift @ar); + $nlib->verb("- processing query type specification: ".join(':',@key_querytype)); + $query[$i] = { 'query_type' => $key_querytype[0] }; + if ($key_querytype[0] eq 'GET' || $key_querytype[0] eq 'LLEN' || + $key_querytype[0] eq 'SLEN' || $key_querytype[0] eq 'HLEN' || + $key_querytype[0] eq 'ZLEN') { + if (scalar(@key_querytype)!=1) { + print "Incorrect specification. GET, LLEN, SLEN, HLEN, ZLEN do not have any arguments\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + } + elsif ($key_querytype[0] eq 'HGET' || $key_querytype[0] eq 'HEXISTS' || + $key_querytype[0] eq 'SEXISTS') { + if (scalar(@key_querytype)!=2) { + print "Incorrect specification of HGET, HEXISTS or SEXIST. Must include hash or set member name as an argument.\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + $query[$i]{'element_name'} = $key_querytype[1]; + } + elsif ($key_querytype[0] eq 'LRANGE' || $key_querytype[0] eq 'ZRANGE') { + if ($key_querytype[0] eq 'ZRANGE' && scalar(@key_querytype)!=4) { + print "Incorrect specification of ZRANGE. Must include type and start and end (min and max scores).\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + elsif ($key_querytype[0] eq 'LRANGE' && (scalar(@key_querytype)<2 || scalar(@key_querytype)>4)) { + print "Incorrect specification of LRANGE. Must include type and start and end range.\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + elsif ($key_querytype[1] ne 'MAX' && $key_querytype[1] ne 'MIN' && + $key_querytype[1] ne 'AVG' && $key_querytype[1] ne 'SUM') { + print "Invalid LRANGE/ZRANGE type $key_querytype[1]. This must be either MAX or MIN or AVG or SUM\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + $query[$i]{'query_subtype'} = $key_querytype[1]; + $query[$i]{'query_range_start'} = $key_querytype[2] if defined($key_querytype[2]); + $query[$i]{'query_range_end'} = $key_querytype[3] if defined($key_querytype[3]); + } + else { + print "Invalid key query $key_querytype[0]. Currently supported are GET, LLEN, SLEN, HLEN, ZLEN, HGET, HEXISTS, SEXISTS, LRANGE and ZRANGE.\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + # key to query and how to name it + if (scalar(@ar)==0) { + print "Invalid query specification. Missing query key name\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + my ($key_query,$key_name) = split(':', shift @ar); + $key_name = $key_query if !defined($key_name) || ! $key_name; + $nlib->verb("- variable $key_name will receive data from $key_query"); + $query[$i]{'key_query'} = $key_query; + $query[$i]{'key_name'} = $key_name; + # parse thresholds and finish processing assigning values to arrays + my $th = $nlib->parse_thresholds_list(join(',',@ar)); + if (exists($th->{'ABSENT'})) { + $nlib->verb("- ".$th->{'ABSENT'}." alert will be issued if $key_query is not present"); + $query[$i]{'alert'} = $th->{'ABSENT'}; + } + if (exists($th->{'WARN'})) { + $nlib->verb("- warning threshold ".$th->{'WARN'}." set"); + $query[$i]{'warn'} = $th->{'WARN'}; + } + if (exists($th->{'CRIT'})) { + $nlib->verb("- critical threshold ".$th->{'CRIT'}." set"); + $query[$i]{'crit'} = $th->{'CRIT'}; + } + $nlib->add_thresholds($key_name,$th); + } +} + +# sets password, host, port and other data based on options entered +sub options_setaccess { + if (!defined($o_host)) { print "Please specify hostname (-H)\n"; print_usage(); exit $ERRORS{"UNKNOWN"}; } + if (defined($o_pwfile) && $o_pwfile) { + if ($o_password) { + print "use either -x or -C to enter credentials\n"; print_usage(); exit $ERRORS{"UNKNOWN"}; + } + open my $file, '<', $o_pwfile or die $!; + while (<$file>) { + # Match first non-blank line that doesn't start with a comment + if (!($_ =~ /^\s*#/) && $_ =~ /\S+/) { + chomp($PASSWORD = $_); + last; + } + } + close $file; + print 'Password file is empty' and exit $ERRORS{"UNKNOWN"} if !$PASSWORD; + } + if (defined($o_password) && $o_password) { + $PASSWORD = $o_password; + } + $HOSTNAME = $o_host if defined($o_host); + $PORT = $o_port if defined($o_port); + $TIMEOUT = $o_timeout if defined($o_timeout); + $DATABASE = $o_database if defined($o_database); +} + +# parse command line options +sub check_options { + my $opt; + my $nlib = shift; + my %Options = (); + Getopt::Long::Configure("bundling"); + GetOptions(\%Options, + 'v:s' => \$o_verb, 'verbose:s' => \$o_verb, "debug:s" => \$o_verb, + 'h' => \$o_help, 'help' => \$o_help, + 'H:s' => \$o_host, 'hostname:s' => \$o_host, + 'p:i' => \$o_port, 'port:i' => \$o_port, + 'C:s' => \$o_pwfile, 'credentials:s' => \$o_pwfile, + 'x:s' => \$o_password, 'password:s' => \$o_password, + 'D:s' => \$o_database, 'database:s' => \$o_database, + 't:i' => \$o_timeout, 'timeout:i' => \$o_timeout, + 'V' => \$o_version, 'version' => \$o_version, + 'a:s' => \$o_variables, 'variables:s' => \$o_variables, + 'c:s' => \$o_crit, 'critical:s' => \$o_crit, + 'w:s' => \$o_warn, 'warn:s' => \$o_warn, + 'f:s' => \$o_perf, 'perfparse:s' => \$o_perf, + 'A:s' => \$o_perfvars, 'perfvars:s' => \$o_perfvars, + 'T:s' => \$o_timecheck, 'response_time:s' => \$o_timecheck, + 'R:s' => \$o_hitrate, 'hitrate:s' => \$o_hitrate, + 'r:s' => \$o_repdelay, 'replication_delay:s' => \$o_repdelay, + 'P:s' => \$o_prevperf, 'prev_perfdata:s' => \$o_prevperf, + 'E:s' => \$o_prevtime, 'prev_checktime:s'=> \$o_prevtime, + 'm:s' => \$o_memutilization, 'memory_utilization:s' => \$o_memutilization, + 'M:s' => \$o_totalmemory, 'total_memory:s' => \$o_totalmemory, + 'q=s' => \@o_querykey, 'query=s' => \@o_querykey, + 'o=s' => \@o_check, 'check|option=s' => \@o_check, + 'rate_label:s' => \$o_ratelabel, + map { ($_) } $nlib->additional_options_list() + ); + + ($o_rprefix,$o_rsuffix)=split(/,/,$o_ratelabel) if defined($o_ratelabel) && $o_ratelabel ne ''; + + # Standard nagios plugin required options + if (defined($o_help)) { help($nlib); exit $ERRORS{"UNKNOWN"} }; + if (defined($o_version)) { p_version(); exit $ERRORS{"UNKNOWN"} }; + + # now start options processing in the library + $nlib->options_startprocessing(\%Options, $o_verb, $o_variables, $o_warn, $o_crit, $o_perf, $o_perfvars, $o_rprefix, $o_rsuffix); + + # additional variables/options calculated and added by this plugin + if (defined($o_timecheck) && $o_timecheck ne '') { + $nlib->verb("Processing timecheck thresholds: $o_timecheck"); + $nlib->add_thresholds('response_time',$o_timecheck); + } + if (defined($o_hitrate) && $o_hitrate ne '') { + $nlib->verb("Processing hitrate thresholds: $o_hitrate"); + $nlib->add_thresholds('hitrate',$o_hitrate); + $nlib->set_threshold('hitrate','ZERO','OK') if !defined($nlib->get_threshold('hitrate','ZERO')); # except case of hitrate=0, don't remember why I added it + } + if (defined($o_memutilization) && $o_memutilization ne '') { + $nlib->verb("Processing memory utilization thresholds: $o_memutilization"); + $nlib->add_thresholds('memory_utilization',$o_memutilization); + } + if (defined($o_totalmemory)) { + if ($o_totalmemory =~ /^(\d+)B/) { + $o_totalmemory = $1; + } + elsif ($o_totalmemory =~ /^(\d+)K/) { + $o_totalmemory = $1*1024; + } + elsif ($o_totalmemory =~ /^(\d+)M/) { + $o_totalmemory = $1*1024*1024; + } + elsif ($o_totalmemory =~ /^(\d+)G/) { + $o_totalmemory = $1*1024*1024*1024; + } + elsif ($o_totalmemory !~ /^(\d+)$/) { + print "Total memory value $o_totalmemory can not be interpreted\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + } + if (defined($o_repdelay) && $o_repdelay ne '') { + $nlib->verb("Processing replication delay thresholds: $o_repdelay"); + $nlib->add_thresholds('replication_delay',$o_repdelay); + } + + # general check option, allows to specify everything, can be repeated more than once + foreach $opt (@o_check) { + $nlib->verb("Processing general check option: ".$opt); + $nlib->add_thresholds(undef,$opt); + } + + # query option processing + option_query($nlib); + + # finish it up + $nlib->options_finishprocessing(); + options_setaccess(); +} + +# Get the alarm signal (just in case nagios screws up) +$SIG{'ALRM'} = sub { + $redis->quit if defined($redis); + print ("ERROR: Alarm signal (Nagios time-out)\n"); + exit $ERRORS{"UNKNOWN"}; +}; + +########## MAIN ####### + +my $nlib = Naglio->lib_init('plugin_name' => 'check_redis.pl', + 'plugins_authors' => 'William Leibzon', + 'plugin_description' => 'Redis Monitoring Plugin for Nagios', + 'usage_function' => \&print_usage, + 'enable_long_options' => 1, + 'enable_rate_of_change' => 1); +$nlib->set_knownvars(\%KNOWN_STATUS_VARS, $PERF_OK_STATUS_REGEX); + +check_options($nlib); +$nlib->verb("check_redis.pl plugin version ".$Version); + +# Check global timeout if plugin screws up +if (defined($TIMEOUT)) { + $nlib->verb("Alarm at $TIMEOUT"); + alarm($TIMEOUT); +} +else { + $nlib->verb("no timeout defined : $o_timeout + 10"); + alarm ($o_timeout+10); +} + +# some more variables for processing of the results +my $dbversion = ""; +my $vnam; +my $vval; +my %dbs=(); # database-specific info, this is almost unused right now +my %slaves=(); +my $avar; + +# connect using tcp and verify the port is working +my $sock = new IO::Socket::INET( + PeerAddr => $HOSTNAME, + PeerPort => $PORT, + Proto => 'tcp', +); +if (!$sock) { + print "CRITICAL ERROR - Can not connect to '$HOSTNAME' on port $PORT\n"; + exit $ERRORS{'CRITICAL'}; +} +close($sock); + +# now do connection using Redis library +my $start_time; +my $dsn = $HOSTNAME.":".$PORT; +$nlib->verb("connecting to $dsn"); +$start_time = [ Time::HiRes::gettimeofday() ] if defined($o_timecheck); + +$redis = Redis-> new ( server => $dsn, 'debug' => (defined($o_verb))?1:0 ); + +if ($PASSWORD) { + $redis->auth($PASSWORD); +} +if ($DATABASE) { + $redis->select($DATABASE); +} + +if (!$redis) { + print "CRITICAL ERROR - Redis Library - can not connect to '$HOSTNAME' on port $PORT\n"; + exit $ERRORS{'CRITICAL'}; +} + +if (!$redis->ping) { + print "CRITICAL ERROR - Redis Library - can not ping '$HOSTNAME' on port $PORT\n"; + exit $ERRORS{'CRITICAL'}; +} + +# This returns hashref of various statistics/info data +my $stats = $redis->info(); + +# Check specified key if option -q was used +for (my $i=0; $iverb("Getting redis key: ".$query[$i]{'key_query'}); + $result = $redis->get($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'LLEN') { + $nlib->verb("Getting number of items for list with redis key: ".$query[$i]{'key_query'}); + $result = $redis->llen($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'HLEN') { + $nlib->verb("Getting number of items for hash with redis key: ".$query[$i]{'key_query'}); + $result = $redis->hlen($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'SLEN') { + $nlib->verb("Getting number of items for set with redis key: ".$query[$i]{'key_query'}); + $result = $redis->scard($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'ZLEN') { + $nlib->verb("Getting number of items for sorted set with redis key: ".$query[$i]{'key_query'}); + $result = $redis->zcard($query[$i]{'key_query'}); + } + elsif ($query[$i]{'query_type'} eq 'HGET') { + $nlib->verb("Getting hash member ".$query[$i]{'element_name'}." with redis key: ".$query[$i]{'key_query'}); + $result = $redis->hget($query[$i]{'key_query'},$query[$i]{'element_name'}); + } + elsif ($query[$i]{'query_type'} eq 'HEXISTS') { + $nlib->verb("Checking if there exists hash member ".$query[$i]{'element_name'}." with redis key: ".$query[$i]{'key_query'}); + $result = $redis->hexists($query[$i]{'key_query'},$query[$i]{'element_name'}); + } + elsif ($query[$i]{'query_type'} eq 'SEXISTS') { + $nlib->verb("Checking if there exists set member ".$query[$i]{'element_name'}." with redis key: ".$query[$i]{'key_query'}); + $result = $redis->sismember($query[$i]{'key_query'},$query[$i]{'element_name'}); + } + elsif ($query[$i]{'query_type'} eq 'LRANGE' || $query[$i]{'query_type'} eq 'ZRANGE') { + my $range_start; + my $range_end; + if (defined($query[$i]{'query_range_start'}) && $query[$i]{'query_range_start'} ne '') { + $range_start=$query[$i]{'query_range_start'}; + } + else { + $range_start=0; + } + if (defined($query[$i]{'query_range_end'}) && $query[$i]{'query_range_end'} ne '') { + $range_end= $query[$i]{'query_range_end'}; + } + elsif ($query[$i]{'query_type'} eq 'LRANGE') { + $nlib->verb("Getting (lrange) redis key: ".$query[$i]{'key_query'}); + $range_end = $redis->llen($query[$i]{'key_query'})-1; + } + else { + print "ERROR - can not do ZRANGE if you do not specify mix and max score."; + exit $ERRORS{"UNKNOWN"}; + } + my @list; + if ($query[$i]{'query_type'} eq 'LRANGE') { + @list = $redis->lrange($query[$i]{'key_query'}, $range_start, $range_end); + } + else { + @list = $redis->zrange($query[$i]{'key_query'}, $range_start, $range_end); + } + if (scalar(@list)>0) { + $result=shift @list; + foreach(@list) { + $result+=$_ if $query[$i]{'query_subtype'} eq 'SUM' || $query[$i]{'query_subtype'} eq 'AVG'; + $result=$_ if ($query[$i]{'query_subtype'} eq 'MIN' && $_ < $result) || + ($query[$i]{'query_subtype'} eq 'MAX' && $_ > $result); + } + $result = $result / (scalar(@list)+1) if $query[$i]{'query_subtype'} eq 'AVG'; + } + } + if (defined($result)) { + $query[$i]{'result'} = $result; + $nlib->add_data($query[$i]{'key_name'}, $result); + $nlib->verb("Result of querying ".$query[$i]{'key_query'}." is: $result"); + } + else { + $nlib->verb("could not get results for ".$query[$i]{'key_query'}); + } + # else { + # if (exists($query[$i]{'alert'}) && $query[$i]{'alert'} ne 'OK') { + # $statuscode=$query[$i]{'alert'} if $statuscode ne 'CRITICAL'; + # $statusinfo.=", " if $statusinfo; + # $statusinfo.= "Query on ".$query[$i]{'key_query'}." did not succeed"; + # } + # } +} + +# end redis session +$redis->quit; + +# load stats data into internal hash array +my $total_keys=0; +my $total_expires=0; +foreach $vnam (keys %{$stats}) { + $vval = $stats->{$vnam}; + if (defined($vval)) { + $nlib->verb("Stats Line: $vnam = $vval"); + if (exists($KNOWN_STATUS_VARS{$vnam}) && $KNOWN_STATUS_VARS{$vnam}[1] eq 'VERSION') { + $dbversion .= $vval; + } + elsif ($vnam =~ /^db\d+$/) { + $dbs{$vnam}= {'name'=>$vnam}; + foreach (split(/,/,$vval)) { + my ($k,$d) = split(/=/,$_); + $nlib->add_data($vnam.'_'.$k,$d); + $dbs{$vnam}{$k}=$d; + $nlib->verb(" - stats data added: ".$vnam.'_'.$k.' = '.$d); + $total_keys+=$d if $k eq 'keys' && Naglio::isnum($d); + $total_expires+=$d if $k eq 'expires' && Naglio::isnum($d); + } + } + elsif ($vnam =~ /~slave/) { + # TODO TODO TODO TODO + } + else { + $nlib->add_data($vnam, $vval); + } + } + else { + $nlib->verb("Stats Data: $vnam = NULL"); + } +} +$nlib->verb("Calculated Data: total_keys=".$total_keys); +$nlib->verb("Calculated Data: total_expires=".$total_expires); +$nlib->add_data('total_keys',$total_keys); +$nlib->add_data('total_expires',$total_expires); + +# Response Time +if (defined($o_timecheck)) { + $nlib->add_data('response_time',Time::HiRes::tv_interval($start_time)); + $nlib->addto_statusdata_output('response_time',sprintf("response in %.3fs",$nlib->vardata('response_time'))); + if (defined($o_perf)) { + $nlib->set_perfdata('response_time','response_time='.$nlib->vardata('response_time'),'s'); + } +} + +# calculate rate variables +$nlib->calculate_ratevars(); + +# Hitrate +my $hitrate=0; +my $hits_total=0; +my $hits_hits=undef; +my $hitrate_all=0; +if (defined($o_hitrate) && defined($nlib->vardata('keyspace_hits')) && defined($nlib->vardata('keyspace_misses'))) { + for $avar ('keyspace_hits', 'keyspace_misses') { + if (defined($o_prevperf) && defined($o_perf)) { + $nlib->set_perfdata($avar,$avar."=".$nlib->vardata($avar),'c'); + } + $hits_hits = $nlib->vardata('keyspace_hits') if $avar eq 'keyspace_hits'; + $hits_total += $nlib->vardata($avar); + } + $nlib->verb("Calculating Hitrate : total=".$hits_total." hits=".$hits_hits); + if (defined($hits_hits) && defined($nlib->prev_perf('keyspace_hits')) && defined($nlib->prev_perf('keyspace_misses')) && $hits_hits > $nlib->prev_perf('keyspace_hits')) { + $hitrate_all = $hits_hits/$hits_total*100 if $hits_total!=0; + $hits_hits -= $nlib->prev_perf('keyspace_hits'); + $hits_total -= $nlib->prev_perf('keyspace_misses'); + $hits_total -= $nlib->prev_perf('keyspace_hits'); + verb("Calculating Hitrate. Adjusted based on previous values. total=".$hits_total." hits=".$hits_hits); + } + if (defined($hits_hits)) { + if ($hits_total!=0) { + $hitrate= sprintf("%.4f", $hits_hits/$hits_total*100); + } + $nlib->add_data('hitrate',$hitrate); + my $sdata .= sprintf(" hitrate is %.2f%%", $hitrate); + $sdata .= sprintf(" (%.2f%% from launch)", $hitrate_all) if ($hitrate_all!=0); + $nlib->addto_statusdata_output('hitrate',$sdata); + if (defined($o_perf)) { + $nlib->set_perfdata('hitrate',"hitrate=$hitrate",'%'); + } + } +} + +# Replication Delay +my $repl_delay=0; +if (defined($o_repdelay) && defined($nlib->vardata('master_last_io_seconds_ago')) && defined($nlib->vardata('role'))) { + if ($nlib->vardata('role') eq 'slave') { + $repl_delay = $nlib->vardata('master_link_down_since_seconds'); + if (!defined($repl_delay) || $repl_delay < $nlib->vardata('master_last_io_seconds_ago')) { + $repl_delay = $nlib->vardata('master_last_io_seconds_ago','s'); + } + if (defined($repl_delay) && $repl_delay>=0) { + $nlib->add_data('replication_delay',$repl_delay); + $nlib->addto_statusdata_output('replication_delay',sprintf("replication_delay is %d", $nlib->vardata('replication_delay'))); + if (defined($o_perf)) { + $nlib->set_perfdata('replication_delay',sprintf("replication_delay=%d", $nlib->vardata('replication_delay'))); + } + } + } +} + +# Memory Use Utilization +if (defined($o_memutilization) && defined($nlib->vardata('used_memory_rss'))) { + if (defined($o_totalmemory)) { + $nlib->add_data('memory_utilization',$nlib->vardata('used_memory_rss')/$o_totalmemory*100); + $nlib->verb('memory utilization % : '.$nlib->vardata('memory_utilization').' = '.$nlib->vardata('used_memory_rss').' (used_memory_rss) / '.$o_totalmemory.' * 100'); + } + elsif ($o_memutilization ne '') { + print "ERROR: Can not calculate memory utilization if you do not specify total memory on a system (-M option)\n"; + print_usage(); + exit $ERRORS{"UNKNOWN"}; + } + if (defined($o_perf) && defined($nlib->vardata('memory_utilization'))) { + $nlib->set_perfdata('memory_utilization',sprintf(" memory_utilization=%.4f", $nlib->vardata('memory_utilization')),'%'); + } + if (defined($nlib->vardata('used_memory_human')) && defined($nlib->vardata('used_memory_peak_human'))) { + my $sdata="memory use is ".$nlib->vardata('used_memory_human')." ("; + $sdata.='peak '.$nlib->vardata('used_memory_peak_human'); + if (defined($nlib->vardata('memory_utilization'))) { + $sdata.= sprintf(", %.2f%% of max", $nlib->vardata('memory_utilization')); + } + if (defined($nlib->vardata('mem_fragmentation_ratio'))) { + $sdata.=", fragmentation ".$nlib->vardata('mem_fragmentation_ratio').'%'; + } + $sdata.=")"; + $nlib->addto_statusdata_output('memory_utilization',$sdata); + } +} + +# Check thresholds in all variables and prepare status and performance data for output +$nlib->main_checkvars(); +$nlib->main_perfvars(); + +# now output the results +print $nlib->statuscode() . ': '.$nlib->statusinfo(); +print " - " if $nlib->statusinfo(); +print "REDIS " . $dbversion . ' on ' . $HOSTNAME. ':'. $PORT; +print ' has '.scalar(keys %dbs).' databases ('.join(',',keys(%dbs)).')'; +print " with $total_keys keys" if $total_keys > 0; +print ', up '.$nlib->uptime_info($nlib->vardata('uptime_in_seconds')) if defined($nlib->vardata('uptime_in_seconds')); +print " - " . $nlib->statusdata() if $nlib->statusdata(); +print $nlib->perfdata(); +print "\n"; + +# end exit +exit $ERRORS{$nlib->statuscode()}; diff --git a/check_redis/check_redis.cfg b/check_redis/check_redis.cfg new file mode 100644 index 0000000..2fc5fb6 --- /dev/null +++ b/check_redis/check_redis.cfg @@ -0,0 +1,5 @@ +# 'check_redis' command definition +define command{ + command_name check_redis + command_line /usr/lib/monitoring-plugins/check_redis $HOSTADDRESS$ $ARG1$ + } diff --git a/check_redis/control b/check_redis/control new file mode 100644 index 0000000..f96e2a7 --- /dev/null +++ b/check_redis/control @@ -0,0 +1,6 @@ +Homepage: http://william.leibzon.org/nagios/ +Watch: http://william.leibzon.org/nagios/plugins/check_redis.pl Version : ([0-9.]+) +Uploaders: Jan Wagner +Description: plugin that verifies redis server is working. +Recommends: libredis-perl +Version: 0.72 diff --git a/check_redis/copyright b/check_redis/copyright new file mode 100644 index 0000000..3d89cc9 --- /dev/null +++ b/check_redis/copyright @@ -0,0 +1,7 @@ +Copyright (c) 2012 William Leibzon + +License: GPL v2 + + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". +