]> jfr.im git - munin-plugins.git/commitdiff
init
authorJohn Runyon <redacted>
Thu, 28 Sep 2023 20:33:23 +0000 (15:33 -0500)
committerJohn Runyon <redacted>
Thu, 28 Sep 2023 20:33:23 +0000 (15:33 -0500)
12 files changed:
http_loadtime [new file with mode: 0755]
if_ [new file with mode: 0755]
if_err_ [new file with mode: 0755]
lpstat [new file with mode: 0755]
mysql_ [new file with mode: 0755]
php_fpm [new file with mode: 0755]
postfix_mailvolume [new file with mode: 0755]
processes [new file with mode: 0755]
snmp__cyberpower [new file with mode: 0755]
snmp__if_err_ [new file with mode: 0755]
snmp__if_pfsense_ppp_ [new file with mode: 0755]
snmp__swap [new file with mode: 0755]

diff --git a/http_loadtime b/http_loadtime
new file mode 100755 (executable)
index 0000000..e743a7e
--- /dev/null
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+: << =cut
+
+=head1 NAME
+
+http_loadtime - Plugin to graph the HTTP response times of specific pages
+
+=head1 CONFIGURATION
+
+The following environment variables are used by this plugin
+
+ target - comma separated URL(s) to fetch (default: "http://localhost/")
+ example:
+   [http_loadtime]
+   env.target http://localhost.de,http://localhost.de/some-site.html
+   env.requisites true
+
+ Do not enable the download of page requisites (env.requisites) for https
+ sites since wget needs incredible long to perform this on big sites...
+
+=head1 AUTHOR
+
+Unknown authors
+(2013) Axel Huebl
+
+=head1 LICENSE
+
+GPLv2
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+target=${target:-"http://localhost/"}
+requisites=${requisites:-"false"}
+
+urls=$(echo "$target" | tr ',' '\n')
+
+
+request_url() {
+    wget --user-agent "Munin - http_loadtime" --no-cache --quiet --output-document=/dev/null "$@" 2>/dev/null
+}
+
+
+escapeUri() {
+    echo "$1" | sed 's!^\(https\?://\)\?[^/]\+/!!;s/[:/.-]/_/g'
+}
+
+shortenUri() {
+    echo "$1" | sed 's!^http\(s\)\?://!\1:!;s!\.simplynuc\.!...!'
+}
+
+
+if [ "$1" = "autoconf" ]; then
+    result="yes"
+    command -v tr   >/dev/null 2>&1 || result=1
+    command -v wget >/dev/null 2>&1 || result=1
+    if [ "$result" != "yes" ]; then
+       echo "no (programs wget and tr required)"
+       exit 0
+    fi
+
+    # check if urls respond
+    #
+    for uri in $urls
+    do
+        if ! request_url --spider "$uri"; then
+            echo "no (Cannot run wget against \"$uri\")"
+            exit 0
+        fi
+    done
+
+    echo yes
+    exit 0
+fi
+
+
+if [ "$1" = "config" ]; then
+    echo "graph_title HTTP loadtime of a page"
+    echo "graph_args --base 1000 -l 0"
+    echo "graph_vlabel Load time in seconds"
+    echo "graph_category apache"
+    echo "graph_info This graph shows the load time in seconds"
+    for uri in $urls
+    do
+        uri_short=$(echo "$(shortenUri "$uri")" | cut -c 1-30)
+        if [ "$uri_short" != "$(shortenUri "$uri")" ]; then uri_short="${uri_short}..."; fi
+        echo "$(escapeUri "$uri").label $uri_short"
+        echo "$(escapeUri "$uri").info $uri"
+    done
+    exit 0
+fi
+
+
+for uri in $urls
+do
+    start=$(date +%s.%N)
+    if [ "$requisites" = "true" ]; then
+        request_url --page-requisites "$uri"
+    else
+        request_url "$uri"
+    fi
+    loadtime=$(echo "$start" "$(date +%s.%N)" | awk '{ print($2 - $1); }')
+
+    echo "$(escapeUri "$uri").value $loadtime"
+done
diff --git a/if_ b/if_
new file mode 100755 (executable)
index 0000000..636cfe7
--- /dev/null
+++ b/if_
@@ -0,0 +1,214 @@
+#!/bin/bash
+# -*- sh -*-
+
+set -e
+
+: << =cut
+
+=head1 NAME
+
+if_ - Wildcard-plugin to monitor traffic and errors on network
+interfaces.
+
+=head1 CONFIGURATION
+
+This plugin does not normally require configuration.
+
+The plugin may need to run as root to determine the maximum speed of
+the network interface.  This is configured like this:
+
+  [if_*]
+      user root
+
+If the interface speed cannot be determined automatically by this
+plugin, you may manually configure the speed using the "speed"
+environment variable, in megabits per second, like this:
+
+  [if_*]
+      env.speed 1000
+
+To set warning and critical levels do like this:
+
+  [if_*]
+      env.warning 10000000
+
+or
+
+  [if_*]
+      env.if_0_warning 10000000
+
+This is a wildcard plugin. To monitor an interface, link
+if_<interface> to this file. For example,
+
+  ln -s /usr/share/munin/plugins/if_ \
+        /etc/munin/plugins/if_eth0
+
+will monitor eth0.
+
+Most devices found in /proc/net/dev can be monitored. Examples include
+ipsec*, eth*, irda*, and lo. Please note that aliases cannot be
+monitored with this plugin.
+
+=head1 AUTHOR
+
+Original author unknown
+
+Copyright (C) 2011 Diego Elio Pettenò <flameeyes@flameeyes.eu>
+
+=head1 LICENSE
+
+GPLv2
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf suggest
+
+=cut
+
+. "$MUNIN_LIBDIR/plugins/plugin.sh"
+
+INTERFACE=${0##*if_}
+speed=${speed:-}
+
+# Who whould have thought it's so much work to determine the
+# maximum speed of a network interface.  Buckle up!
+findspeed_mbps() {
+    # Do not use interface name to guess technology.  Many many
+    # wifi drivers use "eth*" names.
+    IWLIST=$(type -p iwlist)
+    if [[ -x "$IWLIST" ]]; then
+        SPEED=$("$IWLIST" "$INTERFACE" rate 2>&1 |
+            awk 'BEGIN { RATE="U" }
+                       { if ($2 == "Mb/s") RATE=$1; }
+                 END   { print RATE; }')
+
+        if [[ "$SPEED" != "U" ]]; then
+            echo "$SPEED"
+            return
+        fi
+    fi
+
+    # sysfs can report the speed if the driver supports it (but it
+    # doesn't work as well for wireless cards, thus why we check for
+    # iwlist first)
+    if [[ -r "/sys/class/net/$INTERFACE/speed" ]]; then
+            SPEED=$(cat "/sys/class/net/$INTERFACE/speed" 2>/dev/null)
+            if [[ "$SPEED" -gt 0 ]]; then
+                echo "$SPEED"
+                return
+            fi
+    fi
+
+    ETHTOOL=$(type -p ethtool)
+    if [[ -x "$ETHTOOL" ]]; then
+        SPEED=$("$ETHTOOL" "$INTERFACE" 2>&1 |
+                 awk '/Speed:/ { gsub(/[^0-9]*/,"",$2); print $2; }')
+
+        if [[ $SPEED == [0-9]* ]]; then
+            echo "$SPEED"
+            return
+        fi
+    fi
+
+    MIITOOL=$(type -p mii-tool)
+    if [[ -x "$MIITOOL" ]]; then
+        case $("$MIITOOL" "$INTERFACE" 2>&1) in
+            *1000base*) echo 1000; return ;;
+            *100base*)  echo 100; return ;;
+            *10base*)   echo 10; return ;;
+        esac
+    fi
+
+    echo U
+}
+
+findspeed() {
+    if [[ "$speed" ]]; then
+        SPEED=$speed
+    else
+        SPEED=$(findspeed_mbps)
+    fi
+
+    if [[ -z "$SPEED" ]] || [[ "$SPEED" == "U" ]]; then
+        printf "up.info Traffic of the %s interface. Unable to determine interface speed." "$INTERFACE"
+        if [[ $EUID -ne 0 ]]; then
+                echo " Please run the plugin as root."
+        else
+                echo " Please install ethtool, wireless-tools, mii-tool or whatever is appropriate for the interface."
+        fi
+
+        return
+    fi
+
+    BPS=$(( SPEED * 1000 * 1000 ))
+
+    cat <<EOF
+up.max $BPS
+up.info Traffic of the $INTERFACE interface. Maximum speed is $SPEED Mb/s.
+down.max $BPS
+EOF
+
+    return
+}
+
+
+case $1 in
+    autoconf)
+        if [[ -r /proc/net/dev ]]; then
+            echo yes
+            exit 0
+        else
+            echo "no (/proc/net/dev not found)"
+            exit 0
+        fi
+        ;;
+    suggest)
+        if [[ -r /proc/net/dev ]]; then
+            sed -rne '/^[[:space:]]*(lo|gre[[:digit:]]|sit[[:digit:]]+|[a-z0-9]+\.[0-9]+):/d;s,^[[:space:]]*([^:]+):.*,\1,p' /proc/net/dev
+        fi
+        exit 0
+        ;;
+    config)
+
+        address="$(ip -j address show dev $INTERFACE  | jq -r '.[0].addr_info[].local' | tr '\n' ' ')"
+        echo "graph_order down up"
+        echo "graph_title $INTERFACE (${address% }) traffic"
+        echo 'graph_args --base 1000'
+        # shellcheck disable=SC2016
+        echo 'graph_vlabel bits in (-) / out (+) per ${graph_period}'
+        echo 'graph_category network'
+        echo "graph_info This graph shows the traffic of the $INTERFACE network interface. Please note that the traffic is shown in bits per second, not bytes. IMPORTANT: On 32-bit systems the data source for this plugin uses 32-bit counters, which makes the plugin unreliable and unsuitable for most 100-Mb/s (or faster) interfaces, where traffic is expected to exceed 50 Mb/s over a 5 minute period.  This means that this plugin is unsuitable for most 32-bit production environments. To avoid this problem, use the ip_ plugin instead.  There should be no problems on 64-bit systems running 64-bit kernels."
+        echo 'down.label received'
+        echo 'down.type DERIVE'
+        echo 'down.graph no'
+        echo 'down.cdef down,8,*'
+        echo 'down.min 0'
+        echo 'up.label bps'
+        echo 'up.type DERIVE'
+        echo 'up.negative down'
+        echo 'up.cdef up,8,*'
+        echo 'up.min 0'
+        print_warning down
+        print_warning up
+        print_critical down
+        print_critical up
+
+        findspeed
+
+        exit 0
+        ;;
+esac
+
+# Escape dots in the interface name (eg. vlans) before using it as a regex
+if [[ -r /sys/class/net/$INTERFACE/statistics/rx_bytes ]]; then
+    echo "down.value $(cat "/sys/class/net/$INTERFACE/statistics/rx_bytes")"
+    echo "up.value $(cat "/sys/class/net/$INTERFACE/statistics/tx_bytes")"
+else
+    awk -v interface="$INTERFACE" \
+        'BEGIN { gsub(/\./, "\\.", interface) }
+        $1 ~ "^" interface ":" {
+            split($0, a, /: */); $0 = a[2];
+            print "down.value " $1 "\nup.value " $9}' \
+        /proc/net/dev
+fi
diff --git a/if_err_ b/if_err_
new file mode 100755 (executable)
index 0000000..cbfd027
--- /dev/null
+++ b/if_err_
@@ -0,0 +1,117 @@
+#!/bin/sh
+# -*- sh -*-
+
+set -e
+
+: << =cut
+
+=head1 NAME
+
+if_err_ - Wildcard plugin to monitor errors, packet drops, and
+collisions of network interfaces
+
+=head1 CONFIGURATION
+
+This is a wildcard plugin. To monitor an interface, link
+if_err_<interface> to this file. E.g.
+
+  ln -s /usr/share/munin/plugins/if_err_ \
+        /etc/munin/plugins/if_err_eth0
+
+...will monitor eth0.
+
+This plugin does not use environment variables.
+
+=head1 USAGE
+
+Any device found in /proc/net/dev can be monitored. Examples include
+ipsec*, eth*, irda* and lo.
+
+Please note that aliases cannot be monitored with this plugin.
+
+=head1 AUTHOR
+
+Unknown author
+
+=head1 LICENSE
+
+GPLv2
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf suggest
+
+=cut
+
+. "$MUNIN_LIBDIR/plugins/plugin.sh"
+
+INTERFACE=${0##*/if_err_}
+
+if [ "$1" = "autoconf" ]; then
+        if [ -r /proc/net/dev ]; then
+                echo yes
+                exit 0
+        else
+                echo "no (/proc/net/dev not found)"
+                exit 0
+        fi
+fi
+
+if [ "$1" = "suggest" ]; then
+    if [ -r /proc/net/dev ]; then
+        sed -rne '/^[[:space:]]*(lo|gre[[:digit:]]|sit[[:digit:]]+|[a-z0-9]+\.[0-9]+):/d;s,^[[:space:]]*([^:]+):.*,\1,p' /proc/net/dev
+    fi
+    exit 0
+fi
+
+if [ "$1" = "config" ]; then
+    address="$(ip -j address show dev $INTERFACE  | jq -r '.[0].addr_info[].local' | tr '\n' ' ')"
+    echo "graph_order rcvd trans"
+    echo "graph_title $INTERFACE (${address% }) errors"
+    echo 'graph_args --base 1000'
+    # shellcheck disable=SC2016
+    echo 'graph_vlabel packets in (-) / out (+) per ${graph_period}'
+    echo 'graph_category network'
+    echo "graph_info This graph shows the amount of errors, packet drops, and collisions on the $INTERFACE network interface."
+    echo 'rcvd.label errors'
+    echo 'rcvd.type COUNTER'
+    echo 'rcvd.graph no'
+    echo 'rcvd.warning 1'
+    echo 'trans.label errors'
+    echo 'trans.type COUNTER'
+    echo 'trans.negative rcvd'
+    echo 'trans.warning 1'
+    echo 'rxdrop.label drops'
+    echo 'rxdrop.type COUNTER'
+    echo 'rxdrop.graph no'
+    echo 'txdrop.label drops'
+    echo 'txdrop.type COUNTER'
+    echo 'txdrop.negative rxdrop'
+    echo 'collisions.label collisions'
+    echo 'collisions.type COUNTER'
+    print_warning rcvd
+    print_critical rcvd
+    print_warning trans
+    print_critical trans
+    exit 0
+fi
+
+# Escape dots in the interface name (eg. vlans) before using it as a regex
+if [ -r "/sys/class/net/$INTERFACE/statistics/rx_bytes" ]; then
+    echo "rcvd.value $(cat "/sys/class/net/$INTERFACE/statistics/rx_errors")"
+    echo "trans.value $(cat "/sys/class/net/$INTERFACE/statistics/tx_errors")"
+    echo "rxdrop.value $(cat "/sys/class/net/$INTERFACE/statistics/rx_dropped")"
+    echo "txdrop.value $(cat "/sys/class/net/$INTERFACE/statistics/tx_dropped")"
+    echo "collisions.value $(cat "/sys/class/net/$INTERFACE/statistics/collisions")"
+else
+    awk -v interface="$INTERFACE" \
+        'BEGIN { gsub(/\./, "\\.", interface) }
+        $1 ~ "^" interface ":" {
+            split($0, a, /: */); $0 = a[2];
+            print "rcvd.value " $3 "\ntrans.value " $11;
+            print "rxdrop.value " $4 "\ntxdrop.value " $12;
+            print "collisions.value " $14;
+        }' \
+        /proc/net/dev
+fi
diff --git a/lpstat b/lpstat
new file mode 100755 (executable)
index 0000000..5d1fb5c
--- /dev/null
+++ b/lpstat
@@ -0,0 +1,174 @@
+#!/usr/bin/perl
+# -*- perl -*-
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+lpstat - Plugin to graph the queue size for the list of printers
+available through the command "lpstat"
+
+=head1 CONFIGURATION
+
+No configuration
+
+=head1 AUTHORS
+
+Anstat Pty Ltd
+Nikolai Langfeldt
+
+=head1 LICENSE
+
+Gnu GPL
+
+=head1 NOTES
+
+This script was initially developed by Anstat Pty Ltd for internal use
+and has kindly been made available to the Open Source community for
+redistribution and further development under the terms of the
+GNU General Public License: http://www.gnu.org/licenses/gpl.html
+
+Readapted to munin by Nikolai Langfeldt for Oslo Airport
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+
+use Getopt::Std;
+
+my $printer;
+my @printers;
+my $status;
+my @jobs;
+my $n_jobs;
+my @exclude;  # Should take this from environment.
+
+# Force C output from lpstat
+$ENV{'LC_MESSAGES'} = "C";
+
+# This is a dumb-down.  Should take hostname(s) from environment or
+# as wildcard plugin.
+my $host = '127.0.0.1';
+
+my $lpstat = exists $ENV{lpstat} ? $ENV{lpstat} : '';
+
+# If the envvar is not set, look for lpstat
+if (!$lpstat) {
+   # Still not found? Check obvious places
+    my @dirs = split(':',$ENV{PATH});
+    push (@dirs, qw(/usr/bin /usr/sbin /usr/local/bin /usr/local/sbin) );
+
+    until ($lpstat or @dirs == 0) {
+        my $dir = shift @dirs;
+        my $path = $dir.'/lpstat';
+        $lpstat = $path if -x $path;
+    }
+} elsif (! -x $lpstat) {
+    # If it is set, verify it
+    warn "Predefined lpstat ($lpstat) is not a executable\n";
+    undef $lpstat;
+}
+
+if (defined($ARGV[0]) && $ARGV[0] eq 'autoconf') {
+    if( ! -x $lpstat ) {
+        print "no (lpstat not found)\n";
+        exit 0;
+    }
+    if( ! open(LPSTAT_R, "$lpstat $host -v 2>/dev/null |") ) {
+        print "no (could not execute lpstat)\n";
+        exit 0;
+    }
+    $_ = <LPSTAT_R>;
+    if ( ! close(LPSTAT_R) ) {
+        print "no (lpstat returned non-zero)\n";
+        exit 0;
+    }
+
+    if (! m/device for /mi) {
+        print "no (no printers configured)\n";
+        exit 0;
+    }
+    print "yes\n";
+    exit 0;
+}
+
+####################################################
+# Check printers are accepting jobs
+####################################################
+# Get list of printers, showing which are accepting jobs...
+if( ! open(LPSTAT_A, "$lpstat $host -a|") ) {
+    print "graph_title Could not execute lpstat command\n";
+    exit -1;
+}
+
+while(<LPSTAT_A>) {
+    chomp;
+    /(\S+) (.*) since/mi ;
+    $printer = $1;
+    $status = $2;
+    if( grep /^$printer$/, @exclude ) {
+       next;
+    }
+    if( /accepting/ ) {
+       @printers = ( @printers, $printer );
+    }
+}
+close(LPSTAT_A);
+
+####################################################
+# Check printers are enabled
+####################################################
+# Get list of printers, showing which are enabled/disabled...
+if( ! open(LPSTAT_P, "$lpstat $host -p|") ) {
+    print "graph_title Could not execute lpstat command\n";
+    exit -1;
+}
+
+my %jobs = ();
+
+while(<LPSTAT_P>) {
+    if ( /^printer\s+(\S+)\s.*disabled/mi ) {
+        $printer=$1;
+        if( grep /^$printer$/, @exclude ) {
+            next;
+        }
+    }
+}
+close(LPSTAT_P);
+
+# Get list of jobs for each printer...
+foreach $printer ( @printers ) {
+    if( grep /^$printer$/, @exclude ) {
+        next;
+    }
+
+    if( ! open(LPSTAT, "$lpstat $host -o $printer|") ) {
+        print STDERR "Could not execute command: '$lpstat -o $printer' \n";
+        exit 2;
+    }
+    @jobs = ( <LPSTAT> );
+    $n_jobs = @jobs;
+    $jobs{$printer}=$n_jobs || 0;
+}
+
+if ( defined($ARGV[0]) && $ARGV[0] eq 'config') {
+    print "graph_title Print queues
+graph_args --base 1000
+graph_vlabel Queued jobs
+graph_category printing
+";
+    foreach my $printer (sort(keys %jobs)) {
+        print "$printer.label $printer\n";
+        print "$printer.type GAUGE\n";
+    }
+    exit 0;
+}
+
+foreach my $printer (sort(keys %jobs)) {
+    print "$printer.value ",$jobs{$printer},"\n";
+}
diff --git a/mysql_ b/mysql_
new file mode 100755 (executable)
index 0000000..b48d36c
--- /dev/null
+++ b/mysql_
@@ -0,0 +1,1586 @@
+#!/usr/bin/perl
+# -*- perl -*-
+
+=encoding utf8
+
+=head1 NAME
+
+mysql_ - Munin plugin to display misc MySQL server status
+
+=head1 APPLICABLE SYSTEMS
+
+Any MySQL platform, tested by the authors on:
+* MySQL 5.0.51
+* MariaDB 5.5.39
+* MariaDB-5.5.39(galera).
+* MySQL 5.6.12
+* MariaDB 10.0.18
+
+Plugins:
+* MariaDB-10 Query Response Time: https://mariadb.com/kb/en/mariadb/query_response_time-plugin/
+
+Information Schema tables:
+* User statistics - MariaDB-5.2+, OurDelta, Percona Server - https://mariadb.com/kb/en/mariadb/user-statistics
+
+=head1 CONFIGURATION
+
+This script is used to generate data for several graphs. To generate
+data for one specific graph, you need to create a symbolic link with a
+name like mysql_<GRAPH> to this script.
+
+If you need to run against multiple MySQL instances on the same host,
+create your symlinks with names like mysql<N>_<GRAPH> where N is any
+non-negative integer. You must also set the env.cachenamespace variable
+to a unique value for each group of symlinks.
+
+To get a list of symlinks that can be created, run:
+
+  ./mysql_ suggest
+
+In addition you might need to specify connection parameters in the
+plugin configuration to override the defaults. These are the defaults:
+
+  [mysql_*]
+    env.mysqlconnection DBI:mysql:information_schema
+    env.mysqluser root
+
+Non-default example:
+
+  [mysql_*]
+    env.mysqlconnection DBI:mysql:information_schema;host=127.0.0.1;port=3306
+    env.mysqluser munin
+    env.mysqlpassword geheim
+    env.cachenamespace munin_mysql_pri
+  [mysql2_*]
+    env.mysqlconnection DBI:mysql:information_schema;host=127.0.0.1;port=13306
+    env.mysqluser munin
+    env.mysqlpassword ryuWyawEv
+    env.cachenamespace munin_mysql_alt
+  [mysql10_*]
+    user munin
+    env.mysqluser munin
+    env.mysqlconnection DBI:mysql:information_schema;mysql_read_default_file=/etc/munin/.my-10.cnf
+    env.cachenamespace munin_mysql_10
+    # here the [client] section of /etc/munin/.my-10.cnf is read. socket= can
+    # be specified here.
+
+Creating a munin user:
+
+  CREATE USER 'munin'@'localhost' IDENTIFIED BY 'ryuWyawEv';
+
+or with a unix_socket plugin (INSTALL PLUGIN unix_socket SONAME 'auth_socket')
+
+  CREATE USER 'munin'@'localhost' IDENTIFIED WITH unix_socket;
+
+Note: requires 'user munin' in the configuration.
+
+The minimum required priviledges of the munin database user is:
+
+  GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'munin'@'localhost';
+
+
+Warning and critical values can be set via the environment in the usual way.
+For example:
+
+  [mysql_replication]
+    env.slave_io_running_warning 0.5
+    env.slave_sql_running_warning 0.5
+    env.seconds_behind_master_warning 300
+    env.seconds_behind_master_critical 600
+
+=head1 DEPENDENCIES
+
+=over
+
+=item Cache::Cache
+
+The plugin uses shared memory to cache the statistics gathered from
+MySQL. This ensures minimal inpact on the MySQL server.
+
+=item DBD::mysql
+
+=back
+
+=head1 INTERPRETATION
+
+=head2 InnoDB
+
+The statistics from innodb are mainly collected from the command
+
+  SHOW ENGINE INNODB STATUS
+
+A nice walk through is found at
+L<http://www.mysqlperformanceblog.com/2006/07/17/show-innodb-status-walk-through/>
+
+=head2 The graphs
+
+FIX point to relevant sections in the MySQL manual and other www
+resources for each graph
+
+=over
+
+=item mysql_replication
+
+slave_io_running and slave_sql_running both translate the "Yes" values to 0 and
+anything else to 1 for their respective fields in the "SHOW SLAVE STATUS" output.
+This can be used to warn on slave failure if the warning and critical values
+are set as seen in a previous section.
+
+=back
+
+=head1 LICENSE
+
+Copyright (C) 2008,2009 Kjell-Magne Ã˜ierud
+
+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, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+=head1 VERSION
+
+git-master + a few munin modifications
+
+This plugin was downloaded from L<http://github.com/kjellm/munin-mysql/>
+
+=head1 MAGICK MARKERS
+
+  #%# family=auto
+  #%# capabilities=suggest autoconf
+
+=cut
+
+use warnings;
+use strict;
+use utf8;
+
+use DBI;
+use File::Basename;
+use Math::BigInt; # Used to append "=> lib 'GMP'" here, but GMP caused
+                 # segfault on some occasions. Removed as I don't
+                 # think the tiny performance boost is worth the
+                 # debugging effort.
+use Storable qw(nfreeze thaw);
+
+use Munin::Plugin;
+
+my $has_cache;
+
+BEGIN {
+    eval 'require Cache::SharedMemoryCache';
+    $has_cache = $@ ? 0 : 1;
+}
+
+
+#---------------------------------------------------------------------
+#  C O N F I G
+#---------------------------------------------------------------------
+
+my %config = (
+    'dsn'        => $ENV{'mysqlconnection'} || 'DBI:mysql:information_schema',
+    'user'       => $ENV{'mysqluser'}       || 'root',
+    'password'   => $ENV{'mysqlpassword'}   || '',
+    'cache_namespace' => $ENV{'cachenamespace'} || 'munin_mysql',
+);
+
+
+#---------------------------------------------------------------------
+#  C A C H E
+#---------------------------------------------------------------------
+
+my %cache_options = (
+    'namespace'          => $config{cache_namespace},
+    'default_expires_in' => 60,
+);
+
+my $shared_memory_cache ;
+if ($has_cache)
+{
+  $shared_memory_cache = Cache::SharedMemoryCache->new(\%cache_options)
+    or die("Couldn't instantiate SharedMemoryCache");
+}
+
+#---------------------------------------------------------------------
+#  G R A P H   D E F I N I T I O N S
+#---------------------------------------------------------------------
+
+# These are defaults to save typing in the graph definitions
+my %defaults = (
+    global_attrs => {
+       args   => '--base 1000',
+    },
+    data_source_attrs => {
+       min   => '0',
+       type  => 'DERIVE',
+       draw  => 'AREASTACK',
+    },
+);
+
+# %graphs contains the graph definitions, it is indexed on the graph
+# name. The information stored for each graph is used for both showing
+# data source values and for printing the graph configuration. Each
+# graph follows the followingformat:
+#
+# $graph{NAME} => {
+#     config => {
+#         # The global attributes for this graph
+#         global_attrs => {}
+#         # Attributes common to all data sources in this graph
+#         data_source_attrs => {}
+#     },
+#     data_sources => [
+#         # NAME - The name of the data source (e.g. variable names
+#         #        from SHOW STATUS)
+#         # DATA_SOURCE_ATTRS - key-value pairs with data source
+#         #                     attributes
+#         {name => 'NAME', (DATA_SOURCE_ATTRS)},
+#         {...},
+#     ],
+my %graphs = ();
+
+#---------------------------------------------------------------------
+
+$graphs{bin_relay_log} = {
+    config => {
+       global_attrs => {
+           title  => 'Binary/Relay Logs',
+           vlabel => 'Log activity (txn/s)',
+       },
+       data_source_attrs => {
+           draw  => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'Binlog_cache_disk_use', label => 'Binlog Cache Disk Use', info => 'Number of transactions which used a temporary disk cache because they could not fit in the regular binary log cache, being larger than binlog_cache_size'},
+       {name => 'Binlog_cache_use',      label => 'Binlog Cache Use', info => 'Number of transaction which used the regular binary log cache, being smaller than binlog_cache_size'},
+    ],
+};
+
+#---------------------------------------------------------------------
+$graphs{binlog_space} = {
+    config => {
+       global_attrs => {
+           title  => 'Binary log space',
+           vlabel => 'Log space',
+           args   => '--base 1024',
+       },
+       data_source_attrs => {
+           draw  => 'LINE1',
+           type  => 'GAUGE',
+       },
+    },
+    data_sources => [
+       {name => 'ma_binlog_size',        label => 'Binary Log Space'},
+    ],
+};
+
+#-------------------------
+$graphs{binlog_groupcommit} = {
+    config => {
+       global_attrs => {
+           title  => 'Binary Log Group Commits',
+           vlabel => 'Commits/Groups',
+       },
+       data_source_attrs => {
+           draw  => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'Binlog_commits', label => 'Binlog commits'},
+       {name => 'Binlog_group_commits',      label => 'Binlog Group Commits'},
+       {name => 'Binlog_group_commit_trigger_count', label => 'Binlog Groups because of binlog_commit_wait_count'},
+       {name => 'Binlog_group_commit_trigger_timeout', label => 'Binlog Groups because of binlog_commit_wait_usec'},
+       {name => 'Binlog_group_commit_trigger_lock_wait', label => 'Binlog Groups because of transactions'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{commands} = {
+    config => {
+       global_attrs => {
+           title  => 'Command Counters',
+           vlabel => 'Commands per ${graph_period}',
+           total  => 'Questions',
+       },
+       data_source_attrs => {},
+    },
+    data_sources => [
+       {name => 'Com_delete',         label => 'Delete'},
+       {name => 'Com_insert',         label => 'Insert'},
+       {name => 'Com_insert_select',  label => 'Insert select'},
+       {name => 'Com_load',           label => 'Load Data'},
+       {name => 'Com_replace',        label => 'Replace'},
+       {name => 'Com_replace_select', label => 'Replace select'},
+       {name => 'Com_select',         label => 'Select'},
+       {name => 'Com_update',         label => 'Update'},
+       {name => 'Com_update_multi',   label => 'Update multi'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{connections} = {
+    config => {
+       global_attrs => {
+           title  => 'Connections',
+           vlabel => 'Connections per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'max_connections',      label  => 'Max connections',
+                                        type   => 'GAUGE',
+                                        draw   => 'AREA',
+                                        colour => 'cdcfc4'},
+       {name => 'Max_used_connections', label  => 'Max used',
+                                        type   => 'GAUGE',
+                                        draw   => 'AREA',
+                                        colour => 'ffd660'},
+       {name => 'Aborted_clients',      label => 'Aborted clients'},
+       {name => 'Aborted_connects',     label => 'Aborted connects'},
+       {name => 'Threads_connected',    label => 'Threads connected',
+                                        type  => 'GAUGE'},
+       {name => 'Threads_running',      label => 'Threads running',
+                                        type  => 'GAUGE'},
+       {name => 'Connections',          label => 'New connections'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{files_tables} = {
+    config => {
+       global_attrs => {
+           title  => 'Files and tables',
+           vlabel => 'Tables',
+       },
+       data_source_attrs => {
+           type  => 'GAUGE',
+           draw  => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'table_open_cache', label  => 'Table cache',
+                                    draw   => 'AREA',
+                                    colour => 'cdcfc4'},
+       {name => 'Open_files',       label => 'Open files'},
+       {name => 'Open_tables',      label => 'Open tables'},
+       {name => 'Opened_tables',    label => 'Opened tables',
+                                    type  => 'DERIVE',
+                                    min   => 0},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_bpool} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Buffer Pool',
+           vlabel => 'Pages',
+           args   => '--base 1024',
+       },
+       data_source_attrs => {
+           draw => 'LINE2',
+           type => 'GAUGE',
+       },
+    },
+    data_sources => [
+       {name => 'ib_bpool_size',     label  => 'Buffer pool size',
+                                     draw   => 'AREA',
+                                     colour => 'ffd660'},
+       {name => 'ib_bpool_dbpages',  label  => 'Database pages',
+                                     draw   => 'AREA',
+                                     colour => 'cdcfc4'},
+       {name => 'ib_bpool_free',     label => 'Free pages'},
+       {name => 'ib_bpool_modpages', label => 'Modified pages'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_bpool_act} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Buffer Pool Activity',
+           vlabel => 'Activity per ${graph_period}',
+           total  => 'Total',
+       },
+       data_source_attrs => {
+           draw => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'ib_bpool_read',    label => 'Pages read'},
+       {name => 'ib_bpool_created', label => 'Pages created'},
+       {name => 'ib_bpool_written', label => 'Pages written'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_insert_buf} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Insert Buffer',
+           vlabel => 'Activity per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'ib_ibuf_inserts',    label => 'Inserts'},
+       {name => 'ib_ibuf_merged_rec', label => 'Merged Records'},
+       {name => 'ib_ibuf_merges',     label => 'Merges'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_io} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB IO',
+           vlabel => 'IO operations per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'ib_io_read',  label => 'File reads'},
+       {name => 'ib_io_write', label => 'File writes'},
+       {name => 'ib_io_log',   label => 'Log writes'},
+       {name => 'ib_io_fsync', label => 'File syncs'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_io_pend} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB IO Pending',
+           vlabel => 'Pending operations',
+       },
+       data_source_attrs => {
+           draw => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'ib_iop_log',         label => 'AIO Log'},
+       {name => 'ib_iop_sync',        label => 'AIO Sync'},
+       {name => 'ib_iop_flush_bpool', label => 'Buf Pool Flush'},
+       {name => 'ib_iop_flush_log',   label => 'Log Flushes'},
+       {name => 'ib_iop_ibuf_aio',    label => 'Insert Buf AIO Read'},
+       {name => 'ib_iop_aioread',     label => 'Normal AIO Reads'},
+       {name => 'ib_iop_aiowrite',    label => 'Normal AIO Writes'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_log} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Log',
+           vlabel => 'Log activity per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'innodb_log_buffer_size', label  => 'Buffer Size',
+                                          type   => 'GAUGE',
+                                          draw   => 'AREA',
+                                          colour => 'fafd9e'},
+       {name => 'ib_log_flush',           label => 'KB Flushed'},
+       {name => 'ib_log_written',         label => 'KB Written'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_rows} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Row Operations',
+           vlabel => 'Operations per ${graph_period}',
+           total  => 'Total',
+       },
+       data_source_attrs => {},
+    },
+    data_sources => [
+       {name => 'Innodb_rows_deleted',  label => 'Deletes'},
+       {name => 'Innodb_rows_inserted', label => 'Inserts'},
+       {name => 'Innodb_rows_read',     label => 'Reads'},
+       {name => 'Innodb_rows_updated',  label => 'Updates'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_semaphores} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Semaphores',
+           vlabel => 'Semaphores per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'ib_spin_rounds', label => 'Spin Rounds'},
+       {name => 'ib_spin_waits',  label => 'Spin Waits'},
+       {name => 'ib_os_waits',    label => 'OS Waits'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{innodb_tnx} = {
+    config => {
+       global_attrs => {
+           title  => 'InnoDB Transactions',
+           vlabel => 'Transactions per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'ib_tnx', label => 'Transactions created'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{myisam_indexes} = {
+    config => {
+       global_attrs => {
+           title  => 'MyISAM Indexes',
+           vlabel => 'Requests per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'Key_read_requests',  label => 'Key read requests'},
+       {name => 'Key_reads',          label => 'Key reads'},
+       {name => 'Key_write_requests', label => 'Key write requests'},
+       {name => 'Key_writes',         label => 'Key writes'},
+   ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{network_traffic} = {
+    config => {
+       global_attrs => {
+           title  => 'Network Traffic',
+           args   => '--base 1024',
+           vlabel => 'Bytes received (-) / sent (+) per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'Bytes_received', label => 'Bytes transfered',
+                                  graph => 'no'},
+       {name => 'Bytes_sent',     label    => 'Bytes transfered',
+                                  negative => 'Bytes_received'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{qcache} = {
+    config => {
+       global_attrs => {
+           title  => 'Query Cache',
+           vlabel => 'Commands per ${graph_period}',
+       },
+       data_source_attrs => {
+            draw => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'Qcache_queries_in_cache', label => 'Queries in cache'},
+       {name => 'Qcache_hits',             label => 'Cache hits'},
+       {name => 'Qcache_inserts',          label => 'Inserts'},
+       {name => 'Qcache_not_cached',       label => 'Not cached'},
+       {name => 'Qcache_lowmem_prunes',    label => 'Low-memory prunes'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{qcache_mem} = {
+    config => {
+       global_attrs => {
+           title  => 'Query Cache Memory',
+           vlabel => 'Bytes',
+           args   => '--base 1024 --lower-limit 0',
+       },
+       data_source_attrs => {
+           draw => 'AREA',
+           type => 'GAUGE',
+       },
+    },
+    data_sources => [
+       {name => 'query_cache_size',    label => 'Cache size'},
+       {name => 'Qcache_free_memory',  label => 'Free mem'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{replication} = {
+    config => {
+       global_attrs => {
+           title  => 'Replication',
+           vlabel => 'Activity',
+       },
+       data_source_attrs => {
+           draw => 'LINE1',
+       },
+    },
+    data_sources => [
+       {name => 'slave_io_running',           label => 'Slave IO Running',
+                                              type  => 'GAUGE',
+                                              draw  => 'AREA'},
+       {name => 'slave_sql_running',          label => 'Slave SQL Running',
+                                              type  => 'GAUGE',
+                                              draw  => 'AREA'},
+       {name => 'Slave_retried_transactions', label => 'Retried Transactions'},
+       {name => 'Slave_open_temp_tables',     label => 'Open Temp Tables'},
+       {name => 'seconds_behind_master',      label => 'Secs Behind Master',
+                                              type  => 'GAUGE'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{select_types} = {
+    config => {
+       global_attrs => {
+           title  => 'Select types',
+           vlabel => 'Commands per ${graph_period}',
+           total  => 'Total',
+       },
+       data_source_attrs => {},
+    },
+    data_sources => [
+       {name => 'Select_full_join',       label => 'Full join'},
+       {name => 'Select_full_range_join', label => 'Full range'},
+       {name => 'Select_range',           label => 'Range'},
+       {name => 'Select_range_check',     label => 'Range check'},
+       {name => 'Select_scan',            label => 'Scan'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{slow} = {
+    config => {
+       global_attrs => {
+           title  => 'Slow Queries',
+           vlabel => 'Slow queries per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'Slow_queries', label => 'Slow queries'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{sorts} = {
+    config => {
+       global_attrs => {
+           title  => 'Sorts',
+           vlabel => 'Sorts / ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'Sort_rows',         label => 'Rows sorted'},
+       {name => 'Sort_range',        label => 'Range'},
+       {name => 'Sort_merge_passes', label => 'Merge passes'},
+       {name => 'Sort_scan',         label => 'Scan'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{table_locks} = {
+    config => {
+       global_attrs => {
+           title  => 'Table locks',
+           vlabel => 'locks per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'Table_locks_immediate', label => 'Table locks immed'},
+       {name => 'Table_locks_waited',    label => 'Table locks waited'},
+    ],
+};
+
+#---------------------------------------------------------------------
+
+$graphs{tmp_tables} = {
+    config => {
+       global_attrs => {
+           title  => 'Temporary objects',
+           vlabel => 'Objects per ${graph_period}',
+       },
+       data_source_attrs => {
+           draw  => 'LINE2',
+       },
+    },
+    data_sources => [
+       {name => 'Created_tmp_disk_tables', label => 'Temp disk tables'},
+       {name => 'Created_tmp_tables',      label => 'Temp tables'},
+       {name => 'Created_tmp_files',       label => 'Temp files'},
+    ],
+};
+
+#---------------------------------------------------------------------
+#  Plugin Graphs
+#  These are mysql plugins of type INFORMATION SCHEMA
+#
+#  These will be added to $graphs if available
+#---------------------------------------------------------------------
+
+my %graph_plugins = ();
+
+$graph_plugins{query_response_time} = {
+    count => {
+        config => {
+            global_attrs => {
+                title  => 'Query Response Time Count',
+                vlabel  => 'queries per ${graph_period}',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        # data_sources are populated by sub plugin_query_response_time
+        data_sources => [
+        ],
+   },
+   total => {
+        config => {
+            global_attrs => {
+                title  => 'Query Response Time Total',
+                vlabel  => 'query time (microseconds) per ${graph_period}',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        # data_sources are populated by sub plugin_query_response_time
+        data_sources => [
+        ],
+   }
+};
+
+$graph_plugins{user_statistics} = {
+    connections => {
+        config => {
+            global_attrs => {
+                title  => 'User Connections',
+                vlabel  => 'connections per ${graph_period}',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        cols => { 'total_connections' => {}, 'concurrent_connections' => {}, 'denied_connections' => {}, 'lost_connections'  => {}},
+        data_sources => [
+        ],
+   },
+   usertime => {
+        config => {
+            global_attrs => {
+                title  => 'User Time',
+                vlabel  => 'seconds',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        cols => { 'connected_time' => {}, 'busy_time' => {}, 'cpu_time' => {} },
+        data_sources => [
+        ],
+   },
+   bytes => {
+        config => {
+            global_attrs => {
+                title  => 'User Bytes',
+                vlabel  => 'bytes',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        cols => { 'bytes_received' => {}, 'bytes_sent' => {}, 'binlog_bytes_written' => {} },
+        data_sources => [
+        ],
+   },
+   rows => {
+        config => {
+            global_attrs => {
+                title  => 'User Rows',
+                vlabel  => 'rows',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        cols => { 'rows_read' => {}, 'rows_sent' => {}, 'rows_deleted' => {}, 'rows_inserted' => {}, 'rows_updated' => {} },
+        data_sources => [
+        ],
+   },
+   commands => {
+        config => {
+            global_attrs => {
+                title  => 'Command breakdown by user',
+                vlabel  => 'commands',
+            },
+            data_source_attrs => {
+                draw  => 'LINE2',
+                type => 'DERIVE',
+            },
+        },
+        cols => { 'select_commands' => {}, 'update_commands' => {}, 'other_commands' => {}, 'commit_transactions' => {}, 'rollback_transactions' => {} },
+        data_sources => [
+        ],
+   }
+
+};
+#---------------------------------------------------------------------
+#  M A I N
+#---------------------------------------------------------------------
+
+
+#
+# Global hash holding the data collected from mysql.
+#
+our $data; # Was 'my'. Changed to 'our' to facilitate testing.
+
+
+sub main {
+    my $graph = basename($0);
+    $graph =~ s/^mysql[0-9]*_//g; # allow multiple instances
+    my $command = $ARGV[0] || 'show';
+
+    my %command_map = (
+       'autoconf' => \&autoconf,
+       'config'   => \&config,
+       'show'     => \&show,
+       'suggest'  => \&suggest,
+    );
+
+    die "Unknown command: $command"
+       unless exists $command_map{$command};
+
+    die "Missing dependency Cache::Cache"
+       unless $has_cache || $command eq 'autoconf';
+
+    return $command_map{$command}->($graph);
+}
+
+
+#---------------------------------------------------------------------
+#  C O M M A N D   H A N D L E R S
+#---------------------------------------------------------------------
+
+# Each command handler should return an appropriate exit code
+
+
+# http://munin-monitoring.org/wiki/ConcisePlugins#autoconf
+sub autoconf {
+    unless ($has_cache) {
+       print "no (Missing dependency Cache::Cache)\n";
+       return 0;
+    }
+
+    eval {
+       db_connect();
+    };
+    if ($@) {
+       my $err = $@;
+       $err =~ s{\s at \s \S+ \s line .*}{}xms;
+       print "no ($err)\n";
+       return 0;
+    }
+    print "yes\n";
+    return 0;
+}
+
+
+# http://munin-monitoring.org/wiki/ConcisePlugins#suggest
+sub suggest {
+
+    # What is the best way to decide which graphs is applicable to a
+    # given system?
+    #
+    # Does the database use InnoDB? A zero count from:
+    #
+    #   SELECT COUNT(*)
+    #     FROM tables
+    #    WHERE table_type = 'base table'
+    #      AND engine     = 'innodb'
+    #
+    # Does the database use binary logs? 'OFF' as the result from:
+    #
+    #   SHOW GLOBAL variables LIKE 'log_bin'
+    #
+    # Is the database setup as a slave? Empty result from:
+    #
+    #   SHOW SLAVE STATUS
+
+    foreach my $graph (sort keys(%graphs)) {
+        next if $graph =~ /innodb_/ && $data->{_innodb_disabled};
+        next if $graph =~ /wsrep_/ && $data->{_galera_disabled};
+        print "$graph\n";
+    }
+
+    return 0;
+}
+
+
+sub config {
+    my $graph_name = shift;
+
+    # In MySQL 5.1 (and probably erlier versions as well) status
+    # variables are unique when looking at the last 19 characters.
+    #
+    #   SELECT RIGHT(variable_name, 19), COUNT(*)
+    #     FROM information_schema.global_status
+    #    GROUP BY RIGHT(variable_name, 19)
+    #   HAVING COUNT(*) > 1;
+    #
+    #   Empty set (0.06 sec)
+    #
+    # There is one duplicate when looking at server variables
+    #
+    #   SELECT RIGHT(variable_name, 19), COUNT(*)
+    #     FROM information_schema.global_variables
+    #    GROUP BY RIGHT(variable_name, 19)
+    #   HAVING COUNT(*) > 1;
+    #
+    #   +--------------------------+----------+
+    #   | RIGHT(variable_name, 19) | COUNT(*) |
+    #   +--------------------------+----------+
+    #   | OW_PRIORITY_UPDATES      |        2 |
+    #   +--------------------------+----------+
+    #   1 row in set (0.05 sec)
+    #
+    #   show global variables like '%OW_PRIORITY_UPDATES';
+    #
+    #   +--------------------------+-------+
+    #   | Variable_name            | Value |
+    #   +--------------------------+-------+
+    #   | low_priority_updates     | OFF   |
+    #   | sql_low_priority_updates | OFF   |
+    #   +--------------------------+-------+
+    #   2 rows in set (0.00 sec)
+    #
+    # Not a problem since we don't graph these
+
+    update_data();
+
+    die 'Unknown graph ' . ($graph_name ? $graph_name : '')
+       unless $graphs{$graph_name};
+
+    my $graph = $graphs{$graph_name};
+
+    my %conf = (%{$defaults{global_attrs}}, %{$graph->{config}{global_attrs}});
+    while (my ($k, $v) = each %conf) {
+       print "graph_$k $v\n";
+    }
+    print "graph_category mysql2\n";
+
+    for my $ds (@{$graph->{data_sources}}) {
+       my %ds_spec = (
+           %{$defaults{data_source_attrs}},
+           %{$graph->{config}{data_source_attrs}},
+           %$ds,
+       );
+       while (my ($k, $v) = each %ds_spec) {
+            # 'name' is only used internally in this script, not understood by munin.
+            printf("%s.%s %s\n", clean_fieldname($ds->{name}), $k, $v) unless ($k eq 'name');
+       }
+       print_thresholds(clean_fieldname($ds->{name}));
+    }
+
+    return 0;
+}
+
+sub show {
+    my $graph_name = shift;
+
+    update_data();
+
+    die 'Unknown graph ' . ($graph_name ? $graph_name : '')
+       unless $graphs{$graph_name};
+
+    my $graph = $graphs{$graph_name};
+
+    die "Can't show data for '$graph_name' because InnoDB is disabled."
+       if $graph_name =~ /innodb_/ && $data->{_innodb_disabled};
+
+    for my $ds (@{$graph->{data_sources}}) {
+        my $value = exists $ds->{value}
+            ? $ds->{value}($data)
+                : $data->{$ds->{name}};
+
+        printf "%s.value %s\n", clean_fieldname($ds->{name}), defined($value) ? $value : 'U';
+    }
+
+    return 0;
+}
+
+
+
+#---------------------------------------------------------------------
+#  U T I L I T Y   S U B S
+#---------------------------------------------------------------------
+
+
+sub db_connect {
+    my $dsn = "$config{dsn};mysql_connect_timeout=5";
+
+    return DBI->connect($dsn, $config{user}, $config{password}, {
+       RaiseError       => 1,
+       PrintError       => 0,
+       FetchHashKeyName => 'NAME_lc',
+    });
+}
+
+
+sub update_data {
+    $data = $shared_memory_cache->get('data');
+    my $graphs_stored = $shared_memory_cache->get('graphs');
+    %graphs = %{thaw($graphs_stored)} if $graphs_stored;
+    return if $data;
+
+    #warn "Need to update cache";
+
+    $data = {};
+
+    my $dbh = db_connect();
+
+    # Set up defaults in case the server is not a slave
+    $data->{relay_log_space} = 0;
+    $data->{slave_running}   = 0;
+    $data->{slave_stopped}   = 0;
+
+    # Set up defaults in case binlog is not enabled
+    $data->{ma_binlog_size} = 0;
+
+    update_variables($dbh);
+    update_plugins($dbh);
+    update_innodb($dbh);
+    update_master($dbh);
+    update_slave($dbh);
+
+    $shared_memory_cache->set('data', $data);
+    $shared_memory_cache->set('graphs', nfreeze(\%graphs));
+}
+
+
+sub update_plugins {
+    my ($dbh) = @_;
+
+    my %plugin_map = (
+       'query_response_time'         => \&plugin_query_response_time,
+    );
+
+    sub add_graphs {
+      my ($f, $sec, $dbh, %g) = @_;
+      if ($f->($dbh) == 0) {
+        while (my ($k, $v) = each %g) {
+          $graphs{$sec . '_' . $k} = $v;
+        }
+      }
+    }
+
+    my $sth = $dbh->prepare("SHOW PLUGINS");
+    $sth->execute();
+    while (my $row = $sth->fetchrow_hashref()) {
+        next if $row->{'type'} ne 'INFORMATION SCHEMA';
+        my $sec = lc $row->{'name'};
+        next if not exists $plugin_map{$sec};
+        add_graphs($plugin_map{$sec}, $sec, $dbh, %{$graph_plugins{$sec}});
+    }
+    $sth->finish();
+
+    my %is_map = (
+       'user_statistics'         => \&is_user_statistics,
+    );
+
+    $sth = $dbh->prepare("SHOW TABLES IN INFORMATION_SCHEMA");
+    $sth->execute();
+    while (my $row = $sth->fetchrow_hashref()) {
+        my $sec = lc $row->{'tables_in_information_schema'};
+        next if not exists $is_map{$sec};
+        add_graphs($is_map{$sec}, $sec, $dbh, %{$graph_plugins{$sec}});
+    }
+    $sth->finish();
+}
+
+sub update_variables {
+    my ($dbh) = @_;
+    my @queries = (
+       'SHOW GLOBAL STATUS',
+       'SHOW GLOBAL VARIABLES',
+    );
+
+    my %variable_name_map = (
+       table_cache => 'table_open_cache', # table_open_cache was
+                                          # previously known as
+                                          # table_cache in MySQL
+                                          # 5.1.2 and earlier.
+    );
+
+    for my $query (@queries) {
+       $data->{$query} = {};
+
+       my $sth = $dbh->prepare($query);
+       $sth->execute();
+       while (my $row = $sth->fetch) {
+           my $var = $variable_name_map{$row->[0]} || $row->[0];
+           $data->{$var} = $row->[1];
+       }
+       $sth->finish();
+    }
+}
+
+
+sub update_innodb {
+    my ($dbh) = @_;
+
+    my $sth = $dbh->prepare('SHOW /*!50000 ENGINE*/ INNODB STATUS');
+    eval {
+       $sth->execute();
+    };
+    if ($@) {
+       if ($@ =~ /Unknown (storage|table) engine 'INNODB'|Cannot call SHOW INNODB STATUS because skip-innodb is defined/i) {
+           $data->{_innodb_disabled} = 1;
+           return;
+       }
+       die $@;
+    }
+    my $row = $sth->fetchrow_hashref();
+    my $status = $row->{'status'};
+    $sth->finish();
+
+    parse_innodb_status($status);
+}
+
+
+sub update_master {
+    my ($dbh) = @_;
+
+    my $sth = $dbh->prepare('SHOW MASTER LOGS');
+    eval {
+       $sth->execute();
+    };
+    if ($@) {
+       # SHOW MASTER LOGS failed becuase binlog is not enabled
+       return if $@ =~ /You are not using binary logging/;
+       die $@;
+    }
+
+    while (my $row = $sth->fetch) {
+       $data->{ma_binlog_size} += $row->[1];
+    }
+
+    $sth->finish();
+}
+
+
+sub update_slave {
+    my ($dbh) = @_;
+
+    my $sth = $dbh->prepare('SHOW SLAVE STATUS');
+    $sth->execute();
+    my $row = $sth->fetchrow_hashref();
+    return unless $row;
+    while (my ($k, $v) = each %$row) {
+       $data->{$k} = $v;
+    }
+    $sth->finish();
+
+    # undef when slave is stopped, or when MySQL fails to calculate
+    # the lag (which happens depresingly often). (mk-heartbeat fixes
+    # this problem.)
+    $data->{seconds_behind_master} ||= 0;
+
+    # Track these two fields so we can trigger warnings if the slave stops
+    # running
+    $data->{slave_sql_running} = ($data->{slave_sql_running} eq 'Yes')
+           ? 0 : 1;
+    $data->{slave_io_running} = ($data->{slave_io_running} eq 'Yes')
+           ? 0 : 1;
+
+}
+
+
+#---------------------------------------------------------------------
+#  Information SCHEMA tables represent data to be processed
+#---------------------------------------------------------------------
+
+
+sub plugin_query_response_time {
+    my ($dbh) = @_;
+
+    return 1 if not defined $data->{query_response_time_stats};
+    return 1 if $data->{query_response_time_stats} eq 'OFF';
+
+    my $sth = $dbh->prepare("SELECT * FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME");
+    $sth->execute();
+    while (my $row = $sth->fetchrow_hashref()) {
+        my $time = $row->{'time'};
+        $data->{'query_response_time_count_' . $time} = $row->{'count'};
+        push @{$graph_plugins{query_response_time}->{count}->{data_sources}}, {name => 'query_response_time_count_' . $time, label => $time };
+        next if $row->{'total'} eq 'TOO LONG';
+        $data->{'query_response_time_total_' . $time} = $row->{'total'} * 1e6;
+        push @{$graph_plugins{query_response_time}->{total}->{data_sources}}, {name => 'query_response_time_total_' . $time, label => $time };
+    }
+    $sth->finish();
+
+    return 0;
+}
+
+sub is_user_statistics {
+    my ($dbh) = @_;
+
+    return 1 if not defined $data->{userstat};
+    return 1 if $data->{userstat} eq 'OFF';
+
+    my $sth = $dbh->prepare("SELECT * FROM INFORMATION_SCHEMA.USER_STATISTICS");
+    $sth->execute();
+    while (my $row = $sth->fetchrow_hashref()) {
+        my $user = $row->{'user'};
+        my $var;
+        while (my ($g, $v) = each %{$graph_plugins{user_statistics}}) {
+          while (my ($userstat,$conf) = each %{$v->{cols}}) {
+            $var = 'user_stats_' . $user . '_' . $userstat;
+            $data->{$var} = int $row->{$userstat};
+            my $ds = { %$conf };
+            $ds->{name} = $var;
+            $ds->{label} = $user . ' ' . $userstat;
+            push @{$graph_plugins{user_statistics}->{$g}->{data_sources}}, $ds;
+          }
+        }
+    }
+    $sth->finish();
+    return 0;
+}
+
+#
+# In 'SHOW ENGINE INNODB STATUS' 64 bit integers are not formated as
+# plain integers. They are either:
+#
+#   - split in two and needs to be shifted together,
+#   - or hexadecimal
+#
+sub innodb_bigint {
+    my ($x, $y) = @_;
+
+    return defined $y
+       ? Math::BigInt->new($x)->blsft(32) + $y
+       : Math::BigInt->new("0x$x");
+}
+
+#---------------------------------------------------------------------
+#  P A R S E   'SHOW ENGINE INNODB STATUS'   O U T P U T
+#---------------------------------------------------------------------
+
+
+# A nice walk through
+# http://www.mysqlperformanceblog.com/2006/07/17/show-innodb-status-walk-through/
+
+# The parsing is split in one subrutine per section. Each subroutine
+# should parse a block with the following structure
+#
+# block body ...
+# more lines ....
+# ----------
+
+sub parse_innodb_status {
+    local $_ = shift;
+
+    # Add a dummy section to the end in case the innodb status output
+    # has been truncated (Happens for status > 64K characters)
+    $_ .= "\n----------\nDUMMY\n";
+
+    my %section_map = (
+
+       'BUFFER POOL AND MEMORY'      => \&parse_buffer_pool_and_memory,
+       'INDIVIDUAL BUFFER POOL INFO' => \&skip,
+       'FILE I/O'                    => \&parse_file_io,
+       'INSERT BUFFER AND ADAPTIVE HASH INDEX'
+           => \&parse_insert_buffer_and_adaptive_hash_index,
+       'LATEST DETECTED DEADLOCK'    => \&skip,
+       'LATEST FOREIGN KEY ERROR'    => \&skip,
+       'LOG'                         => \&parse_log,
+       'ROW OPERATIONS'              => \&skip,
+       'SEMAPHORES'                  => \&parse_semaphores,
+       'TRANSACTIONS'                => \&parse_transactions,
+       'BACKGROUND THREAD'           => \&skip,
+    );
+
+    skip_heading();
+    for (;;) {
+       m/\G(.*)\n/gc;
+       my $sec = $1;
+
+       last if $sec eq 'END OF INNODB MONITOR OUTPUT';
+       if ($sec eq 'DUMMY') {
+           handle_incomplete_innodb_status();
+           last;
+       }
+
+       die "Unknown section: $1" unless exists $section_map{$sec};
+       die "Parse error. Expected a section separator" unless m/\G-+\n/gc;
+
+       $section_map{$sec}->();
+    }
+}
+
+
+# This regular expression handles the different formating of 64-bit
+# integers in different versions of the innodb engine. Either two
+# decimal 32-bit integers seperated by a space, or a single
+# hexadecimal 64-bit integer.
+my $innodb_bigint_rx = qr{([[a-fA-F\d]+)(?: (\d+))?};
+
+
+sub match_dashes { return m/\G-+\n(?!-)/gc; }
+
+
+sub skip_line    { return m/\G.*\n/gc; }
+
+
+sub skip_heading {
+    # Heading is 6 lines
+    for my $foo (1...6) {
+       skip_line or die('Parse error');
+    }
+}
+
+
+sub parse_section {
+    my ($parser) = @_;
+
+    #warn substr($_, pos(), 10);
+    for (;;) {
+       return if match_dashes();
+       next if $parser->();
+       skip_line();
+    }
+}
+
+
+sub skip { parse_section(sub {}); }
+
+
+sub parse_semaphores {
+    parse_section(
+       sub {
+           m/\GMutex spin waits (\d+), rounds (\d+), OS waits (\d+)\n/gc && do {
+               $data->{ib_spin_waits}  = $1;
+               $data->{ib_spin_rounds} = $2;
+               $data->{ib_os_waits}    = $3;
+               return 1;
+           };
+       }
+    );
+}
+
+
+sub parse_transactions {
+    parse_section(
+       sub {
+           m/\GTrx id counter $innodb_bigint_rx\n/gc && do {
+               $data->{ib_tnx} = innodb_bigint($1, $2);
+               return 1;
+           };
+           m/\GPurge done for trx's n:o < $innodb_bigint_rx undo n:o < $innodb_bigint_rx\n/gc && do {
+               if (defined $3) {
+                   # old format
+                   $data->{ib_tnx_prg} = innodb_bigint($1, $2);
+                   # FIX add to data? innodb_bigint($3, $4);
+               }
+               else {
+                   # new format
+                   $data->{ib_tnx_prg} = innodb_bigint($1);
+                   # FIX add to data? innodb_bigint($2);
+               }
+               return 1;
+           };
+           m/\GHistory list length (\d+)\n/gc && do {
+               $data->{ib_tnx_hist} = $1;
+               return 1;
+           };
+       }
+    );
+
+}
+
+
+sub parse_file_io {
+    parse_section(
+       sub {
+           m/\GPending normal aio reads: (\d+), aio writes: (\d+),\n\s*ibuf aio reads: (\d+), log i\/o's: (\d+), sync i\/o's: (\d+)\n/gc && do {
+               $data->{ib_iop_aioread}  = $1;
+               $data->{ib_iop_aiowrite} = $2;
+               $data->{ib_iop_ibuf_aio} = $3;
+               $data->{ib_iop_log}      = $4;
+               $data->{ib_iop_sync}     = $5;
+               return 1;
+           };
+           m/\GPending flushes \(fsync\) log: (\d+); buffer pool: (\d+)\n/gc && do {
+               $data->{ib_iop_flush_log}   = $1;
+               $data->{ib_iop_flush_bpool} = $2;
+               return 1;
+           };
+           m/\G(\d+) OS file reads, (\d+) OS file writes, (\d+) OS fsyncs\n/gc && do {
+               $data->{ib_io_read}  = $1;
+               $data->{ib_io_write} = $2;
+               $data->{ib_io_fsync} = $3;
+               return 1;
+           };
+       }
+    );
+}
+
+
+sub parse_insert_buffer_and_adaptive_hash_index {
+    parse_section(
+       sub {
+      # MySQL < 5.5
+      m/\G(\d+) inserts, (\d+) merged recs, (\d+) merges\n/gc && do {
+        $data->{ib_ibuf_inserts}    = $1;
+        $data->{ib_ibuf_merged_rec} = $2;
+        $data->{ib_ibuf_merges}     = $3;
+        return 1;
+      };
+      # MySQL >= 5.5
+      m/\Gmerged operations:\n insert (\d+), delete mark \d+, delete \d+\ndiscarded operations:\n insert (\d+), delete mark \d+, delete \d+\n/gc && do {
+        $data->{ib_ibuf_inserts} = $1;
+        $data->{ib_ibuf_merged_rec} = $1 + $2;
+        return 1;
+      };
+      m/\GIbuf: size (\d+), free list len (\d+), seg size (\d+),(?: (\d+) merges)?\n/gc && do {
+        $data->{ib_ibuf_size}     = $1;
+        $data->{ib_ibuf_free_len} = $2;
+        $data->{ib_ibuf_seg_size} = $3;
+        $data->{ib_ibuf_merges}   = $4 if defined $4; # MySQL >= 5.5
+        return 1;
+      };
+       }
+    );
+}
+
+
+sub parse_log {
+    parse_section(
+       sub {
+           m/\GLog sequence number $innodb_bigint_rx\n/gc && do {
+               $data->{ib_log_written} = innodb_bigint($1, $2);
+               return 1;
+           };
+           m/\GLog flushed up to\s+$innodb_bigint_rx\n/gc && do {
+               $data->{ib_log_flush} = innodb_bigint($1, $2);
+               return 1;
+           };
+           m/\G(\d+) log i\/o's done.*\n/gc && do {
+               $data->{ib_io_log} = $1;
+               return 1;
+           };
+       }
+    );
+}
+
+
+sub parse_buffer_pool_and_memory {
+    parse_section(
+       sub {
+           m/\GBuffer pool size\s+(\d+)\n/gc && do {
+               $data->{ib_bpool_size} = $1;
+               return 1;
+           };
+           m/\GFree buffers\s+(\d+)\n/gc && do {
+               $data->{ib_bpool_free} = $1;
+               return 1;
+           };
+           m/\GDatabase pages\s+(\d+)\n/gc && do {
+               $data->{ib_bpool_dbpages} = $1;
+               return 1;
+           };
+           m/\GModified db pages\s+(\d+)\n/gc && do {
+               $data->{ib_bpool_modpages} = $1;
+               return 1;
+           };
+           m/\GPages read (\d+), created (\d+), written (\d+)\n/gc && do {
+               $data->{ib_bpool_read}    = $1;
+               $data->{ib_bpool_created} = $2;
+               $data->{ib_bpool_written} = $3;
+               return 1;
+           };
+       }
+    );
+}
+
+
+sub handle_incomplete_innodb_status {
+
+    warn "Output from SHOW ENGINE INNDOB STATUS was truncated. "
+       . "This happens if the output of SEIS exceeds 64KB. "
+       . "Several of the InnoDB graphs might be affected by this.";
+
+    # FIX Is it possible to find some of the missing values from SHOW
+    # STATUS?
+}
+
+
+exit main() unless caller;
+
+
+1;
diff --git a/php_fpm b/php_fpm
new file mode 100755 (executable)
index 0000000..9872cb6
--- /dev/null
+++ b/php_fpm
@@ -0,0 +1,125 @@
+#!/bin/sh
+# -*- sh -*-
+
+: << =cut
+
+=head1 NAME
+
+php_fpm - Plugin to show PHP-FPM status
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+. "${MUNIN_LIBDIR:-/usr/share/munin}/plugins/plugin.sh"
+
+URL="${URL:-http://localhost/fpm?json}"
+CURL_OPTS="${CURL_OPTS:---location} --silent"
+
+if [ "$1" = "autoconf" ]; then
+       if ! command -v curl >/dev/null 2>&1; then
+               echo "no (no curl)"
+               exit 0
+       fi
+       if ! command -v jq >/dev/null 2>&1; then
+               echo "no (no jq)"
+               exit 0
+       fi
+       if ! curl --fail $CURL_OPTS $URL  >/dev/null 2>&1; then
+               echo "no (curl $CURL_OPTS $URL failed)"
+               exit 0
+       fi
+       echo yes
+       exit 0
+fi
+
+if [ "$1" = "config" ]; then
+
+       echo 'multigraph php_fpm_workers'
+       echo 'graph_title PHP-FPM workers'
+       echo 'graph_args --base 1000 -l 0 '
+       echo 'graph_scale no'
+       echo 'graph_vlabel # workers'
+       echo 'graph_category php'
+       echo 'total.label Total workers'
+       echo 'total.draw AREA'
+       echo 'total.colour ffd660'
+       echo 'idle.label Idle workers'
+       echo 'active.label Active workers'
+       echo 'max.label Max reached'
+       echo 'max.info Max reached since start'
+       echo 'max.colour 000000'
+
+       echo 'multigraph php_fpm_connections'
+       echo 'graph_title PHP-FPM Connections'
+       echo 'graph_args --base 1000 -l 0'
+       echo 'graph_scale no'
+       echo 'graph_vlabel Connections/second'
+       echo 'graph_category php'
+       echo 'rate.label Connection rate'
+       echo 'rate.type DERIVE'
+
+       echo 'multigraph php_fpm_slow'
+       echo 'graph_title PHP-FPM Slow requests'
+       echo 'graph_args --base 1000 -l 0'
+       echo 'graph_scale no'
+       echo 'graph_vlabel # since start'
+       echo 'graph_category php'
+       echo 'requests.label Slow requests'
+
+       echo 'multigraph php_fpm_queue'
+       echo 'graph_title PHP-FPM Listen queue'
+       echo 'graph_args --base 1000 -l 0'
+       echo 'graph_scale no'
+       echo 'graph_vlabel Queue size'
+       echo 'graph_category php'
+       echo 'current.label Current'
+       echo 'max.label Highest'
+       echo 'len.label Capacity'
+       echo 'len.draw AREA'
+       echo 'len.colour ffd660'
+
+       exit 0
+fi
+
+jsonblob="$(curl $CURL_OPTS $URL)"
+if ! echo "$jsonblob" | jq -e >/dev/null; then
+       echo 'multigraph php_fpm_workers'
+       echo 'total.value U'
+       echo 'idle.value U'
+       echo 'active.value U'
+       echo 'max.value U'
+
+       echo 'multigraph php_fpm_connections'
+       echo 'rate.value U'
+
+       echo 'multigraph php_fpm_slow'
+       echo 'requests.value U'
+
+       echo 'multigraph php_fpm_queue'
+       echo 'current.value U'
+       echo 'max.value U'
+       echo 'len.value U'
+
+       exit 0
+fi
+       
+echo 'multigraph php_fpm_workers'
+echo "idle.value $(echo "$jsonblob" | jq '."idle processes"')"
+echo "active.value $(echo "$jsonblob" | jq '."active processes"')"
+echo "total.value $(echo "$jsonblob" | jq '."total processes"')"
+echo "max.value $(echo "$jsonblob" | jq '."max active processes"')"
+
+echo 'multigraph php_fpm_connections'
+echo "rate.value $(echo "$jsonblob" | jq '."accepted conn"')"
+
+echo 'multigraph php_fpm_slow'
+echo "requests.value $(echo "$jsonblob" | jq '."slow requests"')"
+
+echo 'multigraph php_fpm_queue'
+echo "current.value $(echo "$jsonblob" | jq '."listen queue"')"
+echo "max.value $(echo "$jsonblob" | jq '."max listen queue"')"
+echo "len.value $(echo "$jsonblob" | jq '."listen queue len"')"
diff --git a/postfix_mailvolume b/postfix_mailvolume
new file mode 100755 (executable)
index 0000000..09373d4
--- /dev/null
@@ -0,0 +1,184 @@
+#!/usr/bin/perl -w
+# -*- perl -*-
+
+=head1 NAME
+
+postfix_mailvolume - Plugin to monitor the volume of mails delivered
+  by postfix.
+
+=head1 APPLICABLE SYSTEMS
+
+Any postfix.
+
+=head1 CONFIGURATION
+
+The following shows the default configuration.
+
+  [postfix*]
+    env.logdir /var/log
+    env.logfile syslog
+
+=head1 INTERPRETATION
+
+The plugin shows the number of bytes of mail that has passed through
+the postfix installation.
+
+=head1 MAGIC MARKERS
+
+  #%# family=auto
+  #%# capabilities=autoconf
+
+=head1 BUGS
+
+None known
+
+=head1 VERSION
+
+v1.1 2018-03-24
+* calculate extra field for mail volume that is actually delivered ("volume_delivered")
+
+=head1 AUTHOR
+
+Copyright (C) 2002-2008.
+
+No author is documented.
+
+=head1 LICENSE
+
+GPLv2
+
+=cut
+
+use strict;
+use warnings;
+use Munin::Plugin;
+
+my $pos = undef;
+# the volume that was actually delivered
+my $volume_delivered = 0;
+my %volumes_per_queue_id = ();
+my $serialized_volumes_queue;
+my %expired_queue_ids = ();
+# Discard old queue IDs after a while (otherwise the state storage grows infinitely). We need to
+# store the IDs long enough for the gap between two delivery attempts. Thus multiple hours are
+# recommended.
+use constant queue_id_expiry => 6 * 3600;
+
+my $LOGDIR  = $ENV{'logdir'}  || '/var/log';
+my $LOGFILE = $ENV{'logfile'} || 'syslog';
+
+
+sub parseLogfile {
+    my ($fname, $start) = @_;
+
+    my ($LOGFILE, $rotated) = tail_open($fname, $start || 0);
+
+    while (my $line = <$LOGFILE>) {
+        chomp ($line);
+
+        if ($line =~ /qmgr.*: ([0-9A-Za-z]+): from=.*, size=([0-9]+)/) {
+            # The line with queue ID and size may pass along multiple times (every time the mail
+            # is moved into the active queue for another delivery attempt). The size should always
+            # be the same.
+            if (not exists($volumes_per_queue_id{$1})) {
+                $volumes_per_queue_id{$1} = {timestamp => time};
+            }
+            # probably it is the same value as before
+            $volumes_per_queue_id{$1}->{size} = $2;
+        } elsif ($line =~ / ([0-9A-Za-z]+): to=.*, status=sent /) {
+            # The "sent" line is repeated for every successful delivery for each recipient.
+            if (exists($volumes_per_queue_id{$1})) {
+                $volume_delivered += $volumes_per_queue_id{$1}->{size};
+                $volumes_per_queue_id{$1}->{timestamp} = time;
+            }
+        }
+    }
+    # remove all expired queue IDs
+    my @expired_queue_ids;
+    for my $key (keys %volumes_per_queue_id) {
+        if (time > $volumes_per_queue_id{$key}->{timestamp} + queue_id_expiry) {
+            push @expired_queue_ids, $key;
+        }
+    }
+    delete(@volumes_per_queue_id{@expired_queue_ids});
+    return tail_close($LOGFILE);
+}
+
+if ( $ARGV[0] and $ARGV[0] eq "autoconf" ) {
+    my $logfile;
+    `which postconf >/dev/null 2>/dev/null`;
+    if (!$?) {
+        $logfile = "$LOGDIR/$LOGFILE";
+
+        if (-f $logfile) {
+            if (-r "$logfile") {
+                print "yes\n";
+                exit 0;
+            } else {
+                print "no (logfile '$logfile' not readable)\n";
+            }
+        } else {
+            print "no (logfile '$logfile' not found)\n";
+        }
+    } else {
+        print "no (postfix not found)\n";
+    }
+
+    exit 0;
+}
+
+
+if ( $ARGV[0] and $ARGV[0] eq "config" ) {
+    print "graph_title Postfix bytes throughput\n";
+    print "graph_args --base 1000 -l 0\n";
+    print "graph_vlabel bytes / \${graph_period}\n";
+    print "graph_scale yes\n";
+    print "graph_category postfix\n";
+    print "volume.label delivered volume\n";
+    print "volume.type DERIVE\n";
+    print "volume.min 0\n";
+    exit 0;
+}
+
+
+my $logfile = "$LOGDIR/$LOGFILE";
+
+if (! -f $logfile) {
+    print "volume.value U\n";
+    exit 0;
+}
+
+# load the stored data
+($pos, $volume_delivered, $serialized_volumes_queue) = restore_state();
+
+
+if (!defined($volume_delivered)) {
+
+    # No state file present.  Avoid startup spike: Do not read log
+    # file up to now, but remember how large it is now, and next
+    # time read from there.
+
+    $pos = (stat $logfile)[7]; # File size
+
+    $volume_delivered = 0;
+    %volumes_per_queue_id = ();
+} else {
+    # decode the serialized hash
+    # source format: "$id1=$size1:$timestamp1 $id2=$size2:$timestamp2 ..."
+    # The "serialized" value may be undefined, in case we just upgraded from the version before
+    # 2018, since that old version stored only two fields in the state file.  Tolerate this.
+    for my $queue_item_descriptor (split(/ /, $serialized_volumes_queue || "")) {
+        (my $queue_item_id, my $queue_item_content) = split(/=/, $queue_item_descriptor);
+        (my $size, my $timestamp) = split(/:/, $queue_item_content);
+        $volumes_per_queue_id{$queue_item_id} = { size => int($size), timestamp => int($timestamp) };
+    }
+    $pos = parseLogfile ($logfile, $pos);
+}
+
+print "volume.value $volume_delivered\n";
+
+# serialize the hash to a string (see "source format" above)
+$serialized_volumes_queue = join(" ", map { sprintf("%s=%s", $_, sprintf("%d:%d", $volumes_per_queue_id{$_}->{size}, $volumes_per_queue_id{$_}->{timestamp})) } keys %volumes_per_queue_id);
+save_state($pos, $volume_delivered, $serialized_volumes_queue);
+
+# vim:syntax=perl
diff --git a/processes b/processes
new file mode 100755 (executable)
index 0000000..24c2803
--- /dev/null
+++ b/processes
@@ -0,0 +1,442 @@
+#!/bin/sh
+# -*- sh -*-
+
+set -e
+
+: << =cut
+
+=head1 NAME
+
+processes - Plugin to monitor processes and process states.
+
+=head1 ABOUT
+
+This plugin requires munin-server version 1.2.5 or 1.3.3 (or higher).
+
+This plugin is backwards compatible with the old processes-plugins found on
+SunOS, Linux and *BSD (i.e. the history is preserved).
+
+All fields have colours associated with them which reflect the type of process
+(sleeping/idle = blue, running = green, stopped/zombie/dead = red, etc.)
+
+=head1 CONFIGURATION
+
+No configuration for this plugin.
+
+=head1 AUTHOR
+
+Copyright (C) 2006 Lars Strand
+
+=head1 LICENSE
+
+GNU General Public License, version 2
+
+=begin comment
+
+This file is part of Munin.
+
+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, write to the Free Software Foundation, Inc., 51 Franklin
+Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+=end comment
+
+=head1 MAGIC MARKERS
+
+=begin comment
+
+These magic markers are used by munin-node-configure when installing
+munin-node.
+
+=end comment
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+# Search for program in $PATH unless predefined.
+awk=${awk:-awk}
+ps=${ps:-ps}
+
+# Find operating system
+OPERSYS=${OPERSYS:-$(uname | cut -f 1 -d _)}
+[ -z "$OPERSYS" ] && echo >&2 "Failed to detect environment via uname" && exit 1
+
+if [ "$1" = "autoconf" ]; then
+    case "$OPERSYS" in
+        Linux|SunOS|FreeBSD|OpenBSD|NetBSD|Darwin|CYGWIN)
+            if ! "$ps" >/dev/null 2>/dev/null; then
+                echo "no (ps=$ps failed)"
+            elif ! echo | "$awk" '{ print "Hei" }' >/dev/null 2>/dev/null; then
+                echo "no (awk=$awk failed)"
+            else
+                echo yes
+            fi
+            exit 0
+            ;;
+        *)
+            echo "no (unknown OS)"
+            exit 0
+            ;;
+    esac
+fi
+
+. "$MUNIN_LIBDIR/plugins/plugin.sh"
+
+# Define colours
+RUNNABLE='22ff22'         # Green
+SLEEPING='0022ff'         # Blue
+STOPPED='cc0000'          # Darker red
+DEBUGGED='ffff00'         # Yellow
+ZOMBIE='990000'           # Darkest red
+UNINTERRUPTIBLE='ffa500'  # Orange
+IDLE='4169e1'             # Royal blue
+PAGING='00aaaa'           # Darker turquoise
+INTERRUPT='ff00ff'        # Fuchsia
+LOCK='ff3333'             # Lighter red
+RUNNING='00ff7f'          # Spring green
+DEAD='ff0000'             # Red
+SUSPENDED='ff1493'        # Deep pink
+TOTAL='c0c0c0'            # Silver
+
+# Taken from ps(1)
+# R - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX, HP-UX      (runable)
+# S - Linux, SunOS, FreeBSD*, OpenBSD*, NetBSD*, OSX*, HP-UX  (sleeping)
+# T - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX, HP-UX      (stopped)
+# Z - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX, HP-UX      (zombie/terminated)
+# D - Linux, FreeBSD, OpenBSD, NetBSD                         (uninterruptible)
+# I - FreeBSD, OpenBSD, NetBSD, OSX, HP-UX                    (idle/intermediate)
+# W - Linux*, FreeBSD*, HP-UX                                 (paging/interrupt/waiting)
+# L - FreeBSD                                                 (lock)
+# O - SunOS                                                   (running)
+# X - Linux, HP-UX*                                           (dead)
+# U - OSX, NetBSD*                                            (uninterruptible/suspended)
+# 0 - HP-UX                                                   (nonexistent)
+# *) Differ meaning
+
+if [ "$1" = "config" ]; then
+    echo "graph_title Processes"
+    echo "graph_info This graph shows the number of processes"
+    echo "graph_category processes"
+    echo "graph_args --base 1000 -l 0"
+    echo "graph_vlabel Number of processes"
+
+    # OS specific flags
+    if [ "$OPERSYS" = "Linux" ]; then
+        echo "graph_order sleeping idle stopped debugged zombie dead paging uninterruptible runnable processes"
+        echo "dead.label dead"
+        echo "dead.draw STACK"
+        echo "dead.colour $DEAD"
+        echo "dead.info The number of dead processes."
+        print_warning dead
+        print_critical dead
+        echo "paging.label paging"
+        echo "paging.draw STACK"
+        echo "paging.colour $PAGING"
+        echo "paging.info The number of paging processes (<2.6 kernels only)."
+        print_warning paging
+        print_critical paging
+       echo "debugged.label debug"
+       echo "debugged.draw STACK"
+       echo "debugged.colour $DEBUGGED"
+       echo "debugged.info The number of processes stopped for debugging"
+        print_warning debugged
+        print_critical debugged
+
+    elif [ "$OPERSYS" = "SunOS" ]; then
+        echo "graph_order sleeping stopped zombie runnable running total"
+        echo "running.label running"
+        echo "running.draw STACK"
+        echo "running.colour $RUNNING"
+        echo "running.info The number of processes that are running on a processor."
+        print_warning running
+        print_critical running
+        # Be backwards compatible.
+        echo "total.label total"
+        echo "total.draw LINE1"
+        echo "total.colour $TOTAL"
+        echo "total.info The total number of processes."
+        print_warning total
+        print_critical total
+
+    elif [ "$OPERSYS" = "FreeBSD" ]; then
+        echo "graph_order sleeping idle stopped zombie lock uninterruptible interrupt runnable processes"
+        echo "lock.label lock"
+        echo "lock.draw STACK"
+        echo "lock.colour $LOCK"
+        echo "lock.info The number of processes that are waiting to acquire a lock."
+        print_warning lock
+        print_critical lock
+        echo "interrupt.label interrupt"
+        echo "interrupt.draw STACK"
+        echo "interrupt.colour $INTERRUPT"
+        echo "interrupt.info The number of idle interrupt threads."
+        print_warning interrupt
+        print_critical interrupt
+
+    elif [ "$OPERSYS" = "OpenBSD" ]; then
+        echo "graph_order sleeping idle stopped zombie uninterruptible runnable processes"
+
+    elif [ "$OPERSYS" = "NetBSD" ]; then
+        echo "graph_order sleeping idle stopped zombie uninterruptible suspended runnable processes"
+        echo "suspended.label suspended"
+        echo "suspended.draw STACK"
+        echo "suspended.colour $SUSPENDED"
+        echo "suspended.info The number of processes that are suspended."
+        print_warning suspended
+        print_critical suspended
+
+    elif [ "$OPERSYS" = "Darwin" ]; then
+        echo "graph_order sleeping idle stopped zombie uninterruptible running processes"
+        echo "uninterruptible.label uninterruptible"
+        echo "uninterruptible.draw STACK"
+        echo "uninterruptible.colour $UNINTERRUPTIBLE"
+        echo "uninterruptible.info The number of uninterruptible processes (usually IO)."
+        print_warning uninterruptible
+        print_critical uninterruptible
+    elif [ "$OPERSYS" = "HP-UX" ]; then
+        echo "graph_order sleeping intermediate stopped terminated waiting growing nonexistent runnable processes"
+        echo "waiting.label waiting"
+        echo "waiting.draw STACK"
+        echo "waiting.colour $INTERRUPT"
+        echo "waiting.info The number of waiting processes."
+        print_warning waiting
+        print_critical waiting
+        echo "terminated.label terminated"
+        echo "terminated.draw STACK"
+        echo "terminated.colour $ZOMBIE"
+        echo "terminated.info The number of processes that are terminated."
+        print_warning terminated
+        print_critical terminated
+        echo "growing.label growing"
+        echo "growing.draw STACK"
+        echo "growing.colour $RUNNING"
+        echo "growing.info The number of growing processes."
+        print_warning growing
+        print_critical growing
+        echo "intermediate.label intermediate"
+        echo "intermediate.draw STACK"
+        echo "intermediate.colour $IDLE"
+        echo "intermediate.info The number of intermediate processes."
+        print_warning intermediate
+        print_critical intermediate
+        echo "nonexistent.label nonexistent"
+        echo "nonexistent.draw STACK"
+        echo "nonexistent.colour $LOCK"
+        echo "nonexistent.info The number of nonexistent processes."
+        print_warning nonexistent
+        print_critical nonexistent
+    fi
+
+    # Common flags for some OS
+    if [ "$OPERSYS" = "FreeBSD" ] || [ "$OPERSYS" = "OpenBSD" ] ||
+    [ "$OPERSYS" = "NetBSD" ] || [ "$OPERSYS" = "Darwin" ]; then
+        echo "idle.label idle"
+        echo "idle.draw STACK"
+        echo "idle.colour $IDLE"
+        echo "idle.info The number of processes that are idle (sleeping for longer than about 20 seconds)."
+        print_warning idle
+        print_critical idle
+        echo "sleeping.label sleeping"
+        echo "sleeping.draw AREA"
+        echo "sleeping.colour $SLEEPING"
+        echo "sleeping.info The number of processes that are sleeping for less than about 20 seconds."
+        print_warning sleeping
+        print_critical sleeping
+    elif [ "$OPERSYS" = "Linux" ]; then
+        echo "idle.label idle"
+        echo "idle.draw STACK"
+        echo "idle.colour $IDLE"
+        echo "idle.info The number of idle kernel threads (>= 4.2 kernels only)."
+        print_warning idle
+        print_critical idle
+        echo "sleeping.label sleeping"
+        echo "sleeping.draw AREA"
+        echo "sleeping.colour $SLEEPING"
+        echo "sleeping.info The number of sleeping processes."
+        print_warning sleeping
+        print_critical sleeping
+    elif [ "$OPERSYS" = "SunOS" ] || [ "$OPERSYS" = "HP-UX" ]; then
+        echo "sleeping.label sleeping"
+        echo "sleeping.draw AREA"
+        echo "sleeping.colour $SLEEPING"
+        echo "sleeping.info The number of sleeping processes."
+        print_warning sleeping
+        print_critical sleeping
+    fi
+
+    if [ "$OPERSYS" = "Linux" ] || [ "$OPERSYS" = "FreeBSD" ] ||
+    [ "$OPERSYS" = "OpenBSD" ] || [ "$OPERSYS" = "NetBSD" ]; then
+        echo "uninterruptible.label uninterruptible"
+        echo "uninterruptible.draw STACK"
+        echo "uninterruptible.colour $UNINTERRUPTIBLE"
+        echo "uninterruptible.info The number of uninterruptible processes (usually IO)."
+        print_warning uninterruptible
+        print_critical uninterruptible
+    fi
+
+    # Common (non-cygwin) flags
+    if [ "$OPERSYS" != "CYGWIN" ]; then
+        echo "stopped.label stopped"
+        echo "stopped.draw STACK"
+        echo "stopped.colour $STOPPED"
+        echo "stopped.info The number of stopped or traced processes."
+        print_warning stopped
+        print_critical stopped
+
+        echo "runnable.label runnable"
+        echo "runnable.draw STACK"
+        echo "runnable.colour $RUNNABLE"
+        echo "runnable.info The number of runnable processes (on the run queue)."
+        print_warning runnable
+        print_critical runnable
+    fi
+
+    if [ "$OPERSYS" != "CYGWIN" ] && [ "$OPERSYS" != "HP-UX" ]; then
+        echo "zombie.label zombie"
+        echo "zombie.draw STACK"
+        echo "zombie.colour $ZOMBIE"
+        echo "zombie.info The number of defunct ('zombie') processes (process terminated and parent not waiting)."
+        print_warning zombie
+        print_critical zombie
+    fi
+
+    if [ "$OPERSYS" != "SunOS" ]; then
+    # Not using 'graph_total' due to backwards compability. SunOS uses 'total'.
+        #echo 'graph_total total'
+        echo "processes.label total"
+        echo "processes.draw LINE1"
+        echo "processes.colour $TOTAL"
+        echo "processes.info The total number of processes."
+    print_warning processes
+    print_critical processes
+    fi
+
+    exit 0
+fi
+
+if [ "$OPERSYS" = "Linux" ]; then
+    # shellcheck disable=SC2016
+    "$ps" --no-header -eo s | "$awk" '
+{ processes++; stat[$1]++ }
+END {
+print "processes.value "        0+processes;
+print "uninterruptible.value "  0+stat["D"];
+print "runnable.value "         0+stat["R"];
+print "sleeping.value "         0+stat["S"];
+print "idle.value "             0+stat["I"];
+print "debugged.value "          0+stat["t"];
+print "stopped.value "          0+stat["T"];
+print "paging.value "           0+stat["W"];
+print "dead.value "             0+stat["X"];
+print "zombie.value "           0+stat["Z"];
+}'
+
+elif [ "$OPERSYS" = "SunOS" ]; then
+    # shellcheck disable=SC2016
+    "$ps" -e -o s | "$awk" '
+{ total++; stat[$1]++ }
+END {
+print "total.value "    0+total;
+print "running.value "  0+stat["O"];
+print "sleeping.value " 0+stat["S"];
+print "runnable.value " 0+stat["R"];
+print "stopped.value "  0+stat["T"];
+print "zombie.value "   0+stat["Z"];
+}'
+elif [ "$OPERSYS" = "FreeBSD" ]; then
+    # shellcheck disable=SC2016
+    "$ps" -axo state= | sed -e 's/^\(.\).*/\1/' | "$awk" '
+{ processes++; stat[$1]++ }
+END {
+print "processes.value "        0+processes;
+print "uninterruptible.value "  0+stat["D"];
+print "idle.value "             0+stat["I"];
+print "lock.value "             0+stat["G"];
+print "runnable.value "         0+stat["R"];
+print "sleeping.value "         0+stat["S"];
+print "stopped.value "          0+stat["T"];
+print "interrupt.value "        0+stat["W"];
+print "zombie.value "           0+stat["Z"];
+}'
+elif [ "$OPERSYS" = "OpenBSD" ]; then
+    # First line is header. Remove it.
+    # shellcheck disable=SC2016
+    "$ps" -axo state= | sed '1d' | sed -e 's/^\(.\).*/\1/' | "$awk" '
+{ processes++; stat[$1]++ }
+END {
+print "processes.value "        0+processes;
+print "uninterruptible.value "  0+stat["D"];
+print "idle.value "             0+stat["I"];
+print "runnable.value "         0+stat["R"];
+print "sleeping.value "         0+stat["S"];
+print "stopped.value "          0+stat["T"];
+print "zombie.value "           0+stat["Z"];
+}'
+elif [ "$OPERSYS" = "NetBSD" ]; then
+    # First line is header. Remove it.
+    # shellcheck disable=SC2016
+    "$ps" -axo state= | sed '1d' | sed -e 's/^\(.\).*/\1/' | "$awk" '
+{ processes++; stat[$1]++ }
+END {
+print "processes.value "        0+processes;
+print "uninterruptible.value "  0+stat["D"];
+print "idle.value "             0+stat["I"];
+print "suspended.value "        0+stat["U"];
+print "runnable.value "         0+stat["R"];
+print "sleeping.value "         0+stat["S"];
+print "stopped.value "          0+stat["T"];
+print "zombie.value "           0+stat["Z"];
+}'
+
+elif [ "$OPERSYS" = "Darwin" ]; then
+    # First line is header. Remove it.
+    # shellcheck disable=SC2016
+    "$ps" -axo state= | sed '1d' | sed -e 's/^\(.\).*/\1/' | "$awk" '
+{ processes++; stat[$1]++ }
+END {
+print "processes.value "        0+processes;
+print "uninterruptible.value "  0+stat["U"];
+print "idle.value "             0+stat["I"];
+print "runnable.value "         0+stat["R"];
+print "sleeping.value "         0+stat["S"];
+print "stopped.value "          0+stat["T"];
+print "zombie.value "           0+stat["Z"];
+}'
+
+elif [ "$OPERSYS" = "CYGWIN" ]; then
+    # First line is header. Remove it. Also remove WINPID duplicates.
+    # shellcheck disable=SC2016
+    "$ps" -aW | sed '1d' | cut -c 30-36 | sort -u | "$awk" '
+{ processes++; }
+END {
+print "processes.value "        0+processes;
+}'
+
+elif [ "$OPERSYS" = "HP-UX" ]; then
+    # First line is header. Remove it.
+    # shellcheck disable=SC2016
+    "$ps" -el | sed '1d' | "$awk" '{print $2}' | "$awk" '
+{ processes++; stat[$1]++ }
+END {
+print "processes.value "        0+processes;
+print "nonexistent.value "      0+stat["0"];
+print "sleeping.value "         0+stat["S"];
+print "waiting.value "          0+stat["W"];
+print "runnable.value "         0+stat["R"];
+print "intermediate.value "     0+stat["I"];
+print "terminated.value "       0+stat["Z"];
+print "stopped.value "          0+stat["T"];
+print "growing.value "          0+stat["X"];
+}'
+
+fi
diff --git a/snmp__cyberpower b/snmp__cyberpower
new file mode 100755 (executable)
index 0000000..fac4878
--- /dev/null
@@ -0,0 +1,223 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+Monitor CyberPower UPS Battery Status.
+
+
+=head1 AUTHOR
+
+Kai Boenke
+
+=head1 LICENSE
+
+Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
+
+=back
+
+
+#####
+# Enable SNMP-Discovery
+###
+=head1 MAGIC MARKERS
+  #%# family=snmpauto
+  #%# capabilities=snmpconf
+=cut
+if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") {
+       print "require 1.3.6.1.2.1.33.1.2.4.0\n";
+       exit 0;
+}
+
+
+#####
+# Initialize
+###
+use strict;
+use Munin::Plugin::SNMP;
+my $session    = Munin::Plugin::SNMP->session();
+
+
+#####
+# Declare OIDs
+###
+use constant oid_cps_battery_runtime   => ".1.3.6.1.2.1.33.1.2.3.0";
+use constant oid_cps_battery_charge    => ".1.3.6.1.2.1.33.1.2.4.0";
+use constant oid_cps_input_voltage     => ".1.3.6.1.2.1.33.1.3.3.1.3.1";
+use constant oid_cps_output_voltage    => ".1.3.6.1.2.1.33.1.4.4.1.2.1";
+use constant oid_cps_output_load       => ".1.3.6.1.2.1.33.1.4.4.1.5.1";
+use constant oid_cps_env_temp          => ".1.3.6.1.4.1.3808.1.1.4.2.1.0";
+use constant oid_cps_env_humidity      => ".1.3.6.1.4.1.3808.1.1.4.3.1.0";
+
+use constant upsStatus                  => ".1.3.6.1.4.1.3808.1.1.1.10.1.0";
+use constant upsBaseBatteryStatus       => ".1.3.6.1.4.1.3808.1.1.1.2.1.1.0";
+use constant upsAdvanceInputStatus      => ".1.3.6.1.4.1.3808.1.1.1.3.2.6.0";
+use constant upsBaseOutputStatus        => ".1.3.6.1.4.1.3808.1.1.1.4.1.1.0";
+use constant upsAdvanceInputFrequency   => ".1.3.6.1.4.1.3808.1.1.1.3.2.4.0";
+use constant upsAdvanceOutputFrequency  => ".1.3.6.1.4.1.3808.1.1.1.4.2.2.0";
+use constant upsAdvanceOutputPower      => ".1.3.6.1.4.1.3808.1.1.1.4.2.5.0";
+
+
+#####
+# Config
+###
+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 "multigraph cyberpower_load
+graph_title CyberPower UPS Status
+graph_info This graph shows battery status information.
+graph_category sensors
+graph_vlabel %
+graph_args --upper-limit 101 -l 0
+graph_scale no
+";
+       print "load.label Total load
+load.draw AREA
+load.type GAUGE
+load.min 0
+load.max 100
+";
+       print "charge.label Battery charge
+charge.draw LINE1
+charge.type GAUGE
+charge.min 0
+charge.max 100
+";
+
+       print "multigraph cyberpower_runtime
+graph_title CyberPower UPS Runtime
+graph_info This graph shows expected runtime information.
+graph_category sensors
+graph_vlabel minutes
+";
+       print "runtime.label Expected runtime
+runtime.draw AREA
+runtime.type GAUGE
+";
+
+       print "multigraph cyberpower_voltage
+graph_title CyberPower UPS Voltages
+graph_info This graph shows voltage information.
+graph_category sensors
+graph_vlabel V
+";
+       print "input.label Input voltage
+input.draw LINE2
+input.type GAUGE
+";
+       print "output.label Output voltage
+output.draw LINE1
+output.type GAUGE
+";
+
+       print "multigraph cyberpower_status
+graph_title CyberPower UPS Statuses
+graph_info This graph shows status information.
+graph_category sensors
+";
+       print "upsStatus.label UPS status
+upsStatus.draw LINE1
+upsStatus.type GAUGE
+";
+#upsStatus: 1 normal, 2 overheat, 3 hardwarefault
+       print "upsBaseBatteryStatus.label Battery status
+upsBaseBatteryStatus.draw LINE1
+upsBaseBatteryStatus.type GAUGE
+";
+#upsBaseBatteryStatus: 2 normal, 1 unknown, 3 low, 4 notpresent
+       print "upsAdvanceInputStatus.label Input status
+upsAdvanceInputStatus.draw LINE1
+upsAdvanceInputStatus.type GAUGE
+";
+#upsAdvanceInputStatus: 1 normal, 2 high, 3 low, 4 freq, 5 blackout, 6 powerFailure
+       print "upsBaseOutputStatus.label Output status
+upsBaseOutputStatus.draw LINE1
+upsBaseOutputStatus.type GAUGE
+";
+#upsBaseOutputStatus:
+#      unknown(1),
+#      onLine(2),
+#      onBattery(3),
+#      onBoost(4),
+#      onSleep(5),
+#      off(6),
+#      rebooting(7),
+#      onECO(8),
+#      onBypass(9),
+#      onBuck(10),
+#      onOverload(11)
+
+       print "multigraph cyberpower_frequency
+graph_title CyberPower UPS Frequency
+graph_info This graph shows frequencies
+graph_category sensors
+graph_vlabel Hz
+graph_args --upper-limit 80 --lower-limit 40
+";
+       print "upsAdvanceInputFrequency.label Input frequency
+upsAdvanceInputFrequency.draw LINE1
+upsAdvanceInputFrequency.type GAUGE
+";
+       print "upsAdvanceOutputFrequency.label Output frequency
+upsAdvanceOutputFrequency.draw LINE1
+upsAdvanceOutputFrequency.type GAUGE
+";
+
+       print "multigraph cyberpower_power
+graph_title CyberPower UPS Power
+graph_info This graph shows power usage
+graph_category sensors
+graph_vlabel W
+";
+       print "upsAdvanceOutputPower.label Output power
+upsAdvanceOutputPower.draw LINE1
+upsAdvanceOutputPower.type GAUGE
+";
+
+
+       exit 0;
+}
+
+
+#####
+# Get Values
+###
+
+print "multigraph cyberpower_load\n";
+my $load       = $session->get_single(oid_cps_output_load);
+my $charge     = $session->get_single(oid_cps_battery_charge);
+if($load ne 'U'){
+       print "load.value ", $load, "\n";
+}
+if($charge ne 'U'){
+       print "charge.value ", $charge, "\n";
+}
+
+print "multigraph cyberpower_runtime\n";
+my $runtime    = $session->get_single(oid_cps_battery_runtime);
+if($runtime ne 'U'){
+       print "runtime.value ", $runtime, "\n";
+}
+
+print "multigraph cyberpower_voltage\n";
+my $input      = $session->get_single(oid_cps_input_voltage);
+my $output     = $session->get_single(oid_cps_output_voltage);
+if($input ne 'U'){
+       print "input.value ", $input, "\n";
+}
+if($output ne 'U'){
+       print "output.value ", $output, "\n";
+}
+
+print "multigraph cyberpower_status\n";
+print "upsStatus.value ", $session->get_single(upsStatus), "\n";
+print "upsBaseBatteryStatus.value ", $session->get_single(upsBaseBatteryStatus), "\n";
+print "upsAdvanceInputStatus.value ", $session->get_single(upsAdvanceInputStatus), "\n";
+print "upsBaseOutputStatus.value ", $session->get_single(upsBaseOutputStatus), "\n";
+
+print "multigraph cyberpower_frequency\n";
+print "upsAdvanceInputFrequency.value ", $session->get_single(upsAdvanceInputFrequency)/10, "\n";
+print "upsAdvanceOutputFrequency.value ", $session->get_single(upsAdvanceOutputFrequency)/10, "\n";
+
+print "multigraph cyberpower_power\n";
+print "upsAdvanceOutputPower.value ", $session->get_single(upsAdvanceOutputPower), "\n";
diff --git a/snmp__if_err_ b/snmp__if_err_
new file mode 100755 (executable)
index 0000000..8bf6e41
--- /dev/null
@@ -0,0 +1,171 @@
+#!/usr/bin/perl -w
+# -*- cperl -*-
+
+=head1 NAME
+
+snmp__if_err_ - SNMP wildcard plugin to monitor errors on network interfaces of any networked equipment.
+
+=head1 APPLICABLE SYSTEMS
+
+Any SNMP capable networked computer equipment.  Using a command such
+as "munin-node-configure --snmp switch.langfeldt.net --snmpversion 2c
+--snmpcommunity public | sh -x" should auto-detect all applicable
+interfaces.  On a typical switch you will get one plugin pr. ethernet
+port.  On a router you might get one plugin pr. VLAN interface.
+
+=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 graph shows a stright forward gauge of errors per second.  This
+graph sums all different kinds of interface errors.
+
+=head1 MIB INFORMATION
+
+The pluguin requires the IF-MIB, the standard IETF MIB for network
+interfaces.  It sums these OIDs: IF-MIB::ifInDiscards,
+IF-MIB::ifInErrors, IF-MIB::ifInUnknownProtos, IF-MIB::ifOutDiscards
+and IF-MIB::ifOutErrors.
+
+=head1 MAGIC MARKERS
+
+  #%# family=snmpauto
+  #%# capabilities=snmpconf
+
+=head1 VERSION
+
+  $Id$
+
+=head1 BUGS
+
+None known.
+
+Earlier versions of this plugin only reported the ifInErrors and
+ifOutErrors numbers.  This does not encompas all errors on a interface
+therefore the change.
+
+=head1 AUTHOR
+
+Copyright (C) 2004-2009.  Original authors Jimmy Olsen, Dagfinn Ilmari
+Mannsaaker.  Porting to Munin::Plugin::SNMP, documentation, grooming
+and updates by Nicolai Langfeldt
+
+=head1 LICENSE
+
+GPLv2
+
+=cut
+
+use strict;
+use Munin::Plugin;
+use Munin::Plugin::SNMP;
+
+
+my $response;
+
+if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") {
+       print "index   1.3.6.1.2.1.2.2.1.1.\n";
+       print "require 1.3.6.1.2.1.2.2.1.10. [1-9]\n"; # ifInOctets
+       exit 0;
+}
+
+my ($host) = Munin::Plugin::SNMP->config_session();
+
+my $iface;
+
+if ($Munin::Plugin::me =~ /if_err_(\d+)$/) {
+    $iface = $1;
+} else {
+    die "Could not determine interface number from ".$Munin::Plugin::me."\n";
+}
+
+my $ifEntryDescr     = "1.3.6.1.2.1.2.2.1.2.$iface";
+my $ifEntryAlias     = "1.3.6.1.2.1.31.1.1.1.18.$iface";
+
+my $ifStatus         = "1.3.6.1.2.1.2.2.1.8.$iface";
+
+my $ifInDiscards     = "1.3.6.1.2.1.2.2.1.13.$iface";
+my $ifInErrors       = "1.3.6.1.2.1.2.2.1.14.$iface";
+my $ifInUnknownProtos= "1.3.6.1.2.1.2.2.1.15.$iface";
+my $ifOutDiscards    = "1.3.6.1.2.1.2.2.1.19.$iface";
+my $ifOutErrors      = "1.3.6.1.2.1.2.2.1.20.$iface";
+
+my $session = Munin::Plugin::SNMP->session();
+
+if ($ARGV[0] and $ARGV[0] eq "config") {
+    my ($host) = Munin::Plugin::SNMP->config_session();
+
+    print "host_name $host\n" unless $host eq 'localhost';
+
+    my $alias = $session->get_single($ifEntryAlias) ||
+      $session->get_single($ifEntryDescr) ||
+       "Interface $iface";
+
+    if (! ($alias =~ /\d+/) ) {
+       # If there are no numbers in the $alias add the if index
+       $alias .=" (if $iface)";
+    }
+
+    my $extrainfo = '';
+
+    if (defined ($response = $session->get_single($ifStatus))) {
+       if ($response == 2) {
+           # Interface is down
+           $extrainfo .= ' The interface is currently down.'
+       }
+    }
+
+    # Any error is too many
+    my $warn = 1;
+
+    print "graph_title Interface $alias errors\n";
+    print "graph_order recv send\n";
+    print "graph_args --base 1000 --logarithmic\n";
+    print "graph_vlabel Errors in (-) / out (+) per \${graph_period}\n";
+    print "graph_category network\n";
+    print "graph_info This graph shows all kinds of errors for the \"$alias\" network interface.$extrainfo\n";
+    print "send.info Number of unsuccessfull send/receive operations (errors) on this interface.\n";
+    print "recv.label recv\n";
+    print "recv.type DERIVE\n";
+    print "recv.graph no\n";
+    print "recv.min 0\n";
+    print "recv.warning ", ($warn), "\n" if defined $warn;
+    print "send.label errors\n";
+    print "send.type DERIVE\n";
+    print "send.negative recv\n";
+    print "send.min 0\n";
+    print "send.warning $warn\n" if defined $warn;
+    exit 0;
+}
+
+if (defined ($response = $session->get_single($ifStatus))) {
+    if ($response == 2) {
+       print "recv.value U\n";
+       print "send.value U\n";
+       exit 0;
+    }
+}
+
+my $recv = ($session->get_single($ifInErrors) || 0) +
+  ($session->get_single($ifInDiscards) || 0) +
+  ($session->get_single($ifInUnknownProtos) || 0);
+
+my $send = ($session->get_single($ifOutErrors) || 0) +
+  ($session->get_single($ifOutDiscards) || 0);
+
+print "recv.value ", $recv, "\n";
+print "send.value ", $send, "\n";
diff --git a/snmp__if_pfsense_ppp_ b/snmp__if_pfsense_ppp_
new file mode 100755 (executable)
index 0000000..aa1b013
--- /dev/null
@@ -0,0 +1,275 @@
+#!/usr/bin/perl -w
+# -*- cperl -*-
+
+=head1 NAME
+
+snmp__if_ - SNMP wildcard plugin to monitor network interfaces of any networked equipment.
+
+=head1 APPLICABLE SYSTEMS
+
+Any SNMP capable networked computer equipment.  Using a command such
+as "munin-node-configure --snmp switch.langfeldt.net --snmpversion 2c
+--snmpcommunity public | sh -x" should auto-detect all applicable
+interfaces.  On a typical switch you will get one plugin pr. ethernet
+port.  On a router you might get one plugin pr. VLAN interface.
+
+=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 graph shows a stright forward "bits per second" incomming and
+outgoing thruput.  "Incomming" is towards the monitored device.
+
+Note: The internal representation of the speeds is in bytes
+pr. second.  The plugin multiplies everyting by 8 to get bits
+pr. second.
+
+=head1 MIB INFORMATION
+
+This plugin requires the IF-MIB the standard IETF MIB for network
+interfaces.  It reports the contents of the
+IF-MIB::ifHCInOctets/IF-MIB::ifHCOutOctets if available,
+IF-MIB::ifInOctets/IF-MIB::ifOutOctets if not.  The former are 64 bit
+counters only available with SNMP 2 and later.  The later are 32 bit
+counters (see FEATURES below).
+
+=head1 MAGIC MARKERS
+
+  #%# family=snmpauto
+  #%# capabilities=snmpconf
+
+=head1 VERSION
+
+  $Id$
+
+=head1 BUGS
+
+None known.
+
+=head1 FEATURES
+
+You may get strange results if you use SNMPv1, or SNMPv2 on
+switches that do not support 64 bit byte counters.  If the interface
+traffic exceeds about 50Mbps a 32 bit byte counter will wrap around in
+less than 5 minutes making the graph for the interface show random
+results.
+
+If you have a switch/device that supports 64 bit byte counters this plugin
+will use them and the graph will be fine.  The graph information will
+inform about this.  You must use SNMPv2c or SNMPv3 to be able to use
+64 bit counters - if the device supports them.
+
+This problem is a feature of the device SNMP implementation or your
+usage of it, it is nothing the plugin can fix.  In the future Munin
+may be able to run the plugin more often than the counter wraps
+around.
+
+=head1 AUTHOR
+
+Copyright (C) 2004-2009 Jimmy Olsen, Daginn Ilmari Mannsaaker.
+Documentation, porting to Munin::Plugin::SNMP and further grooming by
+Nicolai Langfeldt.
+
+Initial SNMPv3 support by "Confusedhacker".
+
+=head1 LICENSE
+
+GPLv2
+
+=cut
+
+use strict;
+use Munin::Plugin;
+use Munin::Plugin::SNMP;
+
+
+my $response;
+my $iface;
+
+# This is the snmpwalk:
+# .1.3.6.1.2.1.2.1.0 = INTEGER: 2
+# .1.3.6.1.2.1.2.2.1.1.1 = INTEGER: 1
+# .1.3.6.1.2.1.2.2.1.1.65539 = INTEGER: 65539
+# .1.3.6.1.2.1.2.2.1.2.1 = STRING: MS TCP Loopback interface
+# .1.3.6.1.2.1.2.2.1.2.65539 = STRING: Broadcom NetXtreme Gigabit Ethernet
+# .1.3.6.1.2.1.2.2.1.3.1 = INTEGER: softwareLoopback(24)
+# .1.3.6.1.2.1.2.2.1.3.65539 = INTEGER: ethernetCsmacd(6)
+# .1.3.6.1.2.1.2.2.1.4.1 = INTEGER: 1520
+# .1.3.6.1.2.1.2.2.1.4.65539 = INTEGER: 1500
+# .1.3.6.1.2.1.2.2.1.5.1 = Gauge32: 10000000
+# .1.3.6.1.2.1.2.2.1.5.65539 = Gauge32: 1000000000
+# .1.3.6.1.2.1.2.2.1.6.1 = STRING:
+# .1.3.6.1.2.1.2.2.1.6.65539 = STRING: 0:30:48:75:65:5e
+# .1.3.6.1.2.1.2.2.1.7.1 = INTEGER: up(1)
+# .1.3.6.1.2.1.2.2.1.7.65539 = INTEGER: up(1)
+# .1.3.6.1.2.1.2.2.1.8.1 = INTEGER: up(1)
+# .1.3.6.1.2.1.2.2.1.8.65539 = INTEGER: up(1)
+#
+# 64 bit counters:
+# .1.3.6.1.2.1.31.1.1.1.6.   Counter64 ifHCInOctets
+# .1.3.6.1.2.1.31.1.1.1.10.  Counter64 ifHCOutOctets
+
+if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") {
+       print "index   1.3.6.1.2.1.2.2.1.1.\n";
+       print "require 1.3.6.1.2.1.2.2.1.5. [1-9]\n";  # Speed
+       print "require 1.3.6.1.2.1.2.2.1.10. [1-9]\n"; # ifInOctets
+       exit 0;
+}
+
+if ($Munin::Plugin::me =~ /_if_(\d+)$/) {
+    # Also accept leading 0 like (snmp_router_if_01 .. snmp_router_if_24)
+    $iface = int $1;
+} else {
+    die "Could not determine interface number from ".$Munin::Plugin::me."\n";
+}
+
+my $ifEntryDescr       = "1.3.6.1.2.1.2.2.1.2.$iface";
+my $ifEntryHiSpeed     = "1.3.6.1.2.1.31.1.1.1.15.$iface";
+my $ifEntrySpeed       = "1.3.6.1.2.1.2.2.1.5.$iface";
+my $ifEntryStatus      = "1.3.6.1.2.1.2.2.1.8.$iface";
+my $ifEntryInOctets    = "1.3.6.1.2.1.2.2.1.10.$iface";
+my $ifEntryOutOctets   = "1.3.6.1.2.1.2.2.1.16.$iface";
+
+# Only available with SNMPv2 and up.
+my $ifEntryAlias       = "1.3.6.1.2.1.31.1.1.1.18.$iface";
+my $ifEntryIn64Octets  = "1.3.6.1.2.1.31.1.1.1.6.$iface";
+my $ifEntryOut64Octets = "1.3.6.1.2.1.31.1.1.1.10.$iface";
+
+my ($session, $error);
+
+# SNMP needed for both config and fetch.
+$session = Munin::Plugin::SNMP->session();
+$session->translate([-nosuchinstance => 0]);
+
+if ($ARGV[0] and $ARGV[0] eq "config") {
+    my ($host,undef,$version) = Munin::Plugin::SNMP->config_session();
+
+    print "host_name $host\n" unless $host eq 'localhost';
+
+    my $alias = $session->get_single($ifEntryAlias) ||
+      $session->get_single($ifEntryDescr) ||
+       "Interface $iface";
+
+    if (! ($alias =~ /\d+/) ) {
+       # If there are no numbers in the $alias add the if index
+       $alias .=" (if $iface)";
+    }
+
+    my $extrainfo = '';
+
+    if (defined ($response = $session->get_single($ifEntryStatus))) {
+       if ($response == 2) {
+           # Interface is down
+           $extrainfo .= ' The interface is currently down.'
+       }
+    }
+
+    my $warn = undef;
+    my $speed = undef;
+
+    if (defined ($speed = $session->get_single($ifEntrySpeed)) && $speed != 64000) {
+        # ifSpeed only supports values up to 4.3Gbps, because it's a
+        # 32-bit value.  For faster interfaces, e.g. 10GE or 8Gb/s FC,
+        # use ifHighSpeed instead, which contains the interface speed
+        # in Mbps (e.g. 1000 for GE).
+        #
+        # To detect whether we should use ifHighSpeed, check for an
+        # ifSpeed value of 2^32-1 (4294967295).  For some reason
+        # Brocade Fabric OS returns 2^32-2 instead, so check that as
+        # well.
+
+       if ($speed == 4294967294 || $speed == 4294967295) {
+               $speed = $session->get_single($ifEntryHiSpeed)
+                       * 1000 * 1000;
+       }
+
+       $warn = ($speed/10)*8;
+
+       # Warn at 1/8th of actuall speed?  Or just remove warning?
+       # Tempted to set warning at 80%. 80% over 5 minutes is pretty
+       # busy.
+
+       my $textspeed = scaleNumber($speed,,'bps','',
+                                  'The interface speed is %.1f%s%s.');
+
+       $extrainfo .= " ".$textspeed if $textspeed;
+    }
+
+    if (defined ($session->get_single($ifEntryIn64Octets))
+       && !($session->get_single($ifEntryIn64Octets) eq '')) {
+
+       # If we get an answer at the 64 bit OID then this switch
+       # supports the extended MIB
+
+       $extrainfo .= " This switch supports 64 bit byte counters and these are used by this plugin.";
+    } else {
+       # If not we only have a 32 bit counter and are lost.
+       $extrainfo .= " NOTE! This switch supports only 32 bit byte counters which makes the plugin unreliable and unsuitable for most 100Mb (or faster) interfaces, where bursts are expected to exceed 50Mbps.  This means that for interfaces where much traffic is sent this plugin will report false thruputs and cannot be trusted.";
+
+       # unless perhaps the operator can get us snmp version 2c or 3?
+       $extrainfo .= " I notice that you use SNMP version 1 which does not support 64 bit quantities.  You may get better results if you switch to SNMP version 2c or 3.  Please refer to the plugin documentation."
+         if $version == 1;
+    }
+
+    print "graph_title Interface $alias traffic\n";
+    print "graph_order recv send\n";
+    print "graph_args --base 1000\n";
+    print "graph_vlabel bits in (-) / out (+) per \${graph_period}\n";
+    print "graph_category network\n";
+    print "graph_info This graph shows traffic for the \"$alias\" network interface.$extrainfo\n";
+    print "send.info Bits sent/received by this interface.\n";
+    print "recv.label recv\n";
+    print "recv.type DERIVE\n";
+    print "recv.graph no\n";
+    print "recv.cdef recv,8,*\n";
+    print "recv.max $speed\n" if $speed;
+    print "recv.min 0\n";
+    print "recv.warning ", ($warn), "\n" if defined $warn && ($warn != 0);
+    print "send.label bps\n";
+    print "send.type DERIVE\n";
+    print "send.negative recv\n";
+    print "send.cdef send,8,*\n";
+    print "send.max $speed\n" if $speed;
+    print "send.min 0\n";
+    print "send.warning $warn\n" if defined $warn && ($warn != 0);
+    exit 0;
+}
+
+if (defined ($response = $session->get_single($ifEntryStatus))) {
+    if ($response == 2) {
+       # Interface is down
+       print "recv.value U\n";
+       print "send.value U\n";
+       exit 0;
+    }
+}
+
+if (defined ($response = $session->get_single($ifEntryIn64Octets) ||
+                        $session->get_single($ifEntryInOctets))) {
+    print "recv.value ", $response, "\n";
+} else {
+    # No response...
+    print "recv.value U\n";
+}
+
+if (defined ($response = $session->get_single($ifEntryOut64Octets) ||
+                        $session->get_single($ifEntryOutOctets))) {
+    print "send.value ", $response, "\n";
+} else {
+    # No response...
+    print "send.value U\n";
+}
diff --git a/snmp__swap b/snmp__swap
new file mode 100755 (executable)
index 0000000..f582cbd
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/perl -w
+# -*- perl -*-
+# vim: ft=perl
+#
+# Copyright (C) 2006 Lars Strand
+#
+# Munin plugin to monitor swap usage by use of SNMP.
+# Based on the snmp__df plugin
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# $Log$
+#
+#%# family=snmpauto
+#%# capabilities=snmpconf
+
+use strict;
+use Munin::Plugin::SNMP;
+
+
+my $response;
+
+if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") {
+    # HOST-RESOURCES-MIB::hrStorageType
+    # HOST-RESOURCES-TYPES::hrStorageVirtualMemory
+    print "require 1.3.6.1.2.1.25.2.3.1.2. 1.3.6.1.2.1.25.2.1.3\n";
+    exit 0;
+}
+
+my $session = Munin::Plugin::SNMP->session();
+
+my $hrStorage = "1.3.6.1.2.1.25.2.";
+my $hrStorageVirtualMemory = "1.3.6.1.2.1.25.2.1.3";
+my $hrStorageSize = "1.3.6.1.2.1.25.2.3.1.5.";
+my $hrStorageUsed = "1.3.6.1.2.1.25.2.3.1.6.";
+
+my $swap_d = $session->get_by_regex($hrStorage, $hrStorageVirtualMemory);
+
+my $swapsize = 0;
+my $swapused = 0;
+
+foreach my $swap (keys %$swap_d) {
+    $swapsize += $session->get_single($hrStorageSize . $swap);
+    $swapused += $session->get_single($hrStorageUsed . $swap);
+}
+
+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 Virtual memory usage\n";
+    if ($swapsize > 0) {
+       print "graph_args -l 0 --base 1024 --upper-limit $swapsize\n";
+    } else {
+       print "graph_args -l 0 --base 1024\n";
+    }
+    print "graph_vlabel Bytes\n";
+    print "graph_category system\n";
+    print "graph_info This graph shows swap usage in bytes.\n";
+    print "swap.label swap\n";
+    print "swap.type DERIVE\n";
+    print "swap.min 0\n";
+    exit 0;
+}
+
+print "swap.value $swapused\n";
+