diff --git a/plugins/cyrus/cyrus-imapd b/plugins/cyrus/cyrus-imapd index 5345caa0..d765af6e 100755 --- a/plugins/cyrus/cyrus-imapd +++ b/plugins/cyrus/cyrus-imapd @@ -1,8 +1,6 @@ #!/bin/sh # -# $Id: cyrus-imapd 18 2011-07-15 09:14:04Z ixs $ -# -# Copyright (C) 2009-2011 Andreas Thienemann +# Copyright (C) 2009 - 2012 Andreas Thienemann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Library General Public License as published by @@ -51,7 +49,7 @@ It displays the following three datapoints: =head1 VERSION - $Revision: 18 $ + 0.0.20120307 =head1 BUGS @@ -71,7 +69,7 @@ GPLv2 CONFIGDIR=$(awk -F : '/^configdirectory:/ { gsub(/ /, "", $2); print $2 }' /etc/imapd.conf 2> /dev/null) PROCDIR="${CONFIGDIR}/proc" -if [ "$1" = "autoconf" ]; then +if [ "$1" == "autoconf" ]; then if [ "x${CONFIGDIR}x" != "xx" ] && [ -d ${PROCDIR} ]; then echo yes else @@ -81,14 +79,14 @@ if [ "$1" = "autoconf" ]; then fi # Check if we actually got some sensible data -if [ "x${CONFIGDIR}x" = "xx" ]; then +if [ "x${CONFIGDIR}x" == "xx" ]; then exit 1 fi # If run with the "config"-parameter, give out information on how the # graphs should look. -if [ "$1" = "config" ]; then +if [ "$1" == "config" ]; then echo 'graph_title Cyrus IMAPd Load' echo 'graph_args --base 1000 -l 0' echo 'graph_vlabel connections' diff --git a/plugins/disk/smart_raw__ b/plugins/disk/smart_raw__ new file mode 100755 index 00000000..e466a7dd --- /dev/null +++ b/plugins/disk/smart_raw__ @@ -0,0 +1,145 @@ + #!/usr/bin/python +# +# Copyright (C) 2011 Andreas Thienemann +# +# 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 3 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, see . +# + +""" +=head1 NAME + +smart_raw__ - Munin plugin to retreive raw SMART values from a disk. + +=head1 APPLICABLE SYSTEMS + +All machines with a SMART capable disk and smartmontools installed. + +This plugin is very useful if you want to need to monitor a single raw S.M.A.R.T. +value reported by a disk. Load Cycle Counts or Pending Sectors come to mind as +these are a good indicator of problems with a disk. + +=head1 CONFIGURATION + +The plugin should be installed as smart_raw_sda_193 which means that the smart value +numbered 193 will be read from the disk sda. + + +Basic configuration for every system is that the plugin needs to be called as root +in order to execute smartmontools. + +Add the following to your /etc/munin/plugin-conf.d/smart_raw: + + [smart_raw_sda_193] + user root + +=head1 INTERPRETATION + +Smart RAW values are provided as is. You need to undertand what you are monitoring... + +=head1 MAGIC MARKERS + + #%# family=contrib + #%# capabilities= + +=head1 VERSION + +0.0.1 + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Andreas Thienemann + +=head1 LICENSE + +GPLv3+ + +=cut +""" + +import subprocess +import sys +import os +import re +import pprint + +# We are a wildcard plugin, figure out what we are being called for +try: + disk = sys.argv[0].split('_')[2] + value = sys.argv[0].split('_')[3] +except IndexError: + sys.exit(1) + +def normalize_name(name): + name = re.sub("[^a-z0-9A-Z]","_", name) + return name + +# Code sniplet from Philipp Keller +# http://code.pui.ch/2007/02/19/set-timeout-for-a-shell-command-in-python/ +def timeout_command(command, timeout): + """call shell-command and either return its output or kill it + if it doesn't normally exit within timeout seconds and return None""" + import subprocess, datetime, os, time, signal + start = datetime.datetime.now() + process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + while process.poll() is None: + time.sleep(0.1) + now = datetime.datetime.now() + if (now - start).seconds> timeout: + os.kill(process.pid, signal.SIGKILL) + os.waitpid(-1, os.WNOHANG) + return None + return process.stdout.read() + +def read_smart(): + """Read SMART attributes""" + out = timeout_command("/usr/sbin/smartctl -A -d ata /dev/%s" % (disk), 2) + smart_attribs = dict() + for line in out.split('\n')[7:-2]: # Cut away the header and the footer + line = line.split() + smart_attribs[line[0]] = { + 'name' : line[1], + 'label' : normalize_name(line[1]), + 'raw' : line [-1], + } + return smart_attribs + +def print_config(): + """Return configuration arguments for munin""" + attribs = read_smart() + print "graph_title S.M.A.R.T raw value %s for drive %s" % (attribs[value]['name'], disk) + print "graph_vlabel Raw value" + print "graph_info RAW smartctl value" + print "graph_category disk" + print "graph_args --base 1000 -l 0" + + print "%s.label %s" % (value, attribs[value]['label']) + sys.exit(0) + +def fetch(): + attribs = read_smart() + print "%s.value %s" % (value, attribs[value]['raw']) + sys.exit(0) + +if "config" in sys.argv[1:]: + print_config() +elif "autoconf" in sys.argv[1:]: + pass +elif "suggest" in sys.argv[1:]: + pass +else: + fetch() diff --git a/plugins/sensors/freeipmi_ b/plugins/sensors/freeipmi_ index ab6860f8..101fea4f 100755 --- a/plugins/sensors/freeipmi_ +++ b/plugins/sensors/freeipmi_ @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright (C) 2011 Andreas Thienemann +# Copyright (C) 2011,2012 Andreas Thienemann # # 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 @@ -40,11 +40,13 @@ likely no bmc to query. In certain cases however bmc-info will just seem to hang for quite some time. In this case, autodetection does not work because the smbios table has -incorrect information. One system know to experience this problem is the +incorrect information. One system known to experience this problem is the HP Proliant Microserver. Adding env.freeipmi_args "--no-probing --driver-type=KCS --driver-address=0xca2 --register-spacing=1" -to the munin plugin configuration will make the plugin work. +to the munin plugin configuration will make the plugin work. This is the +specific line for the HP Proliant Microserver mentioned above. Your mileage +may vary. Basic configuration for every system is that the plugin needs to be called as root. diff --git a/plugins/sensors/snmp__areca_ b/plugins/sensors/snmp__areca_ new file mode 100755 index 00000000..dbb41454 --- /dev/null +++ b/plugins/sensors/snmp__areca_ @@ -0,0 +1,292 @@ +#!/usr/bin/python + +# Copyright (C) 2009 - 2012 Andreas Thienemann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License as published by +# the Free Software Foundation; version 2 only +# +# 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 Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +""" +=head1 NAME + +snmp__areca_ - Munin plugin to get temperature, voltage or fan speed values +from Areca network enabled RAID controllers via SNMP. + +=head1 APPLICABLE SYSTEMS + +All machines with a SNMP capable ARECA raid controller. + +=head1 CONFIGURATION + +Make sure your Areca controller is accessible via SNMP from the munin host: +snmpwalk -v 1 -c snmp_community ip.add.re.ss + +The plugin is a wildcard plugin and can thus be used to retrieve different +values depending on the name of the file. + +Linking it as snmp_10.8.1.230_areca_fan would retrieve the fan speed values from +the Areca controller at 10.8.1.230. + +Valid values are fan, temp and volt. + +Add the following to your /etc/munin/plugin-conf.d/snmp__areca file: + + [snmp_ip.add.re.ss_*] + community private + version 1 + +Then test the plugin by calling the following commands: + +munin-run snmp_10.8.1.230_areca_temp config +munin-run snmp_10.8.1.230_areca_temp + +Both commands should output sensible data without failing. + +=head1 INTERPRETATION + +The plugin shows the temperature in Celsius or the fanspeed in rotations per minute. + +=head1 MAGIC MARKERS + + #%# family=contrib + #%# capabilities= + +=head1 VERSION + +0.0.1 + +=head1 BUGS + +None known. If you know of any, please raise a ticket at https://trac.bawue.org/munin/wiki/areca__snmp_ + +=head1 AUTHOR + +Andreas Thienemann + +=head1 LICENSE + +GPLv2 + +=cut +""" + +import pprint +import time +import sys +import re +import os +from pysnmp import v1, v2c, role, asn1 + +request_conf = { + "volt" : { + "label" : "Voltages", + "vlabel" : "Volt", + "graph" : "--base 1000 --logarithmic", + "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.8" + }, + "fan" : { + "label" : "Fans", + "vlabel" : "RPM", + "graph" : "--base 1000 -l 0", + "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.9" + }, + "temp" : { + "label" : "Temperatures", + "vlabel" : "Celsius", + "graph" : "--base 1000 -l 0", + "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.10" + } +} + +# Sanity check and parsing of the commandline +host = None +port = 161 +request = None +try: + match = re.search("^(?:|.*\/)snmp_([^_]+)_areca_(.+)$", sys.argv[0]) + host = match.group(1) + request = match.group(2) + match = re.search("^([^:]+):(\d+)$", host) + if match is not None: + host = match.group(1) + port = match.group(2) +except: + pass + +if host is None or request is None: + print "# Error: Incorrect filename. Cannot parse host or request." + sys.exit(1) + +# Parse env variables +if os.getenv("community") is not None: + community = os.getenv("community") +else: + community = "public" +if os.getenv("version") is not None: + version = os.getenv("version") +else: + version = "1" + + +def get_data(): + # Fetch the data + results = snmpwalk(request_conf[request]["oid"]) + + # parse data + vals = [] + for i in range(0, len(results)): + idx, res = results[i][0].split(request_conf[request]["oid"])[1].split(".")[1:], results[i][1] + if idx[1] == '1': + vals.append([]) + vals[int(idx[2])-1].append(res) + if idx[1] == '2': + vals[int(idx[2])-1].append(res) + if idx[1] == '3': + if request == "volt": + res = float(res)/1000 + vals[int(idx[2])-1].append(res) + + return vals + +def snmpwalk(root): + + # Create SNMP manager object + client = role.manager((host, port)) + + # Create a SNMP request&response objects from protocol version + # specific module. + try: + req = eval('v' + version).GETREQUEST() + nextReq = eval('v' + version).GETNEXTREQUEST() + rsp = eval('v' + version).GETRESPONSE() + + except (NameError, AttributeError): + print '# Unsupported SNMP protocol version: %s\n%s' % (version, usage) + sys.exit(-1) + + # Store tables headers + head_oids = [root] + + encoded_oids = map(asn1.OBJECTID().encode, head_oids) + + result = []; + + while 1: + + # Encode OIDs, encode SNMP request message and try to send + # it to SNMP agent and receive a response + (answer, src) = client.send_and_receive(req.encode(community=community, encoded_oids=encoded_oids)) + + # Decode SNMP response + rsp.decode(answer) + + # Make sure response matches request (request IDs, communities, etc) + if req != rsp: + raise 'Unmatched response: %s vs %s' % (str(req), str(rsp)) + + # Decode BER encoded Object IDs. + oids = map(lambda x: x[0], map(asn1.OBJECTID().decode, rsp['encoded_oids'])) + + # Decode BER encoded values associated with Object IDs. + vals = map(lambda x: x[0](), map(asn1.decode, rsp['encoded_vals'])) + + # Check for remote SNMP agent failure + if rsp['error_status']: + # SNMP agent reports 'no such name' when walk is over + if rsp['error_status'] == 2: + # Switch over to GETNEXT req on error + # XXX what if one of multiple vars fails? + if not (req is nextReq): + req = nextReq + continue + # One of the tables exceeded + for l in oids, vals, head_oids: + del l[rsp['error_index']-1] + else: + raise 'SNMP error #' + str(rsp['error_status']) + ' for OID #' + str(rsp['error_index']) + + # Exclude completed OIDs + while 1: + for idx in range(len(head_oids)): + if not asn1.OBJECTID(head_oids[idx]).isaprefix(oids[idx]): + # One of the tables exceeded + for l in oids, vals, head_oids: + del l[idx] + break + else: + break + + if not head_oids: + return result + + # Print out results + for (oid, val) in map(None, oids, vals): + result.append((oid, str(val))) + # print oid + ' ---> ' + str(val) + + # BER encode next SNMP Object IDs to query + encoded_oids = map(asn1.OBJECTID().encode, oids) + + # Update request object + req['request_id'] = req['request_id'] + 1 + + # Switch over GETNEXT PDU for if not done + if not (req is nextReq): + req = nextReq + + raise "error" + + +def print_config(): + print "graph_title " + request_conf[request]["label"] + print "graph_vlabel " + request_conf[request]["vlabel"] + print "graph_args " + request_conf[request]["graph"] + print "graph_category sensors" + print "host_name", host + for dataset in get_data(): + if request == "volt": + if dataset[1] == "Battery Status": + continue + else: + print request + dataset[0] + ".label", dataset[1] + ref_val = float(dataset[1].split()[-1][:-1]) + print request + dataset[0] + ".warning", str(ref_val * 0.95) + ":" + str(ref_val * 1.05) + print request + dataset[0] + ".critical", str(ref_val * 0.80) + ":" + str(ref_val * 1.20) + if request == "temp": + print request + dataset[0] + ".label", dataset[1] + if dataset[1].startswith("CPU"): + print request + dataset[0] + ".warning", 55 + print request + dataset[0] + ".critical", 60 + if dataset[1].startswith("System"): + print request + dataset[0] + ".warning", 40 + print request + dataset[0] + ".critical", 45 + if request == "fan": + print request + dataset[0] + ".label", dataset[1] + print request + dataset[0] + ".warning", 2400 + print request + dataset[0] + ".critical", 2000 + + sys.exit(0) + + +if "config" in sys.argv[1:]: + print_config() +elif "snmpconf" in sys.argv[1:]: + print "require 1.3.6.1.4.1.18928.1.2.2.1.8.1.1" + sys.exit(0) +else: + for dataset in get_data(): + # Filter Battery Status (255 == Not installed) + if request == "volt" and dataset[1] == "Battery Status": + continue + print request + dataset[0] + ".value", dataset[2] + sys.exit(0) diff --git a/plugins/sensors/snmp__thecus_fans b/plugins/sensors/snmp__thecus_fans new file mode 100755 index 00000000..ec43e277 --- /dev/null +++ b/plugins/sensors/snmp__thecus_fans @@ -0,0 +1,104 @@ +#!/usr/bin/perl -w +# -*- cperl -*- + +=head1 NAME + +snmp__thecus_fans - Munin plugin to retrive fanspeed readings from a Thecus +NAS device running SNMP. + +=head1 APPLICABLE SYSTEMS + +All Thecus NAS devices which have the third party NETSNMPD module installed. +This is available at http://www.fajo.de/main/thecus/modules/netsnmpd + +=head1 CONFIGURATION + +As a rule SNMP plugins need site specific configuration. The default +configuration (shown here) will only work on insecure sites/devices. + + [snmp_*] + env.version 2 + env.community public + +In general SNMP is not very secure at all unless you use SNMP version +3 which supports authentication and privacy (encryption). But in any +case the community string for your devices should not be "public". + +Please see 'perldoc Munin::Plugin::SNMP' for further configuration +information. + +=head1 INTERPRETATION + +The plugin reports the current fan speed readings as reported by the thecusIO +module. + +=head1 MIB INFORMATION + +Private MIB + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + + 0.0.20120307 + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Copyright (C) 2011 - 2012 Andreas Thienemann + +Based on the snmp__uptime plugin as a template. + +=head1 LICENSE + +GPLv2 or (at your option) any later version. + +=cut + +use strict; +use Munin::Plugin::SNMP; +use vars qw($DEBUG); + +$DEBUG = $ENV{'MUNIN_DEBUG'}; + +my $response; + +if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") { + print "index 1.3.6.1.4.1.14822.101.21.\n"; + print "require 1.3.6.1.4.1.14822.101.21.5200.1.1.0 [0-9]\n"; + print "require 1.3.6.1.4.1.14822.101.21.5200.1.2.0 [0-9]\n"; + exit 0; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") { + my ($host) = Munin::Plugin::SNMP->config_session(); + print "host_name $host\n" unless $host eq 'localhost'; + print "graph_title Thecus Fans +graph_args --base 1000 -l 0 +graph_vlabel RPM +graph_info This graph shows the RPMs of the fans as reported by the ThecusIO module. +graph_category sensors +fan1.label Fan 1 +fan1.info Thecus CPU Fan +fan2.label Fan 2 +fan2.info Thecus System Fan +"; + exit 0; +} + +my $session = Munin::Plugin::SNMP->session(-translate => + [ -timeticks => 0x0 ]); + +my $fan1 = $session->get_single ("1.3.6.1.4.1.14822.101.21.5200.1.1.0") || 'U'; +my $fan2 = $session->get_single ("1.3.6.1.4.1.14822.101.21.5200.1.2.0") || 'U'; + +#print "Retrived uptime is '$uptime'\n" if $DEBUG; + +print "fan1.value ", $fan1, "\n"; +print "fan2.value ", $fan2, "\n"; diff --git a/tools/nagios/check_munin b/tools/nagios/check_munin new file mode 100644 index 00000000..23d7dc7d --- /dev/null +++ b/tools/nagios/check_munin @@ -0,0 +1,187 @@ +#!/usr/bin/python +# +# Copyright (C) 2009 Andreas Thienemann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License as published by +# the Free Software Foundation; version 2 only +# +# 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 Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +# +# Nagios script to query a munin host for plugin values +# +# Can be used as an active check instead of check_dummy +# + +import optparse +import socket +import pprint +import sys +import re + +parser = optparse.OptionParser("usage: %prog -H -M [-P ] -D [] []") +parser.add_option("-H", "--host", dest="host", type="string", + help="specify host to poll") +parser.add_option("-M", "--module", dest="module", type="string", + help="munin module to poll") +parser.add_option("-P", "--port", dest="port", default=4949, + type="int", help="port number to poll") +parser.add_option("-D", "--debug", action="store_true", dest="debug", default=False, + help="Debug output") + +(options, args) = parser.parse_args() + +HOST = options.host +PORT = options.port +MODULE = options.module +DEBUG = options.debug + +if HOST == None or MODULE == None: + parser.error("options -H and -M are required.") + +def compare(val, thresh): + # Compare value to warning and critical threshoulds + # Handle different threshold formats: max, :max, min:, min:max + + val = float(val) + + # max + match = re.match("^[:]?([-+]?[0-9]+)$", str(thresh)) + if match: + max = float(match.group(1)) + if val > max: + return 3 + + + # min + match = re.match("^([-+]?[0-9]+):$", str(thresh)) + if match: + min = float(match.group(1)) + if val < min: + return 2 + + # min:max + match = re.match("^([-+]?[0-9]+):([-+]?[0-9]+)$", str(thresh)) + if match: + min, max = float(match.group(1)), float(match.group(2)) + if val < min or val > max: + return 1 + + # okay + return 0 + +def output(l, cat, desc, ret): + if len(l[cat]) > 0: + print MODULE, desc + ";" + for line in l["critical"]: + print "CRITICAL: " + line + ";" + for line in l["warning"]: + print "WARNING: " + line + ";" + for line in l["ok"]: + print "OK: " + line + ";" + sys.exit(ret) + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((HOST, PORT)) + conn = s.makefile('wb', 0) +except: + print "Couldn't connect to requested host" + sys.exit(3) + + +if conn.readline().startswith("# munin node at"): + conn.writelines("config" + MODULE + "\n") + order = [] + data = {} + while True: + line = conn.readline() + if DEBUG: + pprint.pprint(line) + # Last message, bail + if line == ".\n": + break + + label = "" + + key, val = line.split(" ", 1) + if key.find(".") is not -1: + label = key.split(".")[0] + if label not in data: + data[label] = { "warning" : "", "critical" : "", "value" : "" } + order.append(label) + # No thresholds passed on the command line + if len(args) == 2: + data[label]["warning"] = args[0] + data[label]["critical"] = args[1] + + # No thresholds passed on the command line, take the munin supplied ones + if len(args) < 2: + if key.endswith("warning"): + data[label]["warning"] = val[:-1] + if key.endswith("critical"): + data[label]["critical"] = val[:-1] + + if data[label]["warning"] == "" or data[label]["critical"] == "": + print "UNKNOWN - Couldn't retrieve thresholds, pass some on the command line" + sys.exit(3) + + + conn.writelines("fetch " + MODULE + "\n") + while True: + line = conn.readline() + # Last line, bail + if line == ".\n": + if DEBUG: + pprint.pprint(data) + break + + key, val = line.split(" ", 1) + label = key.split(".")[0] + if key.endswith("value"): + data[label]["value"] = val[:-1] + + conn.writelines("quit\n") + +else: + print "UNKNOWN - No munin node detected" + sys.exit(3) + +conn.close() +s.close() + +l = { "ok" : [], "warning" : [], "critical" : [] } +for entry in order: + # compare actual data: 3 max exceeded, 2 minimum underrun, 1 outside limit, 0 okay + for tresh in ["critical", "warning"]: + val = data[entry]["value"] + tval = data[entry][tresh] + tmp = "" + if compare(val, tval) == 3: + tmp = entry + ": " + val + " has exceeded the maximum threshold of " + tval + break + elif compare(val, tval) == 2: + tmp = entry + ": " + val + " has underrun the minimum threshold of " + tval + break + elif compare(val, tval) == 1: + tmp = entry + ": " + val + " is outside of range " + tval + break + + if tmp != "": + l[tresh].append(tmp) + else: + l["ok"].append(entry + ": " + val + " is okay") + + +output(l, "critical", "CRITICAL", 2) +output(l, "warning", "WARNING", 1) +output(l, "ok", "OK", 0)