diff --git a/README.md b/README.md index e9b3d4cc..db71671e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# This is the repository for all user contributed stuff +This is the repository for all user contributed stuff -## contrib/plugins/ - 3rd-party plugins +# contrib/plugins/ - 3rd-party plugins **This is usually where you want to begin your journey.** @@ -9,7 +9,7 @@ That web site is for the time being disabled, new updates are done here. If a dedicated website comes back alive, its plugin backend will be this git repo. -## contrib/templates/ - 3rd-party templates +# contrib/templates/ - 3rd-party templates Feel free to update templates here, or even to create new ones. @@ -21,7 +21,7 @@ It should serves as a base for small editions that can be resynced in SVN trunk, * don't copy the whole template * directly edit files in this directory -## contrib/tools/ - 3rd-party tools +# contrib/tools/ - 3rd-party tools Here, you can put just any kind of tool. Please use this directory instead of a random place on the internet. It makes things way more easy to search for others. diff --git a/images/README.md b/images/README.md new file mode 100644 index 00000000..fedf12ba --- /dev/null +++ b/images/README.md @@ -0,0 +1,2 @@ +Please **don't** put screenshots of your plugins here. +Put them right next to your plugins. diff --git a/plugins/cyrus/cyrus-imapd b/plugins/cyrus/cyrus-imapd index 5345caa0..d765af6e 100755 --- a/plugins/cyrus/cyrus-imapd +++ b/plugins/cyrus/cyrus-imapd @@ -1,8 +1,6 @@ #!/bin/sh # -# $Id: cyrus-imapd 18 2011-07-15 09:14:04Z ixs $ -# -# Copyright (C) 2009-2011 Andreas Thienemann +# Copyright (C) 2009 - 2012 Andreas Thienemann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Library General Public License as published by @@ -51,7 +49,7 @@ It displays the following three datapoints: =head1 VERSION - $Revision: 18 $ + 0.0.20120307 =head1 BUGS @@ -71,7 +69,7 @@ GPLv2 CONFIGDIR=$(awk -F : '/^configdirectory:/ { gsub(/ /, "", $2); print $2 }' /etc/imapd.conf 2> /dev/null) PROCDIR="${CONFIGDIR}/proc" -if [ "$1" = "autoconf" ]; then +if [ "$1" == "autoconf" ]; then if [ "x${CONFIGDIR}x" != "xx" ] && [ -d ${PROCDIR} ]; then echo yes else @@ -81,14 +79,14 @@ if [ "$1" = "autoconf" ]; then fi # Check if we actually got some sensible data -if [ "x${CONFIGDIR}x" = "xx" ]; then +if [ "x${CONFIGDIR}x" == "xx" ]; then exit 1 fi # If run with the "config"-parameter, give out information on how the # graphs should look. -if [ "$1" = "config" ]; then +if [ "$1" == "config" ]; then echo 'graph_title Cyrus IMAPd Load' echo 'graph_args --base 1000 -l 0' echo 'graph_vlabel connections' diff --git a/plugins/disk/file_age b/plugins/disk/file_age new file mode 100755 index 00000000..29636d14 --- /dev/null +++ b/plugins/disk/file_age @@ -0,0 +1,133 @@ +#!/bin/bash + +. $MUNIN_LIBDIR/plugins/plugin.sh + +case $1 in + config) + GRAPH_ORDER="" + COUNTER=1 + while [ $COUNTER -gt 0 ]; do + FILE_PATH="file${COUNTER}_path" + + # Is the path for this file specified? + eval FILE=\$$FILE_PATH + if [ "$FILE" == "" ]; then + break; + fi + + # It is! Add it to the graphs. + GRAPH_ORDER="$GRAPH_ORDER file_$COUNTER" + + # Does this file have a specified label? + LABEL_COUNTER="file${COUNTER}_label" + eval LABEL=\$$LABEL_COUNTER + if [ "$LABEL" == "" ]; then + LABEL=`basename $FILE` + fi + + # Associated warning level? + WARNING="file${COUNTER}_warning" + eval WARNING=\$$WARNING + if [ "$WARNING" != "" ]; then + echo "file_$COUNTER.warning $WARNING" + fi + + # Associated critical level? + CRITICAL="file${COUNTER}_critical" + eval CRITICAL=\$$CRITICAL + if [ "$CRITICAL" != "" ]; then + echo "file_$COUNTER.critical $CRITICAL" + fi + + echo "file_$COUNTER.label $LABEL" + echo "file_$COUNTER.type GAUGE" + echo "file_$COUNTER.min 0" + let COUNTER=COUNTER+1 + done; + + echo "graph_order $GRAPH_ORDER" + echo "graph_title File age" + echo 'graph_args --base 1000 -l 0' + echo 'graph_vlabel seconds' + echo 'graph_category disk' + + exit 0 + ;; +esac + + +COUNTER=1 +while [ $COUNTER -gt 0 ]; do + FILE_COUNTER="file${COUNTER}_path" + eval FILE=\$$FILE_COUNTER + if [ "$FILE" == "" ]; then + break; + fi + + # If the file isn't readable, say it's zero. + if [ ! -r "$FILE" ]; then + VALUE=0 + else + VALUE=$(($(date +%s) - $(stat -c '%Y' "$FILE"))) + fi + + echo "file_$COUNTER.value $VALUE" + let COUNTER=COUNTER+1 +done; +exit + +# -*- sh -*- + +: << =cut + +=head1 NAME + +file_age - Monitors the age of files. + +=head1 CONFIGURATION + +Since there is no way for the plugin to guess which files you want monitored, you're going to have to set each file up separately. Put the following in a file in your plugin-conf.d directory. + +[file_age] + user root # May not be necessary, depending on which files you want monitored. + + env.file1_path /var/log/syslog # Mandatory, complete path to file. + env.file1_label System syslog # Optional label if you don't want the file name to be displayed. + env.file1_warning 86400 # Optional warning level. Measured in seconds. 86400 is one day of seconds. + env.file1_critical 864000 # Optional critical level. Measured in seconds. + +Continue with file2, file3, etc... + +Here, have some seconds: + + 3600 One hour + 7300 Two hours + 10800 Three hours + 21600 Six hours + 43200 Twelve hours + 86400 One day + 172800 Two days + 259200 Three days + 604800 One week + +=head1 AUTHOR + +Edward Plainview + +=head1 DONATIONS + +If you wish to donate money for this plugin, please read https://it.sverigedemokraterna.se/donera/ + +=head1 LICENSE + +GPLv3 + +=head1 MAGIC MARKERS + + #%# family=auto + +=head1 VERSION + +1.0 released 2012-02-26 + +=cut diff --git a/plugins/disk/smart_raw__ b/plugins/disk/smart_raw__ new file mode 100755 index 00000000..e466a7dd --- /dev/null +++ b/plugins/disk/smart_raw__ @@ -0,0 +1,145 @@ + #!/usr/bin/python +# +# Copyright (C) 2011 Andreas Thienemann +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +=head1 NAME + +smart_raw__ - Munin plugin to retreive raw SMART values from a disk. + +=head1 APPLICABLE SYSTEMS + +All machines with a SMART capable disk and smartmontools installed. + +This plugin is very useful if you want to need to monitor a single raw S.M.A.R.T. +value reported by a disk. Load Cycle Counts or Pending Sectors come to mind as +these are a good indicator of problems with a disk. + +=head1 CONFIGURATION + +The plugin should be installed as smart_raw_sda_193 which means that the smart value +numbered 193 will be read from the disk sda. + + +Basic configuration for every system is that the plugin needs to be called as root +in order to execute smartmontools. + +Add the following to your /etc/munin/plugin-conf.d/smart_raw: + + [smart_raw_sda_193] + user root + +=head1 INTERPRETATION + +Smart RAW values are provided as is. You need to undertand what you are monitoring... + +=head1 MAGIC MARKERS + + #%# family=contrib + #%# capabilities= + +=head1 VERSION + +0.0.1 + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Andreas Thienemann + +=head1 LICENSE + +GPLv3+ + +=cut +""" + +import subprocess +import sys +import os +import re +import pprint + +# We are a wildcard plugin, figure out what we are being called for +try: + disk = sys.argv[0].split('_')[2] + value = sys.argv[0].split('_')[3] +except IndexError: + sys.exit(1) + +def normalize_name(name): + name = re.sub("[^a-z0-9A-Z]","_", name) + return name + +# Code sniplet from Philipp Keller +# http://code.pui.ch/2007/02/19/set-timeout-for-a-shell-command-in-python/ +def timeout_command(command, timeout): + """call shell-command and either return its output or kill it + if it doesn't normally exit within timeout seconds and return None""" + import subprocess, datetime, os, time, signal + start = datetime.datetime.now() + process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + while process.poll() is None: + time.sleep(0.1) + now = datetime.datetime.now() + if (now - start).seconds> timeout: + os.kill(process.pid, signal.SIGKILL) + os.waitpid(-1, os.WNOHANG) + return None + return process.stdout.read() + +def read_smart(): + """Read SMART attributes""" + out = timeout_command("/usr/sbin/smartctl -A -d ata /dev/%s" % (disk), 2) + smart_attribs = dict() + for line in out.split('\n')[7:-2]: # Cut away the header and the footer + line = line.split() + smart_attribs[line[0]] = { + 'name' : line[1], + 'label' : normalize_name(line[1]), + 'raw' : line [-1], + } + return smart_attribs + +def print_config(): + """Return configuration arguments for munin""" + attribs = read_smart() + print "graph_title S.M.A.R.T raw value %s for drive %s" % (attribs[value]['name'], disk) + print "graph_vlabel Raw value" + print "graph_info RAW smartctl value" + print "graph_category disk" + print "graph_args --base 1000 -l 0" + + print "%s.label %s" % (value, attribs[value]['label']) + sys.exit(0) + +def fetch(): + attribs = read_smart() + print "%s.value %s" % (value, attribs[value]['raw']) + sys.exit(0) + +if "config" in sys.argv[1:]: + print_config() +elif "autoconf" in sys.argv[1:]: + pass +elif "suggest" in sys.argv[1:]: + pass +else: + fetch() diff --git a/plugins/ejabberd/ejabberd_scanlog b/plugins/ejabberd/ejabberd_scanlog/ejabberd_scanlog similarity index 100% rename from plugins/ejabberd/ejabberd_scanlog rename to plugins/ejabberd/ejabberd_scanlog/ejabberd_scanlog diff --git a/images/network/ejabberd_scanlog.png b/plugins/ejabberd/ejabberd_scanlog/ejabberd_scanlog.png similarity index 100% rename from images/network/ejabberd_scanlog.png rename to plugins/ejabberd/ejabberd_scanlog/ejabberd_scanlog.png diff --git a/plugins/forum/vbulletin4_users b/plugins/forum/vbulletin4_users new file mode 100644 index 00000000..5f6ee7e5 --- /dev/null +++ b/plugins/forum/vbulletin4_users @@ -0,0 +1,94 @@ +#!/usr/bin/perl +# +# Plugin to monitor the number of guests/users on a vbulletin4 forum +# Since we are using a regexp, this will work for EN forum only... adapt the regexp if your forum is not using that language. +# visual-regexp is a great help for tuning the regexp (package exists on Debian and Ubuntu repositories) +# +# +# Parameters supported: +# +# config +# autoconf +# +# Configurable variables +# +# url to online.php, this page is part of vbulletin distribution, pp=1 so the page is fast to load and doesn't give us too many unwanted info +# +# $Log$ +# +# +# Not sure about those lines... +# +# Magic markers: +#%# family=auto +#%# capabilities=autoconf + +my $ret = undef; + +if (! eval "require LWP::UserAgent;") +{ + $ret = "LWP::UserAgent not found"; +} + +# CHANGE ME +my $URL = exists $ENV{'url'} ? $ENV{'url'} : "http://www.url.to/forums/online.php?pp=1"; + + +# We will use this variable, not on this release ;-) +my $forum_type = exists $ENV{'type'} ? $ENV{'type'} : "vbulletin"; + +# same here +my %regexp = ("vbulletin"=> "(\d+)[^\d]+(\d+)\sguests\", + "punbb" => "", + "phpbb" => ""); + +my $type = undef; +my $timeout = 30; + +if ( defined $ARGV[0] and $ARGV[0] eq "autoconf" ) +{ + if ($ret) + { + print "no ($ret)\n"; + exit 1; + } + +} + +if ( defined $ARGV[0] and $ARGV[0] eq "config" ) +{ + print "graph_title Forum Users\n"; + print "graph_args -l 0\n"; + print "graph_vlabel current users\n"; + print "graph_category Forum\n"; + print "graph_total Total\n"; + + print "members.label Members\n"; + print "members.draw AREA\n"; + print "guests.draw STACK\n"; + print "guests.label Guests\n"; + + + + exit 0; +} + + +my $ua = LWP::UserAgent->new(timeout => $timeout); +my $url = sprintf $URL; +my $response = $ua->request(HTTP::Request->new('GET',$url)); + +# part of the output we want to catch :

42 members and 420 guests

--> 42 - 420 +if ($response->content =~ /

(\d+)\smembers[^\d]+(\d+)\sguests<\/h1>/im) + { + + print "members.value $1\n"; + print "guests.value $2\n"; + + } else { + print "members.value U\n"; + print "guests.value U\n"; + } + + +# vim:syntax=perl diff --git a/plugins/forum/vbulletin_users b/plugins/forum/vbulletin_users index 6a501482..9fb43980 100755 --- a/plugins/forum/vbulletin_users +++ b/plugins/forum/vbulletin_users @@ -43,7 +43,7 @@ my %regexp = ("vbulletin"=> "(\d+)[^\d]+(\d+)\sguests\", "phpbb" => ""); my $type = undef; -my $timoeout = 30; +my $timeout = 30; if ( defined $ARGV[0] and $ARGV[0] eq "autoconf" ) { diff --git a/plugins/games/game b/plugins/games/game new file mode 100644 index 00000000..db6f485f --- /dev/null +++ b/plugins/games/game @@ -0,0 +1,216 @@ +#!/usr/bin/php + $value) { + if ($section != 'settings') + $servers[$section] = array($value['game'], $value['address'], $value['port']); + } + + // Create a new GameQ object and pass it a list of servers + $gq = new GameQ(); + $gq->addServers($servers); + + // Set timeout from the config file + $gq->setOption('timeout', $ini_array['settings']['timeout']); + $gq->setOption('sock_count', $ini_array['settings']['sock_count']); + $gq->setOption('sock_start', $ini_array['settings']['sock_start']); + + // This filter makes sure a subset of data is always available, next to the normal data + $gq->setFilter('normalise'); + + // Send request(s) + $results = $gq->requestData(); + + return $results; +} + +// Parse command line arguments if required. +if (isset($_SERVER['argv'][1])) { + if ($_SERVER['argv'][1] == 'config') { + // Load our config.ini + $ini_array = parse_ini_file($config, true); + + // Load games.ini so we can show pretty game names + $games = parse_ini_file('gameq/GameQ/games.ini', true); + + // Query the game servers + $results = queryServers($ini_array); + // Cache the query in the state file + $fp = fopen($state, 'w+') or die("I could not open state file."); + fwrite($fp, serialize($results)); + fclose($fp); + + + // Loop through each server, printing graphs. + foreach ($results as $name => $server) { + // If sub graphs and the game total graphs are enabled, make this be a sub-graph. + if ($ini_array['settings']['show_game_total'] && $ini_array['settings']['enable_sub_graphs']) + $machine_name = "gameserver_" . $ini_array[$name]['game'] . ".$name"; + else + $machine_name = "gameserver_" . $ini_array[$name]['game'] . "_$name"; + $title = $ini_array[$name]['name'] . " players"; + $info = "The number of players connected to the " . $games[$ini_array[$name]['game']]['name'] . " server"; + printMultigraph($ini_array, $machine_name, $title, $info, $server['gq_maxplayers']); + } + + // Game total graphs. + if ($ini_array['settings']['show_game_total']) { + $game_total_max = array(); + + // Count players connected to each game + foreach ($results as $name => $server) { + if (!isset($game_total_max[$ini_array[$name]['game']])) + $game_total_max[$ini_array[$name]['game']] = $server['gq_maxplayers']; + else + $game_total_max[$ini_array[$name]['game']] += $server['gq_maxplayers']; + } + + // Print all the game total graphs. + foreach ($game_total_max as $game => $total) + { + $machine_name = "gameserver_" . $game; + $title = "Total " . $games[$game]['name'] . " players"; + $info = "Total players connected to all " . $games[$game]['name'] . " servers"; + printMultigraph($ini_array, $machine_name, $title, $info, $total); + } + } + + // Global total graph + if ($ini_array['settings']['show_global_total']) { + $total_max = 0; + // Add up all the players connected to all the servers + foreach ($results as $name => $server) { + $total_max += $server['gq_maxplayers']; + } + + printMultigraph($ini_array, "gameserver", "Total Players", "Total players connected to all servers", $total_max); + } + } + if ($_SERVER['argv'][1] == 'autoconf') { + if (!@include("gameq/GameQ.php")) + p("no (GameQ Library not found)"); + elseif (!file_exists($config)) + p("no ($config not found)"); + else + p("yes"); + } + die(); +} + +// No command line arguments, print values. + +// Load our config.ini +$ini_array = parse_ini_file($config, true); + +// Load games.ini so we can show pretty game names +$games = parse_ini_file('gameq/GameQ/games.ini', true); + +$results = unserialize(file_get_contents($state)); + +// Print individual game values +foreach ($results as $name => $server){ + if ($ini_array['settings']['show_game_total'] && $ini_array['settings']['enable_sub_graphs']) + $machine_name = "gameserver_" . $ini_array[$name]['game'] . ".$name"; + else + $machine_name = "gameserver_" . $ini_array[$name]['game'] . "_$name"; + printValue($machine_name, $server['gq_numplayers']); +} + +// Print game total values +if ($ini_array['settings']['show_game_total']) { + $game_total = array(); + foreach ($results as $name => $server) { + // Add up counts for the total graph + if (!isset($game_total_max[$ini_array[$name]['game']])) + $game_total[$ini_array[$name]['game']] = $server['gq_numplayers']; + else + $game_total[$ini_array[$name]['game']] += $server['gq_numplayers']; + } + foreach ($game_total as $game => $total) + { + $machine_name = "gameserver_" . $game; + printValue($machine_name, $total); + } +} + +// Are global totals enabled? +if ($ini_array['settings']['show_global_total']) { + $total = 0; + foreach ($results as $name => $server) { + $total += $server['gq_numplayers']; + } + printValue("gameserver", $total); +} + + +?> diff --git a/plugins/games/munin-game.ini b/plugins/games/munin-game.ini new file mode 100644 index 00000000..d03d1d0a --- /dev/null +++ b/plugins/games/munin-game.ini @@ -0,0 +1,28 @@ +[settings] +timeout = 1000 ; Timeout in ms when attempting to connect to servers +sock_count = 64 ; Maximum number of sockets that are used simultaneously +sock_start = 0 ; Start of port range to use + +; Note that changing any of the below options may change your graph names, which may reset your statistics. +show_global_total = True ; Show a graph that shows the total players connected to all servers? +show_game_total = True ; Show a graph that shows the total players connected to all servers of the same type? +enable_sub_graphs = True ; Make each game be a sub-graph of it's game total graph. + +; The following variables allow you to override the style of graphs. +; The server graphs are named gameserver_gamename.machinename, for example gameserver_tf2.myamazingserver +; The game total graphs are named gameserver_gamename, for example gameserver_tf2 +; The global total graph is named gameserver. +;graph_name_colour = 000000 ; Override the color of graph_name, for example gameserver_et.myetserver_color +;graph_name_draw = LINE1 ; Override the draw method of graph_name, for example gameserver_ + +[mytf2server] ; Machine name of the server +name = "My TF2 Server" ; Display name of the server +game = tf2 ; Game type, see GameQ/games.ini inside the GameQ library for a list of valid options. +address = 1.2.3.4 ; Server IP or hostname. +port = 27015 ; Server port + +[myetserver] +name = "My Enemy Territory Server" +game = et +address = 1.2.3.4 +port = 27960 diff --git a/plugins/java/jmx/examples/java/java_cpu.conf b/plugins/java/jmx/examples/java/java_cpu.conf new file mode 100644 index 00000000..1e56cd34 --- /dev/null +++ b/plugins/java/jmx/examples/java/java_cpu.conf @@ -0,0 +1,22 @@ +graph_args --upper-limit 100 -l 0 +graph_scale no +graph_title CPU Usage +graph_vlabel 1000* CPU time % +graph_category Java +graph_order java_cpu_time java_cpu_user_time + +java_cpu_time.label cpu +java_cpu_time.jmxObjectName java.lang:type=Threading +java_cpu_time.jmxAttributeName CurrentThreadCpuTime +java_cpu_time.type DERIVE +java_cpu_time.min 0 +java_cpu_time.graph yes +java_cpu_time.cdef java_cpu_time,3000000,/ + +java_cpu_user_time.label user +java_cpu_user_time.jmxObjectName java.lang:type=Threading +java_cpu_user_time.jmxAttributeName CurrentThreadUserTime +java_cpu_user_time.type DERIVE +java_cpu_user_time.min 0 +java_cpu_user_time.graph yes +java_cpu_user_time.cdef java_cpu_user_time,3000000,/ \ No newline at end of file diff --git a/plugins/java/jmx/examples/java/java_process_memory.conf b/plugins/java/jmx/examples/java/java_process_memory.conf new file mode 100644 index 00000000..ddbdc4d0 --- /dev/null +++ b/plugins/java/jmx/examples/java/java_process_memory.conf @@ -0,0 +1,46 @@ +graph_title Process Memory +graph_vlabel Bytes +graph_category Java +graph_order java_memory_nonheap_committed java_memory_nonheap_max java_memory_nonheap_used java_memory_heap_committed java_memory_heap_max java_memory_heap_used os_memory_physical os_memory_vm + +java_memory_nonheap_committed.label non-heap committed +java_memory_nonheap_committed.jmxObjectName java.lang:type=Memory +java_memory_nonheap_committed.jmxAttributeName NonHeapMemoryUsage +java_memory_nonheap_committed.jmxAttributeKey committed + +java_memory_nonheap_max.label non-heap max +java_memory_nonheap_max.jmxObjectName java.lang:type=Memory +java_memory_nonheap_max.jmxAttributeName NonHeapMemoryUsage +java_memory_nonheap_max.jmxAttributeKey max + +java_memory_nonheap_used.label non-heap used +java_memory_nonheap_used.jmxObjectName java.lang:type=Memory +java_memory_nonheap_used.jmxAttributeName NonHeapMemoryUsage +java_memory_nonheap_used.jmxAttributeKey used + +java_memory_heap_committed.label heap committed +java_memory_heap_committed.jmxObjectName java.lang:type=Memory +java_memory_heap_committed.jmxAttributeName HeapMemoryUsage +java_memory_heap_committed.jmxAttributeKey committed + +java_memory_heap_max.label heap max +java_memory_heap_max.jmxObjectName java.lang:type=Memory +java_memory_heap_max.jmxAttributeName HeapMemoryUsage +java_memory_heap_max.jmxAttributeKey max + +java_memory_heap_used.label heap used +java_memory_heap_used.jmxObjectName java.lang:type=Memory +java_memory_heap_used.jmxAttributeName HeapMemoryUsage +java_memory_heap_used.jmxAttributeKey used + +os_memory_physical.label os free mem +os_memory_physical.jmxObjectName java.lang:type=OperatingSystem +os_memory_physical.jmxAttributeName FreePhysicalMemorySize +os_memory_physical.graph no + +os_memory_vm.label os vmem committed +os_memory_vm.jmxObjectName java.lang:type=OperatingSystem +os_memory_vm.jmxAttributeName CommittedVirtualMemorySize +os_memory_vm.graph no + + diff --git a/plugins/java/jmx/examples/java/java_threads.conf b/plugins/java/jmx/examples/java/java_threads.conf new file mode 100644 index 00000000..f8b64cd4 --- /dev/null +++ b/plugins/java/jmx/examples/java/java_threads.conf @@ -0,0 +1,14 @@ +graph_title Thread Count +graph_vlabel Thread Count +graph_category Java +graph_order java_thread_count java_thread_count_peak + +java_thread_count.label count +java_thread_count.jmxObjectName java.lang:type=Threading +java_thread_count.jmxAttributeName ThreadCount + +java_thread_count_peak.label peak +java_thread_count_peak.jmxObjectName java.lang:type=Threading +java_thread_count_peak.jmxAttributeName PeakThreadCount + + diff --git a/plugins/java/jmx/examples/tomcat/catalina_requests.conf b/plugins/java/jmx/examples/tomcat/catalina_requests.conf new file mode 100644 index 00000000..acb48175 --- /dev/null +++ b/plugins/java/jmx/examples/tomcat/catalina_requests.conf @@ -0,0 +1,16 @@ +graph_title Requests Per Second +graph_vlabel requests per second +graph_category Tomcat +graph_order catalina_request_count catalina_error_count + +catalina_request_count.label requests +catalina_request_count.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_request_count.jmxAttributeName requestCount +catalina_request_count.type DERIVE +catalina_request_count.min 0 + +catalina_error_count.label errors +catalina_error_count.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_error_count.jmxAttributeName errorCount +catalina_error_count.type DERIVE +catalina_error_count.min 0 diff --git a/plugins/java/jmx/examples/tomcat/catalina_threads.conf b/plugins/java/jmx/examples/tomcat/catalina_threads.conf new file mode 100644 index 00000000..32ab44c2 --- /dev/null +++ b/plugins/java/jmx/examples/tomcat/catalina_threads.conf @@ -0,0 +1,12 @@ +graph_title Thread Count +graph_vlabel Thread Count +graph_category Tomcat +graph_order catalina_threads_count catalina_threads_busy + +catalina_threads_busy.label busy +catalina_threads_busy.jmxObjectName Catalina:name=http-8080,type=ThreadPool +catalina_threads_busy.jmxAttributeName currentThreadsBusy + +catalina_threads_count.label current +catalina_threads_count.jmxObjectName Catalina:name=http-8080,type=ThreadPool +catalina_threads_count.jmxAttributeName currentThreadCount diff --git a/plugins/java/jmx/examples/tomcat/catalina_times.conf b/plugins/java/jmx/examples/tomcat/catalina_times.conf new file mode 100644 index 00000000..e3790259 --- /dev/null +++ b/plugins/java/jmx/examples/tomcat/catalina_times.conf @@ -0,0 +1,32 @@ +graph_title Response Time +graph_vlabel Time, ms +graph_category Rules Engine +graph_args --upper-limit 100 -l 0 +graph_scale no +graph_category Tomcat +graph_order catalina_request_count catalina_max_time catalina_proc_time catalina_proc_tpr + +catalina_request_count.label requests +catalina_request_count.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_request_count.jmxAttributeName requestCount +catalina_request_count.graph no +catalina_request_count.type DERIVE +catalina_request_count.min 0 + +catalina_proc_time.label time +catalina_proc_time.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_proc_time.jmxAttributeName processingTime +catalina_proc_time.type DERIVE +catalina_proc_time.min 0 +catalina_proc_time.graph no + +catalina_proc_tpr.label avg +catalina_proc_tpr.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_proc_tpr.jmxAttributeName processingTime +catalina_proc_tpr.cdef catalina_request_count,0,EQ,0,catalina_proc_time,catalina_request_count,/,IF + +catalina_max_time.label peak +catalina_max_time.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_max_time.jmxAttributeName maxTime + + diff --git a/plugins/java/jmx/examples/tomcat/catalina_traffic.conf b/plugins/java/jmx/examples/tomcat/catalina_traffic.conf new file mode 100644 index 00000000..f6d9498d --- /dev/null +++ b/plugins/java/jmx/examples/tomcat/catalina_traffic.conf @@ -0,0 +1,18 @@ +graph_title Traffic +graph_vlabel Bytes rec(-)/sent(+) per second +graph_category Tomcat +graph_order catalina_bytes_received catalina_bytes_sent + +catalina_bytes_sent.label bps +catalina_bytes_sent.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_bytes_sent.jmxAttributeName bytesSent +catalina_bytes_sent.type DERIVE +catalina_bytes_sent.min 0 +catalina_bytes_sent.negative catalina_bytes_received + +catalina_bytes_received.label received +catalina_bytes_received.jmxObjectName Catalina:name=http-8080,type=GlobalRequestProcessor +catalina_bytes_received.jmxAttributeName bytesReceived +catalina_bytes_received.type DERIVE +catalina_bytes_received.min 0 +catalina_bytes_received.graph no \ No newline at end of file diff --git a/plugins/java/jmx/plugin/jmx_ b/plugins/java/jmx/plugin/jmx_ new file mode 100755 index 00000000..9fe33f2f --- /dev/null +++ b/plugins/java/jmx/plugin/jmx_ @@ -0,0 +1,54 @@ +#!/bin/sh +# +# Wildcard-plugin to monitor Java JMX (http://java.sun.com/jmx)attributes. +# To monitor a # specific set of JMX attributes, +# link to this file. E.g. +# +# ln -s /usr/share/plugins/jmx_ /etc/munin/plugins/jmx_java_threads +# + +# ...will monitor Java thread count, assuming java_threads.conf file is located in +# /etc/munin/plugins folder. +# +# For Java process to be monitored, it must expose JMX remote interface. +# With Java 1.5 it can be done by adding parameters as: +# +# -Dcom.sun.management.jmxremote.port= -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false +# +# For Tomcat to be monitored, add the following line in catalina.bat script: +# set JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote.port= -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false +# +# By default, the plugin monitors localhost on = 1616 using URL +# service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi +# It can be changed by specifying parameters in /etc/munin/plugin-conf.d/munin-node +# +# [jmx_*] +# env.jmxurl service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi +# +# Read more on JMX configuring at http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.html +# $Log$ +# +LINK=`readlink $0` +CONFIGNAME=`basename $0 | sed 's/^jmx_//g'`.conf +RDIR=`dirname $LINK` + +if [ "$jmxurl" = "" ]; then +SERVICE=service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi +else +SERVICE="$jmxurl" +fi + +if [ "$jmxuser" != "" ]; then +CREDS="--user=$jmxuser --pass=$jmxpass" +fi + +# checks +test -z $CONFIGNAME || test -z "$RDIR" && exit 1 + +JMXQUERY="java -cp $RDIR/jmxquery.jar org.munin.JMXQuery --url=$SERVICE $CREDS --conf=$RDIR/$CONFIGNAME" + + +case $1 in + (config) $JMXQUERY config;; + (*) $JMXQUERY ;; +esac diff --git a/plugins/java/jmx/plugin/jmxquery.jar b/plugins/java/jmx/plugin/jmxquery.jar new file mode 100644 index 00000000..5c37d4bd Binary files /dev/null and b/plugins/java/jmx/plugin/jmxquery.jar differ diff --git a/plugins/java/jmx/readme.txt b/plugins/java/jmx/readme.txt new file mode 100644 index 00000000..cae58a28 --- /dev/null +++ b/plugins/java/jmx/readme.txt @@ -0,0 +1,66 @@ +-------- JMX plugin for Munin --------- + +Java JMX Munin plugin enables you to monitor JMX attributes in Munin. +As soon as JMX embedded in Java 5, any Java process may expose parameters to be monitored using JMX interface, +look http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.html and http://java.sun.com/jmx for details +In Java version < 5 it is still possible to expose JMX interface using third party libraries + +To see what can be monitored by JMX, run /bin/jconsole.exe and connect to +the host/port you setup in your Java process. + +Some examples are: +* standard Java JMX implementation exposes memory, threads, OS, garbage collector parameters +* Tomcat exposes multiple parameters - requests, processing time, threads, etc.. +* spring framework allows to expose Java beans parameters to JMX +* your application may expose any attributes for JMX by declaration or explicitly. +* can monitor localhost or remote processes + +-------- Installation --------- + +Pre-requsisites are: +- installed munin-node +- Java version 5 JRE + +1) Files from "plugin" folder must be copied to /usr/share/munin/plugins (or another - where your munin plugins located) +2) Make sure that jmx_ executable : chmod a+x /usr/share/munin/plugins/jmx_ +3) Copy configuration files that you want to use, from "examples" folder, into /usr/share/munin/plugins folder +4) create links from the /etc/munin/plugins folder to the /usr/share/munin/plugins/jmx_ +The name of the link must follow wildcard pattern: +jmx_, +where configname is the name of the configuration (config filename without extension), for example: +ln -s /usr/share/munin/plugins/jmx_ /etc/munin/plugins/jmx_process_memory +5) optionally specify the environment variable for JMX URL. The default URL corresponds to localhost:1616. +If you have different port listening by JMX or different hostname to monitor, specify jmxurl parameter +in /etc/munin/plugin-conf.d/munin-node: + +[jmx_*] +env.jmxurl service:jmx:rmi:///jndi/rmi://localhost:1616/jmxrmi + +-------- Check Installation --------- + +To check that all installed properly, try invoke plugins from command line, using links like: + +root@re:/etc/munin/plugins# ./jmx_java_process_memory config +graph_category Java +... +root@re:/etc/munin/plugins# ./jmx_java_process_memory +java_memory_nonheap_committed.value 35291136 +... + +If you have configured environment for jmxurl, do not forget to export it before! + +-------- Configuration Files --------- + +Folder "examples" contains configuration files for Java and Tomcat monitoring examples. +The format of configuration file is a superset of Munin plugin "config" command output +(http://munin.projects.linpro.no/wiki/protocol-config) +It has the following additions: + +.jmxObjectName JMX object name, e.g. java.lang:type=Memory +.jmxAttributeName JMX attribute name, e.g. NonHeapMemoryUsage +.jmxAttributeKey If attribute is a composed data (structure), the name of the field in structure, e.g. max + +% separates comments in file + + + diff --git a/plugins/java/jmx2munin/.gitignore b/plugins/java/jmx2munin/.gitignore new file mode 100644 index 00000000..0a99f329 --- /dev/null +++ b/plugins/java/jmx2munin/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.classpath +.project +.fatjar +target +eclipse +old +bin diff --git a/plugins/java/jmx2munin/README.md b/plugins/java/jmx2munin/README.md new file mode 100644 index 00000000..d1899db0 --- /dev/null +++ b/plugins/java/jmx2munin/README.md @@ -0,0 +1,79 @@ +# jmx2munin + +The [jmx2munin](http://github.com/tcurdt/jmx2munin) project exposes JMX MBean attributes to [Munin](http://munin-monitoring.org/). +Some of it's features: + + * strictly complies to the plugin format + * exposes composite types like Lists, Maps, Set as useful as possible + * String values can be mapped to numbers + +# How to use + +This is what the Munin script will call. So you should test this first. Of course with your parameters. This example expose all Cassandra information to Munin. + + java -jar jmx2munin.jar \ + -url service:jmx:rmi:///jndi/rmi://localhost:8080/jmxrmi \ + -query "org.apache.cassandra.*:*" + +The "url" parameters specifies the JMX URL, the query selects the MBeans (and optionally also the attributes) to expose. + + java -jar jmx2munin.jar \ + -url service:jmx:rmi:///jndi/rmi://localhost:8080/jmxrmi \ + -query "org.apache.cassandra.*:*" \ + -attribute org_apache_cassandra_db_storageservice_livenodes_size + +The script that does the actual interaction with munin you can find in the contrib section. It's the one you should link in the your Munin plugin directory. + + :/etc/munin/plugins$ ls -la cassandra_* + lrwxrwxrwx 1 root root 37 2011-04-07 19:58 cassandra_nodes_in_cluster -> /usr/share/munin/plugins/jmx2munin.sh + +In the plugin conf you point to the correct configuration + + [cassandra_*] + env.query org.apache.cassandra.*:* + + [cassandra_nodes_in_cluster] + env.config cassandra/nodes_in_cluster + +A possible configuration could look like this + + graph_title Number of Nodes in Cluster + graph_vlabel org_apache_cassandra_db_storageservice_livenodes_size + org_apache_cassandra_db_storageservice_livenodes_size.label number of nodes + +The script will extract the attributes from the config and caches the JMX results to reduce the load when showing many values. + +# More advanced + +Sometimes it can be useful to track String values by mapping them into an enum as they really describe states. To find this possible candidates you can call: + + java -jar jmx2munin.jar \ + -url service:jmx:rmi:///jndi/rmi://localhost:8080/jmxrmi \ + -query "org.apache.cassandra.*:*" \ + list + +It should output a list of possible candidates. This can now be turned into a enum configuration file: + + [org.apache.cassandra.db.StorageService:OperationMode] + 0 = ^Normal + 1 = ^Client + 2 = ^Joining + 3 = ^Bootstrapping + 4 = ^Leaving + 5 = ^Decommissioned + 6 = ^Starting drain + 7 = ^Node is drained + +Which you then can provide: + + java -jar jmx2munin.jar \ + -url service:jmx:rmi:///jndi/rmi://localhost:8080/jmxrmi \ + -query "org.apache.cassandra.*:*" \ + -enums /path/to/enums.cfg + +Now matching values get replaced by their numerical representation. On the left needs to be a unique number on the right side is a regular expression. If a string cannot be matched according to the spec "U" for "undefined" will be returned. + +# License + +Licensed under the Apache License, Version 2.0 (the "License") +You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 diff --git a/plugins/java/jmx2munin/contrib/jmx2munin.cfg/cassandra/nodes_in_cluster b/plugins/java/jmx2munin/contrib/jmx2munin.cfg/cassandra/nodes_in_cluster new file mode 100644 index 00000000..7fe323e3 --- /dev/null +++ b/plugins/java/jmx2munin/contrib/jmx2munin.cfg/cassandra/nodes_in_cluster @@ -0,0 +1,3 @@ +graph_title Number of Nodes in Cluster +graph_vlabel org_apache_cassandra_db_storageservice_livenodes_size +org_apache_cassandra_db_storageservice_livenodes_size.label number of nodes diff --git a/plugins/java/jmx2munin/contrib/jmx2munin.sh b/plugins/java/jmx2munin/contrib/jmx2munin.sh new file mode 100644 index 00000000..2ccb1841 --- /dev/null +++ b/plugins/java/jmx2munin/contrib/jmx2munin.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# [cassandra_nodes_in_cluster] +# env.config cassandra/nodes_in_cluster +# env.query org.apache.cassandra.*:* + +if [ -z "$MUNIN_LIBDIR" ]; then + MUNIN_LIBDIR="`dirname $(dirname "$0")`" +fi + +if [ -f "$MUNIN_LIBDIR/plugins/plugin.sh" ]; then + . $MUNIN_LIBDIR/plugins/plugin.sh +fi + +if [ "$1" = "autoconf" ]; then + echo yes + exit 0 +fi + +if [ -z "$url" ]; then + # this is very common so make it a default + url="service:jmx:rmi:///jndi/rmi://127.0.0.1:8080/jmxrmi" +fi + +if [ -z "$config" -o -z "$query" -o -z "$url" ]; then + echo "Configuration needs attributes config, query and optinally url" + exit 1 +fi + +JMX2MUNIN_DIR="$MUNIN_LIBDIR/plugins" +CONFIG="$JMX2MUNIN_DIR/jmx2munin.cfg/$config" + +if [ "$1" = "config" ]; then + cat "$CONFIG" + exit 0 +fi + +JAR="$JMX2MUNIN_DIR/jmx2munin.jar" +CACHED="/tmp/jmx2munin" + +if test ! -f $CACHED || test `find "$CACHED" -mmin +2`; then + + java -jar "$JAR" \ + -url "$url" \ + -query "$query" \ + $ATTRIBUTES \ + > $CACHED + + echo "cached.value `date +%s`" >> $CACHED +fi + +ATTRIBUTES=`awk '/\.label/ { gsub(/\.label/,""); print $1 }' $CONFIG` + +for ATTRIBUTE in $ATTRIBUTES; do + grep $ATTRIBUTE $CACHED +done \ No newline at end of file diff --git a/plugins/java/jmx2munin/pom.xml b/plugins/java/jmx2munin/pom.xml new file mode 100644 index 00000000..2bbbd026 --- /dev/null +++ b/plugins/java/jmx2munin/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + + org.vafer + jmx2munin + jmx2munin + 1.0 + + Munin plugin to access JMX information + + http://github.com/tcurdt/jmx2munin + + + + tcurdt + Torsten Curdt + tcurdt at vafer.org + +1 + + + + + + Apache License 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + scm:git:git://github.com:tcurdt/jmx2munin.git + scm:git:git://github.com:tcurdt/jmx2munin.git + http://github.com/tcurdt/jmx2munin/tree/master + + + + + com.beust + jcommander + 1.17 + + + + junit + junit + 4.5 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + UTF-8 + + + + org.apache.maven.plugins + maven-surefire-plugin + + never + + **/*TestCase.java + + + **/Abstract* + + true + false + + + + org.apache.maven.plugins + maven-source-plugin + 2.1 + + true + + + + create-source-jar + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-shade-plugin + 1.4 + + + package + + shade + + + false + + + com.beust:jcommander + + + + + org.vafer.jmx.munin.Munin + + + + + + + + + diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Enums.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Enums.java new file mode 100644 index 00000000..ab5b4831 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Enums.java @@ -0,0 +1,77 @@ +package org.vafer.jmx; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import javax.management.ObjectName; + +public final class Enums { + + private TreeMap> sections = new TreeMap>(); + + public boolean load(String filePath) throws IOException { + BufferedReader input = null; + LinkedHashMap section = new LinkedHashMap(); + try { + input = new BufferedReader(new InputStreamReader(new FileInputStream(filePath))); + String line; + int linenr = 0; + while((line = input.readLine()) != null) { + linenr += 1; + line = line.trim(); + if (line.startsWith("#")) { + continue; + } + if (line.startsWith("[") && line.endsWith("]")) { + // new section + String id = line.substring(1, line.length() - 1); + section = new LinkedHashMap(); + sections.put(id, section); + } else { + String[] pair = line.split("="); + if (pair.length == 2) { + Integer number = Integer.parseInt(pair[0].trim()); + Pattern pattern = Pattern.compile(pair[1].trim()); + if (section.put(number, pattern) != null) { + System.err.println("Line " + linenr + ": previous definitions of " + number); + } + } + } + } + } finally { + if (input != null) { + input.close(); + } + } + return false; + } + + public static String id(ObjectName beanName, String attributeName) { + StringBuilder sb = new StringBuilder(); + sb.append(beanName.getDomain()); + sb.append('.'); + sb.append(beanName.getKeyProperty("type")); + sb.append(':'); + sb.append(attributeName); + return sb.toString(); + } + + public Number resolve(String id, String value) { + LinkedHashMap section = sections.get(id); + if (section == null) { + return null; + } + for(Map.Entry entry : section.entrySet()) { + if (entry.getValue().matcher(value).matches()) { + return entry.getKey(); + } + } + return null; + } +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Filter.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Filter.java new file mode 100644 index 00000000..e7d67a8a --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Filter.java @@ -0,0 +1,9 @@ +package org.vafer.jmx; + +import javax.management.ObjectName; + +public interface Filter { + + public boolean include(ObjectName bean, String attribute); + +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/ListOutput.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/ListOutput.java new file mode 100644 index 00000000..4e050faf --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/ListOutput.java @@ -0,0 +1,26 @@ +package org.vafer.jmx; + +import java.util.HashSet; +import java.util.Set; + +import javax.management.ObjectName; + +public final class ListOutput implements Output { + + private final Set seen = new HashSet(); + + public void output(ObjectName beanName, String attributeName, Object value) { + Value.flatten(beanName, attributeName, value, new Value.Listener() { + public void value(ObjectName beanName, String attributeName, String value) { + final String id = Enums.id(beanName, attributeName); + if (!seen.contains(id)) { + System.out.println("[" + id + "]"); + seen.add(id); + } + } + public void value(ObjectName beanName, String attributeName, Number value) { + } + }); + } + +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/NoFilter.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/NoFilter.java new file mode 100644 index 00000000..6188d5c7 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/NoFilter.java @@ -0,0 +1,10 @@ +package org.vafer.jmx; + +import javax.management.ObjectName; + +public final class NoFilter implements Filter { + + public boolean include(ObjectName bean, String attribute) { + return true; + } +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Output.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Output.java new file mode 100644 index 00000000..eb9e6ca2 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Output.java @@ -0,0 +1,9 @@ +package org.vafer.jmx; + +import javax.management.ObjectName; + +public interface Output { + + public void output(ObjectName beanName, String attributeName, Object value); + +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Query.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Query.java new file mode 100644 index 00000000..e27bc4f5 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Query.java @@ -0,0 +1,52 @@ +package org.vafer.jmx; + +import java.io.IOException; +import java.util.Collection; + +import javax.management.AttributeNotFoundException; +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanServerConnection; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +public final class Query { + + public void run(String url, String expression, Filter filter, Output output) throws IOException, MalformedObjectNameException, InstanceNotFoundException, ReflectionException, IntrospectionException, AttributeNotFoundException, MBeanException { + JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(url)); + MBeanServerConnection connection = connector.getMBeanServerConnection(); + final Collection mbeans = connection.queryMBeans(new ObjectName(expression), null); + + for(ObjectInstance mbean : mbeans) { + final ObjectName mbeanName = mbean.getObjectName(); + final MBeanInfo mbeanInfo = connection.getMBeanInfo(mbeanName); + final MBeanAttributeInfo[] attributes = mbeanInfo.getAttributes(); + for (final MBeanAttributeInfo attribute : attributes) { + if (attribute.isReadable()) { + if (filter.include(mbeanName, attribute.getName())) { + final String attributeName = attribute.getName(); + try { + output.output( + mbean.getObjectName(), + attributeName, + connection.getAttribute(mbeanName, attributeName) + ); + } catch(Exception e) { + // System.err.println("Failed to read " + mbeanName + "." + attributeName); + } + } + } + } + + } + connector.close(); + } +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Value.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Value.java new file mode 100644 index 00000000..87af5f8a --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/Value.java @@ -0,0 +1,52 @@ +package org.vafer.jmx; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.management.ObjectName; + +public final class Value { + + public interface Listener { + public void value(ObjectName beanName, String attributeName, String value); + public void value(ObjectName beanName, String attributeName, Number value); + } + + public static void flatten(ObjectName beanName, String attributeName, Object value, Listener listener) { + if (value instanceof Number) { + + listener.value(beanName, attributeName, (Number) value); + + } else if (value instanceof String) { + + listener.value(beanName, attributeName, (String) value); + + } else if (value instanceof Set) { + + final Set set = (Set) value; + flatten(beanName, attributeName + ".size", set.size(), listener); + for(Object entry : set) { + flatten(beanName, attributeName + "[" + entry + "]", 1, listener); + } + + } else if (value instanceof List) { + + final List list = (List)value; + listener.value(beanName, attributeName + ".size", list.size()); + for(int i = 0; i map = (Map) value; + listener.value(beanName, attributeName + ".size", map.size()); + for(Map.Entry entry : map.entrySet()) { + flatten(beanName, attributeName + "[" + entry.getKey() + "]", entry.getValue(), listener); + } + + } else { + // System.err.println("Failed to convert " + beanName + "." + attributeName); + } + } +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/Munin.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/Munin.java new file mode 100644 index 00000000..9f1ffdc7 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/Munin.java @@ -0,0 +1,67 @@ +package org.vafer.jmx.munin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.vafer.jmx.Enums; +import org.vafer.jmx.Filter; +import org.vafer.jmx.ListOutput; +import org.vafer.jmx.NoFilter; +import org.vafer.jmx.Query; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; + +public final class Munin { + + @Parameter(description = "") + private List args = new ArrayList(); + + @Parameter(names = "-url", description = "jmx url", required = true) + private String url; + + @Parameter(names = "-query", description = "query expression", required = true) + private String query; + + @Parameter(names = "-enums", description = "file string to enum config") + private String enumsPath; + + @Parameter(names = "-attribute", description = "attributes to return") + private List attributes = new ArrayList(); + + private void run() throws Exception { + final Filter filter; + if (attributes == null || attributes.isEmpty()) { + filter = new NoFilter(); + } else { + filter = new MuninAttributesFilter(attributes); + } + + final Enums enums = new Enums(); + if (enumsPath != null) { + enums.load(enumsPath); + } + + final String cmd = args.toString().toLowerCase(Locale.US); + if ("[list]".equals(cmd)) { + new Query().run(url, query, filter, new ListOutput()); + } else { + new Query().run(url, query, filter, new MuninOutput(enums)); + } + } + + public static void main(String[] args) throws Exception { + Munin m = new Munin(); + + JCommander cli = new JCommander(m); + try { + cli.parse(args); + } catch(Exception e) { + cli.usage(); + System.exit(1); + } + + m.run(); + } +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/MuninAttributesFilter.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/MuninAttributesFilter.java new file mode 100644 index 00000000..e1a49e83 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/MuninAttributesFilter.java @@ -0,0 +1,24 @@ +package org.vafer.jmx.munin; + +import java.util.HashSet; +import java.util.List; + +import javax.management.ObjectName; + +import org.vafer.jmx.Filter; + +public final class MuninAttributesFilter implements Filter { + + private final HashSet attributes = new HashSet(); + + public MuninAttributesFilter(List pAttributes) { + for (String attribute : pAttributes) { + attributes.add(attribute.trim().replaceAll("_size$", "")); + } + } + + public boolean include(ObjectName bean, String attribute) { + return attributes.contains(MuninOutput.attributeName(bean, attribute)); + } + +} diff --git a/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/MuninOutput.java b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/MuninOutput.java new file mode 100644 index 00000000..9fb50b12 --- /dev/null +++ b/plugins/java/jmx2munin/src/main/java/org/vafer/jmx/munin/MuninOutput.java @@ -0,0 +1,93 @@ +package org.vafer.jmx.munin; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Locale; + +import javax.management.ObjectName; + +import org.vafer.jmx.Enums; +import org.vafer.jmx.Output; +import org.vafer.jmx.Value; + +public final class MuninOutput implements Output { + + private final Enums enums; + + public MuninOutput(Enums enums) { + this.enums = enums; + } + + public static String attributeName(ObjectName bean, String attribute) { + StringBuilder sb = new StringBuilder(); + sb.append(fieldname(beanString(bean))); + sb.append('_'); + sb.append(fieldname(attribute)); + return sb.toString().toLowerCase(Locale.US); + } + + private static String fieldname(String s) { + return s.replaceAll("[^A-Za-z0-9]", "_"); + } + + private static String beanString(ObjectName beanName) { + StringBuilder sb = new StringBuilder(); + sb.append(beanName.getDomain()); + + Hashtable properties = beanName.getKeyPropertyList(); + + String keyspace = "keyspace"; + if (properties.containsKey(keyspace)) { + sb.append('.'); + sb.append(properties.get(keyspace)); + properties.remove(keyspace); + } + + String type = "type"; + if (properties.containsKey(type)) { + sb.append('.'); + sb.append(properties.get(type)); + properties.remove(type); + } + + ArrayList keys = new ArrayList(properties.keySet()); + Collections.sort(keys); + + for(String key : keys) { + sb.append('.'); + sb.append(properties.get(key)); + } + + return sb.toString(); + // return beanName.getCanonicalName(); + } + + public void output(ObjectName beanName, String attributeName, Object value) { + Value.flatten(beanName, attributeName, value, new Value.Listener() { + public void value(ObjectName beanName, String attributeName, String value) { + final Number v = enums.resolve(Enums.id(beanName, attributeName), value); + if (v != null) { + value(beanName, attributeName, v); + } else { + value(beanName, attributeName, Double.NaN); + } + } + public void value(ObjectName beanName, String attributeName, Number value) { + final String v; + + if (Double.isNaN(value.doubleValue())) { + v = "U"; + } else { + final NumberFormat f = NumberFormat.getInstance(); + f.setMaximumFractionDigits(2); + f.setGroupingUsed(false); + v = f.format(value); + } + + System.out.println(attributeName(beanName, attributeName) + ".value " + v); + } + }); + } +} \ No newline at end of file diff --git a/plugins/jvm/jstat__gccount b/plugins/java/jstat__gccount similarity index 100% rename from plugins/jvm/jstat__gccount rename to plugins/java/jstat__gccount diff --git a/plugins/jvm/jstat__gctime b/plugins/java/jstat__gctime similarity index 100% rename from plugins/jvm/jstat__gctime rename to plugins/java/jstat__gctime diff --git a/plugins/jvm/jstat__heap b/plugins/java/jstat__heap similarity index 100% rename from plugins/jvm/jstat__heap rename to plugins/java/jstat__heap diff --git a/plugins/jvm/jvm_sun_memory b/plugins/java/jvm_sun_memory similarity index 100% rename from plugins/jvm/jvm_sun_memory rename to plugins/java/jvm_sun_memory diff --git a/plugins/jvm/jvm_sun_minorgcs b/plugins/java/jvm_sun_minorgcs similarity index 100% rename from plugins/jvm/jvm_sun_minorgcs rename to plugins/java/jvm_sun_minorgcs diff --git a/plugins/jvm/jvm_sun_tenuredgcs b/plugins/java/jvm_sun_tenuredgcs similarity index 100% rename from plugins/jvm/jvm_sun_tenuredgcs rename to plugins/java/jvm_sun_tenuredgcs diff --git a/plugins/mail/dovecot b/plugins/mail/dovecot index beb62633..03119c26 100755 --- a/plugins/mail/dovecot +++ b/plugins/mail/dovecot @@ -68,7 +68,7 @@ fi # Total Logins ###################### echo -en "login_total.value " -NEW_TOTAL=$(egrep 'dovecot.*Login' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) +NEW_TOTAL=$(egrep '[dovecot]?.*Login' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) OLD_TOTAL=$(grep TOTAL $STAT_FILE | cut -f2 -d '=') TOTAL=$($EXPR_BIN $NEW_TOTAL - $OLD_TOTAL) if [ $TOTAL -gt 0 ]; then @@ -80,8 +80,8 @@ echo -n ###################### # Connected Users ###################### -DISCONNECTS=$(egrep 'dovecot.*Disconnected' $LOGFILE | sort | wc -l) -CONNECTS=$(egrep 'dovecot.*Login' $LOGFILE | sort | wc -l) +DISCONNECTS=$(egrep '[dovecot]?.*Disconnected' $LOGFILE | sort | wc -l) +CONNECTS=$(egrep '[dovecot]?.*Login' $LOGFILE | sort | wc -l) DISCON=$($EXPR_BIN $CONNECTS - $DISCONNECTS) if [ $DISCON -lt 0 ]; then DISCON=0 @@ -93,7 +93,7 @@ echo -n # TLS Logins ###################### echo -en "login_tls.value " -NEW_TLS=$(egrep 'dovecot.*Login.*TLS' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) +NEW_TLS=$(egrep '[dovecot]?.*Login.*TLS' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) OLD_TLS=$(grep TLS $STAT_FILE | cut -f2 -d '=') TLS=$($EXPR_BIN $NEW_TLS - $OLD_TLS) if [ $TLS -gt 0 ]; then @@ -106,7 +106,7 @@ echo -n # SSL Logins ###################### echo -en "login_ssl.value " -NEW_SSL=$(egrep 'dovecot.*Login.*SSL' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) +NEW_SSL=$(egrep '[dovecot]?.*Login.*SSL' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) OLD_SSL=$(grep SSL $STAT_FILE | cut -f2 -d '=') SSL=$($EXPR_BIN $NEW_SSL - $OLD_SSL) if [ $SSL -gt 0 ]; then @@ -119,7 +119,7 @@ echo -n # IMAP Logins ###################### echo -en "login_imap.value " -NEW_IMAP=$(egrep 'dovecot.*imap.*Login' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) +NEW_IMAP=$(egrep '[dovecot]?.*imap.*Login' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) OLD_IMAP=$(grep IMAP $STAT_FILE | cut -f2 -d '=') IMAP=$($EXPR_BIN $NEW_IMAP - $OLD_IMAP) if [ $IMAP -gt 0 ]; then @@ -132,7 +132,7 @@ echo -n # POP3 Logins ###################### echo -en "login_pop3.value " -NEW_POP3=$(egrep 'dovecot.*pop3.*Login' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) +NEW_POP3=$(egrep '[dovecot]?.*pop3.*Login' $LOGFILE | grep "`date '+%b %e'`" | sort | wc -l) OLD_POP3=$(grep POP3 $STAT_FILE | cut -f2 -d '=') POP3=$($EXPR_BIN $NEW_POP3 - $OLD_POP3) if [ $POP3 -gt 0 ]; then diff --git a/plugins/minecraft/minecraft-users b/plugins/minecraft/minecraft-users index a1038962..7e4861f3 100755 --- a/plugins/minecraft/minecraft-users +++ b/plugins/minecraft/minecraft-users @@ -1,32 +1,36 @@ -#!/bin/sh +#!/usr/local/bin/ruby # Config: -# [minecraft_players -# playerfile /etc/minecraft/players.txt -# subtract true +# [minecraft_users] +# env.host awesomeserver.com +# env.port 25566 # -# playerfile - location of player list file, for example from the OnlineUsers -# plugin -# subtract - OnlineUsers has a header above the user list, set this to true -# to subtract 1 from the output to compensate -case $1 in - config) - cat <<'EOM' -graph_title Connected players -graph_vlabel players -players.label players -graph_info Number of players connected to Minecraft -graph_category Minecraft -EOM - exit 0;; -esac -echo -n "players.value " +require 'socket' -count=`wc -l ${playerfile} | cut -d' ' -f1` -if [ $subtract="true" ]; -then - echo -n "$(($count - 1))" -else - echo $count -fi +if ARGV[0] == 'config' + puts "graph_title Connected players" + puts "graph_vlabel players" + puts "players.label players" + puts "graph_info Number of players connected to Minecraft" + puts "graph_category Minecraft" + exit +end +host = ENV['host'] +host = 'localhost' unless host + +port = ENV['port'] +port = '25566' unless port + +socket = TCPSocket.new(host, port) +socket.puts "QUERY" + +response = socket.read +response = response.split("\n") + +server_port = response[0].split(" ", 2)[1].to_i +player_count = response[1].split(" ", 2)[1].to_i +max_players = response[2].split(" ", 2)[1].to_i +player_list = response[3].split(" ", 2)[1].chomp[1..-2] + +puts "players.value #{player_count}" \ No newline at end of file diff --git a/plugins/network/arris-tm502g_ b/plugins/network/arris-tm502g_ new file mode 100755 index 00000000..8e1bf35d --- /dev/null +++ b/plugins/network/arris-tm502g_ @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# -*- python -*- + +# This plugin graphs the following values of the ARRIS TM502G cable +# modem: +# +# * upstream and downstream powers +# * downstream signal-to-noise ratio +# +# The values are retrieved from the cable modem's status web pages at +# 192.168.100.1. So, this plugin must be installed on a munin node +# which can access those pages. +# +# Symlink this plugin into the node's plugins directory (like +# /etc/munin/plugins) as arris-tm502g_power for the powers, and +# arris-tm502g_snr for the SNR. +# +# Author: Kenyon Ralph +# +# The latest version of this plugin can be found in the munin contrib +# repository at https://github.com/munin-monitoring/contrib. Issues +# with this plugin may be reported there. Patches accepted through the +# normal github process of forking the repository and submitting a +# pull request with your commits. + +import html.parser +import os +import urllib.request +import sys + +plugin_name=list(os.path.split(sys.argv[0]))[1] +plugin_var=plugin_name.split('_', 1)[-1] + +if len(sys.argv) == 2 and sys.argv[1] == 'config': + if plugin_var == 'power': + print('graph_title ARRIS Cable Modem Power') + print('graph_vlabel Signal Strength (dBmV)') + print('graph_info This graph shows the downstream and upstream power reported by an ARRIS TM502G cable modem.') + print('downstream.label Downstream Power (dBmV)') + print('upstream.label Upstream Power (dBmV)') + if plugin_var == 'snr': + print('graph_title ARRIS Cable Modem SNR') + print('graph_vlabel Signal-to-Noise Ratio (dB)') + print('graph_info This graph shows the downstream signal-to-noise ratio reported by an ARRIS TM502G cable modem.') + print('snr.label Signal-to-Noise Ratio (dB)') + print('graph_category network') + sys.exit(0) + +class ArrisHTMLParser(html.parser.HTMLParser): + stats = list() + down_power = 'U' + up_power = 'U' + snr = 'U' + def handle_data(self, data): + data = data.strip() + if data != '' and 'dB' in data: + self.stats.append(data) + def done(self): + """Call this when done feeding the HTML page to the parser.""" + self.down_power = self.stats[0].split()[0] + self.snr = self.stats[1].split()[0] + self.up_power = self.stats[2].split()[0] + +page = urllib.request.urlopen("http://192.168.100.1/") +parser = ArrisHTMLParser() +for line in page: + parser.feed(line.decode()) +parser.done() + +if plugin_var == 'power': + print('downstream.value ' + parser.down_power) + print('upstream.value ' + parser.up_power) + sys.exit(0) + +if plugin_var == 'snr': + print('snr.value ' + parser.snr) + sys.exit(0) diff --git a/plugins/network/ifem_ b/plugins/network/ifem_ index 63d4d522..89d42bd2 100755 --- a/plugins/network/ifem_ +++ b/plugins/network/ifem_ @@ -2,9 +2,9 @@ # # Wildcard-plugin to monitor FreeBSD em(4) and igb(4) network interfaces # using sysctl dev.em.0.mac_stats 64-bit counters -# To monitor an # interface, link if_ to this file. E.g. +# To monitor an # interface, link ifem_ to this file. E.g. # -# ln -s /usr/share/munin/node/plugins-auto/if_ /etc/munin/node.d/if_em0 +# ln -s /usr/share/munin/node/plugins-auto/ifem_ /etc/munin/node.d/ifem_em0 # # ...will monitor em0. # diff --git a/plugins/network/ipfwcnt_ b/plugins/network/ipfwcnt_ index 58a41242..fff98860 100755 --- a/plugins/network/ipfwcnt_ +++ b/plugins/network/ipfwcnt_ @@ -2,11 +2,11 @@ # # Copyright (C) 2009 Alexey Illarionov # -# Wildcard plugin to monitor ipfw rules counters +# Wildcard plugin to monitor ipfw rules counters # Usage: # # Method 1: -# +# # Link ipfwcnt_ to this file. E.g. # # ln -s ipfwcnt_ ipfwcnt_100 @@ -28,19 +28,19 @@ # 3. Add rules configuration to plugins.conf: # [ipfwcnt_rl0-in] # user root -# env rules group0 group1 group2 nogroup -# env rule_group0 100 -# env rule_group0_label group0 -# env rule_group0_info Incoming traffic of group 0 -# env rule_group1 200 -# env rule_group1_label group1 -# env rule_group1_info Incoming traffic of group 1 -# env rule_group2 300 -# env rule_group2_label group2 -# env rule_group2_info Incoming traffic of group 2 -# env rule_nogroup 400 -# env rule_nogroup_label nogroup -# env rule_nogroup_info Incoming traffic of no group +# env.rules group0 group1 group2 nogroup +# env.rule_group0 100 +# env.rule_group0_label group0 +# env.rule_group0_info Incoming traffic of group 0 +# env.rule_group1 200 +# env.rule_group1_label group1 +# env.rule_group1_info Incoming traffic of group 1 +# env.rule_group2 300 +# env.rule_group2_label group2 +# env.rule_group2_info Incoming traffic of group 2 +# env.rule_nogroup 400 +# env.rule_nogroup_label nogroup +# env.rule_nogroup_info Incoming traffic of no group # # ... will monitor ipfw rules 100,200,300,400 # diff --git a/plugins/network/netstat_bsd_ b/plugins/network/netstat_bsd_/netstat_bsd_ similarity index 100% rename from plugins/network/netstat_bsd_ rename to plugins/network/netstat_bsd_/netstat_bsd_ diff --git a/images/network/netstat_bsd_.png b/plugins/network/netstat_bsd_/netstat_bsd_.png similarity index 100% rename from images/network/netstat_bsd_.png rename to plugins/network/netstat_bsd_/netstat_bsd_.png diff --git a/plugins/networker/nsr_device_writing b/plugins/network/nsr_device_writing similarity index 100% rename from plugins/networker/nsr_device_writing rename to plugins/network/nsr_device_writing diff --git a/plugins/network/openvpn_clients b/plugins/network/openvpn_clients deleted file mode 100755 index 522f5598..00000000 --- a/plugins/network/openvpn_clients +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/perl -# -# Copyright (C) 2005-2007 Rodolphe Quiedeville -# -# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# If you improve this script please send your version to my email address -# with the copyright notice upgrade with your name. -# -# Munin's plugin to monitor number of clients connected to openvpn server -# -# Usage: copy or link into /etc/munin/plugins -# -# Parameters: -# -# config (required) -# autoconf (optional - used by munin-config) -# -# $Log$ -# Revision 1.2 2007/01/17 15:57:19 rodo -# Correct family -# -# Revision 1.1 2005/10/11 14:12:19 Rodolphe Quiedeville -# -# Magic markers (optinal - used by munin-config and some installation -# scripts): -# -#%# family=auto -#%# capabilities=autoconf - -use strict; - -my $statuslogfile = "/etc/openvpn/openvpn-status.log"; -my $clients = 0; - -if($ARGV[0] and $ARGV[0] eq "autoconf" ) { - if(-f $statuslogfile) { - if(-r $statuslogfile) { - print "yes\n"; - exit 0; - } else { - print "no (logfile not readable)\n"; - } - } else { - print "no (logfile not found)\n"; - } - exit 1; -} - -if ($ARGV[0] and $ARGV[0] eq "config" ){ - print "graph_title OpenVpn\n"; - print "graph_args --base 1000 -l 0\n"; - print "graph_scale yes\n"; - print "graph_vlabel clients\n"; - print "graph_category network\n"; - print "graph_info This graph shows the numbers of clients connected to openvpn server.\n"; - print "clients.label clients\n"; - print "clients.info The number of clients connected to openvpn server\n"; - exit 0; -} - -if (-f "$statuslogfile") { - open(IN, "$statuslogfile") or exit 4; - my $flagu = 0; - while() { - if(/^ROUTING TABLE$/) { - $flagu = 0; - } - if ($flagu) { - $clients = $clients + 1; - } - if(/^Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since$/) { - $flagu = 1; - } - } - close(IN); -} - -print "clients.value " . $clients."\n"; diff --git a/plugins/openvpn/openvpn_multi b/plugins/network/openvpn_multi similarity index 100% rename from plugins/openvpn/openvpn_multi rename to plugins/network/openvpn_multi diff --git a/plugins/network/tor-bandwidth-usage b/plugins/network/tor-bandwidth-usage index 316d57ca..b1871ade 100755 --- a/plugins/network/tor-bandwidth-usage +++ b/plugins/network/tor-bandwidth-usage @@ -60,9 +60,13 @@ sub Authenticate if (open(COOKIE, "<$ENV{cookiefile}")) { my $cookie; binmode COOKIE; - read(COOKIE, $cookie, 32); + $authline .= " "; + while (read(COOKIE, $cookie, 32)) { + foreach my $byte (unpack "C*", $cookie) { + $authline .= sprintf "%02x", $byte; + } + } close COOKIE; - $authline .= ' "' . $cookie . '"'; } } elsif (defined($ENV{password})) { $authline .= ' "' . $ENV{password} . '"'; diff --git a/plugins/other/jmx b/plugins/other/jmx deleted file mode 100755 index 0f4820b2..00000000 Binary files a/plugins/other/jmx and /dev/null differ diff --git a/plugins/other/jmx2munin b/plugins/other/jmx2munin deleted file mode 100755 index ccae5568..00000000 Binary files a/plugins/other/jmx2munin and /dev/null differ diff --git a/plugins/postfix/postfix_mailqueue_ b/plugins/postfix/postfix_mailqueue_ new file mode 100755 index 00000000..5ee84556 --- /dev/null +++ b/plugins/postfix/postfix_mailqueue_ @@ -0,0 +1,138 @@ +#!/bin/sh +# -*- sh -*- + +: << =cut + +=head1 NAME + +postfix_mailqueue_ - Plugin to monitor postfix mail spools per running postfix + +=head1 ABOUT + +A guide to postfix mail queue manageent can be found at +L + +A summary: + +=over 4 + +=item maildrop + +Messages that have been submitted via the Postfix sendmail(1) command, +but not yet brought into the main Postfix queue by the pickup(8) +service. + +=item hold + +Messages placed in the "hold" queue stay there until the administrator +intervenes + +=item incoming + +Inbound mail from the network, or mail picked up by the local +pickup(8) daemon from the maildrop directory. + +=item active + +Messages that the queue manager has opened for delivery. Only a limited number +of messages is allowed to enter the active queue (leaky bucket strategy, for a +fixed delivery rate). + +=item deferred + +Mail that could not be delivered upon the first attempt. The queue manager +implements exponential backoff by doubling the time between delivery attempts. + +=item corrupt + +Unreadable or damaged queue files are moved here for inspection. + +=back + +=head1 CONFIGURATION + +Uses the last part of the symlink name to get the postfix queue directory for the config file. +It then extract the queue path from the configuration file and uses it as a spooldir. +A environment spooldir can be set as a fallback. + + [postfix_mailqueue] + env.spooldir /var/spool/postfix + +=head1 AUTHOR + +Unknown. + +Extended to multiple queue use by Clemens Schwaighofer (gullevek@gullevek.org) in 2010. + +=head1 LICENSE + +Unknown. + +=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 + +# atempt to get spooldir via postconf, but environment overrides. + +# Remember that postconf is not available unless postfix is. +CONFIG=${0##*postfix_mailqueue_} +CONFIG="/etc/"$CONFIG"/" +POSTCONFSPOOL="$(postconf -c $CONFIG -h queue_directory 2>/dev/null || echo /var/spool/postfix)" +SPOOLDIR=${spooldir:-$POSTCONFSPOOL} + +. $MUNIN_LIBDIR/plugins/plugin.sh + +case $1 in + autoconf|detect) + if [ -d $SPOOLDIR ] ; then + echo yes + exit 0 + else + echo "no (spooldir not found)" + exit 0 + fi + ;; + config) + echo "graph_title Postfix Mailqueue $CONFIG"; + cat <<'EOF' +graph_vlabel Mails in queue +graph_category postfix +graph_total Total +active.label active +deferred.label deferred +maildrop.label maildrop +incoming.label incoming +corrupt.label corrupt +hold.label held +EOF + for field in active deferred maildrop incoming corrupt hold; do + print_warning $field + print_critical $field + done + exit 0 + ;; +esac + +cd $SPOOLDIR >/dev/null 2>/dev/null || { + echo "# Cannot cd to $SPOOLDIR" + exit 1 +} + +cat < =~ /^sum:(\d+)/) +# { +# $sum = $1; +# } +# while () +# { +# if (/^([0-9a-z.\-]+):(\d+)$/) +# { +# $status->{$1} = $2; +# } +# } +# close IN; +#} + +if (! -d $configdir) +{ + print "sum.value U\n"; + foreach my $i (@status_list) + { + print "r$i.value U\n"; + } + exit 0; +} + + +parseLogfile($configdir); + +if ($ARGV[0] and $ARGV[0] eq "config") +{ + # descriptions for the rrd file + my %descriptions = ( + 'crefused' => 'Connection refused', + 'ctimeout' => 'Connection timed out', + 'rtimeout' => 'Lost connection', + 'refusedtalk' => 'Host refused connection', + 'nohost' => 'Host not found', + 'msrefused' => 'Mail service refused', + 'noroute' => 'Route not found', + 'usernotfound' => 'User not found', + 'err450' => '450 mailbox not okay (REJECT)', + 'err452' => '452 mailbox is full', + 'err421' => '421 service not okay (REJECT)', + 'err421a' => '421 service not okay (REJECT, SB)', + 'err4' => 'General 4xx error', + 'lostc' => 'Lost connection', + 'active' => 'Active running', + 'other' => 'Other error' + ); + + + print "graph_title Postfix mailqueue log for $postfix\n"; + print "graph_args --base 1000 -l 0\n"; # numbers not bytes + print "graph_vlabel Mails in Queue log\n"; + print "graph_scale no\n"; # so we do not print "micro, milli, kilo, etc" +# print "graph_total Total\n"; + print "graph_category postfix\n"; + foreach my $i (@status_list) + { + if ($descriptions{$i}) + { + print "r$i.label ".$descriptions{$i}."\n"; + print "r$i.type GAUGE\n"; + print "r$i.draw ".(!$field ? 'AREA' : 'STACK')."\n"; + print "r$i.min 0\n"; + $field = 'AREA'; + } + } + print "sum.label Sum\n"; + print "sum.type GAUGE\n"; + print "sum.draw LINE2\n"; + print "sum.min 0\n"; + exit 0; +} + +print "sum.value $sum\n"; +foreach my $i (@status_list) +{ + print "r$i.value ".($status->{$i} ? $status->{$i} : 0)."\n"; +} + +#if(-l $statefile) { +# die("$statefile is a symbolic link, refusing to touch it."); +#} +#open (OUT, '>', $statefile) or die "Unable to open statefile: $!\n"; +#print OUT "sum:$sum\n"; +#foreach my $i (@status_list) +#{ +# print OUT "$i:".($status->{$i} ? $status->{$i} : 0)."\n"; +#} +#close OUT; + +sub parseLogfile +{ + my ($fname) = @_; + + # the search parts + %search = ( + 'crefused' => 'Connection refused', + 'ctimeout' => 'Connection timed out', + 'rtimeout' => 'read timeout', + 'refusedtalk' => 'refused to talk to me: 554', + 'nohost' => 'Host not found', + 'msrefused' => 'server refused mail service"', + 'noroute' => 'No route to host', + 'usernotfound' => 'address rejected', + 'err450' => ': 450 ', + 'err452' => ': 452 ', + 'err421' => ': 421 ', + 'err421a' => ': 421)', + 'err4' => 'said: 4', + 'lostc' => 'lost connection with', + ); + + my $command = "mailq -C $fname"; + + open(FILE, "$command|") || die ("Cannot open $command: $!"); + while () + { + if (/^\w{10,}\*\s/o) + { + $status->{'active'} ++; + } + elsif (/^\s*\(/o) + { + $set = 0; + foreach $i (@status_list) + { + if ($search{$i} && index($_, $search{$i}) >= 0 && !$set) + { + $status->{$i} ++; + $set = 1; + } + } + if (!$set) + { + $status->{'other'} ++; + } + } + } + close(FILE) || die ("Cannot close $command: $!"); + + foreach $i (keys %{$status}) + { + $sum += $status->{$i}; + } +} + +# vim:syntax=perl diff --git a/plugins/postfix/postfix_mailstats_ b/plugins/postfix/postfix_mailstats_ new file mode 100755 index 00000000..a77e73fc --- /dev/null +++ b/plugins/postfix/postfix_mailstats_ @@ -0,0 +1,208 @@ +#!/usr/bin/perl -w +# -*- perl -*- + +=head1 NAME + +postfix_mailstats_ - Plugin to monitor the number of mails delivered and +rejected by postfix + +=head1 CONFIGURATION + +Uses the last part of the symlink name for grepping the correct data from the +postfix log file. The name must be syslog_name from the postfix config. +The environment settings still applay to this plugin. + +Configuration parameters for /etc/munin/postfix_mailstats_, +if you need to override the defaults below: + + [postfix_mailstats] + env.logdir - Which logfile to use + env.logfile - What file to read in logdir + +=head2 DEFAULT CONFIGURATION + + [postfix_mailstats] + env.logdir /var/log + env.logfile mail.log + +=head1 AUTHOR + +Records show that the plugin was contributed by Nicolai Langfeldt in +2003. Nicolai can't find anything in his email about this and expects +the plugin is based on the corresponding exim plugin - to which it now +bears no resemblence. + +Extended for multiple queue use by Clemens Schwaighofer (gullevek@gullevek.org) in 2010. + +=head1 LICENSE + +GPLv2 + +=head1 MAGIC MARKERS + +=begin comment + +These magic markers are used by munin-node-configure when installing +munin-node. + +=end comment + + #%# family=manual + #%# capabilities=autoconf + +=head1 RANDOM COMMENTS + +Would be cool if someone ported this to Munin::Plugin for both state +file and log tailing. + +=cut + +# get the postfix queue number to look for +$0 =~ /postfix_mailstats_([\w\d]+)$/; +my $postfix = $1; +my $statefile = "$ENV{MUNIN_PLUGSTATE}/munin-plugin-".$postfix."_mailstats.state"; +my $pos; +my $delivered = 0; +my $rejects = {}; +my $LOGDIR = $ENV{'logdir'} || '/var/log'; +my $LOGFILE = $ENV{'logfile'} || 'mail.log'; + +my $logfile = "$LOGDIR/$LOGFILE"; + +if ($ARGV[0] and $ARGV[0] eq "autoconf") +{ + my $logfile; + if (-d $LOGDIR) + { + 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 (could not find logdir '$LOGDIR')\n"; + } + + exit 0; +} + + +if (-f $statefile) +{ + open (IN, '<', $statefile) or die "Unable to open state-file: $!\n"; + if ( =~ /^(\d+):(\d+)/) + { + ($pos, $delivered) = ($1, $2); + } + while () + { + if (/^([0-9a-z.\-]+):(\d+)$/) + { + $rejects->{$1} = $2; + } + } + close IN; +} + +if (! -f $logfile) +{ + print "delivered.value U\n"; + foreach my $i (sort keys %{$rejects}) + { + print "r$i.value U\n"; + } + exit 0; +} + +$startsize = (stat $logfile)[7]; + +if (!defined $pos) +{ + # Initial run. + $pos = $startsize; +} + +parseLogfile($logfile, $pos, $startsize); +$pos = $startsize; + +if ( $ARGV[0] and $ARGV[0] eq "config" ) +{ + print "graph_title Postfix message throughput for $postfix\n"; + print "graph_args --base 1000 -l 0\n"; + print "graph_vlabel mails / \${graph_period}\n"; + print "graph_scale no\n"; + print "graph_total Total\n"; + print "graph_category postfix\n"; + print "delivered.label delivered\n"; + print "delivered.type DERIVE\n"; + print "delivered.draw AREA\n"; + print "delivered.min 0\n"; + foreach my $i (sort keys %{$rejects}) + { + print "r$i.label reject $i\n"; + print "r$i.type DERIVE\n"; + print "r$i.draw STACK\n"; + print "r$i.min 0\n"; + } + exit 0; +} + +print "delivered.value $delivered\n"; +foreach my $i (sort keys %{$rejects}) +{ + print "r$i.value ", $rejects->{$i}, "\n"; +} + +if (-l $statefile) +{ + die ("$statefile is a symbolic link, refusing to touch it."); +} +open (OUT, '>', $statefile) or die "Unable to open statefile: $!\n"; +print OUT "$pos:$delivered\n"; +foreach my $i (sort keys %{$rejects}) +{ + print OUT "$i:", $rejects->{$i}, "\n"; +} +close OUT; + +sub parseLogfile +{ + my ($fname, $start, $stop) = @_; + open (LOGFILE, $fname) + or die "Unable to open logfile $fname for reading: $!\n"; + seek (LOGFILE, $start, 0) + or die "Unable to seek to $start in $fname: $!\n"; + + while (tell (LOGFILE) < $stop) + { + my $line = ; + chomp ($line); + + if ($line =~ /qmgr.*from=.*size=[0-9]*/ || + $line =~ /$postfix\/smtp.* status=sent /) + { + $delivered++; + } + elsif ($line =~ /$postfix\/smtpd.*reject: \S+ \S+ \S+ (\S+)/ || + $line =~ /$postfix\/cleanup.* reject: (\S+)/) + { + $rejects->{$1}++; + } + } + close(LOGFILE) or warn "Error closing $fname: $!\n"; +} + +# vim:syntax=perl diff --git a/plugins/postfix/postfix_mailvolume_multi b/plugins/postfix/postfix_mailvolume_multi new file mode 100755 index 00000000..4054d4a6 --- /dev/null +++ b/plugins/postfix/postfix_mailvolume_multi @@ -0,0 +1,220 @@ +#!/usr/bin/perl -w +# -*- perl -*- + +=head1 NAME + +postfix_mailvolume - Plugin to monitor the volume of mails delivered + by multiple postfix and stores per postfix delivered data. + +=head1 APPLICABLE SYSTEMS + +Any postfix. + +=head1 CONFIGURATION + +The following shows the default configuration. + + [postfix*] + env.logdir /var/log + env.logfile syslog + +=head2 Needed additional configuration + +To correctly get all the postfix log data, the postfix system_log prefix names need to be defined with the env.postfix config setting. +If this is not set, the script tries to find all the postfix config folders in /etc/postfix* and get the syslog names from there + + env.postfix postfix10 postfix11 postfix12 + +=head1 INTERPRETATION + +The plugin shows the number of bytes of mail that has passed through +the postfix installation per postfix mailer running. + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=head1 BUGS + +None known + +=head1 VERSION + + $Id: postfix_mailvolume.in 2314 2009-08-03 11:28:34Z ssm $ + +=head1 AUTHOR + +Copyright (C) 2011. + +Clemens Schwaighofer (gullevek@gullevek.org) + +=head1 LICENSE + +GPLv2 + +=cut + +use strict; +use Munin::Plugin; + +my $pos = undef; +my $syslog_name = ''; +my @postfix_syslog_name = (); +my %volume = (); +my @restore_state = (); +my $i = 1; +my $LOGDIR = $ENV{'logdir'} || '/var/log'; +my $LOGFILE = $ENV{'logfile'} || 'syslog'; +my $POSTFIX = $ENV{'postfix'} || ''; +# get the postfix syslog_name from the POSTFIX env var, if not set, find them in the /etc/postfix* type +if (!$POSTFIX) +{ + foreach my $dir (grep -d, glob "/etc/postfix*") + { + # remove the leading etc + $dir =~ s/\/etc\///g; + # add data to the postfix string + $POSTFIX .= ' ' if ($POSTFIX); + $POSTFIX .= $dir; + } +} +if ($POSTFIX) +{ + foreach my $config (split(/ /, $POSTFIX)) + { + # find the syslog name + $syslog_name = `postconf -c /etc/$config | grep "syslog_name"`; + # remove any pending whitespace or line breaks + chomp($syslog_name); + $syslog_name =~ s/syslog_name = //g; + # add this to the postfix syslog name array + push(@postfix_syslog_name, $syslog_name); + # also init set the syslog name 0 + $volume{$syslog_name} = 0; + } +} +else +{ + print "Cannot get any postfix syslog_name data\n"; + exit 1; +} + +sub parseLogfile +{ + my ($fname, $start) = @_; + + my ($LOGFILE, $rotated) = tail_open($fname, $start); + + my $line; + + while ($line =<$LOGFILE>) + { + chomp ($line); + # get the postfix syslog name and the size + if ($line =~ /\ (\w+)\/qmgr.*from=.*size=([0-9]+)/) + { + $volume{$1} += $2; + } + } + 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 per postfix\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 "graph_total Throughput sum\n"; + # loop through the postfix names and create per config an entry + foreach $syslog_name (@postfix_syslog_name) + { + print $syslog_name."_volume.label ".$syslog_name." throughput\n"; + print $syslog_name."_volume.type DERIVE\n"; + print $syslog_name."_volume.min 0\n"; + } + exit 0; +} + + +my $logfile = "$LOGDIR/$LOGFILE"; + +if (! -f $logfile) { + print "delivered.value U\n"; + exit 1; +} + +@restore_state = restore_state(); +# first is pos, rest is postfix entries +$pos = $restore_state[0]; +# per postfix values are store: postfix config,value +for ($i = 1; $i < @restore_state; $i ++) +{ + my ($key, $value) = split(/,/, $restore_state[$i]); + $volume{$key} = $value; +} + +if (!$pos) +{ + # 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 + foreach $syslog_name (@postfix_syslog_name) + { + $volume{$syslog_name} = 0; + } +} +else +{ + $pos = parseLogfile($logfile, $pos); +} + +@restore_state = ($pos); +foreach $syslog_name (sort keys %volume) +{ + print $syslog_name."_volume.value ".$volume{$syslog_name}."\n"; + push(@restore_state, $syslog_name.','.$volume{$syslog_name}); +} + +# save the current state +save_state(@restore_state); + +# vim:syntax=perl + +__END__ diff --git a/plugins/postgresql/pgbouncer_ b/plugins/postgresql/pgbouncer_ new file mode 100755 index 00000000..610c4834 --- /dev/null +++ b/plugins/postgresql/pgbouncer_ @@ -0,0 +1,298 @@ +#!/usr/bin/perl -w + +# re-write of python version of pgbouncer stats +# data from stats, pools (cleint, server) + +use strict; +use Munin::Plugin; +use DBD::Pg; + +# check that multigraph is avaailable +need_multigraph(); +# get the script name +my $plugin_name = $Munin::Plugin::me; +# set the DB connection vars +my $db_user = $ENV{'pgbouncer_user'} || 'postgres'; +my $db_port = $ENV{'pgbouncer_port'} || '6432'; +my $db_host = $ENV{'pgbouncer_host'} || 'localhost'; +my $db_pass = $ENV{'pgbouncer_pass'} || ''; +my $db_name = 'pgbouncer'; +my @data = (); +# get the DB (pool) name we want to fetch +$plugin_name =~ /pgbouncer_(.*)$/; +my $pool_name = $1; +# bail if no name +if (!$pool_name) +{ + print "Cannot get pool name\n"; + exit 1; +} + +# command line arguments for autconf and config +if (defined($ARGV[0])) +{ + # autoconf, nothing to do + if ($ARGV[0] eq 'autoconf') + { + my $dbh = DBI->connect("DBI:Pg:dbname=$db_name;host=$db_host;port=$db_port", $db_user, $db_pass); + if (!$dbh) + { + print "no\n"; + exit 1; + } + else + { + print "yes\n"; + exit 0; + } + $dbh->disconnect(); + } + + if ($ARGV[0] eq 'config') + { + # create the basic RRD + # stats: average connections + print "multigraph ".$plugin_name."_stats_avg_req\n"; + print "graph_title PgBouncer $pool_name average connections\n"; + print "graph_args --base 1000\n"; # numbers not bytes + print "graph_vlabel Average connections\n"; + print "graph_scale no\n"; # so we do not print "micro, milli, kilo, etc" + print "graph_category pgbouncer\n"; + print $pool_name."_avg_req.type GAUGE\n"; + print $pool_name."_avg_req.label Avg Req\n"; + print $pool_name."_avg_req.min 0\n"; + print $pool_name."_avg_req.draw LINE2\n"; + # stats: average time for query + print "multigraph ".$plugin_name."_stats_avg_query\n"; + print "graph_title PgBouncer $pool_name average query time\n"; + print "graph_args --base 1000\n"; # numbers not bytes + print "graph_vlabel Average time per query (microseconds)\n"; + print "graph_category pgbouncer\n"; + print $pool_name."_avg_query.type GAUGE\n"; + print $pool_name."_avg_query.label Avg Time\n"; + print $pool_name."_avg_query.min 0\n"; + print $pool_name."_avg_query.draw LINE2\n"; + # stats: in/out bytes + print "multigraph ".$plugin_name."_stats_bytesinout\n"; + print "graph_title PgBouncer $pool_name average bytes received/sent\n"; + print "graph_args --base 1024\n"; # numbers in bytes + print "graph_vlabel Average bytes received (-)/sent (+)\n"; + print "graph_category pgbouncer\n"; + # bytes received + print $pool_name."_avg_recv.type GAUGE\n"; + print $pool_name."_avg_recv.label Avg received\n"; + print $pool_name."_avg_recv.min 0\n"; + print $pool_name."_avg_recv.draw LINE1\n"; + print $pool_name."_avg_recv.graph no\n"; + # bytes sent + print $pool_name."_avg_sent.type GAUGE\n"; + print $pool_name."_avg_sent.label Avg rcvd/sent\n"; + print $pool_name."_avg_sent.min 0\n"; + print $pool_name."_avg_sent.draw LINE1\n"; + print $pool_name."_avg_sent.negative ".$pool_name."_avg_recv\n"; + # pools: server (sv_) + print "multigraph ".$plugin_name."_pools_server\n"; + print "graph_title PgBouncer $pool_name servers\n"; + print "graph_category pgbouncer\n"; + print "graph_args --base 1000\n"; # numbers not bytes + print "graph_vlabel Server connections\n"; + print "graph_scale no\n"; + # active connections + print $pool_name."_server_active.label active\n"; + print $pool_name."_server_active.min 0\n"; + print $pool_name."_server_active.type GAUGE\n"; + print $pool_name."_server_active.draw AREA\n"; + # idle connections + print $pool_name."_server_idle.label idle\n"; + print $pool_name."_server_idle.min 0\n"; + print $pool_name."_server_idle.type GAUGE\n"; + print $pool_name."_server_idle.draw STACK\n"; + # used connections + print $pool_name."_server_used.label used\n"; + print $pool_name."_server_used.min 0\n"; + print $pool_name."_server_used.type GAUGE\n"; + print $pool_name."_server_used.draw STACK\n"; + # tested connections + print $pool_name."_server_tested.label tested\n"; + print $pool_name."_server_tested.min 0\n"; + print $pool_name."_server_tested.type GAUGE\n"; + print $pool_name."_server_tested.draw STACK\n"; + # logged in connections + print $pool_name."_server_login.label login\n"; + print $pool_name."_server_login.min 0\n"; + print $pool_name."_server_login.type GAUGE\n"; + print $pool_name."_server_login.draw STACK\n"; + # pools: client (cl_) + print "multigraph ".$plugin_name."_pools_client\n"; + print "graph_title PgBouncer $pool_name clients\n"; + print "graph_category pgbouncer\n"; + print "graph_args --base 1000\n"; # numbers not bytes + print "graph_vlabel Client connections\n"; + print "graph_scale no\n"; + # active client connections + print $pool_name."_client_active.label active\n"; + print $pool_name."_client_active.min 0\n"; + print $pool_name."_client_active.type GAUGE\n"; + print $pool_name."_client_active.draw AREA\n"; + # waiting client connections + print $pool_name."_client_waiting.label waiting\n"; + print $pool_name."_client_waiting.min 0\n"; + print $pool_name."_client_waiting.type GAUGE\n"; + print $pool_name."_client_waiting.draw STACK\n"; + # pools: maxwait (longest waiting connection, should be 0) + print "multigraph ".$plugin_name."_pools_maxwait\n"; + print "graph_title PgBouncer $pool_name maximum waiting time\n"; + print "graph_args --base 1000\n"; # numbers not bytes + print "graph_vlabel Maximum wait time (seconds)\n"; + print "graph_category pgbouncer\n"; + print $pool_name."_maxwait.type GAUGE\n"; + print $pool_name."_maxwait.label Wait Time\n"; + print $pool_name."_maxwait.min 0\n"; + print $pool_name."_maxwait.draw LINE2\n"; + print $pool_name."_maxwait.warning 1\n"; # warn if not 0 + print $pool_name."_maxwait.critical 10\n"; # go critical if 10 seconds waiting + # END graph + exit 0; + } +} + +# connect to data +my $dbh = DBI->connect("DBI:Pg:dbname=$db_name;host=$db_host;port=$db_port", $db_user, $db_pass) + or die ("Cannot connect to database"); +# go trough each set and get the data +foreach my $get ('pools', 'stats') +{ + # prep and execute the show query + my $pre = $dbh->prepare("SHOW $get") + or die ("Cannot prepare query"); + $pre->execute() + or die ("Cannot execute statement") + while (@data = $pre->fetchrow) + { + # first defines the pool + if ($data[0] eq $pool_name) + { + # print values for the stats: average reqeust, average query time, bytes in/out + if ($get eq 'stats') + { + print "multigraph ".$plugin_name."_".$get."_avg_req\n"; + print $pool_name."_avg_req.value ".$data[5]."\n"; + print "multigraph ".$plugin_name."_".$get."_avg_query\n"; + print $pool_name."_avg_query.value ".$data[8]."\n"; + print "multigraph ".$plugin_name."_".$get."_bytesinout\n"; + print $pool_name."_avg_recv.value ".$data[6]."\n"; + print $pool_name."_avg_sent.value ".$data[7]."\n"; + } + # print data for the pools: server, client + if ($get eq 'pools') + { + print "multigraph ".$plugin_name."_".$get."_server\n"; + print $pool_name."_server_active.value ".$data[4]."\n"; + print $pool_name."_server_idle.value ".$data[5]."\n"; + print $pool_name."_server_used.value ".$data[6]."\n"; + print $pool_name."_server_tested.value ".$data[7]."\n"; + print $pool_name."_server_login.value ".$data[8]."\n"; + print "multigraph ".$plugin_name."_".$get."_client\n"; + print $pool_name."_client_active.value ".$data[2]."\n"; + print $pool_name."_client_waiting.value ".$data[3]."\n"; + print "multigraph ".$plugin_name."_".$get."_maxwait\n"; + print $pool_name."_maxwait.value ".$data[9]."\n"; + } + } + } +} +# close connection +$dbh->disconnect(); + +exit 0; + +__END__ + +=head1 NAME + +pgbouncer_ is a plugin to get the pool and stat values for a single pgbouncer pool name + +=head1 APPLICATION + +perl and DBD::Pg is required, and pgbounce must been installed with a correct setup access for a stat account + +=head1 CONFIGURATION + +the plugin that will be run needs to have the pool name after the plguin base name. + +=head2 plugin configuration + +eg: pgbouncer_foo will run for the pool named foo. + +see SHOW POOLS database list for the pool name + +=head2 munin plugin config file + +in the plugin config file under the [pgbouncer] name the access information ca be set. + +eg: + [pgbouncer*] + env.pgbouncer_pass barfoo + +more extended would be: + [pgbouncer*] + env.pgbouncer_pass barfoo + env.pgbouncer_user bar + env.pgbouncer_port 6542 + env.pgbouncer_host localhost + +The database name is always pgbouncer + +=head1 OUTPUT + +The plugin will output 5 graphs in the group pgbouncer + +=head2 Average bytes received/sent + +This graph will show the average bytes sent and received by the pgbouncer for this pool + +=head2 Avaerage connections + +This graph will show the average amount of connections to the pgbouncer for this pool + +=head2 Average query time + +This graph shows the average query time as processed by the pgbouncer for this pool in microseconds. The data will be shorted by standard SI. eg, m = milli, k = kilo. + +So 4.61K is 4610 milliseconds + +=head2 Client connections + +This graph shows the active and waiting client connections to pgbouncer for this pool + +=head2 Server connections + +This graph shows the server connections to pgbouncer for this pool. The following data sets are shown: active, idle, used, tested, login + +=head2 Max wait + +how long the oldest cllient the queue has waited, should be always 0 + +=head1 ACKNOWLEDGEMENTS + +Original idea derived from a simple python script by Dimitri Fontaine + +=head1 SEE ALSO + +See further info on stats and pools on the pgbouncer homepage: + http://pgbouncer.projects.postgresql.org/doc/usage.html#_show_commands + +=head1 VERSION + +1.0 + +=head1 AUTHOR + +Clemens Schwaighofer + +=head1 LICENSE + +GPLv2 + + +=cut diff --git a/plugins/postgresql/postgres_size_detail_ b/plugins/postgresql/postgres_size_detail_ new file mode 100755 index 00000000..520b3aac --- /dev/null +++ b/plugins/postgresql/postgres_size_detail_ @@ -0,0 +1,83 @@ +#!/usr/bin/perl +# -*- cperl -*- +# +# Copyright (C) 2012, Clemens Schwaighofer +# +# 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 NAME + +postgres_size_detail_ - Plugin to monitor PostgreSQL database size for one database in detail (data, index, sequence, view) + +=head1 CONFIGURATION + +Configuration is done through libpq environment variables, for example +PGUSER, PGDATABASE, etc. For more information, see L. + +To monitor a specific database, link to postgres_size_. +This script cannot monitor all database detail sizes at the same time, a valid database name needs to be given. + +=head1 SEE ALSO + +L + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf suggest + +=head1 AUTHOR + +Magnus Hagander , Redpill Linpro AB + +=head1 COPYRIGHT/License. + +Copyright (c) 2012, Clemens Schwaighofer (gullevek@gullevek.org) + +All rights reserved. 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. + +=cut + +use strict; +use warnings; + +use Munin::Plugin::Pgsql; + +my $pg = Munin::Plugin::Pgsql->new( + basename => 'postgres_size_detail_', + title => 'PostgreSQL detail database size', + info => 'Size of database in detail', + vlabel => 'Size', + paramdatabase => 1, + basequery => "SELECT CASE WHEN relkind = 'r' OR relkind = 't' THEN 'db_detail_data' WHEN relkind = 'i' THEN 'db_detail_index' WHEN relkind = 'v' THEN 'db_detail_view' WHEN relkind = 'S' THEN 'db_detail_sequence' ELSE 'db_detail_other' END AS state, + SUM(relpages::bigint * 8 * 1024) AS size + FROM pg_class pg, pg_namespace pgn WHERE pg.relnamespace = pgn.oid AND pgn.nspname NOT IN ('information_schema', 'pg_catalog') GROUP BY state", + configquery => [ + "VALUES ('db_detail_data','Data size'),('db_detail_index','Index size'),('db_detail_sequence','Sequence size'),('db_detail_view','View size'),('db_detail_other','Other size')", + ], + suggestquery => + "SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate AND NOT datname='postgres' UNION ALL SELECT 'ALL' ORDER BY 1 LIMIT 10", + stack => 1, + graphmin => 0, + graphdraw => 'AREA', + extraconfig => 'graph_total Total' +); + +$pg->Process(); + diff --git a/plugins/processes/processes b/plugins/processes/processes deleted file mode 100755 index c6986646..00000000 --- a/plugins/processes/processes +++ /dev/null @@ -1,282 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2006 Lars Strand -# -# Munin-plugin to monitor processes on Linux -# FreeBSD, OpenBSD, NetBSD, Solaris and OSX. -# -# Require 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.) -# -# -# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# -# Parameters understood: -# -# config (required) -# autoconf (optional - used by munin-config) -# -# $Log$ -# -# -# -# Magick markers (optional - used by munin-config and som installation -# scripts): -# -#%# family=auto -#%# capabilities=autoconf - -# Search for program in $PATH unless predefined. -[ $awk ] || awk="awk" -[ $ps ] || ps="ps" - -# Find operating system -[ $OPERSYS ] || OPERSYS=`uname` || exit 1 - -if [ "$1" = "autoconf" ]; then - echo yes - exit 0 -fi - -# Define colours -RUNNABLE='22ff22' # Green -SLEEPING='0022ff' # Blue -STOPPED='cc0000' # Darker red -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 (runable) -# S - Linux, SunOS, FreeBSD*, OpenBSD*, NetBSD*, OSX* (sleeping) -# T - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX (stopped) -# Z - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX (zombie) -# D - Linux, FreeBSD, OpenBSD, NetBSD (uninterruptible) -# I - FreeBSD, OpenBSD, NetBSD, OSX (idle) -# W - Linux*, FreeBSD* (paging/interrupt) -# L - FreeBSD (lock) -# O - SunOS (running) -# X - Linux (dead) -# U - OSX, NetBSD* (uninterruptible/suspended) -# *) 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" - - # OS specific flags - if [ "$OPERSYS" = "Linux" ]; then - echo "graph_order sleeping stopped 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." - echo "paging.label paging" - echo "paging.draw STACK" - echo "paging.colour $PAGING" - echo "paging.info The number of paging processes (<2.6 kernels only)." - - 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." - # Be backwards compatible. - echo "total.label total" - echo "total.draw LINE1" - echo "total.colour $TOTAL" - echo "total.info The total number of processes." - - 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." - echo "interrupt.label interrupt" - echo "interrupt.draw STACK" - echo "interrupt.colour $INTERRUPT" - echo "interrupt.info The number of idle interrupt threads." - - 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." - - 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)." - 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)." - 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." - else - echo "sleeping.label sleeping" - echo "sleeping.draw AREA" - echo "sleeping.colour $SLEEPING" - echo "sleeping.info The number of sleeping processes." - 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)." - fi - - # Common flags - 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)." - - echo "stopped.label stopped" - echo "stopped.draw STACK" - echo "stopped.colour $STOPPED" - echo "stopped.info The number of stopped or traced processes." - - echo "runnable.label runnable" - echo "runnable.draw STACK" - echo "runnable.colour $RUNNABLE" - echo "runnable.info The number of runnable processes (on the run queue)." - - 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." - fi - - exit 0 -fi - -if [ "$OPERSYS" = "Linux" ]; then - $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 "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 - $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 - $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. - $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. - $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. - $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"]; -}' -fi diff --git a/plugins/redis/resque b/plugins/redis/resque new file mode 100755 index 00000000..4abbfde9 --- /dev/null +++ b/plugins/redis/resque @@ -0,0 +1,136 @@ +#!/usr/bin/perl + +# +## Copyright (C) 2012 Andrey Pankov +## +## 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## +## $Log$ +## +## Based on resque monitoring code from https://github.com/caius/redis-munin +## +## Installation process: +## +## 1. Download the plugin to your plugins directory (e.g. /usr/share/munin/plugins) +## 2. Create 4 symlinks at the directory that is used by munin for plugins detection (e.g. /etc/munin/plugins): resque_failed, resque_queues, resque_workers_count, resque_workers_working +## 3. Edit plugin-conf.d/munin-node if it is needed (env.host and env.port variables are accepted) +## 4. Restart munin-node service + +use strict; +use Redis; + +my $HOST = exists $ENV{'host'} ? $ENV{'host'} : "127.0.0.1"; +my $PORT = exists $ENV{'port'} ? $ENV{'port'} : 6379; + +my $server = "$HOST:$PORT"; +my $r = Redis->new( server => sprintf("%s:%d", $HOST, $PORT) ); + +my $config = ( defined $ARGV[0] and $ARGV[0] eq "config" ); + + +$0 =~ s/(.+)resque_//g; +my $opt = $0 ? $0 : 'default'; + +if ($opt eq 'failed') { + if ($config) { + print "graph_title Resque Failure rate\n"; + print "graph_category Resque\n"; + print "graph_info This graph shows resque jobs that failed\n"; + print "graph_args --lower-limit 0\n"; + print 'graph_vlabel fails/s\n'; + + print "failed.label Failed per/s\n"; + print "failed.type COUNTER\n"; + } + else { + my $value = $r->get('resque:stat:failed') || 0; + print "failed.value $value\n"; + } +} +elsif ($opt eq 'queues') { + if ($config) { + print "graph_title Resque queue rates\n"; + print "graph_category Resque\n"; + print "graph_vlabel queue rates/s\n"; + print "graph_info This graph monitors the in and out rate of the queues\n"; + print "graph_args --lower-limit 0\n"; + + my @queues = $r->smembers( 'resque:queues' ); + for my $name (@queues) { + $name =~ s/:/_/; + + print "${name}_pushed.label ${name}_pushed\n"; + print "${name}_pushed.type COUNTER\n"; + + print "${name}_finished.label ${name}_finished\n"; + print "${name}_finished.type COUNTER\n"; + } + } + else { + my @queues = $r->smembers( 'resque:queues' ); + for my $queue (@queues) { + my $name = $queue; + $name =~ s/:/_/; + + my $pushed = $r->get("resque:stat:${queue}:pushed") || 0; + print "${name}_pushed.value ${pushed}\n"; + + my $finished = $r->get("resque:stat:${queue}:finished") || 0; + print "${name}_finished.value ${finished}\n"; + } + } +} +elsif ($opt eq 'workers_count') { + if ($config) { + print "graph_title Resque Workers Count\n"; + print "graph_category Resque\n"; + print "graph_info This graph shows number of resque workers\n"; + print "graph_args --lower-limit 0\n"; + print "graph_vlabel workers\n"; + + print "workers_count.label No. of workers\n"; + print "workers_count.type COUNTER\n"; + } + else { + my @workers = $r->smembers('resque:workers'); + print "workers_count.value " . (scalar @workers) . "\n"; + } +} +elsif ($opt eq 'workers_working') { + if ($config) { + print "graph_title Resque Workers in use\n"; + print "graph_category Resque\n"; + print "graph_info This graph shows the \%age of resque workers busy\n"; + print "graph_args --lower-limit 0 --upper-limit 100\n"; + print "graph_vlabel %\n"; + + print "workers_working.label Workers Busy\n"; + print "workers_working.type GAUGE\n"; + } + else { + my @workers = $r->smembers('resque:workers'); + my $working = 0; + for my $worker (@workers) { + my $value = $r->get("worker:$worker") || 0; + $working++ if $value; + } + my $value = scalar @workers; + if ($value) { + $value = $working * 100 / $value; + } + print "workers_working.value $value\n"; + } +} diff --git a/plugins/sensors/cleware b/plugins/sensors/cleware index ac183cc1..0e26e3b6 100755 --- a/plugins/sensors/cleware +++ b/plugins/sensors/cleware @@ -1,15 +1,26 @@ #!/bin/bash -# Written by Lars Falk-Petersen, cleware@falk-petersen.no -# Version 0.3. To be used with http://www.vanheusden.com/clewarecontrol/ +# Written by Lars Falk-Petersen +# Version 0.4. See https://github.com/ways +# To be used with http://www.vanheusden.com/clewarecontrol/ # Clewarecontrol device serial must be set in config file: # #Put the following lines in /etc/munin/plugin-conf.d/cleware -# [cleware*] -# #device serial. find it by running: clewarecontrol -l -# env.serial 7778 -# #path of clewarecontrol -# env.bin /usr/bin/clewarecontrol +# [cleware*] +# #device serial. find it by running: clewarecontrol -l +# env.serial 7778 +# #path of clewarecontrol +# env.bin /usr/bin/clewarecontrol +# +# Munin must be able to read and write to the device. +# Run the following as root: +# # chgrp munin /dev/usb/hiddev0 +# # chmod g+rw /dev/usb/hiddev0 +# +# If that works, you have to make it permanent. On Ubuntu: +# Create this file: /etc/udev/rules.d/99-hidraw-permissions.rules +# #Allow munin to read hidraws from Cleware +# SUBSYSTEM=="usb", KERNEL=="hiddev*", GROUP="munin", MODE="0660" case $1 in config) @@ -32,11 +43,10 @@ if [ ! $serial ]; then exit 1 fi -$bin -c 1 -ag > /dev/null 2>&1 +$bin -d $serial -c 1 -ag > /dev/null 2>&1 echo -n "temp.value " $bin -d $serial -b -c 1 -rt echo -n "hum.value " $bin -d $serial -b -c 1 -rh - diff --git a/plugins/sensors/freeipmi_ b/plugins/sensors/freeipmi_ index ab6860f8..101fea4f 100755 --- a/plugins/sensors/freeipmi_ +++ b/plugins/sensors/freeipmi_ @@ -1,6 +1,6 @@ #!/usr/bin/python # -# Copyright (C) 2011 Andreas Thienemann +# Copyright (C) 2011,2012 Andreas Thienemann # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -40,11 +40,13 @@ likely no bmc to query. In certain cases however bmc-info will just seem to hang for quite some time. In this case, autodetection does not work because the smbios table has -incorrect information. One system know to experience this problem is the +incorrect information. One system known to experience this problem is the HP Proliant Microserver. Adding env.freeipmi_args "--no-probing --driver-type=KCS --driver-address=0xca2 --register-spacing=1" -to the munin plugin configuration will make the plugin work. +to the munin plugin configuration will make the plugin work. This is the +specific line for the HP Proliant Microserver mentioned above. Your mileage +may vary. Basic configuration for every system is that the plugin needs to be called as root. diff --git a/plugins/sensors/snmp__areca_ b/plugins/sensors/snmp__areca_ new file mode 100755 index 00000000..dbb41454 --- /dev/null +++ b/plugins/sensors/snmp__areca_ @@ -0,0 +1,292 @@ +#!/usr/bin/python + +# Copyright (C) 2009 - 2012 Andreas Thienemann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +""" +=head1 NAME + +snmp__areca_ - Munin plugin to get temperature, voltage or fan speed values +from Areca network enabled RAID controllers via SNMP. + +=head1 APPLICABLE SYSTEMS + +All machines with a SNMP capable ARECA raid controller. + +=head1 CONFIGURATION + +Make sure your Areca controller is accessible via SNMP from the munin host: +snmpwalk -v 1 -c snmp_community ip.add.re.ss + +The plugin is a wildcard plugin and can thus be used to retrieve different +values depending on the name of the file. + +Linking it as snmp_10.8.1.230_areca_fan would retrieve the fan speed values from +the Areca controller at 10.8.1.230. + +Valid values are fan, temp and volt. + +Add the following to your /etc/munin/plugin-conf.d/snmp__areca file: + + [snmp_ip.add.re.ss_*] + community private + version 1 + +Then test the plugin by calling the following commands: + +munin-run snmp_10.8.1.230_areca_temp config +munin-run snmp_10.8.1.230_areca_temp + +Both commands should output sensible data without failing. + +=head1 INTERPRETATION + +The plugin shows the temperature in Celsius or the fanspeed in rotations per minute. + +=head1 MAGIC MARKERS + + #%# family=contrib + #%# capabilities= + +=head1 VERSION + +0.0.1 + +=head1 BUGS + +None known. If you know of any, please raise a ticket at https://trac.bawue.org/munin/wiki/areca__snmp_ + +=head1 AUTHOR + +Andreas Thienemann + +=head1 LICENSE + +GPLv2 + +=cut +""" + +import pprint +import time +import sys +import re +import os +from pysnmp import v1, v2c, role, asn1 + +request_conf = { + "volt" : { + "label" : "Voltages", + "vlabel" : "Volt", + "graph" : "--base 1000 --logarithmic", + "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.8" + }, + "fan" : { + "label" : "Fans", + "vlabel" : "RPM", + "graph" : "--base 1000 -l 0", + "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.9" + }, + "temp" : { + "label" : "Temperatures", + "vlabel" : "Celsius", + "graph" : "--base 1000 -l 0", + "oid" : ".1.3.6.1.4.1.18928.1.2.2.1.10" + } +} + +# Sanity check and parsing of the commandline +host = None +port = 161 +request = None +try: + match = re.search("^(?:|.*\/)snmp_([^_]+)_areca_(.+)$", sys.argv[0]) + host = match.group(1) + request = match.group(2) + match = re.search("^([^:]+):(\d+)$", host) + if match is not None: + host = match.group(1) + port = match.group(2) +except: + pass + +if host is None or request is None: + print "# Error: Incorrect filename. Cannot parse host or request." + sys.exit(1) + +# Parse env variables +if os.getenv("community") is not None: + community = os.getenv("community") +else: + community = "public" +if os.getenv("version") is not None: + version = os.getenv("version") +else: + version = "1" + + +def get_data(): + # Fetch the data + results = snmpwalk(request_conf[request]["oid"]) + + # parse data + vals = [] + for i in range(0, len(results)): + idx, res = results[i][0].split(request_conf[request]["oid"])[1].split(".")[1:], results[i][1] + if idx[1] == '1': + vals.append([]) + vals[int(idx[2])-1].append(res) + if idx[1] == '2': + vals[int(idx[2])-1].append(res) + if idx[1] == '3': + if request == "volt": + res = float(res)/1000 + vals[int(idx[2])-1].append(res) + + return vals + +def snmpwalk(root): + + # Create SNMP manager object + client = role.manager((host, port)) + + # Create a SNMP request&response objects from protocol version + # specific module. + try: + req = eval('v' + version).GETREQUEST() + nextReq = eval('v' + version).GETNEXTREQUEST() + rsp = eval('v' + version).GETRESPONSE() + + except (NameError, AttributeError): + print '# Unsupported SNMP protocol version: %s\n%s' % (version, usage) + sys.exit(-1) + + # Store tables headers + head_oids = [root] + + encoded_oids = map(asn1.OBJECTID().encode, head_oids) + + result = []; + + while 1: + + # Encode OIDs, encode SNMP request message and try to send + # it to SNMP agent and receive a response + (answer, src) = client.send_and_receive(req.encode(community=community, encoded_oids=encoded_oids)) + + # Decode SNMP response + rsp.decode(answer) + + # Make sure response matches request (request IDs, communities, etc) + if req != rsp: + raise 'Unmatched response: %s vs %s' % (str(req), str(rsp)) + + # Decode BER encoded Object IDs. + oids = map(lambda x: x[0], map(asn1.OBJECTID().decode, rsp['encoded_oids'])) + + # Decode BER encoded values associated with Object IDs. + vals = map(lambda x: x[0](), map(asn1.decode, rsp['encoded_vals'])) + + # Check for remote SNMP agent failure + if rsp['error_status']: + # SNMP agent reports 'no such name' when walk is over + if rsp['error_status'] == 2: + # Switch over to GETNEXT req on error + # XXX what if one of multiple vars fails? + if not (req is nextReq): + req = nextReq + continue + # One of the tables exceeded + for l in oids, vals, head_oids: + del l[rsp['error_index']-1] + else: + raise 'SNMP error #' + str(rsp['error_status']) + ' for OID #' + str(rsp['error_index']) + + # Exclude completed OIDs + while 1: + for idx in range(len(head_oids)): + if not asn1.OBJECTID(head_oids[idx]).isaprefix(oids[idx]): + # One of the tables exceeded + for l in oids, vals, head_oids: + del l[idx] + break + else: + break + + if not head_oids: + return result + + # Print out results + for (oid, val) in map(None, oids, vals): + result.append((oid, str(val))) + # print oid + ' ---> ' + str(val) + + # BER encode next SNMP Object IDs to query + encoded_oids = map(asn1.OBJECTID().encode, oids) + + # Update request object + req['request_id'] = req['request_id'] + 1 + + # Switch over GETNEXT PDU for if not done + if not (req is nextReq): + req = nextReq + + raise "error" + + +def print_config(): + print "graph_title " + request_conf[request]["label"] + print "graph_vlabel " + request_conf[request]["vlabel"] + print "graph_args " + request_conf[request]["graph"] + print "graph_category sensors" + print "host_name", host + for dataset in get_data(): + if request == "volt": + if dataset[1] == "Battery Status": + continue + else: + print request + dataset[0] + ".label", dataset[1] + ref_val = float(dataset[1].split()[-1][:-1]) + print request + dataset[0] + ".warning", str(ref_val * 0.95) + ":" + str(ref_val * 1.05) + print request + dataset[0] + ".critical", str(ref_val * 0.80) + ":" + str(ref_val * 1.20) + if request == "temp": + print request + dataset[0] + ".label", dataset[1] + if dataset[1].startswith("CPU"): + print request + dataset[0] + ".warning", 55 + print request + dataset[0] + ".critical", 60 + if dataset[1].startswith("System"): + print request + dataset[0] + ".warning", 40 + print request + dataset[0] + ".critical", 45 + if request == "fan": + print request + dataset[0] + ".label", dataset[1] + print request + dataset[0] + ".warning", 2400 + print request + dataset[0] + ".critical", 2000 + + sys.exit(0) + + +if "config" in sys.argv[1:]: + print_config() +elif "snmpconf" in sys.argv[1:]: + print "require 1.3.6.1.4.1.18928.1.2.2.1.8.1.1" + sys.exit(0) +else: + for dataset in get_data(): + # Filter Battery Status (255 == Not installed) + if request == "volt" and dataset[1] == "Battery Status": + continue + print request + dataset[0] + ".value", dataset[2] + sys.exit(0) diff --git a/plugins/sensors/snmp__thecus_fans b/plugins/sensors/snmp__thecus_fans new file mode 100755 index 00000000..ec43e277 --- /dev/null +++ b/plugins/sensors/snmp__thecus_fans @@ -0,0 +1,104 @@ +#!/usr/bin/perl -w +# -*- cperl -*- + +=head1 NAME + +snmp__thecus_fans - Munin plugin to retrive fanspeed readings from a Thecus +NAS device running SNMP. + +=head1 APPLICABLE SYSTEMS + +All Thecus NAS devices which have the third party NETSNMPD module installed. +This is available at http://www.fajo.de/main/thecus/modules/netsnmpd + +=head1 CONFIGURATION + +As a rule SNMP plugins need site specific configuration. The default +configuration (shown here) will only work on insecure sites/devices. + + [snmp_*] + env.version 2 + env.community public + +In general SNMP is not very secure at all unless you use SNMP version +3 which supports authentication and privacy (encryption). But in any +case the community string for your devices should not be "public". + +Please see 'perldoc Munin::Plugin::SNMP' for further configuration +information. + +=head1 INTERPRETATION + +The plugin reports the current fan speed readings as reported by the thecusIO +module. + +=head1 MIB INFORMATION + +Private MIB + +=head1 MAGIC MARKERS + + #%# family=snmpauto + #%# capabilities=snmpconf + +=head1 VERSION + + 0.0.20120307 + +=head1 BUGS + +None known. + +=head1 AUTHOR + +Copyright (C) 2011 - 2012 Andreas Thienemann + +Based on the snmp__uptime plugin as a template. + +=head1 LICENSE + +GPLv2 or (at your option) any later version. + +=cut + +use strict; +use Munin::Plugin::SNMP; +use vars qw($DEBUG); + +$DEBUG = $ENV{'MUNIN_DEBUG'}; + +my $response; + +if (defined $ARGV[0] and $ARGV[0] eq "snmpconf") { + print "index 1.3.6.1.4.1.14822.101.21.\n"; + print "require 1.3.6.1.4.1.14822.101.21.5200.1.1.0 [0-9]\n"; + print "require 1.3.6.1.4.1.14822.101.21.5200.1.2.0 [0-9]\n"; + exit 0; +} + +if (defined $ARGV[0] and $ARGV[0] eq "config") { + my ($host) = Munin::Plugin::SNMP->config_session(); + print "host_name $host\n" unless $host eq 'localhost'; + print "graph_title Thecus Fans +graph_args --base 1000 -l 0 +graph_vlabel RPM +graph_info This graph shows the RPMs of the fans as reported by the ThecusIO module. +graph_category sensors +fan1.label Fan 1 +fan1.info Thecus CPU Fan +fan2.label Fan 2 +fan2.info Thecus System Fan +"; + exit 0; +} + +my $session = Munin::Plugin::SNMP->session(-translate => + [ -timeticks => 0x0 ]); + +my $fan1 = $session->get_single ("1.3.6.1.4.1.14822.101.21.5200.1.1.0") || 'U'; +my $fan2 = $session->get_single ("1.3.6.1.4.1.14822.101.21.5200.1.2.0") || 'U'; + +#print "Retrived uptime is '$uptime'\n" if $DEBUG; + +print "fan1.value ", $fan1, "\n"; +print "fan2.value ", $fan2, "\n"; diff --git a/plugins/system/buddyinfo b/plugins/system/buddyinfo deleted file mode 100755 index fe8b454d..00000000 --- a/plugins/system/buddyinfo +++ /dev/null @@ -1,146 +0,0 @@ -#! /usr/bin/perl -w - -=head1 NAME - -buddyinfo - Plugin to monitor memory fragmentation on Linux systems. - -=head1 APPLICABLE SYSTEMS - -Linux 2.6 - -=head1 CONFIGURATION - -None needed. - -=head1 INTERPRETATION - -Linux manages virtual memory on a page granularity. There are some operations -however that require physically contiguous pages to be allocated by the kernel. -Such allocations may fail if the memory gets fragmented and even though there -are enough pages free, but they are not contiguous. - -This plugin monitors the amount of contiguous areas, called higher order pages. -The order means the exponent of two of the size of the area, so order 2 means -2^2 = 4 pages. - -=head1 SEE ALSO - -See C in the Linux source tree for the -description of the buddyinfo file. - -=head1 MAGIC MARKERS - - #%# family=manual - #%# capabilities=autoconf - -=head1 AUTHOR - -Gábor Gombás - -=head1 LICENSE - -GPLv2 or later - -=cut - -use strict; -use Munin::Plugin; -use POSIX; -use Carp; - -need_multigraph(); - -if ($ARGV[0] and $ARGV[0] eq 'autoconf') { - if (-f "/proc/buddyinfo") { - print "yes\n"; - } - else { - print "no (/proc/buddyinfo is missing)\n"; - } - exit 0; -} - -# The most common page size is 4k, but it is not universal -my $pagesize = POSIX::sysconf(&POSIX::_SC_PAGESIZE) / 1024; - -my $zones = {}; - -open(FH, '< /proc/buddyinfo') - or croak "Failed to open '/proc/buddyinfo': $!"; -while (my $line = ) { - chomp $line; - - $line =~ m/Node (\d+), zone\s+(\S+)\s+(\S.*)$/; - my $name = "Node $1, zone $2"; - my @cnt = split(/ +/, $3); - $zones->{$name} = \@cnt; -} -close FH; - -my $totals = []; -foreach my $zone (keys %$zones) { - for my $i (0 .. $#{$zones->{$zone}}) { - $totals->[$i] += $zones->{$zone}->[$i] - } -} - -sub do_config { - print "multigraph buddyinfo\n"; - print "graph_title Memory fragmentation\n"; - print "graph_args --base 1024 --lower-limit 0\n"; - print "graph_vlabel pages free\n"; - print "graph_category system\n"; - print "graph_info This graph shows the number of free pages of different size\n"; - print "graph_order " . join(' ', map { "order$_" } (0 .. $#{$totals})) . "\n"; - for my $i (0 .. $#{$totals}) { - print "order$i.label Order $i\n"; - print "order$i.info Number of order $i (" . ($pagesize * 2 ** $i) . " KiB) pages\n"; - print "order$i.type GAUGE\n"; - print "order$i.draw LINE2\n"; - print "order$i.min 0\n"; - } - for my $zone (sort keys %$zones) { - my $zoneid = $zone; - $zoneid = clean_fieldname($zone); - - print "multigraph buddyinfo.$zoneid\n"; - print "graph_title Memory fragmentation in $zone\n"; - print "graph_args --base 1024 --lower-limit 0\n"; - print "graph_vlabel pages free\n"; - print "graph_category system\n"; - print "graph_info This graph shows the number of free pages in $zone\n"; - print "graph_order " . - join(' ', map { "order$_" } (0 .. $#{$zones->{$zone}})) . "\n"; - for my $i (0 .. $#{$zones->{$zone}}) { - print "order$i.label Order $i\n"; - print "order$i.info Number of order $i (" . - ($pagesize * 2 ** $i) . " KiB) pages\n"; - print "order$i.type GAUGE\n"; - print "order$i.draw LINE2\n"; - print "order$i.min 0\n"; - } - } -} - -sub do_fetch { - print "multigraph buddyinfo\n"; - for my $i (0 .. $#{$totals}) { - print "order$i.value " . $totals->[$i] . "\n"; - } - for my $zone (sort keys %$zones) { - my $zoneid = $zone; - $zoneid =~ tr/ ,/__/; - - print "multigraph buddyinfo.$zoneid\n"; - for my $i (0 .. $#{$zones->{$zone}}) { - print "order$i.value " . $zones->{$zone}->[$i] . "\n"; - } - } -} - -if ($ARGV[0] and $ARGV[0] eq 'config') { - do_config(); - exit 0; -} - -do_fetch(); diff --git a/plugins/system/cpu b/plugins/system/cpu deleted file mode 100755 index 20d43dcf..00000000 --- a/plugins/system/cpu +++ /dev/null @@ -1,974 +0,0 @@ -#!/usr/bin/perl -w -# -*- perl -*- - -=head1 NAME - -cpu - Plugin to monitor CPU usage and frequencies. - -=head1 APPLICABLE SYSTEMS - -All Linux systems, but read below section - -=head1 CONFIGURATION - -The plugin automatically selects which graphics drawing. -Charts related to the frequencies of processors depends on the settings of the kernel: -Power management and ACPI options -> CPU Frequency scaling -> CPU frequency translation statistics -> Advanced statistics - -=head2 WARNING AND CRITICAL SETTINGS - -You can set warning and critical levels for each of the data -series the plugin reports. The following environment variables are -used as default for all fields: - - env.warning - env.critical - -But each field (system user nice etc...) can be controlled separately: -For example: - - env.system_warning 70 - env.user_warning 70 - env.idle_critical 1 - -Also each field of each cpu can be controlled separately -For example: - - env.cpu1_system_warning 70 - env.cpu0_user_warning 70 - env.cpu0_idle_critical 1 - -Algoritm is easy: for example current graph limit is env.cpu0_idle_critical if defined env.cpu0_idle_critical or env.idle_critical if defined env.idle_critical -or env.critical if defined env.critical. Or no limit - -=head1 INTERPRETATION - -The plugin shows each cpu usage in percent, shows the CPU frequency, -frequency shift frequencies, the percentage of use of frequencies - -=head1 MAGIC MARKERS - - #%# family=auto - #%# capabilities=autoconf - - -=head1 VERSION - - 2.0 - -=head1 BUGS - -none known - -=head1 AUTHOR - -Gorlow Maxim aka Sheridan (email and jabber) - -=head1 LICENSE - -GPLv2 - -=cut - -use strict; -use warnings; -use Munin::Plugin; -use Data::Dumper; -my @cnames = qw(user nice system idle iowait irq softirq steal guest); -my $stat_file = "/proc/stat"; -my $freq_path = "/sys/devices/system/cpu"; -my $limits = {}; -my $cpuinfo = {}; $cpuinfo->{'cpu_count'} = 0; -my @stat_file_content = (); -my $freq_mul = 1000; # CPU frequency multiplier from kHz to Hz - - - -my $graphs = -{ - 'cpu_utilisation' => # multigraph - { - 'title' => 'CPU:t: utilisation', - 'args' => '--base 1000 -r --lower-limit 0 --upper-limit 100', - 'vlabel' => '%', - 'scale' => 'no', - 'info' => 'This graph shows how CPU:t: time is spent :i:' - }, - 'cpu_all' => # single - { - 'title' => 'All CPU utilisation', - 'args' => '--base 1000 -r --lower-limit 0 --upper-limit 100', - 'vlabel' => '%', - 'scale' => 'no', - 'info' => 'This graph shows how CPU time is spent on each processor', - 'category' => 'cpu' - }, - 'cpu_freq_trans' => # multi - { - 'title' => 'CPU frequency transitions', - 'args' => '--base 1000', - 'vlabel' => 'count', - 'scale' => 'no', - 'info' => 'This graph shows CPU transitions of each processor', - }, - 'cpu_freq' => # child of cpu_freq_trans - { - 'title' => 'CPU:t: frequency (total)', - 'args' => '--base 1000 -r --lower-limit 0 --upper-limit 100', - 'vlabel' => '% of total', - 'info' => 'This graph shows CPU:t: frequency :i:', - 'category' => 'cpu' - }, - 'cpu_freq_ps' => # child of cpu_freq_trans - { - 'title' => 'CPU:t: frequency (per secund)', - 'args' => '--base 1000 -r --lower-limit 0 --upper-limit 100', - 'vlabel' => '% per secund', - 'info' => 'This graph shows CPU:t: frequency per secund from last update :i:', - 'category' => 'cpu' - }, - 'cpu_freq_trans_table' => # child of cpu_freq_trans - { - 'title' => 'CPU:t: frequency switches (total)', - 'args' => '--base 1000 -r --lower-limit 0 --upper-limit 100', - 'vlabel' => '% of total', - 'scale' => 'no', - 'info' => 'This graph shows CPU:t: frequency switches :i:', - }, - 'cpu_freq_trans_table_ps' => # child of cpu_freq_trans - { - 'title' => 'CPU:t: frequency switches (per secund)', - 'args' => '--base 1000 -r --lower-limit 0 --upper-limit 100', - 'vlabel' => '% per secund', - 'scale' => 'no', - 'info' => 'This graph shows CPU:t: frequency switches per secund from last update :i:', - } -}; - -my $transparent = 'CC'; -my $fields = -{ - 'user' => - { - 'label' => 'User', - 'info' => 'Normal processes executing in user mode', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'nice' => - { - 'label' => 'Nice', - 'info' => 'Niced processes executing in user mode', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'system' => - { - 'label' => 'System', - 'info' => 'Processes executing in kernel mode', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'idle' => - { - 'label' => 'Idle', - 'info' => 'Twiddling thumbs', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'colour'=> 'FFFFDD'.$transparent, - 'min' => 0, - 'max' => 100 - }, - 'iowait' => - { - 'label' => 'I/O wait', - 'info' => 'Waiting for I/O to complete', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'irq' => - { - 'label' => 'IRQ', - 'info' => 'Servicing interrupts', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'softirq' => - { - 'label' => 'Software IRQ', - 'info' => 'Servicing software interrupts', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'steal' => - { - 'label' => 'Steal', - 'info' => 'Involuntary wait', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'guest' => - { - 'label' => 'Guest', - 'info' => 'Running a guest', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'cpu_util' => - { - 'label' => ':t:', - 'info' => ':t: utilisation', - 'type' => 'GAUGE', - 'draw' => 'LINE0.5', - 'min' => 0, - 'max' => 100 - }, - 'freq_percent' => - { - 'label' => 'CPU:t: frequency', - 'info' => 'CPU:t: frequency percent', - 'type' => 'GAUGE', - 'draw' => 'LINE0.5', - 'min' => 0, - 'max' => 100 - }, - 'freq_hz' => - { - 'label' => ':t:', - 'info' => 'CPU :t: frequency', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - }, - 'freq_trans' => - { - 'label' => ':t:', - 'info' => ':t: frequency transitions', - 'type' => 'GAUGE', - 'draw' => 'LINE0.5', - 'min' => 0 - }, - 'freq_trans_table' => - { - 'label' => ':t:', - 'info' => ':t: frequency switch', - 'type' => 'GAUGE', - 'draw' => 'AREASTACK', - 'min' => 0, - 'max' => 100 - } - -}; - - - -# ----------------- main ---------------- -load_cpuinfo(); -need_multigraph(); -remove_unavialabled_counters(); - -if (defined($ARGV[0]) and ($ARGV[0] eq 'autoconf')) -{ - printf("%s\n", -e $stat_file ? "yes" : "no (stats not exists)"); - exit (0); -} - -if (defined($ARGV[0]) and ($ARGV[0] eq 'config')) -{ - print_config(); - exit (0); -} - -print_values(); -#print Dumper prepare_graphs_fields(); -exit(0); - -# ----------------- sub's ---------------- - -# ----------------------- trim whitespace at begin and end of string ------------ -sub trim -{ - my($string)=@_; - for ($string) { s/^\s+//; s/\s+$//; } - return $string; -} - - -my $items_exists = {}; -sub check_exists -{ - my ($t, $cpu_num) = @_[0..1]; - if (defined($cpu_num)) - { - unless (exists($items_exists->{$t}{$cpu_num})) - { - if ($t eq 'freq_hz') { $items_exists->{$t}{$cpu_num} = ( -e sprintf("%s/cpu%s/cpufreq/scaling_min_freq" , $freq_path, $cpu_num) and - -e sprintf("%s/cpu%s/cpufreq/scaling_cur_freq" , $freq_path, $cpu_num) and - -e sprintf("%s/cpu%s/cpufreq/scaling_max_freq" , $freq_path, $cpu_num)); } - elsif ($t eq 'freq_trans') { $items_exists->{$t}{$cpu_num} = -e sprintf("%s/cpu%s/cpufreq/stats/time_in_state", $freq_path, $cpu_num); } - elsif ($t eq 'freq_times') { $items_exists->{$t}{$cpu_num} = -e sprintf("%s/cpu%s/cpufreq/stats/total_trans" , $freq_path, $cpu_num); } - elsif ($t eq 'freq_ttable') { $items_exists->{$t}{$cpu_num} = -e sprintf("%s/cpu%s/cpufreq/stats/trans_table" , $freq_path, $cpu_num); } - } - return $items_exists->{$t}{$cpu_num}; - } - else - { - unless(exists($items_exists->{$t}{'total'})) - { - my $c = 0; - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) { $c++ if (check_exists($t, $i)); } - $items_exists->{$t}{'total'} = $c > 0; - } - return $items_exists->{$t}{'total'}; - } -} - -# ------------------------- remove unavialable fields from graph -------------------------- -sub remove_unavialabled_counters -{ - my @cpu = split(/\s+/, (grep(/cpu\s/, get_stat_file_content()))[0]); - my $counters_count = scalar(@cpu) - 3; - @cnames = @cnames[0..$counters_count]; -} - -# ----------------------- get sysfs file content ---------------- -my $fcontents = {}; -sub get_sys_file_content -{ - my $file = $_[0]; - return 'nan' if (-z $file); - unless (exists($fcontents->{$file})) - { - open (FH, '<', $file) or die "$! $file \n"; - $fcontents->{$file} = ; - close (FH); - chomp $fcontents->{$file}; - } - return $fcontents->{$file}; -} - -# -------------------------- loading cpu info --------------------------------- -sub load_cpuinfo -{ - my $file = "/proc/cpuinfo"; - open (FH, '<', $file) or die "$! $file \n"; - my $cpu_num = -1; - for my $line () - { - chomp $line; - $cpu_num++ if $line =~ m/^processor\s+:/; - $cpuinfo->{$cpu_num}{'name'} = trim((split(/:/,$line))[1]) if $line =~ m/^model name\s+:/; - $cpuinfo->{$cpu_num}{'bogomips'} = trim((split(/:/,$line))[1]) if $line =~ m/^bogomips\s+:/; - if (not exists($cpuinfo->{$cpu_num}{'info'}) and exists($cpuinfo->{$cpu_num}{'name'}) and exists($cpuinfo->{$cpu_num}{'bogomips'})) - { - $cpuinfo->{$cpu_num}{'info'} = sprintf("[%s (%s bogomips)]", $cpuinfo->{$cpu_num}{'name'}, $cpuinfo->{$cpu_num}{'bogomips'}) ; - } - } - close (FH); - $cpuinfo->{'cpu_count'} = $cpu_num+1; -} - - -# -------------------------- loading stat file lines --------------------------------- -sub get_stat_file_content -{ - if(scalar(@stat_file_content) == 0) - { - open (FH, '<', $stat_file) or die "$! $stat_file \n"; - for () - { - next unless $_ =~ m/cpu/; - chomp $_; - push(@stat_file_content, $_); - } - close (FH); - } - return @stat_file_content; -} - -# -------------------------------- replacing strings ------------------------ -sub replace_template -{ - my ($string, $needle, $replacement) = @_[0..2]; - $string =~ s/$needle/$replacement/g; - return $string; -} - -sub replace_templates -{ - my ($src, $replacement_t, $replacement_i) = @_[0..2]; - my $dst = {}; - for my $key ( keys %{$src} ) - { - $dst->{$key} = $src->{$key}; - if($key =~ m/label|info|title/) { $dst->{$key} = replace_template($dst->{$key}, ':t:', $replacement_t); } - if($key =~ m/info|title/) { $dst->{$key} = replace_template($dst->{$key}, ':i:', $replacement_i); } - } - return $dst; -} - -sub append_order -{ - my ($pg, $graph_name, $field_name) = @_[0..2]; - $pg->{$graph_name}{'graph'}{'order'} = exists($pg->{$graph_name}{'graph'}{'order'}) ? sprintf("%s %s", $pg->{$graph_name}{'graph'}{'order'}, $field_name) : $field_name; -} - -sub append_field -{ - my ($pg, $graph_name, $field_name, $field_src, $replacement_t, $replacement_i) = @_[0..5]; - $pg->{$graph_name}{'fields'}{$field_name} = replace_templates($fields->{$field_src}, $replacement_t, $replacement_i); - append_order($pg, $graph_name, $field_name); -} - -sub append_graph -{ - my ($pg, $graph_name, $category, $graph_src, $replacement_t, $replacement_i) = @_[0..5]; - $pg->{$graph_name}{'graph'} = replace_templates($graphs->{$graph_src}, $replacement_t, $replacement_i); - $pg->{$graph_name}{'graph'}{'category'} = $category; -} - -# ---------------------------------------- preparing data for graphs ------------------------------------ -sub prepare_graphs_fields -{ - my $pg = {}; - # ------------------ cpu_utilisation ----------------------------------- - # ---------- general ---------------------- - append_graph($pg, 'cpu_utilisation', 'cpu', 'cpu_utilisation', '', ''); - for my $cname (@cnames) { append_field($pg, 'cpu_utilisation', $cname, $cname, '', ''); append_utilisation_limits($pg, 'cpu_utilisation', $cname, undef); } - if(check_exists('freq_hz')) { for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) { append_field($pg, 'cpu_utilisation', sprintf("fp_%s", $i), 'freq_percent', $i, ''); } } - # ---------------- childs ------------------- - if ($cpuinfo->{'cpu_count'} > 1) - { - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - my $graph_name = sprintf("cpu_utilisation.cpu%s", $i); - append_graph($pg, $graph_name, sprintf("CPU %s", $i), 'cpu_utilisation', $i, $cpuinfo->{$i}{'info'}); - for my $cname (@cnames) { append_field($pg, $graph_name, $cname, $cname, '', ''); append_utilisation_limits($pg, $graph_name, $cname, $i); } - if(check_exists('freq_hz', $i)) { append_field($pg, $graph_name, 'fp', 'freq_percent', '', ''); } - } - } - - - # --------------- cpu_frequency -------------------------------------------- - if(check_exists('freq_trans')) - { - # ------------ general -------------------- - # - cpu frequency transitions - - append_graph($pg, 'cpu_frequency', 'cpu', 'cpu_freq_trans', '', ''); - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) { if (check_exists('freq_trans', $i)) { append_field($pg, 'cpu_frequency', sprintf("cpu_%s", $i), 'freq_trans', sprintf("CPU%s", $i), ''); } } - append_field($pg, 'cpu_frequency', 'total', 'freq_trans', 'Total', ''); - # ---------------- childs ------------------- - if(check_exists('freq_times')) - { - my $frequences = get_frequency_times(); - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if(check_exists('freq_times', $i)) - { - # - cpu frequencyes - - my $graph_name = sprintf("cpu_frequency.percent_cpu%s", $i); - append_graph($pg, $graph_name, sprintf("CPU %s", $i), 'cpu_freq', $i, $cpuinfo->{$i}{'info'}); - for my $freq (@{$frequences->{'names'}{$i}}) { append_field($pg, $graph_name, sprintf("hz_%s", $freq), 'freq_hz', scaleNumber($freq, 'Hz'), ''); } - # - cpu frequencyes per secund - - $graph_name = sprintf("cpu_frequency.percent_ps_cpu%s", $i); - append_graph($pg, $graph_name, sprintf("CPU %s", $i), 'cpu_freq_ps', $i, $cpuinfo->{$i}{'info'}); - for my $freq (@{$frequences->{'names'}{$i}}) { append_field($pg, $graph_name, sprintf("hz_%s", $freq), 'freq_hz', scaleNumber($freq, 'Hz'), ''); } - } - } - } - if(check_exists('freq_ttable')) - { - my $f_table = get_frequency_trans_table(); - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if(check_exists('freq_ttable', $i)) - { - # - cpu frequencyes table - - my $graph_name = sprintf("cpu_frequency.trans_table_cpu%s", $i); - append_graph($pg, $graph_name, sprintf("CPU %s", $i), 'cpu_freq_trans_table', $i, $cpuinfo->{$i}{'info'}); - for my $from (sort keys %{$f_table->{'values'}{$i}}) - { - for my $to (sort keys %{$f_table->{'values'}{$i}{$from}}) - { - next if ($from eq $to); - append_field($pg, $graph_name, sprintf("f_%s_t_%s", $from, $to), 'freq_trans_table', sprintf(". %9s -> %s", scaleNumber($from, 'Hz'), scaleNumber($to, 'Hz')), ''); - } - } - # - cpu frequencyes table per secund - - $graph_name = sprintf("cpu_frequency.trans_table_ps_cpu%s", $i); - append_graph($pg, $graph_name, sprintf("CPU %s", $i), 'cpu_freq_trans_table_ps', $i, $cpuinfo->{$i}{'info'}); - for my $from (sort keys %{$f_table->{'values'}{$i}}) - { - for my $to (sort keys %{$f_table->{'values'}{$i}{$from}}) - { - next if ($from eq $to); - append_field($pg, $graph_name, sprintf("f_%s_t_%s", $from, $to), 'freq_trans_table', sprintf(". %9s -> %s", scaleNumber($from, 'Hz'), scaleNumber($to, 'Hz')), ''); - } - } - } - } - } - } - - - # --------------- cpu_all ----------------------------------------- - if ($cpuinfo->{'cpu_count'} > 1) - { - append_graph($pg, 'cpu_all', 'cpu', 'cpu_all', '', ''); - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) { append_field($pg, 'cpu_all', sprintf("cpu_%s", $i), 'cpu_util', sprintf("CPU%s", $i)); } - append_field($pg, 'cpu_all', 'total', 'cpu_util', 'Combined'); - } - return $pg; -} - - -# ------------------------------------ printing limits (for utilisation graphs) ---------------- -sub append_utilisation_limits -{ - my ($pg, $graph_name, $cname, $i) = @_[0..3]; - for my $type (qw(warning critical)) - { - my $field = sprintf("%s_%s", $cname, $type); - my $cpu_field = defined($i) ? sprintf("cpu%s_%s_%s", $i, $cname, $type) : undef; - my $limit = (defined($i) and defined($limits->{'utilisation'}{$cpu_field})) ? - $limits->{'utilisation'}{$cpu_field} : - ( - defined($limits->{'utilisation'}{$field}) ? - $limits->{'utilisation'}{$field} : - ( - defined($limits->{'utilisation'}{$type}) ? - $limits->{'utilisation'}{$type} : - undef - ) - ); - if(defined($limit)) { $pg->{$graph_name}{'fields'}{$cname}{$type} = $limit; } - } -} - -# ---------------- loading limits ------------- -sub load_limits -{ - $limits->{'utilisation'}{'warning'} = $ENV{warning} || undef; - $limits->{'utilisation'}{'critical'} = $ENV{critical} || undef; - for my $cname (@cnames) - { - for my $t (qw(warning critical)) - { - my $name = sprintf("%s_%s", $cname, $t); - $limits->{'utilisation'}{$name} = $ENV{$name} || undef; - for (my $i = 0; $i <= $cpuinfo->{'cpu_count'}; $i++) - { - $name = sprintf("cpu%s_%s_%s",$i, $cname, $t); - $limits->{'utilisation'}{$name} = $ENV{$name} || undef; - } - } - } -} - -# --------------------------------- graph configs ---------------------------- -sub print_config -{ - load_limits(); - my $config = prepare_graphs_fields(); - for my $g (sort keys %{$config}) - { - printf("multigraph %s\n", $g); - for my $go (sort keys %{$config->{$g}{'graph'}}) { printf("graph_%s %s\n", $go, $config->{$g}{'graph'}{$go}); } - for my $f (sort keys %{$config->{$g}{'fields'}}) { for my $fo (sort keys %{$config->{$g}{'fields'}{$f}}) { printf("%s.%s %s\n", $f, $fo, $config->{$g}{'fields'}{$f}{$fo}); } } - print "\n"; - } -} - -# ----------------------------------- saving state data using munin -------------------- -sub save_state_data -{ - my $data = $_[0]; - my $d = Data::Dumper->new([$data]); - $d->Indent(0); - save_state($d->Dump); -} - -# -------------------------------- loading previous state data using munin ------------------- -sub restore_state_data -{ - my $VAR1; - my $states = (restore_state())[0]; - eval $states if defined $states; - return $VAR1; -} - -sub load_stats -{ - my $stats = {}; - # need to store -------------------- - $stats->{'timestamp'} = time(); - $stats->{'cpu_util'} = get_cpu_utilisation_stats() ; - $stats->{'f_trans'} = get_frequency_transitions() if check_exists('freq_trans') ; - $stats->{'f_times'} = get_frequency_times() if check_exists('freq_times') ; - $stats->{'f_ttable'} = get_frequency_trans_table() if check_exists('freq_ttable'); - - save_state_data($stats); - - # no need to store -------------------- - $stats->{'f_minmax'} = get_cpu_curr_max_freqences() if check_exists('freq_hz') ; - - - #print Dumper $stats; - return $stats; -} - -# ---------------------------------- loading cpu stats from loaded lines ------------------------ -sub get_cpu_utilisation_stats -{ - my $stats = {}; - for (my $i = 0; $i <= $cpuinfo->{'cpu_count'}; $i++) - { - my $rx = $i == $cpuinfo->{'cpu_count'} ? 'cpu' : sprintf ("cpu%s", $i); - my $cn = $i == $cpuinfo->{'cpu_count'} ? 'total' : $i; - my @tmp = split(/\s+/, (grep(/$rx\s/, get_stat_file_content()))[0]); - my $j = 1; - for my $cname (@cnames) - { - $stats->{$cn}{$cname} = $tmp[$j]; - $j++; - } - } - return $stats; -} -# ------------------ loading frequency transitions for each cpu ---------------------------- -sub get_frequency_transitions -{ - my $stats = {}; - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - next unless (check_exists('freq_trans', $i)); - $stats->{$i} = get_sys_file_content(sprintf("%s/cpu%s/cpufreq/stats/total_trans", $freq_path, $i)); - } - return $stats; -} - -# ------------------ loading frequency times for each cpu ---------------------------- -sub get_frequency_times -{ - my $stat = {}; - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - next unless (check_exists('freq_times', $i)); - my $total = 0; - my $file = sprintf("%s/cpu%s/cpufreq/stats/time_in_state", $freq_path, $i); - open (FH, '<', $file) or die "$! $file \n"; - for my $line () - { - chomp $line; - my ($hz, $count) = split(/\s+/, $line); - $hz = $hz*$freq_mul; - $stat->{'values'}{$i}{$hz} = $count; - push(@{$stat->{'names'}{$i}}, $hz); - $total += $count; - } - close (FH); - $stat->{'total'}{$i} = $total; - } - return $stat; -} - -# ------------------ loading current and max frequency for each cpu ---------------------------- -sub get_cpu_curr_max_freqences -{ - my $freq = {}; - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - next unless (check_exists('freq_hz', $i)); - my $cpu_path = sprintf("%s/cpu%s/cpufreq", $freq_path, $i); - $freq->{'cur'}{$i} = get_sys_file_content(sprintf("%s/scaling_cur_freq", $cpu_path))*$freq_mul; - $freq->{'max'}{$i} = get_sys_file_content(sprintf("%s/scaling_max_freq", $cpu_path))*$freq_mul; - $freq->{'min'}{$i} = get_sys_file_content(sprintf("%s/scaling_min_freq", $cpu_path))*$freq_mul; - } - return $freq; -} - -sub get_frequency_trans_table -{ - my $tbl = {}; - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - next unless (check_exists('freq_ttable', $i)); - my @frequences; - my $fcount = 0; - my $total = 0; - my $file = sprintf("%s/cpu%s/cpufreq/stats/trans_table", $freq_path, $i); - open (FH, '<', $file) or die "$! $file \n"; - for my $line () - { - chomp $line; - my ($left, $right) = split(/:/, $line); - next if($left =~ m/From/); - if($left =~ m/\d+/) - { - my $frequence = trim($left)*$freq_mul; - my @counters = split(/\s+/, trim($right)); - for (my $j = 0; $j<$fcount; $j++) - { - $tbl->{'values'}{$i}{$frequence}{$frequences[$j]*$freq_mul} = $counters[$j]; - $total += $counters[$j]; - } - } - else - { - @frequences = split(/\s+/, trim($right)); - $fcount = scalar(@frequences); - } - } - $tbl->{'total'}{$i} = $total; - close (FH); - } - return $tbl; -} - -sub one_second_part -{ - my ($curr, $prev, $timediff) = @_[0..2]; - #print "$prev, $curr, $timediff\n"; - return 'NaN' if ($curr < $prev or $timediff < 0); - return $curr - $prev if $timediff == 0; - return ($curr - $prev)/$timediff; -} - -sub divide -{ - my ($divider, $divident) = @_[0..1]; - return 'NaN' if $divident == 0; - return $divider/$divident; -} - -# -------------------------------- calculating fields values ------------------------------ -sub calculate -{ - my ($pstats, $cstats) = @_[0..1]; - my $result = {}; - my $timediff = $cstats->{'timestamp'} - $pstats->{'timestamp'}; - # --- cpu utilisation ---- - for my $cpu (keys %{$cstats->{'cpu_util'}}) - { - # ------ calculating 1% - $result->{'cpu_util'}{'1%'}{$cpu} = 0; - for my $cname (@cnames) - { - $result->{'cpu_util'}{'diff'}{$cpu}{$cname} = one_second_part($cstats->{'cpu_util'}{$cpu}{$cname}, $pstats->{'cpu_util'}{$cpu}{$cname}, $timediff); - $result->{'cpu_util'}{'1%'}{$cpu} += $result->{'cpu_util'}{'diff'}{$cpu}{$cname} if $result->{'cpu_util'}{'diff'}{$cpu}{$cname} ne 'NaN'; - } - $result->{'cpu_util'}{'1%'}{$cpu} = $result->{'cpu_util'}{'1%'}{$cpu}/100; - # ------ calculating used percents - $result->{'cpu_util'}{'used'}{$cpu} = 0; - for my $cname (@cnames) - { - $result->{'cpu_util'}{'%'}{$cpu}{$cname} = divide($result->{'cpu_util'}{'diff'}{$cpu}{$cname}, $result->{'cpu_util'}{'1%'}{$cpu}); - next if $cname eq 'idle'; - $result->{'cpu_util'}{'used'}{$cpu} += $result->{'cpu_util'}{'%'}{$cpu}{$cname} if $result->{'cpu_util'}{'%'}{$cpu}{$cname} ne 'NaN'; - } - } - # ------ freq min max ---- - if (check_exists('freq_hz')) - { - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - $result->{'f_minmax'}{'%'}{$i} = divide($cstats->{'f_minmax'}{'cur'}{$i} - $cstats->{'f_minmax'}{'min'}{$i}, - (($cstats->{'f_minmax'}{'max'}{$i} - $cstats->{'f_minmax'}{'min'}{$i}) / 100 )) if (check_exists('freq_hz', $i)); - } - } - # ---- freq trans ---- - if (check_exists('freq_trans')) - { - $result->{'f_trans'}{'total'} = 0; - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if(check_exists('freq_trans', $i)) - { - $result->{'f_trans'}{$i} = one_second_part($cstats->{'f_trans'}{$i}, $pstats->{'f_trans'}{$i}, $timediff); - $result->{'f_trans'}{'total'} += $result->{'f_trans'}{$i} if $result->{'f_trans'}{$i} ne 'NaN'; - } - } - } - # -- freq times --- - if (check_exists('freq_times')) - { - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if (check_exists('freq_times', $i)) - { - my $oneprc = $cstats->{'f_times'}{'total'}{$i}/100; - my $ps_total = 0; - for my $hz (@{$cstats->{'f_times'}{'names'}{$i}}) - { - $result->{'f_times'}{$i}{$hz} = divide($cstats->{'f_times'}{'values'}{$i}{$hz}, $oneprc); - $cstats->{'f_times'}{'%_ps'}{$i}{$hz} = one_second_part($cstats->{'f_times'}{'values'}{$i}{$hz}, $pstats->{'f_times'}{'values'}{$i}{$hz}, $timediff); - $ps_total += $cstats->{'f_times'}{'%_ps'}{$i}{$hz}; - } - $ps_total = $ps_total/100; - for my $hz (@{$cstats->{'f_times'}{'names'}{$i}}) - { - $result->{'f_times_ps'}{$i}{$hz} = divide($cstats->{'f_times'}{'%_ps'}{$i}{$hz}, $ps_total); - } - } - } - } - # ------- freq trans table --- - if (check_exists('freq_ttable')) - { - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if (check_exists('freq_ttable', $i)) - { - my $oneprc = $cstats->{'f_ttable'}{'total'}{$i}/100; - my $ps_total = 0; - for my $from (keys %{$cstats->{'f_ttable'}{'values'}{$i}}) - { - for my $to (keys %{$cstats->{'f_ttable'}{'values'}{$i}{$from}}) - { - next if ($from eq $to); - $result->{'f_ttable'}{$i}{$from}{$to} = divide($cstats->{'f_ttable'}{'values'}{$i}{$from}{$to}, $oneprc); - $cstats->{'f_ttable'}{'%_ps'}{$i}{$from}{$to} = one_second_part($cstats->{'f_ttable'}{'values'}{$i}{$from}{$to}, $pstats->{'f_ttable'}{'values'}{$i}{$from}{$to}, $timediff); - $ps_total += $cstats->{'f_ttable'}{'%_ps'}{$i}{$from}{$to}; - } - } - $ps_total = $ps_total/100; - for my $from (keys %{$cstats->{'f_ttable'}{'values'}{$i}}) - { - for my $to (keys %{$cstats->{'f_ttable'}{'values'}{$i}{$from}}) - { - next if ($from eq $to); - $result->{'f_ttable_ps'}{$i}{$from}{$to} = divide($cstats->{'f_ttable'}{'%_ps'}{$i}{$from}{$to}, $ps_total); - } - } - } - } - } - #print Dumper $result; - return $result; -} - -# ---------------------------------------- preparing values ------------------------------------ -sub prepare_graphs_values -{ - my ($data) = $_[0]; - my $pg = {}; - # ------------------ cpu_utilisation ----------------------------------- - # ---------- general ---------------------- - for my $cname (@cnames) { $pg->{'cpu_utilisation'}{$cname} = $data->{'cpu_util'}{'%'}{'total'}{$cname}; } - if(check_exists('freq_hz')) { for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) { $pg->{'cpu_utilisation'}{sprintf("fp_%s", $i)} = $data->{'f_minmax'}{'%'}{$i}; } } - # ---------------- childs ------------------- - if ($cpuinfo->{'cpu_count'} > 1) - { - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - my $graph_name = sprintf("cpu_utilisation.cpu%s", $i); - for my $cname (@cnames) { $pg->{$graph_name}{$cname} = $data->{'cpu_util'}{'%'}{$i}{$cname}; } - if(check_exists('freq_hz', $i)) { $pg->{$graph_name}{'fp'} = $data->{'f_minmax'}{'%'}{$i}; } - } - } - - - # --------------- cpu_frequency -------------------------------------------- - if(check_exists('freq_trans')) - { - # ------------ general -------------------- - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) { $pg->{'cpu_frequency'}{sprintf("cpu_%s", $i)} = $data->{'f_trans'}{$i} if (check_exists('freq_trans', $i)); } - $pg->{'cpu_frequency'}{'total'} = $data->{'f_trans'}{'total'}; - # ---------------- childs ------------------- - if(check_exists('freq_times')) - { - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if(check_exists('freq_times', $i)) - { - my $graph_name = sprintf("cpu_frequency.percent_cpu%s", $i); - for my $freq (keys %{$data->{'f_times'}{$i}}) { $pg->{$graph_name}{sprintf("hz_%s", $freq)} = $data->{'f_times'}{$i}{$freq}; } - $graph_name = sprintf("cpu_frequency.percent_ps_cpu%s", $i); - for my $freq (keys %{$data->{'f_times_ps'}{$i}}) { $pg->{$graph_name}{sprintf("hz_%s", $freq)} = $data->{'f_times_ps'}{$i}{$freq}; } - } - } - } - if(check_exists('freq_ttable')) - { - for (my $i=0; $i < $cpuinfo->{'cpu_count'}; $i++) - { - if(check_exists('freq_ttable', $i)) - { - my $graph_name = sprintf("cpu_frequency.trans_table_cpu%s", $i); - for my $from (keys %{$data->{'f_ttable'}{$i}}) - { - for my $to (keys %{$data->{'f_ttable'}{$i}{$from}}) - { - $pg->{$graph_name}{sprintf("f_%s_t_%s", $from, $to)} = $data->{'f_ttable'}{$i}{$from}{$to}; - } - } - $graph_name = sprintf("cpu_frequency.trans_table_ps_cpu%s", $i); - for my $from (keys %{$data->{'f_ttable_ps'}{$i}}) - { - for my $to (keys %{$data->{'f_ttable_ps'}{$i}{$from}}) - { - $pg->{$graph_name}{sprintf("f_%s_t_%s", $from, $to)} = $data->{'f_ttable_ps'}{$i}{$from}{$to}; - } - } - } - } - } - } - - # --------------- cpu_all -------------------------------------------- - if ($cpuinfo->{'cpu_count'} > 1) - { - for (my $i = 0; $i < $cpuinfo->{'cpu_count'}; $i++) { $pg->{'cpu_all'}{sprintf("cpu_%s", $i)} = $data->{'cpu_util'}{'used'}{$i}; } - $pg->{'cpu_all'}{'total'} = $data->{'cpu_util'}{'used'}{'total'}; - } - return $pg; -} - - - -# -------------------------------- printing values ----------------------------------- -sub print_values -{ - my $pstats = restore_state_data(); - my $cstats = load_stats(); - if (exists ($pstats->{'timestamp'})) - { - my $values = prepare_graphs_values(calculate($pstats, $cstats)); - #print Dumper $values; - for my $g (sort keys %{$values}) - { - printf("multigraph %s\n", $g); - for my $f (sort keys %{$values->{$g}}) { printf("%s.value %s\n", $f, $values->{$g}{$f}); } - print "\n"; - } - } -} - -__END__ - -- user: normal processes executing in user mode -- nice: niced processes executing in user mode -- system: processes executing in kernel mode -- idle: twiddling thumbs -- iowait: waiting for I/O to complete -- irq: servicing interrupts -- softirq: servicing softirqs -- steal: involuntary wait -- guest: running a guest diff --git a/plugins/system/cpu_by_process b/plugins/system/cpu_by_process deleted file mode 100755 index 41d33d53..00000000 --- a/plugins/system/cpu_by_process +++ /dev/null @@ -1,135 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2006 Holger Levsen -# -# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# Configuration variables -# vservers - specify the vservers to include in the graph (default: all) -# limits - if true, turn on limit graphing (default: false) -# -# NOTE: If no configuration variables are set, the defaults will be used - -# Example /etc/munin/plugin-conf.d/munin-node -# -# The first group monitors the vservers named "vserver1 vserver2 -# vserver3 vserver4" and looks to see if the resource limit has been -# breached, if so it sends a message to nagios via send_nsca, and -# sends an email to notify that this has happened. -# -# The second monitors the vservers "vserver5 vserver6 vserver7" and -# has no limit notifications turned on. -# -# The third monitors all vservers on the system, in one graph, and it has -# no limit notifications defined. -# -# You can use any combination of these to fit your needs. -# -# -# [vsrmem_group1] -# user root -# env.vservers vserver1 vserver2 vserver3 vserver4 -# env.limits 1 -# contacts nagios email -# contact.nagios.command /usr/bin/send_nsca -H your.nagios-host.here -c /etc/send_nsca.cfg -# contact.email.command mail -s "Munin-notification for ${var:group} :: ${var:host}" your@email.address.here -# -# [vsrmem_group2] -# user root -# env.vservers vserver5 vserver6 vserver7 -# env.limits 0 -# -# [vserver_rmemory] -# user root -# -# Graph Vserver RSS usage and limits -# -# Changelog -# version 0.1 - 2006 April xx - Holger Levsen -# - initial author -# version 0.2 - 2006 April 24 - Micah Anderson -# - Add dynamic arch page size determination -# - Some cleanup and clarification -# version 0.3 - 2006 May 3 - Micah Anderson -# - Add ability to group vservers via environment vars -# - Fix missing close quotes and standardize indents -# - Add limit notification -# - Update documentation to include info on groups and limits -# version 0.4 - 2006 Jun 22 - Micah Anderson -# - Fix error that results if NodeName is set to include a domain name - -#scriptname=`basename $0` -#vsname=`echo $scriptname | perl -ne '/^vserver_proc_VM_(.*)/ and print $1'` - -#if [ "$1" = "suggest" ]; then -# ls -1 /etc/vservers -# exit 0 -#elif [ -z "$vsname" ]; then -# echo "Must be used with a vserver name; try '$0 suggest'" >&2 -# exit 2 -#fi - -#xid=`cat /etc/vservers/$vsname/context` - -if [ "$1" = "config" ]; then - echo "graph_title CPU time by Process" - echo 'graph_args --base 1000 -l 0' - echo 'graph_vlabel seconds' - echo 'graph_category system' - echo "graph_info Shows CPU time used by each process name" - - # ps -eo time,comm h | perl -e ' - ps -eo pid,time,comm | perl -e ' - $junk = <>; - while (<>) - { - @a = split; - $proc = $a[2]; - $var = $proc; - $var =~ s|/.*||; - $var =~ s|\.$||; - $var =~ tr|a-zA-Z0-9|_|c; - $procs{$var} = $proc; - } - my $stack = 0; - sub draw() { return $stack++ ? "STACK" : "AREA" } - print map - { - "$_.label $procs{$_}\n" . - "$_.min 0\n" . - "$_.type DERIVE\n" . - "$_.draw " . draw() . "\n" - } - sort keys %procs; - ' - exit 0 -else - # ps -eo time,comm h | perl -e ' - ps -eo pid,time,comm | perl -e ' - $junk = <>; - while (<>) - { - @a = split; - $cpu = $a[1]; - $var = $a[2]; - $var =~ s|/.*||; - $var =~ s|\.$||; - $var =~ tr|a-zA-Z0-9|_|c; - @b = split /:/, $cpu; - $cpu = (($b[0] * 60) + $b[1]) * 60 + $b[2]; - $total{$var} += $cpu; - } - print map {"$_.value $total{$_}\n"} sort keys %total' -fi diff --git a/plugins/system/cpufreq b/plugins/system/cpufreq deleted file mode 100755 index 3f25b7c5..00000000 --- a/plugins/system/cpufreq +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh -# -# Plugin to monitor CPU speed on system that allow to reduce it for power saving -# -# probably only useful on laptops if you configure it to lower CPU frequency for -# less power consumptoin. This plugin works by reading from the /sys file system. -# -# by dominik dot stadler at gmx.at -# -# Magic markers (optional - only used by munin-config and some -# installation scripts): -# -#%# family=auto -#%# capabilities=autoconf - -if [ "$1" = "autoconf" ]; then - if [ -r /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]; then - echo yes - exit 0 - else - echo no - exit 1 - fi -fi - - -if [ "$1" = "config" ]; then - echo 'graph_title CPU speed' - echo 'graph_args --base 1000 -l 0' - echo 'graph_vlabel speed' -# echo 'graph_scale no' - echo 'graph_category system' - echo 'graph_info This graph shows the speed of the system fan.' - echo 'maxspeed.label maxspeed' - echo 'maxspeed.info The maximum speed of the CPU.' - echo 'minspeed.label minspeed' - echo 'minspeed.info The minimum speed of the CPU.' - echo 'cpuspeed.label speed' - echo 'cpuspeed.info The current speed of the CPU.' - - exit 0 -fi - -echo -n "maxspeed.value " -cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq -echo -n "minspeed.value " -cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq -echo -n "cpuspeed.value " - -# cpuinfo_cur_freq is not readable for user munin, therefore using scaling_cur_freq for now, should -# be equal information, see http://wiki.ubuntuusers.de/Prozessortaktung -# We could also configure this plugin to require root access, but I like it better this way. -#cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq -cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq - diff --git a/plugins/system/cpufreq-avg b/plugins/system/cpufreq-avg deleted file mode 100755 index 0544722f..00000000 --- a/plugins/system/cpufreq-avg +++ /dev/null @@ -1,144 +0,0 @@ -#!/bin/sh -# -# Plugin to measure average CPU frequency for each CPU/core. -# -# Contributed by Mark Edwards -# -# Usage: Place in /etc/munin/plugins (or link it there using ln -s) -# -# Parameters understood: -# -# config (required) -# autoconf (optional - used by munin-config) -# -# $Log$ -# -# Revision 0.2 2010/01/04 16:37:00 medwards -# Minor bugfixes in config section -# -# Revision 0.1 2010/01/04 16:13:00 medwards -# First version -# -# -# -# Magic markers - optional - used by installation scripts and -# munin-config: -# -#%# family=auto -#%# capabilities=autoconf - -if [ "$1" = "autoconf" ]; then - if [ -r /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state ]; then - echo yes - exit 0 - else - echo no - exit 1 - fi -fi - -cpu_count=`grep -c "^processor" /proc/cpuinfo` - -if [ "$1" = "config" ]; then - - echo 'graph_title Average CPU Frequency' - up_lim=`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq` - low_lim=`cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq` - up_lim=`expr $up_lim \* 1000` # Convert to Hz - low_lim=`expr $low_lim \* 1000` # Convert to Hz - echo "graph_args -u $up_lim -l $low_lim -r --base 1000" - echo 'graph_vlabel Hz' - echo 'graph_category system' - cpu=0 - while [ $cpu -lt $cpu_count ] - do - echo "cpu_$cpu.label CPU $cpu" - echo "cpu_$cpu.type GAUGE" - echo "cpu_$cpu.info Hz" - cpu=`expr $cpu + 1` - done - exit 0 -fi - -# Run measurements -cpu=0 -while [ $cpu -lt $cpu_count ] -do - - time_in_state=`cat /sys/devices/system/cpu/cpu$cpu/cpufreq/stats/time_in_state` - - # Check/create statefile(s) - statefile="/var/lib/munin/plugin-state/cpufreq-avg_cpu$cpu.state" - if [ ! -r "$statefile" ] - then - echo "$time_in_state" > $statefile - if [ "$?" -ne "0" ] - then - exit ${1} - else - cpu=`expr $cpu + 1` - continue - fi - fi - state=`cat $statefile` - - # Calculated total time since last state - total_time=0 - total_time=$( - echo "$time_in_state" | { - i=0 - while read line - do - this_freq=`echo $line | awk '{ print $1; }'` - this_time=`echo $line | awk '{ print $2; }'` - this_time_state=`echo "$state" | grep $this_freq | awk '{ print $2; }'` - if [ $this_time -ge $this_time_state ] # Only measure if state is valid - then - time_diff=`expr $this_time - $this_time_state` # Calculate time since last state - total_time=`expr $total_time + $time_diff` - fi - i=`expr $i + 1` - done - echo $total_time - } - ) - - # Measure average CPU frequency if total time calculation was successful - - frequency=0 - frequency=$( - echo "$time_in_state" | { - i=0 - while read line - do - this_freq=`echo $line | awk '{ print $1; }'` - this_time=`echo $line | awk '{ print $2; }'` - this_time_state=`echo "$state" | grep $this_freq | awk '{ print $2; }'` - this_freq=`expr $this_freq \* 1000` # Convert to Hz - this_time=`expr $this_time - $this_time_state` # Calculate time since last state - if [ $total_time -gt 0 ] - then - calc=`echo "($this_time / $total_time) * $this_freq" | bc -l` - frequency=`echo "$frequency + $calc" | bc -l` - fi - i=`expr $i + 1` - done - echo $frequency - } - ) - - # Round result to an integer and return it - frequency=`echo "scale=0 ; ($frequency+0.5)/1" | bc -l` - if [ $frequency -gt 0 ] - then - echo "cpu_$cpu.value $frequency" - fi - - # Update statefile - echo "$time_in_state" > $statefile - - cpu=`expr $cpu + 1` - -done - -exit 0 \ No newline at end of file diff --git a/plugins/system/cpufreq-info b/plugins/system/cpufreq-info deleted file mode 100755 index 103827a7..00000000 --- a/plugins/system/cpufreq-info +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash -# -# Plugin to measure CPU frequency via cpufreq-info binary. -# This makes the plugin run on linux machines only. -# However the same goes for using sysfs directly. -# -# Contributed by Jo Schulze -# -# Config variables: -# -# -# Requires: -# cpufrequtils http://www.kernel.org/pub/linux/utils/kernel/cpufreq/cpufrequtils.html -# -# @remarks -# jo20061130 using cpufreq-info should simplify the whole thing -# jo20061202 tested on AMD K8 X2, intel Core 2 Duo -# -# $Log$ -# -# Magic markers - optional - used by installation scripts and -# munin-config: -# -#%# family=manual -#%# capabilities=autoconf - -LC_ALL="C" - -CINFOBIN="/usr/bin/cpufreq-info" - -nCPU=$(grep -c "^processor" /proc/cpuinfo) - -function getFreq () -{ - i=0 - while ((i < nCPU)); do - affc=`$CINFOBIN -a -c $i` - internal=`echo $affc | tr ' ' '_'` - cpus=( $affc ) - n=${#cpus[@]} - - freq=`$CINFOBIN -f -c $i` - echo "freq_$internal.value $freq" - - ((i += n)) - done -} - -function getAvail () -{ - i=0 - while ((i < nCPU)); do - affc=`$CINFOBIN -a -c $i` - internal=`echo $affc | tr ' ' '_'` - label=`echo $affc | tr ' ' ','` - cpus=( $affc ) - n=${#cpus[@]} - - echo "freq_$internal.label CPU $i (Core $label)" - echo "freq_$internal.type GAUGE" - echo "freq_$internal.info Hz" - - ((i += n)) - done -} - -function config () -{ -cat <) - { - $d =~ /(cpu[0-9]+)/i; - print "$1\n"; - } - exit 0; -} - -if (eval "require Storable") -{ - Storable->import(qw /lock_store lock_nstore lock_retrieve/); -} -else -{ - die "Sorry, you don't have Storable. (update your perl!)\n"; -} - -# Let's see what is requested. -my $target; -if ($0 =~ /_(cpu\d+)$/i) -{ - $target = $1; -} -else -{ - die "Error: we need to know what cpu you want, so link this plugin as cpufreq_cpu0 orso."; -} - -my $statedir = $ENV{"statedir"} || "/var/lib/munin/plugin-state/"; -my $statefile = $statedir.($ENV{"statefile"} || "/cpufreq_$target.state"); -$statefile = glob($statefile); # Make it saner, remove tildes. Not foolproof though ;) - -my $cpufreq; - -eval { $cpufreq = lock_retrieve($statefile); }; -unless(defined($cpufreq)) -{ - print STDERR "Couldn't read state file! (ignore this error on first run)\n"; - $cpufreq = {}; -} - -my $cpufreq_now = {}; -foreach my $d () -{ - unless(open(TIS, "<", "$d"."/time_in_state")) - { - die "Could not open ${d}/time_in_state: $!\n"; - } - $d =~ /(cpu[0-9]+)/i; - my $cpu = $1; - while() - { - if (/^(\d+)\s(\d+)/) - { - $cpufreq_now->{$cpu}{$1} = $2; - $cpufreq_now->{total}{$cpu} = 0 unless(defined($cpufreq_now->{$cpu})); - $cpufreq_now->{total}{$cpu} += $2; - } - } - close(TIS); -} - -# Let's figure out the percentages. -my %freq_p; -my $cpu = $target; -foreach my $freq (keys %{$cpufreq_now->{$cpu}}) -{ - my $new = $cpufreq_now->{$cpu}{$freq}; - my $old = (defined($cpufreq->{$cpu}{$freq})?$cpufreq->{$cpu}{$freq}:0); # If no old data, we average everything. - my $total = $cpufreq_now->{total}{$cpu}; - $total -= $cpufreq->{total}{$cpu} if (defined($cpufreq->{total}{$cpu})); - if (defined($total) && $total > 0) - { - my $p = ($new - $old) / $total; - $freq_p{$cpu}{$freq} = $p * 100.0; - $freq_p{avg}{$cpu} += $p * $freq; # Average speed, weighted average - } - else - { - $freq_p{$cpu}{$freq} = 0; - } -} - -if ( $ARGV[0] and $ARGV[0] eq "config" ) -{ - print "graph_title Cpu frequency usage of $target\n"; - print "graph_args --base 1000 -l 0\n"; - print "graph_vlabel CPU Frequency %\n"; - print "graph_category System\n"; - print "graph_info This graph shows information about the cpu frequency scaling of your CPU(s)\n"; - - my $count = 0; - my ($r,$g,$blue) = (0,255,0); - my $range = scalar(keys(%{$freq_p{$cpu}})); - my $step = (255+255) / ($range - 1); - # In order to let color work, let's go from green to yellow to red -- 00FF00 to FFFF00 to FF0000. 256 and 256 steps. - foreach my $freq (sort { $a <=> $b } keys %{$freq_p{$cpu}}) - { - printf "freq_%d.label %s\n", $freq, "$freq KHz"; - printf "freq_%d.info %s\n", $freq, "Time $cpu spent (percentage) running on $freq KHz"; - printf "freq_%d.type GAUGE\n", $freq; - printf "freq_%d.draw %s\n", $freq, ($count++?"STACK":"AREA"); - printf "freq_%d.colour %02X%02X%02X\n", $freq, $r,$g,$blue; - # Update color - my $s = $step; - if ($r < 255) - { - $r += $s; - if ($r > 255) - { - $s = $r - 255; - $r = 255; - $g -= $s; - } - } - else - { - $g -= $step; - $g = 0 if ($g < 0); - } - } - printf "cpuspeed_avg.label %s\n", "Average speed of cpu $cpu"; - printf "cpuspeed_avg.info %s\n", "Average speed of cpu $cpu scaled to a percentage"; - printf "cpuspeed_avg.GAUGE\n"; - printf "cpuspeed_avg.LINE2\n"; - printf "cpuspeed_avg.colour 0000FF\n"; - exit 0; -} - -# Print requested garbage. -foreach my $freq (sort { $a <=> $b } keys %{$freq_p{$cpu}}) -{ - printf "freq_%d.value %s\n", $freq, $freq_p{$cpu}{$freq}; -} -# Average speed should be as a percentage as well. So divide it by the max freq. -my $max_freq = (sort { $a <=> $b } keys %{$freq_p{$cpu}})[-1]; -if (defined($max_freq) && $max_freq > 0) -{ - printf "cpuspeed_avg.value %d\n", $freq_p{avg}{$cpu} / $max_freq * 100; -} - -# Save state! -eval { lock_nstore($cpufreq_now, $statefile) }; -if ($@) -{ - print STDERR "[$0] Error writing state file!\n"; -} - -exit 0; diff --git a/plugins/system/cpuspeed b/plugins/system/cpuspeed deleted file mode 100755 index 98766372..00000000 --- a/plugins/system/cpuspeed +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# -# Munin-plugin to monitor the CPU temperature using mbmon -# -# Used the plugin-frame from "cpuload" by Bjørn Ruberg, published under the GNU GPL -# -# Plugin monitors the cpu-frequency for both cores of my AMD 64-X2-3600+. Works fine running Debian -# GNU/Linux 4.0 (Etch). I do not know any perl and will not proceed in developing this plugin any further. -# If you wish to, feel free to do so yourself. If I am able to, I will help with any problems, contact me -# via Mail (x-stars gmx.de) or through the Debian-German-User-Mailing-List. -# -# Frank-Michael Schulze, 21-09-2007 -# Licensed under: GNU GPL - - - - -if [ "$1" = "config" ]; then - echo "graph_title CPU speed" - echo 'graph_category system' - echo "graph_info This graph shows the cpu-speed for each core, as reported by the kernel" - echo 'core0.label Core 0 speed in MHz' - echo 'core1.label Core 1 speed in MHz' - echo "core0.info Core 0 speed in MHz" - echo "core1.info Core 1 speed in MHz" - echo "core0.type GAUGE" - echo "core1.type GAUGE" - exit 0 -fi - -echo -n "core0.value " -cat /proc/cpuinfo | grep MHz | head -n 1 | cut -c 12- | awk '{ sum += $1 } END { print sum }' -echo -n "core1.value " -cat /proc/cpuinfo | grep MHz | tail -n 1 | cut -c 12- | awk '{ sum += $1 } END { print sum }' diff --git a/plugins/system/cpuspeed2 b/plugins/system/cpuspeed2 deleted file mode 100755 index 4a5801c9..00000000 --- a/plugins/system/cpuspeed2 +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# -# Munin-plugin to monitor the cpu speeds of all available cpus -# -# Armin Haaf, 4-11-2007 -# Licensed under: GNU GPL - -MAX_CORES=1024 - - -if [ "$1" = "config" ]; then - echo "graph_title CPU speed" - echo 'graph_category system' - echo "graph_info This graph shows the cpu-speed for each core, as reported by the kernel" - - i=0 - while [ $i -lt $MAX_CORES ] - do - MODEL=`cat /proc/cpuinfo | grep -A 6 "processor.*:.*$i" | grep "model name"` - if [ $? -ne 0 ] - then - break - fi - MODEL=`echo $MODEL | cut -c 12-` - echo "core$i.label Core $i speed in MHz" - echo "core$i.info Core $i speed in MHz $MODEL" - echo "core$i.type GAUGE" - i=$[$i+1] - done - exit 0 -fi - -i=0 -while true -do - cat /proc/cpuinfo | grep -A 6 "processor.*:.*$i" > /dev/null - if [ $? -ne 0 ] - then - break - fi - echo -n "core$i.value " - cat /proc/cpuinfo | grep -A 6 "processor.*:.*$i" | grep "cpu MHz" | cut -c 12- | cut -f 1 -d . - i=$[$i+1] -done - diff --git a/plugins/system/cpuspeed_sane b/plugins/system/cpuspeed_sane deleted file mode 100755 index 097b9f63..00000000 --- a/plugins/system/cpuspeed_sane +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh -# -# Plugin to graph cpu speed on FreeBSD -# -# Parameters: -# -# sysctl - Override path to sysctl program -# -#%# family=auto -#%# capabilities=autoconf - -sysctl=${sysctl:-/sbin/sysctl} - -if [ "$1" = "autoconf" ]; then - if [ -x ${sysctl} ]; then - ${sysctl} dev.cpu.0.freq 2>/dev/null | grep 'dev' >/dev/null 2>/dev/null - if [ "$?" = "0" ]; then - echo yes - exit 0 - fi - echo "no (dev.cpu.0.freq not found)" - exit 1 - else - echo "no (sysctl binary not found)" - exit 2 - fi -fi - -if [ "$1" = "config" ]; then - - echo 'graph_title CPU speed' - echo 'graph_args --base 1000 -l 0' - echo 'graph_vlabel speed in MHz' - echo 'graph_category system' - echo 'graph_scale no' - echo 'graph_info Current CPU speed in MHz. Available levels for the CPU:' `$sysctl -n dev.cpu.0.freq_levels|sed 's!/[0-9]*!!g;s! !, !g'` 'MHz' - - echo cpu0.label cpu0 - echo cpu0.info `$sysctl -n hw.model` Speed - - exit 0 -fi - -file=/usr/local/var/munin/plugin-state/cpuspeed - -echo -n "cpu0.value " -if find $file -mtime -300s 2>/dev/null|grep -Fq $file ; then - head -1 $file -else - $sysctl -n dev.cpu.0.freq -fi - -# Get/cache cpuspeed "later". -export sysctl file -sh -c '( - rand=$(dd if=/dev/urandom bs=1 count=1 2>/dev/null|od -A n -D) - rand=$(expr $rand \* 60 / 256 + 25) - sleep $rand - $sysctl -n dev.cpu.0.freq > $file -)&' >/dev/null 2>&1 diff --git a/plugins/system/snmp__fn b/plugins/system/snmp__fn/snmp__fn similarity index 100% rename from plugins/system/snmp__fn rename to plugins/system/snmp__fn/snmp__fn diff --git a/images/snmp__fn-cpu.png b/plugins/system/snmp__fn/snmp__fn-cpu.png similarity index 100% rename from images/snmp__fn-cpu.png rename to plugins/system/snmp__fn/snmp__fn-cpu.png diff --git a/images/snmp__fn-memory.png b/plugins/system/snmp__fn/snmp__fn-memory.png similarity index 100% rename from images/snmp__fn-memory.png rename to plugins/system/snmp__fn/snmp__fn-memory.png diff --git a/images/snmp__fn-sessions.png b/plugins/system/snmp__fn/snmp__fn-sessions.png similarity index 100% rename from images/snmp__fn-sessions.png rename to plugins/system/snmp__fn/snmp__fn-sessions.png diff --git a/images/snmp__fn-vpnsessions.png b/plugins/system/snmp__fn/snmp__fn-vpnsessions.png similarity index 100% rename from images/snmp__fn-vpnsessions.png rename to plugins/system/snmp__fn/snmp__fn-vpnsessions.png diff --git a/plugins/system/uptime b/plugins/system/uptime deleted file mode 100755 index f12b4be7..00000000 --- a/plugins/system/uptime +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh -# -# Plugin to get system uptime and kernel age -# -# Magic markers - optional - used by installation scripts and -# munin-config: -# -#%# family=manual -#%# capabilities=autoconf - -if [ "$1" = "autoconf" ]; then - echo "yes" - exit 0 -fi - -if [ "$1" = "config" ]; then - - cat </dev/null 2>&1"); - my $ret = system(@who); - if ($ret == 0) { - print "yes\n"; - exit 0; - } else { - print "no\n"; - exit 1; - } -} - -if ((exists $ARGV[0]) && ($ARGV[0] eq "config")) { - print "graph_title Users Online\n"; - print "graph_args --base 1000 -l 0\n"; - print "graph_scale no\n"; - print "graph_vlabel Number of users\n"; - print "graph_category system\n"; - print "graph_info This graph shows the amount of (unique) users logged in\n"; - print "users.label total users\n"; - print "users.info something like who | wc -l\n"; - print "uusers.label unique users\n"; - print "uusers.info something like who | cut -f -1 -d ' ' | sort | uniq | wc -l\n"; - exit 0; -} - -my @who = split(/\s+/, `$who_cmd -q | head -1`); -print "users.value ".scalar(@who)."\n"; - -my %who; -$who{$_} = 1 foreach (@who); -print "uusers.value ".scalar(keys %who)."\n"; - diff --git a/plugins/zfs/zfs_stats_ b/plugins/zfs/zfs_stats_ index a9d9801e..40a4072b 100755 --- a/plugins/zfs/zfs_stats_ +++ b/plugins/zfs/zfs_stats_ @@ -213,25 +213,25 @@ utilization() { echo 'max_size.label Maximum Size' echo 'max_size.draw AREA' - echo 'target_size.label Target Size' - echo 'target_size.draw AREA' echo 'size.label Size' echo 'size.draw AREA' - echo 'recently_size.label Recently Used Cache Size' - echo 'recently_size.draw AREA' - echo 'frequently_size.label Frequently Used Cache Size' - echo 'frequently_size.draw AREA' echo 'min_size.label Minimum Size' - echo 'min_size.draw AREA' + echo 'min_size.draw AREA' + echo 'target_size.label Target Size' + echo 'target_size.draw LINE1' + echo 'recently_size.label Recently Used Cache Size' + echo 'recently_size.draw LINE1' + echo 'frequently_size.label Frequently Used Cache Size' + echo 'frequently_size.draw LINE1' exit 0 else echo 'max_size.value ' $MAX_SIZE - echo 'target_size.value ' $TARGET_SIZE echo 'size.value ' $SIZE + echo 'min_size.value ' $MIN_SIZE + echo 'target_size.value ' $TARGET_SIZE echo 'recently_size.value ' $MRU_SIZE echo 'frequently_size.value ' $MFU_SIZE - echo 'min_size.value ' $MIN_SIZE fi } diff --git a/tools/nagios/check_munin b/tools/nagios/check_munin new file mode 100644 index 00000000..23d7dc7d --- /dev/null +++ b/tools/nagios/check_munin @@ -0,0 +1,187 @@ +#!/usr/bin/python +# +# Copyright (C) 2009 Andreas Thienemann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +# +# Nagios script to query a munin host for plugin values +# +# Can be used as an active check instead of check_dummy +# + +import optparse +import socket +import pprint +import sys +import re + +parser = optparse.OptionParser("usage: %prog -H -M [-P ] -D [] []") +parser.add_option("-H", "--host", dest="host", type="string", + help="specify host to poll") +parser.add_option("-M", "--module", dest="module", type="string", + help="munin module to poll") +parser.add_option("-P", "--port", dest="port", default=4949, + type="int", help="port number to poll") +parser.add_option("-D", "--debug", action="store_true", dest="debug", default=False, + help="Debug output") + +(options, args) = parser.parse_args() + +HOST = options.host +PORT = options.port +MODULE = options.module +DEBUG = options.debug + +if HOST == None or MODULE == None: + parser.error("options -H and -M are required.") + +def compare(val, thresh): + # Compare value to warning and critical threshoulds + # Handle different threshold formats: max, :max, min:, min:max + + val = float(val) + + # max + match = re.match("^[:]?([-+]?[0-9]+)$", str(thresh)) + if match: + max = float(match.group(1)) + if val > max: + return 3 + + + # min + match = re.match("^([-+]?[0-9]+):$", str(thresh)) + if match: + min = float(match.group(1)) + if val < min: + return 2 + + # min:max + match = re.match("^([-+]?[0-9]+):([-+]?[0-9]+)$", str(thresh)) + if match: + min, max = float(match.group(1)), float(match.group(2)) + if val < min or val > max: + return 1 + + # okay + return 0 + +def output(l, cat, desc, ret): + if len(l[cat]) > 0: + print MODULE, desc + ";" + for line in l["critical"]: + print "CRITICAL: " + line + ";" + for line in l["warning"]: + print "WARNING: " + line + ";" + for line in l["ok"]: + print "OK: " + line + ";" + sys.exit(ret) + +try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((HOST, PORT)) + conn = s.makefile('wb', 0) +except: + print "Couldn't connect to requested host" + sys.exit(3) + + +if conn.readline().startswith("# munin node at"): + conn.writelines("config" + MODULE + "\n") + order = [] + data = {} + while True: + line = conn.readline() + if DEBUG: + pprint.pprint(line) + # Last message, bail + if line == ".\n": + break + + label = "" + + key, val = line.split(" ", 1) + if key.find(".") is not -1: + label = key.split(".")[0] + if label not in data: + data[label] = { "warning" : "", "critical" : "", "value" : "" } + order.append(label) + # No thresholds passed on the command line + if len(args) == 2: + data[label]["warning"] = args[0] + data[label]["critical"] = args[1] + + # No thresholds passed on the command line, take the munin supplied ones + if len(args) < 2: + if key.endswith("warning"): + data[label]["warning"] = val[:-1] + if key.endswith("critical"): + data[label]["critical"] = val[:-1] + + if data[label]["warning"] == "" or data[label]["critical"] == "": + print "UNKNOWN - Couldn't retrieve thresholds, pass some on the command line" + sys.exit(3) + + + conn.writelines("fetch " + MODULE + "\n") + while True: + line = conn.readline() + # Last line, bail + if line == ".\n": + if DEBUG: + pprint.pprint(data) + break + + key, val = line.split(" ", 1) + label = key.split(".")[0] + if key.endswith("value"): + data[label]["value"] = val[:-1] + + conn.writelines("quit\n") + +else: + print "UNKNOWN - No munin node detected" + sys.exit(3) + +conn.close() +s.close() + +l = { "ok" : [], "warning" : [], "critical" : [] } +for entry in order: + # compare actual data: 3 max exceeded, 2 minimum underrun, 1 outside limit, 0 okay + for tresh in ["critical", "warning"]: + val = data[entry]["value"] + tval = data[entry][tresh] + tmp = "" + if compare(val, tval) == 3: + tmp = entry + ": " + val + " has exceeded the maximum threshold of " + tval + break + elif compare(val, tval) == 2: + tmp = entry + ": " + val + " has underrun the minimum threshold of " + tval + break + elif compare(val, tval) == 1: + tmp = entry + ": " + val + " is outside of range " + tval + break + + if tmp != "": + l[tresh].append(tmp) + else: + l["ok"].append(entry + ": " + val + " is okay") + + +output(l, "critical", "CRITICAL", 2) +output(l, "warning", "WARNING", 1) +output(l, "ok", "OK", 0) diff --git a/tools/pmmn/bin/pmmn b/tools/pmmn/bin/pmmn index ce149228..e2749346 100755 --- a/tools/pmmn/bin/pmmn +++ b/tools/pmmn/bin/pmmn @@ -1,6 +1,7 @@ #! /usr/bin/perl # Poor man's Munin Node # Usable with only Perl 5 +#(c) 2012 LGPL - Steve Schnepp use warnings; use strict; @@ -12,12 +13,14 @@ use Pod::Usage; $| = 1; -my $VERSION = "0.0.1"; +my $VERSION = "1.0.0"; my $port; # Default is stdin/stdout my $verbose; my $host; my $plugin_dir = "plugins"; +my $spoolfetch_dir; + { my $man = 0; my $help = 0; @@ -28,6 +31,8 @@ my $plugin_dir = "plugins"; 'plugin-dir|d=s' => \$plugin_dir, 'host|h=s' => \$host, + 'spoolfetch-dir|s=s' => \$spoolfetch_dir, + 'help|?' => \$help, man => \$man, ) or pod2usage(2); @@ -54,6 +59,7 @@ while(my $line = <>) { next; } elsif ($cmd eq "nodes") { print "$host\n"; + print "."; next; } elsif ($cmd eq "quit") { exit(0); @@ -67,21 +73,28 @@ while(my $line = <>) { } closedir(PLUGIN_DIR); next; - } elsif (-e $plugin_filename) { - my $arg_plugin; - if ($cmd eq "config") { - $arg_plugin = "config"; - } elsif ($cmd eq "fetch") { - $arg_plugin = ""; - } else { - # Ignore + } elsif ($cmd eq "config" || $cmd eq "alert" || $cmd eq "fetch") { + if (-d $plugin_filename || ! -x $plugin_filename) { + print "# Unknown plugin [$arg] for $cmd"; next; } + my $arg_plugin = ($cmd eq "fetch") ? "" : $cmd; system($plugin_filename, $arg_plugin); print "."; + next; + } elsif ($cmd eq "cap") { + print "cap "; + print "spool " if $spoolfetch_dir; + next; + } elsif ($cmd eq "spoolfetch" && $spoolfetch_dir) { + system("$spoolfetch_dir/spoolfetch_$host", $arg); + print "."; + next; } + + # Arriving here is not a good sign + print "# Unknown command. Try list, nodes, config, fetch, version, alert or quit"; } continue { - #print " " x 4096; print "\n"; }; @@ -103,6 +116,8 @@ Options: --plugin-dir Plugin directory (default is current dir) --host Host name (default is /bin/hostname) + --spoolfetch-dir Spoolfetch plugin dirs (default is disabled) + --help brief help message --man full documentation