contrib-munin/tools/munin-node-from-hell/muninnode-from-hell

409 lines
13 KiB
Python
Executable File

#!/usr/bin/python
# .- coding: utf-8 -.
#
# 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 __init__(self):
self.current_load = None
self.current_locks = None
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)
def find_load(self):
# At about a thousand node instances you get this:
#IOError: [Errno 24] Too many open files: '/proc/loadavg'
# cache it for a bit..
if (not self.current_load) or random.randint(0,100) == 1:
load = open("/proc/loadavg", "r").read()
load, rest = load.split(" ", 1)
self.current_load = float(load)
return self.current_load
def find_locks(self):
if (not self.current_locks) or random.randint(0,100) == 1:
fp = open("/proc/locks", "r")
self.current_locks = len(fp.readlines())
return self.current_locks
class load(MuninPlugin):
def fetch(self, conf):
self.sleep_fetch(conf)
return "load.value %.2f" % self.find_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)
return "locks.value %i" % self.find_locks()
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):
conftext = """graph_title Always in LEVEL
graph_vlabel Level
graph_scale no
graph_info A simple graph that is always in LEVEL
graph_category always_LEVEL
generic.label Level
generic.info Level usually above warning level
generic.warning 5
generic.critical 10"""
def fetch(self, conf):
return "generic.value 10"
def config(self, conf):
return self.conftext.replace("LEVEL","warning")
modules["always_warning"] = always_warning()
class always_critical(always_warning):
def fetch(self, conf):
return "generic.value 20"
def config(self, conf):
return self.conftext.replace("LEVEL","critical")
modules["always_critical"] = always_critical()
class failing_plugin(MuninPlugin):
"A really broken plugin"
def fetch(self, conf):
return "# Bad exit"
def config(self, conf):
return "# Bad exit"
modules["failing_plugin"] = failing_plugin()
class failing_plugin2(MuninPlugin):
def fetch(self, conf):
return "# Bad exit"
def config(self, conf):
return """graph_title Config works, fetch fails
graph_vlabel Level
graph_category failing
generic.label generic_label_here
generic.info never_really_used"""
modules["failing_plugin2"] = failing_plugin2()
class failing_plugin3(MuninPlugin):
def config(self, conf):
return """graph_title A plugin with two dses but only fetch value for one
graph_args --base 1000 -l 0
fivemin.label 1 minute load
onemin.label 5 minute load"""
def fetch(self, conf):
return "onemin.value 1"
modules["failing_plugin3"] = failing_plugin3()
class graph_area(MuninPlugin):
"A plugin that uses STACK and AREA. From proc_pri. Use: testing the grapher"
def fetch(self, conf):
return """high.value 3
low.value 2
locked.value 1"""
def config(self, conf):
return """graph_title AREA and STACK
graph_order low high locked
graph_category graphtest
graph_info This graph shows nuber of processes at each priority
graph_args --base 1000 -l 0
graph_vlabel Number of processes
high.label high priority
high.draw STACK
high.info The number of high-priority processes (tasks)
low.label low priority
low.draw AREA
low.info The number of low-priority processes (tasks)
locked.label locked in memory
locked.draw STACK
locked.info The number of processes that have pages locked into memory (for real-time and custom IO)
"""
modules["graph_area"] = graph_area()
class utf8_graphcat(MuninPlugin):
"A plugin with a graph category which has UTF-8 in it"
def fetch(self, conf):
return "apples.value %.2f" % self.find_load()
def config(self, conf):
return """graph_title Example UTF-8 graph
graph_vlabel apples
graph_category foo™
apples.label apples
graph_info Apples eaten
apples.info Apples eaten"""
modules["utf8_graphcat"] = utf8_graphcat()
class utf8_graphname(MuninPlugin):
"A plugin with a UTF-8 name"
def fetch(self, conf):
return "apples.value %.2f" % self.find_load()
def config(self, conf):
return """graph_title Example UTF-8 graph
graph_vlabel apples
graph_category system
apples.label apples
graph_info Apples eaten
apples.info Apples eaten"""
modules["utf8_™graphname"] = utf8_graphname()
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):
if self.server.args.get("verbose"): 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] [--verbose] [--muninconf] <configfile> <configfileN>" % sys.argv[0]
def main():
if len(sys.argv) <= 2:
usage()
sys.exit(1)
verbose = False
if "--verbose" in sys.argv:
verbose = True
config = ConfigParser.RawConfigParser()
for configfile in sys.argv[1:]:
if not configfile.endswith(".conf"):
continue
if verbose:
print "Reading config file %s" % configfile
config.read(configfile)
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 instantiation 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["verbose"] = verbose
instanceconfig["name"] = "%s-%s" % (instancename, portinstance)
instanceconfig["expanded_port"] = portinstance
instances.append(instanceconfig)
# XXX: need to store what handlers we should have.
print instances
# 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:
if verbose: print "Starting up.."
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()