1
0
Fork 0
mirror of https://github.com/munin-monitoring/contrib.git synced 2025-07-22 02:51:03 +00:00

Add munin-node-from-hell, a quite unfriendly munin node

This commit is contained in:
Lasse Karstensen 2012-01-16 18:19:35 +01:00
parent 5cc0fa32fe
commit d33f5b319f
7 changed files with 469 additions and 0 deletions

View 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.

View 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>

View 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

View 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()

View 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

View 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

View 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