mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-22 14:16:00 +00:00
Add munin-node-from-hell, a quite unfriendly munin node
This commit is contained in:
parent
5cc0fa32fe
commit
d33f5b319f
7 changed files with 469 additions and 0 deletions
17
tools/munin-node-from-hell/MIT-LICENSE
Normal file
17
tools/munin-node-from-hell/MIT-LICENSE
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
43
tools/munin-node-from-hell/README.rst
Normal file
43
tools/munin-node-from-hell/README.rst
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
munin-node from hell
|
||||||
|
====================
|
||||||
|
|
||||||
|
This is a simple implementation of a munin node (http://munin-monitoring.org/)
|
||||||
|
that is made to give the polling server a hard time.
|
||||||
|
|
||||||
|
In practice this is the munin-node that we use to develop and test the awesome
|
||||||
|
stuff we do at http://hostedmunin.com/ . Use it as you feel fit :)
|
||||||
|
|
||||||
|
Current features controlled via config file:
|
||||||
|
|
||||||
|
* Respond slowly or never to queries.
|
||||||
|
* Have plugins that always are in warning or alarm.
|
||||||
|
* Extensive number of plugins.
|
||||||
|
* Run on multiple ports at the same time, to test huge amounts of clients.
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
munin-node-from-hell takes two arguments; the mode and which config file to
|
||||||
|
use. Mode is either --run or --muninconf.
|
||||||
|
|
||||||
|
This software is meant to run as an ordinary unix user, please don't run
|
||||||
|
it as root without some thought.
|
||||||
|
|
||||||
|
You probably want:
|
||||||
|
|
||||||
|
./munin-node-from-hell --run simple.conf
|
||||||
|
|
||||||
|
To make a config snippet to put in munin.conf:
|
||||||
|
|
||||||
|
./munin-node-from-hell --muninconf simple.conf > snippet.conf
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
See the file MIT-LICENSE for details.
|
||||||
|
|
||||||
|
Contact
|
||||||
|
-------
|
||||||
|
|
||||||
|
Lasse Karstensen <lasse.karstensen@gmail.com>
|
68
tools/munin-node-from-hell/example-config.conf
Normal file
68
tools/munin-node-from-hell/example-config.conf
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# example config file for muninnode-from-hell.
|
||||||
|
#
|
||||||
|
# Flow:
|
||||||
|
# * basic stuff in [base].
|
||||||
|
# * a set of plugins should be defined in a [pluginprofile:PROFILENAME]
|
||||||
|
# * an instance (ie, server running on a local port) is added with
|
||||||
|
# [instance:INSTANCENAME]
|
||||||
|
#
|
||||||
|
# Instances has:
|
||||||
|
# port = XXXX
|
||||||
|
# AND/OR
|
||||||
|
# portrange = 2000-2005
|
||||||
|
#
|
||||||
|
# mode = sleepy|exp
|
||||||
|
# sleepyness = 10 # when mode=sleepy, this is the uniform sleep time.
|
||||||
|
# lambd = 10 # when mode=exp, this is the mean sleep time.
|
||||||
|
# exp is good to emulate load peaks, it can easily sleep 0.02 or 20 seconds.
|
||||||
|
# (but less often 30 :))
|
||||||
|
|
||||||
|
[base]
|
||||||
|
# when building an example config with --muninconf, what hostname to output.
|
||||||
|
hostname = localhost
|
||||||
|
|
||||||
|
[pluginprofile:tarpit++]
|
||||||
|
plugins = tarpit, load, locks, locks, load, tarpit, load, locks, locks, load, load, load
|
||||||
|
|
||||||
|
[pluginprofile:base]
|
||||||
|
plugins = load, locks, locks, load, load, locks, locks, load, load, load
|
||||||
|
|
||||||
|
[pluginprofile:manyservices]
|
||||||
|
plugins = load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load, load, locks, locks, load, load, locks, locks, load, load, load
|
||||||
|
|
||||||
|
[instance:bar]
|
||||||
|
pluginprofile = base
|
||||||
|
port = 4000
|
||||||
|
mode = sleepy
|
||||||
|
# 40*10 should be aborted due to server time constraints.
|
||||||
|
sleepyness = 40
|
||||||
|
#lambd = 5
|
||||||
|
|
||||||
|
[instance:25sec]
|
||||||
|
pluginprofile = base
|
||||||
|
port = 4001
|
||||||
|
mode = exp
|
||||||
|
#sleepyness = 30
|
||||||
|
# mean 2sec
|
||||||
|
lambd = 2
|
||||||
|
|
||||||
|
|
||||||
|
[instance:baz]
|
||||||
|
pluginprofile = base
|
||||||
|
pluginmultiplier = 2
|
||||||
|
port = 4940
|
||||||
|
mode = sleepy
|
||||||
|
sleepyness = 5
|
||||||
|
#10
|
||||||
|
#lambd = 5
|
||||||
|
|
||||||
|
# bringer of chaos
|
||||||
|
[instance:qux]
|
||||||
|
pluginprofile = base
|
||||||
|
port = 4948
|
||||||
|
mode = exp
|
||||||
|
lambd = 10
|
||||||
|
|
||||||
|
[instance:tarpit]
|
||||||
|
pluginprofile = tarpit++
|
||||||
|
port = 3000
|
290
tools/munin-node-from-hell/muninnode-from-hell
Executable file
290
tools/munin-node-from-hell/muninnode-from-hell
Executable file
|
@ -0,0 +1,290 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Artificial munin node that behaves in all the ways you would like
|
||||||
|
# ordinary nodes _not_ to behave.
|
||||||
|
#
|
||||||
|
# Intended use is for designing and debugging munin-server poller to handle
|
||||||
|
# such problems.
|
||||||
|
#
|
||||||
|
# See the file MIT-LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2011 Karstensen IT
|
||||||
|
# Written by Lasse Karstensen <lasse.karstensen@gmail.com>, Dec 2011.
|
||||||
|
|
||||||
|
import os, sys, time, random
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import SocketServer
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
VERSION = "muninnode-from-hell v0.1"
|
||||||
|
modules = {}
|
||||||
|
|
||||||
|
class MuninPlugin:
|
||||||
|
def sleep_fetch(self, conf):
|
||||||
|
period = None
|
||||||
|
if conf.get("mode") == "sleepy" and conf.get("sleepyness"):
|
||||||
|
period = float(conf.get("sleepyness"))
|
||||||
|
if conf.get("mode") == "exp" and conf.get("lambd"):
|
||||||
|
period = random.expovariate(1 / float(conf.get("lambd")))
|
||||||
|
|
||||||
|
if period:
|
||||||
|
#print "will sleep %.3f seconds" % period
|
||||||
|
time.sleep(period)
|
||||||
|
|
||||||
|
def sleep_config(self, conf):
|
||||||
|
return self.sleep_fetch(conf)
|
||||||
|
|
||||||
|
|
||||||
|
class load(MuninPlugin):
|
||||||
|
def fetch(self, conf):
|
||||||
|
self.sleep_fetch(conf)
|
||||||
|
load = open("/proc/loadavg", "r").read()
|
||||||
|
load, rest = load.split(" ", 1)
|
||||||
|
load = float(load)
|
||||||
|
return "load.value %.2f" % load
|
||||||
|
|
||||||
|
def config(self, conf):
|
||||||
|
self.sleep_config(conf)
|
||||||
|
return """graph_title Load average
|
||||||
|
graph_args --base 1000 -l 0
|
||||||
|
graph_vlabel load
|
||||||
|
graph_scale no
|
||||||
|
graph_category system
|
||||||
|
load.label load
|
||||||
|
graph_info The load average of the machine describes how many processes are in the run-queue (scheduled to run "immediately").
|
||||||
|
load.info 5 minute load average """
|
||||||
|
modules["load"] = load()
|
||||||
|
|
||||||
|
class locks(MuninPlugin):
|
||||||
|
def fetch(self, conf):
|
||||||
|
self.sleep_fetch(conf)
|
||||||
|
fp = open("/proc/locks", "r")
|
||||||
|
fdata = fp.readlines()
|
||||||
|
return "locks.value %i" % len(fdata)
|
||||||
|
|
||||||
|
def config(self, conf):
|
||||||
|
self.sleep_config(conf)
|
||||||
|
return """graph_title Filesystem locks
|
||||||
|
graph_vlabel number of locks
|
||||||
|
graph_scale no
|
||||||
|
graph_info This graph shows file system lock info
|
||||||
|
graph_category system
|
||||||
|
locks.label number of locks
|
||||||
|
locks.info Number of active locks"""
|
||||||
|
modules["locks"] = locks()
|
||||||
|
|
||||||
|
class tarpit(MuninPlugin):
|
||||||
|
"Nasty plugin that never responds"
|
||||||
|
def fetch(self, conf):
|
||||||
|
time.sleep(1000)
|
||||||
|
|
||||||
|
def config(self, conf):
|
||||||
|
time.sleep(1000)
|
||||||
|
modules["tarpit"] = tarpit()
|
||||||
|
|
||||||
|
class always_warning(MuninPlugin):
|
||||||
|
def fetch(self, conf):
|
||||||
|
return "generic.value 10"
|
||||||
|
|
||||||
|
def config(self, conf):
|
||||||
|
return """graph_title Always in warning
|
||||||
|
graph_vlabel Level
|
||||||
|
graph_scale no
|
||||||
|
graph_info A simple graph that is always in warning or alarm
|
||||||
|
graph_category active_notification
|
||||||
|
generic.label Level
|
||||||
|
generic.info Level usually above warning level
|
||||||
|
generic.warn 5
|
||||||
|
generic.crit 10"""
|
||||||
|
modules["always_warning"] = always_warning()
|
||||||
|
|
||||||
|
class always_alarm(always_warning):
|
||||||
|
def fetch(self, conf):
|
||||||
|
return "generic.value 20"
|
||||||
|
modules["always_alarm"] = always_alarm()
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentTCPserver(SocketServer.ThreadingTCPServer):
|
||||||
|
def __init__(self, server_address, RequestHandlerClass, args):
|
||||||
|
SocketServer.ThreadingTCPServer.__init__(self,server_address, RequestHandlerClass)
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
|
||||||
|
class MuninHandler(SocketServer.StreamRequestHandler):
|
||||||
|
"""
|
||||||
|
Munin server implementation.
|
||||||
|
|
||||||
|
This is based on munin_node.py by Chris Holcombe / http://sourceforge.net/projects/pythonmuninnode/
|
||||||
|
|
||||||
|
Possible commands:
|
||||||
|
list, nodes, config, fetch, version or quit
|
||||||
|
"""
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
print "%s: Connection from %s:%s. server args is %s" \
|
||||||
|
% (self.server.args["name"], self.client_address[0], self.client_address[1], self.server.args)
|
||||||
|
# slow path
|
||||||
|
hostname = self.server.args["name"]
|
||||||
|
full_hostname = hostname
|
||||||
|
|
||||||
|
moduleprofile = self.server.args["pluginprofile"]
|
||||||
|
modulenames = set(moduleprofile)
|
||||||
|
|
||||||
|
self.wfile.write("# munin node at %s\n" % hostname)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = self.rfile.readline().strip()
|
||||||
|
try:
|
||||||
|
cmd, args = line.split(" ", 1)
|
||||||
|
except ValueError:
|
||||||
|
cmd = line
|
||||||
|
args = ""
|
||||||
|
|
||||||
|
if not cmd or cmd == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
if cmd == "list":
|
||||||
|
# List all plugins that are available
|
||||||
|
self.wfile.write(" ".join(self.server.args["plugins"].keys()) + "\n")
|
||||||
|
elif cmd == "nodes":
|
||||||
|
# We just support this host
|
||||||
|
self.wfile.write("%s\n.\n" % full_hostname)
|
||||||
|
elif cmd == "config":
|
||||||
|
# display the config information of the plugin
|
||||||
|
if not self.server.args["plugins"].has_key(args):
|
||||||
|
self.wfile.write("# Unknown service\n.\n" )
|
||||||
|
else:
|
||||||
|
config = self.server.args["plugins"][args].config(self.server.args)
|
||||||
|
if config is None:
|
||||||
|
self.wfile.write("# Unknown service\n.\n")
|
||||||
|
else:
|
||||||
|
self.wfile.write(config + "\n.\n")
|
||||||
|
elif cmd == "fetch":
|
||||||
|
# display the data information as returned by the plugin
|
||||||
|
if not self.server.args["plugins"].has_key(args):
|
||||||
|
self.wfile.write("# Unknown service\n.\n")
|
||||||
|
else:
|
||||||
|
data = self.server.args["plugins"][args].fetch(self.server.args)
|
||||||
|
if data is None:
|
||||||
|
self.wfile.write("# Unknown service\n.\n")
|
||||||
|
else:
|
||||||
|
self.wfile.write(data + "\n.\n")
|
||||||
|
elif cmd == "version":
|
||||||
|
# display the server version
|
||||||
|
self.wfile.write("munin node on %s version: %s\n" %
|
||||||
|
(full_hostname, VERSION))
|
||||||
|
else:
|
||||||
|
self.wfile.write("# Unknown command. Try list, nodes, " \
|
||||||
|
"config, fetch, version or quit\n")
|
||||||
|
|
||||||
|
|
||||||
|
def start_servers(instances):
|
||||||
|
# TODO: Listen to IPv6
|
||||||
|
HOST = "0.0.0.0"
|
||||||
|
servers = {}
|
||||||
|
for iconf in instances:
|
||||||
|
print "Setting up instance %s at port %s" \
|
||||||
|
% (iconf["name"], iconf["expanded_port"])
|
||||||
|
|
||||||
|
server = ArgumentTCPserver((HOST, iconf["expanded_port"]), MuninHandler, iconf)
|
||||||
|
server_thread = threading.Thread(target=server.serve_forever)
|
||||||
|
server_thread.daemon = True
|
||||||
|
server_thread.start()
|
||||||
|
|
||||||
|
servers[iconf["name"]] = server
|
||||||
|
return servers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print "Usage: %s [--run] [--muninconf] <configfile>" % sys.argv[0]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) <= 2:
|
||||||
|
usage()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
config = ConfigParser.RawConfigParser()
|
||||||
|
config.read(sys.argv[2])
|
||||||
|
|
||||||
|
instancekeys = [ key for key in config.sections() if key.startswith("instance:") ]
|
||||||
|
servers = {}
|
||||||
|
|
||||||
|
instances = []
|
||||||
|
|
||||||
|
for key in instancekeys:
|
||||||
|
instancename = key.split(":", 2)[1]
|
||||||
|
portrange = []
|
||||||
|
if config.has_option(key, "port"):
|
||||||
|
portrange = [ config.getint(key, "port") ]
|
||||||
|
if config.has_option(key, "portrange"):
|
||||||
|
rangestr = config.get(key, "portrange")
|
||||||
|
ranges = rangestr.split("-")
|
||||||
|
range_expanded = range(int(ranges[0]), int(ranges[1])+1, 1)
|
||||||
|
portrange += range_expanded
|
||||||
|
|
||||||
|
if len(portrange) == 0:
|
||||||
|
print "WARN: No port or portrange defined for instance %s" \
|
||||||
|
% instancename
|
||||||
|
|
||||||
|
pluginprofile = "pluginprofile:%s" % config.get(key, "pluginprofile")
|
||||||
|
if not config.has_section(pluginprofile):
|
||||||
|
print "WARN: Definition for pluginprofile %s not found, skipping" \
|
||||||
|
% config.get(key, "pluginprofile")
|
||||||
|
continue
|
||||||
|
|
||||||
|
plugins = {}
|
||||||
|
tentative_pluginlist = config.get(pluginprofile, "plugins").split(",")
|
||||||
|
assert(len(tentative_pluginlist) > 0)
|
||||||
|
for tentative_plugin in tentative_pluginlist:
|
||||||
|
tentative_plugin = tentative_plugin.strip()
|
||||||
|
if not modules.has_key(tentative_plugin):
|
||||||
|
print "WARN: Pluginprofile %s specifies unknown plugin %s" \
|
||||||
|
% (pluginprofile, tentative_plugin)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# support more than one instanciation of the same plugin.
|
||||||
|
plugininstancename = tentative_plugin
|
||||||
|
i=2
|
||||||
|
while (plugins.has_key(plugininstancename)):
|
||||||
|
plugininstancename = tentative_plugin + str(i)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
plugins[plugininstancename] = modules[tentative_plugin]
|
||||||
|
|
||||||
|
for portinstance in portrange:
|
||||||
|
instanceconfig = dict()
|
||||||
|
|
||||||
|
for k,v in config.items(key):
|
||||||
|
instanceconfig[k] = v
|
||||||
|
|
||||||
|
instanceconfig["plugins"] = plugins
|
||||||
|
|
||||||
|
instanceconfig["name"] = "%s-%s" % (instancename, portinstance)
|
||||||
|
instanceconfig["expanded_port"] = portinstance
|
||||||
|
|
||||||
|
instances.append(instanceconfig)
|
||||||
|
# XXX: need to store what handlers we should have.
|
||||||
|
|
||||||
|
# output sample munin config for the poller
|
||||||
|
if "--muninconf" in sys.argv:
|
||||||
|
for i in instances:
|
||||||
|
print "[%s;%s]\n\taddress %s\n\tuse_node_name yes\n\tport %s\n" \
|
||||||
|
% ( "fromhell", i["name"], config.get("base","hostname"), i["port"])
|
||||||
|
|
||||||
|
|
||||||
|
if "--run" in sys.argv:
|
||||||
|
servers = start_servers(instances)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print "Caught Ctrl-c, shutting down.."
|
||||||
|
for port, server in servers.items():
|
||||||
|
server.shutdown()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
19
tools/munin-node-from-hell/notifications.conf
Normal file
19
tools/munin-node-from-hell/notifications.conf
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Example config file for muninnode-from-hell.
|
||||||
|
#
|
||||||
|
# Run an instance with plugins that always has some
|
||||||
|
# notification level. (unknown / warning / critical)
|
||||||
|
|
||||||
|
[instance:notifications]
|
||||||
|
pluginprofile = notif
|
||||||
|
port = 3000
|
||||||
|
|
||||||
|
#
|
||||||
|
#[instance:baz]
|
||||||
|
#port = 4940
|
||||||
|
#sleepyness = 30
|
||||||
|
[pluginprofile:notif]
|
||||||
|
plugins = always_warning, always_alarm
|
||||||
|
|
||||||
|
[base]
|
||||||
|
# when building an example config with --muninconf, what hostname to output.
|
||||||
|
hostname = localhost
|
16
tools/munin-node-from-hell/simple.conf
Normal file
16
tools/munin-node-from-hell/simple.conf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Example config file for muninnode-from-hell.
|
||||||
|
#
|
||||||
|
# This is the simplest possible config, just run an ordinary munin-node
|
||||||
|
# with a trivial amount of plugins on a single port.
|
||||||
|
#
|
||||||
|
|
||||||
|
[instance:simple]
|
||||||
|
pluginprofile = base
|
||||||
|
port = 4000
|
||||||
|
|
||||||
|
[pluginprofile:base]
|
||||||
|
plugins = load, locks
|
||||||
|
|
||||||
|
[base]
|
||||||
|
# when building an example config with --muninconf, what hostname to output.
|
||||||
|
hostname = localhost
|
16
tools/munin-node-from-hell/tarpit.conf
Normal file
16
tools/munin-node-from-hell/tarpit.conf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Config file for muninnode-from-hell.
|
||||||
|
#
|
||||||
|
|
||||||
|
[instance:tarpit]
|
||||||
|
pluginprofile = tarpit
|
||||||
|
port = 3000
|
||||||
|
|
||||||
|
[pluginprofile:tarpit]
|
||||||
|
plugins = tarpit, load, locks
|
||||||
|
|
||||||
|
[pluginprofile:base]
|
||||||
|
plugins = load, locks, locks, load, load, locks, locks, load, load, load
|
||||||
|
|
||||||
|
[base]
|
||||||
|
# when building an example config with --muninconf, what hostname to output.
|
||||||
|
hostname = localhost
|
Loading…
Add table
Add a link
Reference in a new issue