diff --git a/plugins/disk/du-2 b/plugins/disk/du-2 index 39787e50..41e4adc1 100755 --- a/plugins/disk/du-2 +++ b/plugins/disk/du-2 @@ -7,12 +7,15 @@ =head1 AUTHOR AND COPYRIGHT - Copyright 2011-2012 Luc Didry + Copyright 2011-2014 Luc Didry =head1 HOWTO CONFIGURE AND USE : =over +=item - /etc/munin/plugins/du + cp du /etc/munin/plugins/ + =item - /etc/munin/plugin-conf.d/du_ [du] @@ -22,14 +25,9 @@ env.suppr /home/ # PLEASE USE \# INSTEAD OF # timeout 900 # 15 MINUTES IN SECONDS -=item - /etc/munin/plugins-enabled - - ln -svf ../plugins-available/site/du - - =item - restart Munin node - sudo killall -TERM munin-node + /etc/init.d/munin-node restart =back @@ -73,7 +71,7 @@ my $TIMEFILE="$Munin::Plugin::pluginstatedir/du.time"; ##### autoconf if( (defined $ARGV[0]) && ($ARGV[0] eq "autoconf") ) { - print "yes\n"; + print "no\n"; ## Done ! munin_exit_done(); } diff --git a/plugins/disk/du_pattern b/plugins/disk/du_pattern new file mode 100755 index 00000000..221cda77 --- /dev/null +++ b/plugins/disk/du_pattern @@ -0,0 +1,78 @@ +#!/bin/sh +# -*- sh -*- +# vim: ft=sh + +: << =cut + +=head1 NAME + + du_pattern - plugin to monitor files size selected and grouped by a pattern + +=head1 CONFIGURATION + + [du_pattern] + env.DIR /var/log/apache/ + env.PATTERN www.example.com www.sample.com + + In PATTERN, all items will be expanded like this : www.example.com* + In this example, you will monitor the size of : + /var/log/apache/www.example.com* + /var/log/apache/www.sample.com* + + It's useful if you want to graph the size of your sites' log archives for example, + one graph per site. + +=head1 AUTHOR AND COPYRIGHT + + Copyright 2013-2014 Luc Didry + +=head1 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 3 of the License, or + 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 . + +=cut + +NAME=${0##*/du_pattern_} + +if [ "$1" = "config" ]; then + echo "multigraph du_pattern_${NAME}" + echo "graph_title Files size in ${DIR}" + echo "graph_vlabel size of files" + echo "graph_category disk" + echo "graph_total Total" + echo "graph_info This graph shows the size of files grouped by a pattern." + FIRST=1 + for i in ${PATTERN} + do + CLEAN=${i//\./_} + echo "$CLEAN.label $CLEAN" + if [[ $FIRST -eq 1 ]] + then + echo "$CLEAN.draw AREA" + FIRST=0 + else + echo "$CLEAN.draw STACK" + fi + done + exit 0 +fi + +for i in ${PATTERN} +do + CLEAN=${i//\./_} + FILES="${DIR}/${i}*" + echo -n "$CLEAN.value " + echo $(du -cbs ${FILES} | grep total | cut -f 1) +done +exit 0 diff --git a/plugins/ejabberd/ejabberd_resources_/ejabberd_resources_ b/plugins/ejabberd/ejabberd_resources_/ejabberd_resources_ index 43d6ed27..f725183d 100755 --- a/plugins/ejabberd/ejabberd_resources_/ejabberd_resources_ +++ b/plugins/ejabberd/ejabberd_resources_/ejabberd_resources_ @@ -14,6 +14,7 @@ # - connect to a running ejabberd node # # OS: *NIX +# Requires lsof/fstat for open_files. # # Author: Artem Sheremet # @@ -120,7 +121,7 @@ code.info The total amount of memory currently allocated for Erlang code. This m ets.info The total amount of memory currently allocated for ets tables. This memory is part of the memory presented as system memory. INFO_FROM_DOC else - pid=$(<$EJABBERD_PID_PATH) + local pid=$(<$EJABBERD_PID_PATH) for memory_type in rss vsz; do memory_value=$(ps -p $pid -o $memory_type=) let memory_value=$memory_value*1024 @@ -140,42 +141,69 @@ function ejabberd_report_ports() { local limit=$(ejabberd_exec 'os:getenv("ERL_MAX_PORTS").' | tr '"' ' ') # string "false" indicates that this variable is not defined, thus a default of 1024 [ $limit = false ] && limit=1024 - local os_limit=$(ejabberd_exec 'os:cmd("ulimit -n").' | tr '"\\n' ' ') - if [ $limit -gt $os_limit ]; then - local real_limit=$os_limit - else - local real_limit=$limit - fi if [ "$1" = "config" ]; then cat </dev/null; then + echo lsof + return 0 + elif hash fstat &>/dev/null; then + echo fstat + return 0 + fi + return 1 +} + +function open_files_number() { + echo $[$($(open_files_counter_util) -np $(<$EJABBERD_PID_PATH) | wc -l)-1] +} + +function ejabberd_report_open_files() { + # this spawns a child process, but in most cases the open files limit is inherited + local limit=$(ejabberd_exec 'os:cmd("ulimit -n").' | tr '"\\n' ' ') + if [ "$1" = "config" ]; then + cat </dev/null && echo open_files exit 0 ;; config) diff --git a/plugins/hadoop/hadoop-dfs-plugin b/plugins/hadoop/hadoop-dfs-plugin index c99288e1..5918f5a5 100755 --- a/plugins/hadoop/hadoop-dfs-plugin +++ b/plugins/hadoop/hadoop-dfs-plugin @@ -9,17 +9,26 @@ # Symlink this file to hadoop_hdfs_block or hadoop_hdfs_capacity to get block # or capacity informations about the DFS. # +# +# Needs following minimal configuration in plugin-conf.d/munin-node: +# [hadoop_hdfs_block] +# user hdfs +# +# [hadoop_hdfs_capacity] +# user hdfs +# # Author: KARASZI Istvan # use strict; use File::Basename qw(basename); +use Munin::Plugin; -my $type = &getType($0); # # main # +my $type = &getType($0); if ($ARGV[0]) { if ($ARGV[0] eq "autoconf") { print "yes\n"; @@ -95,9 +104,12 @@ sub getCapacity { } sub getStatistics { - open(DFSADMIN, "hadoop dfsadmin -report|") || die("Cannot open dfsadmin: $!"); + open(DFSADMIN, "hdfs dfsadmin -report|") || die("Cannot open dfsadmin: $!"); while(defined(my $line = )) { chomp($line); + if ($line =~ /-------------------------------------------------/) { + last + } if ($type eq "block") { &getBlock($line); diff --git a/plugins/other/mailman-queue-check b/plugins/mail/mailman-queue-check similarity index 97% rename from plugins/other/mailman-queue-check rename to plugins/mail/mailman-queue-check index 8240a71c..4c75c792 100755 --- a/plugins/other/mailman-queue-check +++ b/plugins/mail/mailman-queue-check @@ -17,6 +17,7 @@ if [ "$1" = "config" ]; then echo "graph_title Mailman Queue" + echo "graph_category mailman" echo "graph_args --base 1000 -l 0" echo "archive.label Archive" echo "archive.draw LINE2" diff --git a/plugins/mail/postfwd2 b/plugins/mail/postfwd2 new file mode 100755 index 00000000..de9844aa --- /dev/null +++ b/plugins/mail/postfwd2 @@ -0,0 +1,269 @@ +#!/usr/bin/perl +# vim: set filetype=perl sw=4 tabstop=4 expandtab smartindent: # + +=head1 NAME + + postfwd2 - plugin to get stats from postfwd2 + +=head1 AUTHOR AND COPYRIGHT + + Copyright 2013-2014 Luc Didry + +=head1 HOWTO CONFIGURE AND USE : + +=over + +=item - /etc/munin/plugin-conf.d/postfwd2 + + [postfwd2] + user root + env.path /usr/local/sbin/postfwd2 # OPTIONAL : looks for postfwd2 in /bin, /sbin, /usr/bin, /usr/sbin, /usr/local/bin, /usr/local/sbin + env.include .*ISBAD #OPTIONAL + env.exclude .*ISGOOD #OPTIONAL + +=item - env.include and env.exclude + + This are perl regexp. + If env.include is set and env.exclude is not, only the policy which name + matchs will be used. + If env.exclude is set and env.include is not, only the policy which name NOT + matchs will be used. + If both are set, a policy which name matchs the both regex will be used, a + policy which matchs only the exclude regexp will NOT be used and a policy + which match not the exclude regex will be used, even if it not matchs the + include regexp. + if none are set, all the policy will be used. + + +=item - /etc/munin/plugins + + cp postfwd2 /etc/munin/plugins + + +=item - restart Munin node + + service munin-node restart + +=back + +=head1 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 3 of the License, or 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 . + +=cut + +use warnings; +use strict; +use Munin::Plugin; + +need_multigraph(); + +my @keys =qw/cache_queries cache_stats policy_requests policy_timeout policy_matchs/; +my %graphs = ( + cache_queries => { + title => 'Cache queries', + vlabel => 'Nb of cache queries', + series => { + cache_queries => { + label => 'Cache queries', + type => 'COUNTER' + } + }, + }, + cache_stats => { + title => 'Cache stats', + vlabel => 'percent', + series => { + cache_requests => { + label => 'Requests hitrate', + type => 'GAUGE' + }, + cache_dns => { + label => 'Dns hitrate', + type => 'GAUGE' + }, + cache_rates => { + label => 'Rates hitrate', + type => 'GAUGE' + } + }, + }, + policy_requests => { + title => 'Policy requests', + vlabel => 'Nb of policy requests', + series => { + policy_requests => { + label => 'Policy requests', + type => 'COUNTER' + } + }, + }, + policy_timeout => { + title => 'Policy timeout', + vlabel => 'Nb of policy timeout', + series => { + policy_timeout => { + label => 'Policy timeout', + type => 'COUNTER' + } + } + }, + policy_matchs => { + title => 'Policy matchs', + vlabel => 'Matchs percentage' + } +); + +my $PLUGIN_NAME = 'postfwd2'; +my $CACHEFILE="$Munin::Plugin::pluginstatedir/postfwd2.cache"; + +my ($include, $exclude) = ($ENV{include}, $ENV{exclude}); +if (defined($include) && (!defined($exclude))) { + $exclude = '.*'; +} + +##### Cache file, to continue to graph old policies which doesn't exist anymore +if (!(-f $CACHEFILE) || !(-e $CACHEFILE)) { + open (FILE, ">", $CACHEFILE) or munin_exit_fail(); + close(FILE); +} +open (FILE, "<", $CACHEFILE) or munin_exit_fail(); +my @policies = ; +close(FILE); +my %policies = map { $_, 1 } @policies; +foreach my $policy (keys %policies) { + chomp $policy; + $graphs{policy_matchs}->{series}->{$policy}->{type} = 'GAUGE'; + $graphs{policy_matchs}->{series}->{$policy}->{label} = $policy; + $graphs{policy_matchs}->{series}->{$policy}->{value} = 0; +} + +##### Check postfwd2 path +if (!defined($ENV{path}) || !(-x $ENV{path})) { + foreach my $path (qw{/bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin}) { + $ENV{path} = $path.'/postfwd2' if (!defined($ENV{path}) && -x $path.'/postfwd2'); + last if (defined($ENV{path})); + } +} +munin_exit_fail() unless (defined($ENV{path}) && -x $ENV{path}); + +##### I have to parse the output BEFORE config, since policy matchs are dependant of the postfwd --dumpstats output +open(DATA, $ENV{path}.' --dumpstats |') or munin_exit_fail(); +my $total_requests; +while(defined (my $data = )) { + if ($data =~ m/^\[STATS\] postfwd2::cache .*: (\d+) queries since/) { + $graphs{cache_queries}->{series}->{cache_queries}->{value} = $1; + } + if ($data =~ m/^\[STATS\] Hitrates: ([\.\d]+)% requests, ([\.\d]+)% dns, ([\.\d]+)% rates$/) { + $graphs{cache_stats}->{series}->{cache_requests}->{value} = $1; + $graphs{cache_stats}->{series}->{cache_dns}->{value} = $2; + $graphs{cache_stats}->{series}->{cache_rates}->{value} = $3; + } + if ($data =~ m/^\[STATS\] postfwd2::policy .*: (\d+) requests since/) { + $graphs{policy_requests}->{series}->{policy_requests}->{value} = $1; + $total_requests = $1; + } + if ($data =~ m/^\[STATS\] Timeouts: .*% \((\d+) of \d+ dns queries\)$/) { + $graphs{policy_timeout}->{series}->{policy_timeout}->{value} = $1; + } + if ($data =~ m/^\[STATS\] +(\d+) matches for id: (.*)$/) { + my ($value, $label) = ($1, $2); + if ( ( !defined($exclude) ) || ( $label !~ m/$exclude/ || ( defined($include) && $label =~ m/$include/ ) ) ) { + if (!defined($policies{$label})) { + open (FILE, ">>", $CACHEFILE) or munin_exit_fail(); + print FILE $label, "\n"; + close(FILE); + $graphs{policy_matchs}->{series}->{$label}->{type} = 'GAUGE'; + $graphs{policy_matchs}->{series}->{$label}->{label} = $label; + } + $graphs{policy_matchs}->{series}->{$label}->{value} = sprintf("%.2f", 100*$value/$total_requests); + } + } +} +close(DATA); + +##### config +if( (defined $ARGV[0]) && ($ARGV[0] eq 'config') ) { + foreach my $key (@keys) { + print 'multigraph postfwd2_', $key, "\n"; + print 'graph_title Postfwd2 ', $graphs{$key}->{title}, "\n"; + print 'graph_vlabel ', $graphs{$key}->{vlabel}, "\n"; + my $args = ($key eq 'cache_stats') ? ' --upper-limit 100 --rigid' : ''; + print 'graph_args --lower-limit 0', $args, "\n"; + print 'graph_category mail', "\n"; + if ($key eq 'policy_matchs') { + print 'graph_width 600', "\n"; + my @pol_keys = sort { $graphs{$key}->{series}->{$b}->{value} <=> $graphs{$key}->{series}->{$a}->{value} } keys %{$graphs{$key}->{series}}; + foreach my $label (@pol_keys) { + print $label, '.label ', $graphs{$key}->{series}->{$label}->{label}, "\n"; + print $label, '.draw LINE', "\n"; + print $label, '.type ', $graphs{$key}->{series}->{$label}->{type}, "\n"; + } + foreach my $label (@pol_keys) { + print 'multigraph postfwd2_', $key, '.', $label, "\n"; + print 'graph_title Postfwd2 ', $label, "\n"; + print 'graph_vlabel ', $graphs{$key}->{vlabel}, "\n"; + print 'graph_width 600', "\n"; + print 'graph_args --lower-limit 0 ', $args, "\n"; + print 'graph_category others', "\n"; + print $label, '.label ', $graphs{$key}->{series}->{$label}->{label}, "\n"; + print $label, '.draw LINE', "\n"; + print $label, '.type GAUGE', "\n"; + } + } else { + foreach my $label (keys %{$graphs{$key}->{series}}) { + print $label, '.label ', $graphs{$key}->{series}->{$label}->{label}, "\n"; + print $label, '.draw AREASTACK', "\n"; + print $label, '.type ', $graphs{$key}->{series}->{$label}->{type}, "\n"; + } + } + } + munin_exit_done(); +} + +##### fetch +foreach my $key (@keys) { + print 'multigraph postfwd2_', $key, "\n"; + if ($key eq 'policy_matchs') { + my @pol_keys = sort { $graphs{$key}->{series}->{$b}->{value} <=> $graphs{$key}->{series}->{$a}->{value} } keys %{$graphs{$key}->{series}}; + foreach my $label (@pol_keys) { + print $label, '.value ', $graphs{$key}->{series}->{$label}->{value}, "\n"; + } + foreach my $label (@pol_keys) { + print 'multigraph postfwd2_', $key, '.', $label, "\n"; + print $label, '.value ', $graphs{$key}->{series}->{$label}->{value}, "\n"; + } + } else { + foreach my $label (keys %{$graphs{$key}->{series}}) { + print $label, '.value ', $graphs{$key}->{series}->{$label}->{value}, "\n"; + } + } +} +munin_exit_done(); + +# +## +### INTERNALS FONCTIONS +############################################################################### +sub munin_exit_done { + munin_exit(0); +} ## sub munin_exit_done + +sub munin_exit_fail { + munin_exit(1); +} ## sub munin_exit_fail + +sub munin_exit { + my $exitcode = shift; + exit($exitcode) if(defined $exitcode); + exit(1); +} ## sub munin_exit diff --git a/plugins/mysql/mysql_connections_per_user b/plugins/mysql/mysql_connections_per_user new file mode 100755 index 00000000..334fe04f --- /dev/null +++ b/plugins/mysql/mysql_connections_per_user @@ -0,0 +1,189 @@ +#!/usr/bin/perl +# +# Copyright (C) 2008 Rackspace US, Inc. +# +# 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; version 2 dated June, +# 1991. +# +# 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 http://www.gnu.org/licenses/gpl.txt +# +# +# This plugin is based off of the Connection Usage +# section of the MySQL Connection Health Page +# +# http://dev.mysql.com/doc/administrator/en/mysql-administrator-health-connection-health.html +# +# To enable, link mysql_connections to this file. E.g. +# +# ln -s /usr/share/node/node/plugins/mysql_connections /etc/munin/plugins/mysql_connections +# +# Revision 1.0 2007/08/03 +# Created by Justin Shepherd +# +# Revision 2.0 2013/01/02 +# Per user support by anarcat@koumbit.org +# +# Parameters: +# +# config +# autoconf +# +# Configuration variables +# +# mysqlopts - Options to pass to mysql +# mysqladmin - Override location of mysqladmin +# numusers - Override maximum number of users to display +# warning - Override default warning limit +# critical - Override default critical limit +# +#%# family=auto +#%# capabilities=autoconf + +use strict; + +# Define the mysqladmin paths, and commands +my $MYSQLADMIN = $ENV{mysqladmin} || "mysqladmin"; +my $TEST_COMMAND = "$MYSQLADMIN $ENV{mysqlopts} processlist"; +my $MYSQL_VARIABLES = "$MYSQLADMIN $ENV{mysqlopts} extended-status variables"; +my $warning = $ENV{warning} || "80"; +my $critical = $ENV{critical} || "90"; +my $numusers = $ENV{numusers} || 10; + +# Pull in any arguments +my $arg = shift(); + +# Check to see how the script was called +if ($arg eq 'config') { + print_graph_information(); +} elsif ($arg eq 'autoconf') { + if (test_service()) { print "yes\n"; } + else { print "no\n"; } +} else { + print_graph_data(); +} +exit; + +sub print_graph_data() { + # Define the values that are returned to munin + + # Return the values to Munin + my $counts = count_thread_users(); + my %counts = %{$counts}; + + sub valsort { + return $counts{$a} <=> $counts{$b}; + } + my $i = 0; + my $total = 0; + foreach my $user (sort valsort keys(%counts)) { + last if $i++ >= $numusers; + $total += $counts{$user}; + print "$user.value $counts{$user}\n"; + } + my $other = poll_variables($MYSQL_VARIABLES,"Threads_connected") - $total; + print "other.value $other\n"; +} + +sub poll_variables { + my $command = shift; + my $expression = shift; + my $ret = 0; + open(SERVICE, "$command |") + or die("Coult not execute '$command': $!"); + while () { + my ($field, $value) = (m/(\w+).*?(\d+(?:\.\d+)?)/); + next unless ($field); + if ($field eq $expression ) { + $ret = "$value"; + } + } + close(SERVICE); + return $ret; +} + + +sub print_graph_information { +print < $counts{$b}; +} +my $i = 0; +foreach my $user (sort valsort keys(%counts)) { + last if $i++ >= $numusers; + print < 1) { + print "STACK\n"; + } + else { + print "AREA\n"; + } +} + +print <) { + my ($threadid, $user) = split "\t"; + next unless ($threadid); + $counts{$user} = 0 unless defined($counts{$user}); + $counts{$user}++; + } + return \%counts; +} + +sub test_service { + my $return = 1; + system ("$MYSQLADMIN --version >/dev/null 2>/dev/null"); + if ($? == 0) + { + system ("$TEST_COMMAND >/dev/null 2>/dev/null"); + if ($? == 0) + { + print "yes\n"; + $return = 0; + } + else + { + print "no (could not connect to mysql)\n"; + } + } + else + { + print "no (mysqladmin not found)\n"; + } + exit $return; +} diff --git a/plugins/network/netatalk3 b/plugins/network/netatalk3 new file mode 100755 index 00000000..db05183b --- /dev/null +++ b/plugins/network/netatalk3 @@ -0,0 +1,144 @@ +#!/bin/bash - +# +#### Abstract ################################################################ +# +# Munin contributed plugin to measure open, locked and files being hold by +# Netatalk version 3.*; +# +#### Author ################################################################## +# +# Otavio Fernandes +# Wednesday, 01/01/2014 +# + +#### Functions ############################################################### +# + +function afpd_full_path () { + echo $(which afpd |head -n 1) +} + +function afp_related_file_exists () { + local afp_file_path=$1 + if [ -z "$afp_file_path" -o ! -f "$afp_file_path" ]; then + echo "ERROR: AFP related file not found: \"${afpd_bin_path}\";" >&2 + exit 1 + fi +} + +function is_netatalk_version_3 () { + local afpd_bin_path=$1 + version_string="$(${afpd_bin_path} -version |head -n1 |grep '^afpd' |cut -d' ' -f2)" + if [[ $version_string != 3\.* ]]; then + cat <&2 +ERROR: Netatalk is not version 3. + Binary: ${afpd_bin_path}; + Version: ${version_string}; +EOM + exit 1 + fi +} + +function count_running_procs () { + local afpd_bin_path=$1 + echo $(ps ax --no-headers -o command |grep "^${afpd_bin_path}" |wc -l) +} + +function count_connected_users () { + local afpd_bin_path=$1 + afpd_procs=$(ps anx --no-headers -o uid,command |grep -E "\w+\d+*\s${afpd_bin_path}" |wc -l) + # one of those processess will be always from root user, so it's not being + # used to externaly connect volumes, therefor being disconsider. + echo $(echo "${afpd_procs} - 1" |bc) +} + +function base_lsof () { + local afpd_bin_path=$1 + process_name="$(basename ${afpd_bin_path})" + excluded_fds="^DEL,^err,^jld,^ltx,^Mxx,^m86,^mem,^mmap,^pd,^rtd,^tr,^txt,^v86" + echo "lsof -a -c ${process_name} -d ${excluded_fds}" +} + +function count_locked_files () { + local afpd_bin_path=$1 + cmd_suffix="$(base_lsof "${afpd_bin_path}")" + echo "$(${cmd_suffix} -F -l |grep '^l[rRwWu]' |wc -l)" +} + +function probe_afp_conf_file () { + local afpd_bin_path=$1 + echo "$(${afpd_bin_path} -version 2>&1 |grep -E '^\s+afp.conf\:' |awk '{print $2}')" +} + +function count_open_shares () { + local afpd_bin_path=$1 + local afp_conf_path=$2 + + mounted=0 + declare -a shares=($(grep '^path.*\=' ${afp_conf_path} |awk -F '=' '{print $2}' |sed -E 's/^\s+//g')) + if [ ! -z "$shares" ]; then + # narrowing lsof results to only list configured mount directories + cmd_lookup_prefix=$(echo ${shares[@]} |tr " " "|") + cmd_suffix="$(base_lsof "${afpd_bin_path}")" + mounted=$(${cmd_suffix} -F n |grep '^n' |egrep "(${cmd_lookup_prefix})" |wc -l) + fi + echo $mounted +} + + +#### Main #################################################################### +# For all routines, holds the entry points dealing with the informed parameter +# or lack of it, meaning just print out collected data. +# + +case "$1" in + config) + # printing configuration for this plugin's produced data + cat < +# +# 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__juniper - Health monitoring plugin for Juniper firewalls. + +=head1 CONFIGURATION + +Make sure your Juniper device is accessible via SNMP (e.g. via snmpwalk) and the munin-node +has been configured correctly. + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + +0.0.1 + +=head1 BUGS + +Open a ticket at https://github.com/ercpe/contrib if you find one. + +=head1 AUTHOR + +Johann Schmitz + +=head1 LICENSE + +GPLv2 + +=cut +""" + +import re +import sys +import os +import logging + +from pysnmp.entity.rfc3413.oneliner import cmdgen + +host = None +port = os.getenv('port', 161) +community = os.getenv('community', None) + +debug = bool(os.getenv('MUNIN_DEBUG', os.getenv('DEBUG', 0))) + +if debug: + logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-7s %(message)s') + +try: + match = re.search("^(?:|.*\/)snmp_([^_]+)_juniper$", 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 + +jnxOperatingTable = '1.3.6.1.4.1.2636.3.1.13.1.5' + +jnxOperatingTemp = '1.3.6.1.4.1.2636.3.1.13.1.7' +jnxOperatingCPU = '1.3.6.1.4.1.2636.3.1.13.1.8' +jnxOperatingBuffer = '1.3.6.1.4.1.2636.3.1.13.1.11' + +class JunOSSnmpClient(object): + def __init__(self, host, port, community): + self.hostname = host + self.transport = cmdgen.UdpTransportTarget((host, int(port))) + self.auth = cmdgen.CommunityData('test-agent', community) + self.gen = cmdgen.CommandGenerator() + + def get_devices(self): + errorIndication, errorStatus, errorIndex, varBindTable = self.gen.bulkCmd( + self.auth, + self.transport, + 0, 20, + jnxOperatingTable) +# ignoreNonIncreasingOids=True) # only available with pysnmp >= 4.2.4 (?) + + if errorIndication: + logging.error("SNMP bulkCmd for devices failed: %s, %s, %s" % (errorIndication, errorStatus, errorIndex)) + return {} + + devices = {} + for row in varBindTable: + for name, value in row: + if not str(name).startswith(jnxOperatingTable): + continue + + # TODO: Find a better way to get the routing engines in a cluster + if str(value).endswith(' Routing Engine'): + devices[str(name)[len(jnxOperatingTable):]] = re.sub("[^\w]", '_', str(value).replace(' Routing Engine', '')) + return devices + + def get_one(self, prefix_oid, suffix): + oid = prefix_oid + suffix + errorIndication, errorStatus, errorIndex, varBindTable = self.gen.getCmd( + self.auth, + self.transport, + oid) + + if errorIndication: + logging.error("SNMP getCmd for %s failed: %s, %s, %s" % (oid, errorIndication, errorStatus, errorIndex)) + return None + + return int(varBindTable[0][1]) + + + + def get_data(self): + devs = self.get_devices() + + return { + 'temp': dict([(name, self.get_one(jnxOperatingTemp, suffix)) for suffix, name in devs.iteritems()]), + 'cpu': dict([(name, self.get_one(jnxOperatingCPU, suffix)) for suffix, name in devs.iteritems()]), + 'buffer': dict([(name, self.get_one(jnxOperatingBuffer, suffix)) for suffix, name in devs.iteritems()]), + } + + def print_config(self): + devices = self.get_devices() + + data_def = [ + ('temp', self.hostname, 'System temperature', '--base 1000', 'System temperature in C', 'system'), + ('cpu', self.hostname, 'CPU usage', '--base 1000 -l 0 --upper-limit 100', 'CPU usage in %', 'system'), + ('buffer', self.hostname, 'Buffer usage', '--base 1000 -l 0 --upper-limit 100', 'Buffer usage in %', 'system'), + ] + + for datarow, hostname, title, args, vlabel, category in data_def: + print """multigraph juniper_{datarow} +host_name {hostname} +graph_title {title} +graph_vlabel {vlabel} +graph_args {args} +graph_category {category} +graph_info {title}""".format(datarow=datarow, hostname=hostname, title=title, args=args, vlabel=vlabel, category=category) + + for suffix, node in devices.iteritems(): + ident = "%s_%s" % (datarow, node) + print """{label}.info {title} on {node} +{label}.label {node} +{label}.type GAUGE +{label}.min 0""".format(title=title, label=ident, node=node) + + def execute(self): + data = self.get_data() + + for pre, values in data.iteritems(): + print "multigraph juniper_%s" % pre + for node, value in values.iteritems(): + print "%s_%s.value %s" % (pre, node, value) + +c = JunOSSnmpClient(host, port, community) + +if "snmpconf" in sys.argv[1:]: + print "require 1.3.6.1.4.1.2636.3.1.13.1.5" + sys.exit(0) +else: + if not (host and port and community): + print "# Bad configuration. Cannot run with Host=%s, port=%s and community=%s" % (host, port, community) + sys.exit(1) + + if "config" in sys.argv[1:]: + c.print_config() + else: + c.execute() diff --git a/plugins/snmp/snmp__juniper_spu b/plugins/snmp/snmp__juniper_spu new file mode 100755 index 00000000..920f0b83 --- /dev/null +++ b/plugins/snmp/snmp__juniper_spu @@ -0,0 +1,229 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (C) 2014 Johann Schmitz +# +# 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__juniper_spu - Network monitoring plugin for the SPU's in Juniper firewalls. + +=head1 CONFIGURATION + +Make sure your Juniper device is accessible via SNMP (e.g. via snmpwalk) and the munin-node +has been configured correctly. + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + +0.0.1 + +=head1 BUGS + +Open a ticket at https://github.com/ercpe/contrib if you find one. + +=head1 AUTHOR + +Johann Schmitz + +=head1 LICENSE + +GPLv2 + +=cut +""" + +import re +import sys +import os +import logging + +from pysnmp.entity.rfc3413.oneliner import cmdgen + +host = None +port = os.getenv('port', 161) +community = os.getenv('community', "public") + +debug = bool(os.getenv('MUNIN_DEBUG', os.getenv('DEBUG', 0))) + +if debug: + logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-7s %(message)s') + +try: + match = re.search("^(?:|.*\/)snmp_([^_]+)_juniper_spu$", 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 + +jnxJsSPUMonitoringObjectsTable = '1.3.6.1.4.1.2636.3.39.1.12.1.1' +#jnxJsSPUMonitoringIndex = jnxJsSPUMonitoringObjectsTable + '.1.1' +#jnxJsSPUMonitoringFPCIndex = jnxJsSPUMonitoringObjectsTable + '.1.2' +#jnxJsSPUMonitoringSPUIndex = jnxJsSPUMonitoringObjectsTable + '.1.3' +#jnxJsSPUMonitoringCPUUsage = jnxJsSPUMonitoringObjectsTable + '.1.4' +#jnxJsSPUMonitoringMemoryUsage = jnxJsSPUMonitoringObjectsTable + '.1.5' +jnxJsSPUMonitoringCurrentFlowSession = jnxJsSPUMonitoringObjectsTable + '.1.6' +jnxJsSPUMonitoringMaxFlowSession = jnxJsSPUMonitoringObjectsTable + '.1.7' +jnxJsSPUMonitoringCurrentCPSession = jnxJsSPUMonitoringObjectsTable + '.1.8' +jnxJsSPUMonitoringMaxCPSession = jnxJsSPUMonitoringObjectsTable + '.1.9' +#jnxJsSPUMonitoringNodeIndex = jnxJsSPUMonitoringObjectsTable + '.1.10' +jnxJsSPUMonitoringNodeDescr = jnxJsSPUMonitoringObjectsTable + '.1.11' +jnxJsSPUMonitoringFlowSessIPv4 = jnxJsSPUMonitoringObjectsTable + '.1.12' +jnxJsSPUMonitoringFlowSessIPv6 = jnxJsSPUMonitoringObjectsTable + '.1.13' +jnxJsSPUMonitoringCPSessIPv4 = jnxJsSPUMonitoringObjectsTable + '.1.14' +jnxJsSPUMonitoringCPSessIPv6 = jnxJsSPUMonitoringObjectsTable + '.1.15' + +class JunOSSnmpClient(object): + + def __init__(self, host, port, community): + self.hostname = host + self.transport = cmdgen.UdpTransportTarget((host, int(port))) + self.auth = cmdgen.CommunityData('test-agent', community) + self.gen = cmdgen.CommandGenerator() + + def get_data(self): + errorIndication, errorStatus, errorIndex, varBindTable = self.gen.bulkCmd( + self.auth, + self.transport, + 0, 10, + jnxJsSPUMonitoringObjectsTable) +# ignoreNonIncreasingOids=True) # only available with pysnmp >= 4.2.4 (?) and broken anyway + + if errorIndication: + logging.error("SNMP bulkCmd for devices failed: %s, %s, %s" % (errorIndication, errorStatus, errorIndex)) + return {} + + devices = {} + values = {} + for row in varBindTable: + for name, value in row: + if not str(name).startswith(jnxJsSPUMonitoringObjectsTable): + continue + + oid = str(name) + + nodeid = oid[oid.rindex('.'):] + + idx = oid[len(jnxJsSPUMonitoringObjectsTable)+3:] + idx = idx[:idx.index('.')] + + if oid.startswith(jnxJsSPUMonitoringNodeDescr): + devices[nodeid] = str(value) + continue + + values[oid] = int(value) + return devices, values + + def print_config(self): + devices, data = self.get_data() + + data_def = [ + ('flow', self.hostname, 'Flow sessions', '--base 1000', '# of sessions', jnxJsSPUMonitoringMaxFlowSession), + ('cp', self.hostname, 'Central point sessions', '--base 1000', '# of sessions', jnxJsSPUMonitoringMaxCPSession), + ] + + for datarow, hostname, title, args, vlabel, max_prefix in data_def: + print """multigraph juniper_{datarow} +host_name {hostname} +graph_title {title} +graph_vlabel {vlabel} +graph_args {args} +graph_category network +graph_info {title}""".format(datarow=datarow, hostname=hostname, title=title, args=args, vlabel=vlabel) + + for suffix, node in devices.iteritems(): + ident = "%s_%s" % (datarow, node) + print """{label}.info {title} on {node} +{label}.label {node} +{label}.type GAUGE +{label}.max {max}""".format(title=title, label=ident, node=node, max=data.get(max_prefix + suffix)) + + for suffix, node in devices.iteritems(): + print """ +multigraph juniper_{datarow}.{node} +host_name {hostname} +graph_title {title} on {node} +graph_vlabel {vlabel} +graph_args {args} +graph_category network +graph_info {title} +{datarow}V4.info Current IPv4 {datarow} sessions +{datarow}V4.label IPv4 +{datarow}V4.draw AREASTACK +{datarow}V4.type GAUGE +{datarow}V6.info Current IPv6 {datarow} sessions +{datarow}V6.label IPv6 +{datarow}V6.draw AREASTACK +{datarow}V6.type GAUGE +{datarow}Current.info Current total {datarow} sessions +{datarow}Current.label Total +{datarow}Current.draw LINE1 +{datarow}Current.type GAUGE +{datarow}Current.colour 000000 +{datarow}Max.info Max. {datarow} sessions supported by the device(s) +{datarow}Max.label Max +{datarow}Max.draw LINE0 +{datarow}Max.type GAUGE +""".format(datarow=datarow, hostname=hostname, title=title, args=args, vlabel=vlabel, node=node) + + def execute(self): + devices, data = self.get_data() + + print "multigraph juniper_flow" + for suffix, node in devices.iteritems(): + print "flow_%s.value %s" % (node, data.get(jnxJsSPUMonitoringCurrentFlowSession + suffix, 0)) + + for suffix, node in devices.iteritems(): + print "multigraph juniper_flow.%s" % node + print "flowV4.value %s" % data.get(jnxJsSPUMonitoringFlowSessIPv4 + suffix, 0) + print "flowV6.value %s" % data.get(jnxJsSPUMonitoringFlowSessIPv6 + suffix, 0) + print "flowCurrent.value %s" % data.get(jnxJsSPUMonitoringCurrentFlowSession + suffix, 0) + print "flowMax.value %s" % data.get(jnxJsSPUMonitoringMaxFlowSession + suffix, 0) + + print "multigraph juniper_cp" + for suffix, node in devices.iteritems(): + print "cp_%s.value %s" % (node, data.get(jnxJsSPUMonitoringCurrentCPSession + suffix, 0)) + + for suffix, node in devices.iteritems(): + print "multigraph juniper_cp.%s" % node + print "cpV4.value %s" % data.get(jnxJsSPUMonitoringCPSessIPv4 + suffix, 0) + print "cpV6.value %s" % data.get(jnxJsSPUMonitoringCPSessIPv6 + suffix, 0) + print "cpCurrent.value %s" % data.get(jnxJsSPUMonitoringCurrentCPSession + suffix, 0) + print "cpMax.value %s" % data.get(jnxJsSPUMonitoringMaxCPSession + suffix, 0) + +c = JunOSSnmpClient(host, port, community) + +if "snmpconf" in sys.argv[1:]: + print "require %s" % (jnxJsSPUMonitoringObjectsTable, ) + sys.exit(0) +else: + if not (host and port and community): + print "# Bad configuration. Cannot run with Host=%s, port=%s and community=%s" % (host, port, community) + sys.exit(1) + + if "config" in sys.argv[1:]: + c.print_config() + else: + c.execute() diff --git a/plugins/sphinx/sphinx_connections b/plugins/sphinx/sphinx_connections index da389a0a..029a6db2 100755 --- a/plugins/sphinx/sphinx_connections +++ b/plugins/sphinx/sphinx_connections @@ -54,6 +54,9 @@ if (!file_exists($sTmpFilePath)) { fclose($fp); $toShow = (int) ($iCurrent - (int) $iOldCount); + if ($toShow < 0) { + $toShow = 0; + } } echo "current.value $toShow\n"; diff --git a/plugins/sphinx/sphinx_queries b/plugins/sphinx/sphinx_queries index 0862b7c3..7a9d9527 100755 --- a/plugins/sphinx/sphinx_queries +++ b/plugins/sphinx/sphinx_queries @@ -54,6 +54,9 @@ if (!file_exists($sTmpFilePath)) { fclose($fp); $toShow = (int) ($iCurrent - (int) $iOldCount); + if ($toShow < 0) { + $toShow = 0; + } } echo "current.value $toShow\n"; diff --git a/plugins/zfs/zfs_cache_efficiency b/plugins/zfs/zfs_cache_efficiency index 7ea7a7a7..7f364dc5 100755 --- a/plugins/zfs/zfs_cache_efficiency +++ b/plugins/zfs/zfs_cache_efficiency @@ -6,10 +6,12 @@ #%# capabilities=autoconf sysctl='/sbin/sysctl' +ostype=`uname -s` +procfile='/proc/spl/kstat/zfs/arcstats' case $1 in config) - cat <