#!/bin/sh
#
# Simple Bourne Shell script that implements Munin protocol and
# some common Linux plugins.
#
# For latest version, see http://muninlite.sf.net/
#
# Copyright (c) 2007-2011 Rune Nordbøe Skillingstad <rune@skillingstad.no>
#
# Licensed under GPLv2 (see LICENSE file for full License)
#

VERSION="2.1.1"

set -eu

NTP_PEER="pool.ntp.org"

# Name of runtime configuration file
CONFIG_FILE=/etc/munin/muninlite.conf

# if plugindir_ is present in $PLUGINS, executables (scripts, binaries) in the specified path
# and matching the pattern will be scanned and operated as plugins
PLUGIN_DIRECTORY=/etc/munin/plugins
PLUGINPATTERN="*"

# Remove unwanted plugins from this list
PLUGINS="df cpu if_ if_err_ load memory processes swap netstat uptime interrupts irqstats ntpdate wireless plugindir_"
# ===== LIB FUNCTIONS =====
clean_fieldname() {
  echo "$@" | sed -e 's/^[^A-Za-z_]/_/' -e 's/[^A-Za-z0-9_]/_/g'
}

# ===== PLUGINS CODE =====
config_df() {
  echo "graph_title Filesystem usage (in %)
graph_args --upper-limit 100 -l 0
graph_vlabel %
graph_category disk
graph_info This graph shows disk usage on the machine."
  for PART in $(df -PT | grep '^/' | grep -vwE "$DF_IGNORE_FILESYSTEM_REGEX" | sed '/\/[a-z0-9]*$/!d;s/.* \([a-z0-9\/]\{1,\}\)$/\1/g')
  do
    PINFO=$(df -P "$PART" | tail -1);
    PNAME=$(clean_fieldname "$(echo "$PINFO" | cut -d " " -f 1)")
    echo "$PNAME.label $PART"
    echo "$PNAME.info $PNAME -> $PART"
    echo "$PNAME.warning 92"
    echo "$PNAME.critical 98"
  done
}
fetch_df() {
  for PART in $(df -PT | grep '^/' | grep -vwE "$DF_IGNORE_FILESYSTEM_REGEX" | sed '/\/[a-z0-9]*$/!d;s/.* \([a-z0-9\/]\{1,\}\)$/\1/g')
  do
    PINFO=$(df -P "$PART" | tail -1);
    PNAME=$(clean_fieldname "$(echo "$PINFO" | cut -d " " -f 1)")
    echo "$PNAME.value" "$(echo "$PINFO" | sed -e 's/\%//g' -e 's/  */ /g' | cut -d " " -f 5)"
  done
}
config_cpu() {
  extinfo=""
  if grep -q '^cpu \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\}' /proc/stat; then
    extinfo="iowait irq softirq"
  fi
  NCPU=$(grep '^cpu[0-9]\+ ' /proc/stat | wc -l)
  PERCENT=$((NCPU * 100))
  graphlimit=$PERCENT
  SYSWARNING=$((PERCENT * 30 / 100))
  SYSCRITICAL=$((PERCENT * 50 / 100))
  USRWARNING=$((PERCENT * 80 / 100))
  echo "graph_title CPU usage"
  echo "graph_order system user nice idle $extinfo" | sed 's/ $//'
  echo "graph_args --base 1000 -r --lower-limit 0 --upper-limit $graphlimit"
  echo "graph_vlabel %"
  echo "graph_scale no"
  echo "graph_info This graph shows how CPU time is spent."
  echo "graph_category system"
  echo "graph_period second"
  echo "system.label system"
  echo "system.draw AREA"
  echo "system.max 5000"
  echo "system.min 0"
  echo "system.type DERIVE"
  echo "system.warning $SYSWARNING"
  echo "system.critical $SYSCRITICAL"
  echo "system.info CPU time spent by the kernel in system activities"
  echo "user.label user"
  echo "user.draw STACK"
  echo "user.min 0"
  echo "user.max 5000"
  echo "user.warning $USRWARNING"
  echo "user.type DERIVE"
  echo "user.info CPU time spent by normal programs and daemons"
  echo "nice.label nice"
  echo "nice.draw STACK"
  echo "nice.min 0"
  echo "nice.max 5000"
  echo "nice.type DERIVE"
  echo "nice.info CPU time spent by nice(1)d programs"
  echo "idle.label idle"
  echo "idle.draw STACK"
  echo "idle.min 0"
  echo "idle.max 5000"
  echo "idle.type DERIVE"
  echo "idle.info Idle CPU time"
  if [ -n "$extinfo" ]; then
    echo "iowait.label iowait"
    echo "iowait.draw STACK"
    echo "iowait.min 0"
    echo "iowait.max 5000"
    echo "iowait.type DERIVE"
    echo "iowait.info CPU time spent waiting for I/O operations to finish"
    echo "irq.label irq"
    echo "irq.draw STACK"
    echo "irq.min 0"
    echo "irq.max 5000"
    echo "irq.type DERIVE"
    echo "irq.info CPU time spent handling interrupts"
    echo "softirq.label softirq"
    echo "softirq.draw STACK"
    echo "softirq.min 0"
    echo "softirq.max 5000"
    echo "softirq.type DERIVE"
    echo "softirq.info CPU time spent handling 'batched' interrupts"
  fi
}
fetch_cpu() {
  extinfo=""
  if grep -q '^cpu \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\} \{1,\}[0-9]\{1,\}' /proc/stat; then
    extinfo="iowait irq softirq"
  fi
  CINFO=$(grep '^cpu ' /proc/stat | cut -c6-)
  echo "user.value" "$(echo "$CINFO" | cut -d " " -f 1)"
  echo "nice.value" "$(echo "$CINFO" | cut -d " " -f 2)"
  echo "system.value" "$(echo "$CINFO" | cut -d " " -f 3)"
  echo "idle.value" "$(echo "$CINFO" | cut -d " " -f 4)"
  if [ -n "$extinfo" ]; then
    echo "iowait.value" "$(echo "$CINFO" | cut -d " " -f 5)"
    echo "irq.value" "$(echo "$CINFO" | cut -d " " -f 6)"
    echo "softirq.value" "$(echo "$CINFO" | cut -d " " -f 7)"
  fi
}
config_if() {
  INTERFACE=$1
  echo "graph_order down up"
  echo "graph_title Interface $INTERFACE traffic"
  echo "graph_args --base 1000"
  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: Since the data source for this plugin use 32bit counters, this plugin is really unreliable and unsuitable for most 100Mb (or faster) interfaces, where bursts are expected to exceed 50Mbps. This means that this plugin is usuitable for most production environments. To avoid this problem, use the ip_ plugin instead."
  echo "down.label received"
  echo "down.type DERIVE"
  echo "down.min 0"
  echo "down.graph no"
  echo "down.cdef down,8,*"
  echo "up.label bps"
  echo "up.type DERIVE"
  echo "up.min 0"
  echo "up.negative down"
  echo "up.cdef up,8,*"
  if [ -n "$(which ethtool)" ] && [ -x "$(which ethtool)" ]; then
    MAX_MBPS=$(ethtool "$INTERFACE" 2>/dev/null | grep "Speed: [0-9]\+Mb/s$" | sed 's/[^0-9]//g')
    if [ -n "$MAX_MBPS" ]; then
      echo "up.max $((MAX_MBPS * 1024 * 1024))"
      echo "down.max $((MAX_MBPS * 1024 * 1024))"
    fi
  fi
}
fetch_if() {
  INTERFACE=$1
  IINFO=$(grep "^ *$INTERFACE:" /proc/net/dev | cut -d ":" -f 2 | sed -e 's/  */ /g' -e 's/^[ \t]*//')
  echo "down.value" "$(echo "$IINFO" | cut -d " " -f 1)"
  echo "up.value" "$(echo "$IINFO" | cut -d " " -f 9)"
}
config_if_err() {
  INTERFACE=$1
  echo "graph_order rcvd trans"
  echo "graph_title Interface $INTERFACE errors"
  echo "graph_args --base 1000"
  echo "graph_vlabel packets in (-) / out (+) per \${graph_period}"
  echo "graph_category network"
  echo "graph_info This graph shows the amount of errors on the $INTERFACE network interface."
  echo "rcvd.label packets"
  echo "rcvd.type COUNTER"
  echo "rcvd.graph no"
  echo "rcvd.warning 1"
  echo "trans.label packets"
  echo "trans.type COUNTER"
  echo "trans.negative rcvd"
  echo "trans.warning 1"
}
fetch_if_err() {
  INTERFACE=$1
  IINFO=$(grep "^ *$INTERFACE:" /proc/net/dev | cut -d ":" -f 2 | sed -e 's/  */ /g' -e 's/^[ \t]*//')
  echo "rcvd.value" "$(echo "$IINFO" | cut -d " " -f 3)"
  echo "trans.value" "$(echo "$IINFO" | cut -d " " -f 11)"
}
config_load() {
  echo "graph_title Load average
graph_args --base 1000 -l 0
graph_vlabel load
graph_scale no
graph_category system
load.label load
load.warning 10
load.critical 120
graph_info The load average of the machine describes how many processes are in the run-queue (scheduled to run \"immediately\").
load.info Average load for the five minutes."
}
fetch_load() {
  echo "load.value" "$(cut -d " " -f 2 /proc/loadavg)"
}
config_memory() {
  MINFO=$(sed 's/ \{1,\}/ /g;' /proc/meminfo)
  MEMTOTAL=$(echo "$MINFO" | grep "^MemTotal:" | cut -d\  -f2)
  PAGETABLES=$(echo "$MINFO" | grep "^PageTables:" | cut -d\  -f2)
  SWAPCACHED=$(echo "$MINFO" | grep "^SwapCached:" | cut -d\  -f2)
  SWAPTOTAL=$(echo "$MINFO" | grep "^SwapTotal:" | cut -d\  -f2)
  VMALLOCUSED=$(echo "$MINFO" | grep "^VmallocUsed:" | cut -d\  -f2)
  SLAB=$(echo "$MINFO" | grep "^Slab:" | cut -d\  -f2)
  MAPPED=$(echo "$MINFO" | grep "^Mapped:" | cut -d\  -f2)
  COMMITTEDAS=$(echo "$MINFO" | grep "^Committed_AS:" | cut -d\  -f2)
  ACTIVE=$(echo "$MINFO" | grep "^Active:" | cut -d\  -f2)
  INACTIVE=$(echo "$MINFO" | grep "^Inactive:" | cut -d\  -f2)
  ACTIVEANON=$(echo "$MINFO" | grep "^ActiveAnon:" | cut -d\  -f2)
  ACTIVECACHE=$(echo "$MINFO" | grep "^ActiveCache:" | cut -d\  -f2)
  INACTIVE=$(echo "$MINFO" | grep "^Inactive:" | cut -d\  -f2)
  INACTDIRTY=$(echo "$MINFO" | grep "^Inact_dirty:" | cut -d\  -f2)
  INACTLAUNDRY=$(echo "$MINFO" | grep "^Inact_laundry:" | cut -d\  -f2)
  INACTCLEAN=$(echo "$MINFO" | grep "^Inact_clean:" | cut -d\  -f2)

  GRAPH_ORDER="apps";
  [ -n "$PAGETABLES" ] && GRAPH_ORDER="$GRAPH_ORDER page_tables"
  [ -n "$SWAPCACHED" ] && GRAPH_ORDER="$GRAPH_ORDER swap_cache"
  [ -n "$VMALLOCUSED" ] && GRAPH_ORDER="$GRAPH_ORDER vmalloc_used"
  [ -n "$SLAB" ] && GRAPH_ORDER="$GRAPH_ORDER slab"
  GRAPH_ORDER="$GRAPH_ORDER cached buffers free swap"

  echo "graph_args --base 1024 -l 0 --vertical-label Bytes --upper-limit $MEMTOTAL"
  echo "graph_title Memory usage"
  echo "graph_category system"
  echo "graph_info This graph shows what the machine uses its memory for."
  echo "graph_order $GRAPH_ORDER"
  echo "apps.label apps"
  echo "apps.draw AREA"
  echo "apps.info Memory used by user-space applications."
  echo "buffers.label buffers"
  echo "buffers.draw STACK"
  echo "buffers.info Block device (e.g. harddisk) cache. Also where \"dirty\" blocks are stored until written."
  echo "swap.label swap"
  echo "swap.draw STACK"
  echo "swap.info Swap space used."
  echo "cached.label cache"
  echo "cached.draw STACK"
  echo "cached.info Parked file data (file content) cache."
  echo "free.label unused"
  echo "free.draw STACK"
  echo "free.info Wasted memory. Memory that is not used for anything at all."
  if [ -n "$SLAB" ]; then
    echo "slab.label slab_cache"
    echo "slab.draw STACK"
    echo "slab.info Memory used by the kernel (major users are caches like inode, dentry, etc)."
  fi
  if [ -n "$SWAPCACHED" ]; then
    echo "swap_cache.label swap_cache"
    echo "swap_cache.draw STACK"
    echo "swap_cache.info A piece of memory that keeps track of pages that have been fetched from swap but not yet been modified."
  fi
  if [ -n "$PAGETABLES" ]; then
    echo "page_tables.label page_tables"
    echo "page_tables.draw STACK"
    echo "page_tables.info Memory used to map between virtual and physical memory addresses.\n"
  fi
  if [ -n "$VMALLOCUSED" ]; then
    echo "vmalloc_used.label vmalloc_used"
    echo "vmalloc_used.draw STACK"
    echo "vmalloc_used.info Virtual memory used by the kernel (used when the memory does not have to be physically contiguous)."
  fi
  if [ -n "$COMMITTEDAS" ]; then
    echo "committed.label committed"
    echo "committed.draw LINE2"
    echo "committed.warn" $((SWAPTOTAL + MEMTOTAL))
    echo "committed.info The amount of memory that would be used if all the memory that's been allocated were to be used."
  fi
  if [ -n "$MAPPED" ]; then
    echo "mapped.label mapped"
    echo "mapped.draw LINE2"
    echo "mapped.info All mmap()ed pages."
  fi
  if [ -n "$ACTIVE" ]; then
    echo "active.label active"
    echo "active.draw LINE2"
    echo "active.info Memory recently used. Not reclaimed unless absolutely necessary."
  fi
  if [ -n "$ACTIVEANON" ]; then
    echo "active_anon.label active_anon"
    echo "active_anon.draw LINE1"
  fi
  if [ -n "$ACTIVECACHE" ]; then
    echo "active_cache.label active_cache"
    echo "active_cache.draw LINE1"
  fi
  if [ -n "$INACTIVE" ]; then
    echo "inactive.label inactive"
    echo "inactive.draw LINE2"
    echo "inactive.info Memory not currently used."
  fi
  if [ -n "$INACTDIRTY" ]; then
    echo "inact_dirty.label inactive_dirty"
    echo "inact_dirty.draw LINE1"
    echo "inact_dirty.info Memory not currently used, but in need of being written to disk."
  fi
  if [ -n "$INACTLAUNDRY" ]; then
    echo "inact_laundry.label inactive_laundry"
    echo "inact_laundry.draw LINE1"
  fi
  if [ -n "$INACTCLEAN" ]; then
    echo "inact_clean.label inactive_clean"
    echo "inact_clean.draw LINE1"
    echo "inact_clean.info Memory not currently used."
  fi
}
fetch_memory() {
  MINFO=$(sed 's/ \{1,\}/ /g;' /proc/meminfo)
  MEMTOTAL=$(echo "$MINFO" | grep "^MemTotal:" | cut -d\  -f2)
  MEMFREE=$(echo "$MINFO" | grep "^MemFree:" | cut -d\  -f2)
  BUFFERS=$(echo "$MINFO" | grep "^Buffers:" | cut -d\  -f2)
  CACHED=$(echo "$MINFO" | grep "^Cached:" | cut -d\  -f2)
  SWAP_TOTAL=$(echo "$MINFO" | grep "^SwapTotal:" | cut -d\  -f2)
  SWAP_FREE=$(echo "$MINFO" | grep "^SwapFree:" | cut -d\  -f2)
  MEMTOTAL=$(echo "$MINFO" | grep "^MemTotal:" | cut -d\  -f2)
  PAGETABLES=$(echo "$MINFO" | grep "^PageTables:" | cut -d\  -f2)
  SWAPCACHED=$(echo "$MINFO" | grep "^SwapCached:" | cut -d\  -f2)
  VMALLOCUSED=$(echo "$MINFO" | grep "^VmallocUsed:" | cut -d\  -f2)
  SLAB=$(echo "$MINFO" | grep "^Slab:" | cut -d\  -f2)
  MAPPED=$(echo "$MINFO" | grep "^Mapped:" | cut -d\  -f2)
  COMMITTEDAS=$(echo "$MINFO" | grep "^Committed_AS:" | cut -d\  -f2)
  ACTIVE=$(echo "$MINFO" | grep "^Active:" | cut -d\  -f2)
  INACTIVE=$(echo "$MINFO" | grep "^Inactive:" | cut -d\  -f2)
  ACTIVEANON=$(echo "$MINFO" | grep "^ActiveAnon:" | cut -d\  -f2)
  ACTIVECACHE=$(echo "$MINFO" | grep "^ActiveCache:" | cut -d\  -f2)
  INACTIVE=$(echo "$MINFO" | grep "^Inactive:" | cut -d\  -f2)
  INACTDIRTY=$(echo "$MINFO" | grep "^Inact_dirty:" | cut -d\  -f2)
  INACTLAUNDRY=$(echo "$MINFO" | grep "^Inact_laundry:" | cut -d\  -f2)
  INACTCLEAN=$(echo "$MINFO" | grep "^Inact_clean:" | cut -d\  -f2)
  APPS=$((MEMTOTAL - MEMFREE - BUFFERS - CACHED))
  SWAP=$((SWAP_TOTAL - SWAP_FREE))
  echo "buffers.value" $((BUFFERS * 1024))
  echo "swap.value" $((SWAP * 1024))
  echo "cached.value" $((CACHED * 1024))
  echo "free.value" $((MEMFREE * 1024))
  if [ -n "$SLAB" ]; then
    echo "slab.value" $((SLAB * 1024))
    APPS=$((APPS - SLAB))
  fi
  if [ -n "$SWAPCACHED" ]; then
    echo "swap_cache.value" $((SWAPCACHED * 1024))
    APPS=$((APPS - SWAPCACHED))
  fi
  if [ -n "$PAGETABLES" ]; then
    echo "page_tables.value" $((PAGETABLES * 1024))
    APPS=$((APPS - PAGETABLES))
  fi
  if [ -n "$VMALLOCUSED" ]; then
    echo "vmalloc_used.value" $((VMALLOCUSED * 1024))
    APPS=$((APPS - VMALLOCUSED))
  fi
  if [ -n "$COMMITTEDAS" ]; then
    echo "committed.value" $((COMMITTEDAS * 1024))
  fi
  if [ -n "$MAPPED" ]; then
    echo "mapped.value" $((MAPPED * 1024))
  fi
  if [ -n "$ACTIVE" ]; then
    echo "active.value" $((ACTIVE * 1024))
  fi
  if [ -n "$ACTIVEANON" ]; then
    echo "active_anon.value" $((ACTIVEANON * 1024))
  fi
  if [ -n "$ACTIVECACHE" ]; then
    echo "active_cache.value" $((ACTIVECACHE * 1024))
  fi
  if [ -n "$INACTIVE" ]; then
    echo "inactive.value" $((INACTIVE * 1024))
  fi
  if [ -n "$INACTDIRTY" ]; then
    echo "inact_dirty.value" $((INACTDIRTY * 1024))
  fi
  if [ -n "$INACTLAUNDRY" ]; then
    echo "inact_laundry.value" $((INACTLAUNDRY * 1024))
  fi
  if [ -n "$INACTCLEAN" ]; then
    echo "inact_clean.value" $((INACTCLEAN * 1024))
  fi

  echo "apps.value" $((APPS * 1024))
}
config_processes() {
  echo "graph_title Number of Processes"
  echo "graph_args --base 1000 -l 0 "
  echo "graph_vlabel number of processes"
  echo "graph_category processes"
  echo "graph_info This graph shows the number of processes in the system."
  echo "processes.label processes"
  echo "processes.info The current number of processes."
}
fetch_processes() {
  echo "processes.value" "$(echo /proc/[0-9]* | wc -w)"
}
config_swap() {
  echo "graph_title Swap in/out"
  echo "graph_args -l 0 --base 1000"
  echo "graph_vlabel pages per \${graph_period} in (-) / out (+)"
  echo "graph_category system"
  echo "swap_in.label swap"
  echo "swap_in.type DERIVE"
  echo "swap_in.max 100000"
  echo "swap_in.min 0"
  echo "swap_in.graph no"
  echo "swap_out.label swap"
  echo "swap_out.type DERIVE"
  echo "swap_out.max 100000"
  echo "swap_out.min 0"
  echo "swap_out.negative swap_in"
}
fetch_swap() {
  if [ -f /proc/vmstat ]; then
    SINFO=$(cat /proc/vmstat)
    echo "swap_in.value" "$(echo "$SINFO" | awk 'BEGIN { result="U"; } { if ($1 == "pswpin") result=$2; } END { print(result); }')"
    echo "swap_out.value" "$(echo "$SINFO" | awk 'BEGIN { result="U"; } { if ($1 == "pswpout") result=$2; } END { print(result); }')"
  else
    SINFO=$(grep "^swap" /proc/stat)
    echo "swap_in.value" "$(echo "$SINFO" | cut -d " " -f 2)"
    echo "swap_out.value" "$(echo "$SINFO" | cut -d " " -f 3)"
  fi
}
config_netstat() {
  echo "graph_title Netstat"
  echo "graph_args -l 0 --base 1000"
  echo "graph_vlabel active connections"
  echo "graph_category network"
  echo "graph_period second"
  echo "graph_info This graph shows the TCP activity of all the network interfaces combined."
  echo "active.label active"
  echo "active.type DERIVE"
  echo "active.max 50000"
  echo "active.min 0"
  echo "active.info The number of active TCP openings per second."
  echo "passive.label passive"
  echo "passive.type DERIVE"
  echo "passive.max 50000"
  echo "passive.min 0"
  echo "passive.info The number of passive TCP openings per second."
  echo "failed.label failed"
  echo "failed.type DERIVE"
  echo "failed.max 50000"
  echo "failed.min 0"
  echo "failed.info The number of failed TCP connection attempts per second."
  echo "resets.label resets"
  echo "resets.type DERIVE"
  echo "resets.max 50000"
  echo "resets.min 0"
  echo "resets.info The number of TCP connection resets."
  echo "established.label established"
  echo "established.type GAUGE"
  echo "established.max 50000"
  echo "established.info The number of currently open connections."
}
fetch_netstat() {
  NINFO=$(netstat -s | sed 's/ \{1,\}/ /g')
  echo "active.value" "$(echo "$NINFO" | grep "active connection" | cut -d " " -f 2)"
  echo "passive.value" "$(echo "$NINFO" | grep "passive connection" | cut -d " " -f 2)"
  echo "failed.value" "$(echo "$NINFO" | grep "failed connection" | cut -d " " -f 2)"
  echo "resets.value" "$(echo "$NINFO" | grep "connection resets" | cut -d " " -f 2)"
  echo "established.value" "$(echo "$NINFO" | grep "connections established" | cut -d " " -f 2)"
}
config_uptime() {
  echo "graph_title Uptime"
  echo "graph_args --base 1000 -l 0 "
  echo "graph_vlabel uptime in days"
  echo "graph_category system"
  echo "uptime.label uptime"
  echo "uptime.draw AREA"
}
fetch_uptime() {
  awk '{printf "uptime.value %.2f",$1/86400; print ""}' /proc/uptime
}
config_interrupts() {
  echo "graph_title Interrupts & context switches"
  echo "graph_args --base 1000 -l 0"
  echo "graph_vlabel interrupts & ctx switches / \${graph_period}"
  echo "graph_category system"
  echo "graph_info This graph shows the number of interrupts and context switches on the system. These are typically high on a busy system."
  echo "intr.info Interrupts are events that alter sequence of instructions executed by a processor. They can come from either hardware (exceptions, NMI, IRQ) or software."
  echo "ctx.info A context switch occurs when a multitasking operatings system suspends the currently running process, and starts executing another."
  echo "intr.label interrupts"
  echo "ctx.label context switches"
  echo "intr.type DERIVE"
  echo "ctx.type DERIVE"
  echo "intr.max 100000"
  echo "ctx.max 100000"
  echo "intr.min 0"
  echo "ctx.min 0"
}
fetch_interrupts() {
  IINFO=$(cat /proc/stat)
  echo "ctx.value" "$(echo "$IINFO" | grep "^ctxt" | cut -d " " -f 2)"
  echo "intr.value" "$(echo "$IINFO" | grep "^intr" | cut -d " " -f 2)"
}
config_irqstats() {
  echo "graph_title Individual interrupts
graph_args --base 1000 -l 0
graph_vlabel interrupts / \${graph_period}
graph_category system"
  CPUS=$(grep 'CPU[0-9]' /proc/interrupts | wc -w)
  IINFO=$(sed -e 's/ \{1,\}/ /g' -e 's/^ //' /proc/interrupts  | grep '.:')
  for ID in $(echo "$IINFO" | cut -d: -f1)
  do
    IDL=$(echo "$IINFO" | grep "^$ID:")
    INFO=$(eval "echo \"$IDL\" | cut -d ' '  -f '$((3 + CPUS))-'")
    if [ -z "$INFO" ]; then
      echo "i$ID.label $ID"
    else
      echo "i$ID.label $INFO"
      echo "i$ID.info Interrupt $ID, for device(s): $INFO"
    fi
    echo "i$ID.type DERIVE"
    echo "i$ID.min 0"
  done
}
fetch_irqstats() {
  CPUS=$(grep 'CPU[0-9]' /proc/interrupts | wc -w)
  IINFO=$(sed -e 's/ \{1,\}/ /g' -e 's/^ //' /proc/interrupts  | grep '.:')
  for ID in $(echo "$IINFO" | cut -d: -f1)
  do
    IDL=$(echo "$IINFO" | grep "^$ID:")
    VALS=$(eval "echo \"$IDL\" | cut -d ' ' -f '2-$((1 + CPUS))'")
    VALUE=0
    for VAL in $VALS;
    do
      if [ "$VAL" -eq "$VAL" ] 2> /dev/null; then
        VALUE=$((VALUE + VAL))
      fi
    done
    echo "i$ID.value $VALUE"
  done
}
config_ntpdate() {
  echo "graph_title NTP offset and dealy to peer $NTP_PEER"
  echo "graph_args --base 1000 --vertical-label msec"
  echo "graph_category time"
  echo "offset.label Offset"
  echo "delay.label Delay"
}

fetch_ntpdate() {
  NTPDATE="/usr/sbin/ntpdate"
  OFFSET=0
  DELAY=0
  if [ -n "$NTP_PEER" ] && [ -x "$NTPDATE" ]; then
    DATA=$("$NTPDATE" -q "$NTP_PEER" | awk '/^server.*offset/{gsub(/,/,""); printf "%s %s", ($6*1000), ($8*1000);}')
    OFFSET=$(echo "$DATA" | cut -d " " -f 1)
    DELAY=$(echo "$DATA" | cut -d " " -f 2)
  fi
  echo "offset.value $OFFSET"
  echo "delay.value $DELAY"
}
config_wireless() {
  interfaces=$(iwinfo 2> /dev/null | sed '/^[a-zA-Z]/!d; s/ .*//')
  radios=$(echo "${interfaces}" | sed '/-/d')
  for radio in ${radios}
  do
    echo "multigraph wireless_${radio}
graph_title WLAN ${radio} AP Statistics
graph_vlabel Strength (dBm)
graph_category network
graph_scale no
signal.label Signal
noise.label Noise"
  done
  for interface in ${interfaces}
  do
    echo "multigraph wireless_assoc_$(clean_fieldname "${interface}")
graph_title WLAN ${interface} associations
graph_vlabel Clients
graph_args --lower-limit 0
graph_category network
graph_scale no
clients.label Clients"
  done
}
fetch_wireless() {
  interfaces=$(iwinfo 2> /dev/null | sed '/^[a-zA-Z]/!d; s/ .*//')
  radios=$(echo "${interfaces}" | sed '/-/d')
  for radio in ${radios}
  do
    echo "multigraph wireless_${radio}"
    iwinfo "${radio}" info | sed -r 's/unknown/0 dBm/g; /Signal.*Noise/!d; s/^.* Signal: ([-0-9]+) dBm  Noise: ([-0-9]+) dBm/signal.value \1\nnoise.value \2/'
  done
  for interface in ${interfaces}
  do
    echo "multigraph wireless_assoc_$(clean_fieldname "${interface}")"
    echo "clients.value $(iwinfo "${interface}" assoc | grep -c SNR)"
  done
}

# ===== NODE CODE =====
do_list() {
  echo "$PLUGINS"
}


do_nodes() {
  echo "$HOSTNAME"
  echo "."
}

do_config() {
  if echo "$PLUGINS" | grep -qwF "$1"; then
    "config_$1"
  else
    echo "# Unknown service"
  fi
  echo "."
}

do_fetch() {
  if echo "$PLUGINS" | grep -qwF "$1"; then
    "fetch_$1"
  else
    echo "# Unknown service"
  fi
  echo "."
}

do_version() {
  echo "munins node on $HOSTNAME version: $VERSION (muninlite)"
}

do_quit() {
  exit 0
}

# ===== Runtime config =====
# shellcheck source=/dev/null
[ -f ${CONFIG_FILE} ] && . ${CONFIG_FILE}
RES=""
for PLUG in $PLUGINS; do
  case "$PLUG" in
    if_|if_err_)
      interface_names=$(sed 's/^ *//; s/:.*$//; / /d; /^lo$/d' /proc/net/dev)
      for INTER in $interface_names; do
        INTERRES=$(echo "$INTER" | sed -e 's/\./VLAN/' -e 's/\-/_/g')
        RES="$RES ${PLUG}${INTERRES}"
        eval "fetch_${PLUG}${INTERRES}() { 'fetch_${PLUG%_}' '$INTER'; }"
        eval "config_${PLUG}${INTERRES}() { 'config_${PLUG%_}' '$INTER'; }"
      done
      ;;
    netstat)
      if netstat -s >/dev/null 2>&1; then
        RES="$RES netstat"
      fi
      ;;
    wireless)
      if iwinfo >/dev/null 2>&1; then
          RES="${RES} ${PLUG}"
      fi
      ;;
    plugindir_)
      for MYPLUGIN in $(if [ -d "$PLUGIN_DIRECTORY" ]; then find -L "$PLUGIN_DIRECTORY" -type f -name "$PLUGINPATTERN"; fi); do
        if [ -f "$MYPLUGIN" ] && [ -x "$MYPLUGIN" ]; then
          # generate a name suitable for shell function names
          MYPLUGINNAME=$(basename "$MYPLUGIN" | sed 's/[^0-9a-zA-Z_]/_/g')
          # detect and avoid name collision
          if echo "$RES" | grep -qwF "$MYPLUGINNAME"; then
            MYPLUGINNAME="plugindir_$MYPLUGINNAME"
          fi
          RES="$RES $MYPLUGINNAME"
          eval "fetch_${MYPLUGINNAME}() { '$MYPLUGIN'; }"
          eval "config_${MYPLUGINNAME}() { '$MYPLUGIN' config; }"
        fi
      done
      ;;
    *)
      RES="$RES $PLUG"
      ;;
  esac
done
# sort plugin names and remove surrounding whitespace
PLUGINS=$(echo "$RES" | xargs -r -n 1 echo | sort | xargs echo)

# ===== MAIN LOOP =====
FUNCTIONS="list nodes config fetch version quit"
HOSTNAME=$( { hostname -f || hostname || cat /proc/sys/kernel/hostname || echo "unknown"; } 2>/dev/null )
echo "# munin node at $HOSTNAME"
while read -r arg0 arg1
do
  arg0=$(echo "$arg0" | xargs)
  arg1=$(echo "$arg1" | xargs)
  if ! echo "$FUNCTIONS" | grep -qwF "$arg0"; then
    echo "# Unknown command. Try $(echo "$FUNCTIONS" | sed -e 's/\( [[:alpha:]]\{1,\}\)/,\1/g' -e 's/,\( [[:alpha:]]\{1,\}\)$/ or\1/')"
  elif [ -n "$arg0" ]; then
    "do_$arg0" "$arg1"
  fi
done
