Révision d33f5b31
Add munin-node-from-hell, a quite unfriendly munin node
| tools/munin-node-from-hell/MIT-LICENSE | ||
|---|---|---|
| 1 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
| 2 |
of this software and associated documentation files (the "Software"), to deal |
|
| 3 |
in the Software without restriction, including without limitation the rights |
|
| 4 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
| 5 |
copies of the Software, and to permit persons to whom the Software is |
|
| 6 |
furnished to do so, subject to the following conditions: |
|
| 7 |
|
|
| 8 |
The above copyright notice and this permission notice shall be included in |
|
| 9 |
all copies or substantial portions of the Software. |
|
| 10 |
|
|
| 11 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 12 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 13 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 14 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 15 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 16 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
| 17 |
THE SOFTWARE. |
|
| tools/munin-node-from-hell/README.rst | ||
|---|---|---|
| 1 |
munin-node from hell |
|
| 2 |
==================== |
|
| 3 |
|
|
| 4 |
This is a simple implementation of a munin node (http://munin-monitoring.org/) |
|
| 5 |
that is made to give the polling server a hard time. |
|
| 6 |
|
|
| 7 |
In practice this is the munin-node that we use to develop and test the awesome |
|
| 8 |
stuff we do at http://hostedmunin.com/ . Use it as you feel fit :) |
|
| 9 |
|
|
| 10 |
Current features controlled via config file: |
|
| 11 |
|
|
| 12 |
* Respond slowly or never to queries. |
|
| 13 |
* Have plugins that always are in warning or alarm. |
|
| 14 |
* Extensive number of plugins. |
|
| 15 |
* Run on multiple ports at the same time, to test huge amounts of clients. |
|
| 16 |
|
|
| 17 |
|
|
| 18 |
Usage |
|
| 19 |
----- |
|
| 20 |
|
|
| 21 |
munin-node-from-hell takes two arguments; the mode and which config file to |
|
| 22 |
use. Mode is either --run or --muninconf. |
|
| 23 |
|
|
| 24 |
This software is meant to run as an ordinary unix user, please don't run |
|
| 25 |
it as root without some thought. |
|
| 26 |
|
|
| 27 |
You probably want: |
|
| 28 |
|
|
| 29 |
./munin-node-from-hell --run simple.conf |
|
| 30 |
|
|
| 31 |
To make a config snippet to put in munin.conf: |
|
| 32 |
|
|
| 33 |
./munin-node-from-hell --muninconf simple.conf > snippet.conf |
|
| 34 |
|
|
| 35 |
License |
|
| 36 |
------- |
|
| 37 |
|
|
| 38 |
See the file MIT-LICENSE for details. |
|
| 39 |
|
|
| 40 |
Contact |
|
| 41 |
------- |
|
| 42 |
|
|
| 43 |
Lasse Karstensen <lasse.karstensen@gmail.com> |
|
| tools/munin-node-from-hell/example-config.conf | ||
|---|---|---|
| 1 |
# example config file for muninnode-from-hell. |
|
| 2 |
# |
|
| 3 |
# Flow: |
|
| 4 |
# * basic stuff in [base]. |
|
| 5 |
# * a set of plugins should be defined in a [pluginprofile:PROFILENAME] |
|
| 6 |
# * an instance (ie, server running on a local port) is added with |
|
| 7 |
# [instance:INSTANCENAME] |
|
| 8 |
# |
|
| 9 |
# Instances has: |
|
| 10 |
# port = XXXX |
|
| 11 |
# AND/OR |
|
| 12 |
# portrange = 2000-2005 |
|
| 13 |
# |
|
| 14 |
# mode = sleepy|exp |
|
| 15 |
# sleepyness = 10 # when mode=sleepy, this is the uniform sleep time. |
|
| 16 |
# lambd = 10 # when mode=exp, this is the mean sleep time. |
|
| 17 |
# exp is good to emulate load peaks, it can easily sleep 0.02 or 20 seconds. |
|
| 18 |
# (but less often 30 :)) |
|
| 19 |
|
|
| 20 |
[base] |
|
| 21 |
# when building an example config with --muninconf, what hostname to output. |
|
| 22 |
hostname = localhost |
|
| 23 |
|
|
| 24 |
[pluginprofile:tarpit++] |
|
| 25 |
plugins = tarpit, load, locks, locks, load, tarpit, load, locks, locks, load, load, load |
|
| 26 |
|
|
| 27 |
[pluginprofile:base] |
|
| 28 |
plugins = load, locks, locks, load, load, locks, locks, load, load, load |
|
| 29 |
|
|
| 30 |
[pluginprofile:manyservices] |
|
| 31 |
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 |
|
| 32 |
|
|
| 33 |
[instance:bar] |
|
| 34 |
pluginprofile = base |
|
| 35 |
port = 4000 |
|
| 36 |
mode = sleepy |
|
| 37 |
# 40*10 should be aborted due to server time constraints. |
|
| 38 |
sleepyness = 40 |
|
| 39 |
#lambd = 5 |
|
| 40 |
|
|
| 41 |
[instance:25sec] |
|
| 42 |
pluginprofile = base |
|
| 43 |
port = 4001 |
|
| 44 |
mode = exp |
|
| 45 |
#sleepyness = 30 |
|
| 46 |
# mean 2sec |
|
| 47 |
lambd = 2 |
|
| 48 |
|
|
| 49 |
|
|
| 50 |
[instance:baz] |
|
| 51 |
pluginprofile = base |
|
| 52 |
pluginmultiplier = 2 |
|
| 53 |
port = 4940 |
|
| 54 |
mode = sleepy |
|
| 55 |
sleepyness = 5 |
|
| 56 |
#10 |
|
| 57 |
#lambd = 5 |
|
| 58 |
|
|
| 59 |
# bringer of chaos |
|
| 60 |
[instance:qux] |
|
| 61 |
pluginprofile = base |
|
| 62 |
port = 4948 |
|
| 63 |
mode = exp |
|
| 64 |
lambd = 10 |
|
| 65 |
|
|
| 66 |
[instance:tarpit] |
|
| 67 |
pluginprofile = tarpit++ |
|
| 68 |
port = 3000 |
|
| tools/munin-node-from-hell/muninnode-from-hell | ||
|---|---|---|
| 1 |
#!/usr/bin/python |
|
| 2 |
# |
|
| 3 |
# Artificial munin node that behaves in all the ways you would like |
|
| 4 |
# ordinary nodes _not_ to behave. |
|
| 5 |
# |
|
| 6 |
# Intended use is for designing and debugging munin-server poller to handle |
|
| 7 |
# such problems. |
|
| 8 |
# |
|
| 9 |
# See the file MIT-LICENSE for licensing information. |
|
| 10 |
# |
|
| 11 |
# Copyright (C) 2011 Karstensen IT |
|
| 12 |
# Written by Lasse Karstensen <lasse.karstensen@gmail.com>, Dec 2011. |
|
| 13 |
|
|
| 14 |
import os, sys, time, random |
|
| 15 |
import socket |
|
| 16 |
import threading |
|
| 17 |
import SocketServer |
|
| 18 |
import ConfigParser |
|
| 19 |
|
|
| 20 |
VERSION = "muninnode-from-hell v0.1" |
|
| 21 |
modules = {}
|
|
| 22 |
|
|
| 23 |
class MuninPlugin: |
|
| 24 |
def sleep_fetch(self, conf): |
|
| 25 |
period = None |
|
| 26 |
if conf.get("mode") == "sleepy" and conf.get("sleepyness"):
|
|
| 27 |
period = float(conf.get("sleepyness"))
|
|
| 28 |
if conf.get("mode") == "exp" and conf.get("lambd"):
|
|
| 29 |
period = random.expovariate(1 / float(conf.get("lambd")))
|
|
| 30 |
|
|
| 31 |
if period: |
|
| 32 |
#print "will sleep %.3f seconds" % period |
|
| 33 |
time.sleep(period) |
|
| 34 |
|
|
| 35 |
def sleep_config(self, conf): |
|
| 36 |
return self.sleep_fetch(conf) |
|
| 37 |
|
|
| 38 |
|
|
| 39 |
class load(MuninPlugin): |
|
| 40 |
def fetch(self, conf): |
|
| 41 |
self.sleep_fetch(conf) |
|
| 42 |
load = open("/proc/loadavg", "r").read()
|
|
| 43 |
load, rest = load.split(" ", 1)
|
|
| 44 |
load = float(load) |
|
| 45 |
return "load.value %.2f" % load |
|
| 46 |
|
|
| 47 |
def config(self, conf): |
|
| 48 |
self.sleep_config(conf) |
|
| 49 |
return """graph_title Load average |
|
| 50 |
graph_args --base 1000 -l 0 |
|
| 51 |
graph_vlabel load |
|
| 52 |
graph_scale no |
|
| 53 |
graph_category system |
|
| 54 |
load.label load |
|
| 55 |
graph_info The load average of the machine describes how many processes are in the run-queue (scheduled to run "immediately"). |
|
| 56 |
load.info 5 minute load average """ |
|
| 57 |
modules["load"] = load() |
|
| 58 |
|
|
| 59 |
class locks(MuninPlugin): |
|
| 60 |
def fetch(self, conf): |
|
| 61 |
self.sleep_fetch(conf) |
|
| 62 |
fp = open("/proc/locks", "r")
|
|
| 63 |
fdata = fp.readlines() |
|
| 64 |
return "locks.value %i" % len(fdata) |
|
| 65 |
|
|
| 66 |
def config(self, conf): |
|
| 67 |
self.sleep_config(conf) |
|
| 68 |
return """graph_title Filesystem locks |
|
| 69 |
graph_vlabel number of locks |
|
| 70 |
graph_scale no |
|
| 71 |
graph_info This graph shows file system lock info |
|
| 72 |
graph_category system |
|
| 73 |
locks.label number of locks |
|
| 74 |
locks.info Number of active locks""" |
|
| 75 |
modules["locks"] = locks() |
|
| 76 |
|
|
| 77 |
class tarpit(MuninPlugin): |
|
| 78 |
"Nasty plugin that never responds" |
|
| 79 |
def fetch(self, conf): |
|
| 80 |
time.sleep(1000) |
|
| 81 |
|
|
| 82 |
def config(self, conf): |
|
| 83 |
time.sleep(1000) |
|
| 84 |
modules["tarpit"] = tarpit() |
|
| 85 |
|
|
| 86 |
class always_warning(MuninPlugin): |
|
| 87 |
def fetch(self, conf): |
|
| 88 |
return "generic.value 10" |
|
| 89 |
|
|
| 90 |
def config(self, conf): |
|
| 91 |
return """graph_title Always in warning |
|
| 92 |
graph_vlabel Level |
|
| 93 |
graph_scale no |
|
| 94 |
graph_info A simple graph that is always in warning or alarm |
|
| 95 |
graph_category active_notification |
|
| 96 |
generic.label Level |
|
| 97 |
generic.info Level usually above warning level |
|
| 98 |
generic.warn 5 |
|
| 99 |
generic.crit 10""" |
|
| 100 |
modules["always_warning"] = always_warning() |
|
| 101 |
|
|
| 102 |
class always_alarm(always_warning): |
|
| 103 |
def fetch(self, conf): |
|
| 104 |
return "generic.value 20" |
|
| 105 |
modules["always_alarm"] = always_alarm() |
|
| 106 |
|
|
| 107 |
|
|
| 108 |
class ArgumentTCPserver(SocketServer.ThreadingTCPServer): |
|
| 109 |
def __init__(self, server_address, RequestHandlerClass, args): |
|
| 110 |
SocketServer.ThreadingTCPServer.__init__(self,server_address, RequestHandlerClass) |
|
| 111 |
self.args = args |
|
| 112 |
|
|
| 113 |
|
|
| 114 |
class MuninHandler(SocketServer.StreamRequestHandler): |
|
| 115 |
""" |
|
| 116 |
Munin server implementation. |
|
| 117 |
|
|
| 118 |
This is based on munin_node.py by Chris Holcombe / http://sourceforge.net/projects/pythonmuninnode/ |
|
| 119 |
|
|
| 120 |
Possible commands: |
|
| 121 |
list, nodes, config, fetch, version or quit |
|
| 122 |
""" |
|
| 123 |
|
|
| 124 |
def handle(self): |
|
| 125 |
print "%s: Connection from %s:%s. server args is %s" \ |
|
| 126 |
% (self.server.args["name"], self.client_address[0], self.client_address[1], self.server.args) |
|
| 127 |
# slow path |
|
| 128 |
hostname = self.server.args["name"] |
|
| 129 |
full_hostname = hostname |
|
| 130 |
|
|
| 131 |
moduleprofile = self.server.args["pluginprofile"] |
|
| 132 |
modulenames = set(moduleprofile) |
|
| 133 |
|
|
| 134 |
self.wfile.write("# munin node at %s\n" % hostname)
|
|
| 135 |
|
|
| 136 |
while True: |
|
| 137 |
line = self.rfile.readline().strip() |
|
| 138 |
try: |
|
| 139 |
cmd, args = line.split(" ", 1)
|
|
| 140 |
except ValueError: |
|
| 141 |
cmd = line |
|
| 142 |
args = "" |
|
| 143 |
|
|
| 144 |
if not cmd or cmd == "quit": |
|
| 145 |
break |
|
| 146 |
|
|
| 147 |
if cmd == "list": |
|
| 148 |
# List all plugins that are available |
|
| 149 |
self.wfile.write(" ".join(self.server.args["plugins"].keys()) + "\n")
|
|
| 150 |
elif cmd == "nodes": |
|
| 151 |
# We just support this host |
|
| 152 |
self.wfile.write("%s\n.\n" % full_hostname)
|
|
| 153 |
elif cmd == "config": |
|
| 154 |
# display the config information of the plugin |
|
| 155 |
if not self.server.args["plugins"].has_key(args): |
|
| 156 |
self.wfile.write("# Unknown service\n.\n" )
|
|
| 157 |
else: |
|
| 158 |
config = self.server.args["plugins"][args].config(self.server.args) |
|
| 159 |
if config is None: |
|
| 160 |
self.wfile.write("# Unknown service\n.\n")
|
|
| 161 |
else: |
|
| 162 |
self.wfile.write(config + "\n.\n") |
|
| 163 |
elif cmd == "fetch": |
|
| 164 |
# display the data information as returned by the plugin |
|
| 165 |
if not self.server.args["plugins"].has_key(args): |
|
| 166 |
self.wfile.write("# Unknown service\n.\n")
|
|
| 167 |
else: |
|
| 168 |
data = self.server.args["plugins"][args].fetch(self.server.args) |
|
| 169 |
if data is None: |
|
| 170 |
self.wfile.write("# Unknown service\n.\n")
|
|
| 171 |
else: |
|
| 172 |
self.wfile.write(data + "\n.\n") |
|
| 173 |
elif cmd == "version": |
|
| 174 |
# display the server version |
|
| 175 |
self.wfile.write("munin node on %s version: %s\n" %
|
|
| 176 |
(full_hostname, VERSION)) |
|
| 177 |
else: |
|
| 178 |
self.wfile.write("# Unknown command. Try list, nodes, " \
|
|
| 179 |
"config, fetch, version or quit\n") |
|
| 180 |
|
|
| 181 |
|
|
| 182 |
def start_servers(instances): |
|
| 183 |
# TODO: Listen to IPv6 |
|
| 184 |
HOST = "0.0.0.0" |
|
| 185 |
servers = {}
|
|
| 186 |
for iconf in instances: |
|
| 187 |
print "Setting up instance %s at port %s" \ |
|
| 188 |
% (iconf["name"], iconf["expanded_port"]) |
|
| 189 |
|
|
| 190 |
server = ArgumentTCPserver((HOST, iconf["expanded_port"]), MuninHandler, iconf) |
|
| 191 |
server_thread = threading.Thread(target=server.serve_forever) |
|
| 192 |
server_thread.daemon = True |
|
| 193 |
server_thread.start() |
|
| 194 |
|
|
| 195 |
servers[iconf["name"]] = server |
|
| 196 |
return servers |
|
| 197 |
|
|
| 198 |
|
|
| 199 |
|
|
| 200 |
def usage(): |
|
| 201 |
print "Usage: %s [--run] [--muninconf] <configfile>" % sys.argv[0] |
|
| 202 |
|
|
| 203 |
def main(): |
|
| 204 |
if len(sys.argv) <= 2: |
|
| 205 |
usage() |
|
| 206 |
sys.exit(1) |
|
| 207 |
|
|
| 208 |
config = ConfigParser.RawConfigParser() |
|
| 209 |
config.read(sys.argv[2]) |
|
| 210 |
|
|
| 211 |
instancekeys = [ key for key in config.sections() if key.startswith("instance:") ]
|
|
| 212 |
servers = {}
|
|
| 213 |
|
|
| 214 |
instances = [] |
|
| 215 |
|
|
| 216 |
for key in instancekeys: |
|
| 217 |
instancename = key.split(":", 2)[1]
|
|
| 218 |
portrange = [] |
|
| 219 |
if config.has_option(key, "port"): |
|
| 220 |
portrange = [ config.getint(key, "port") ] |
|
| 221 |
if config.has_option(key, "portrange"): |
|
| 222 |
rangestr = config.get(key, "portrange") |
|
| 223 |
ranges = rangestr.split("-")
|
|
| 224 |
range_expanded = range(int(ranges[0]), int(ranges[1])+1, 1) |
|
| 225 |
portrange += range_expanded |
|
| 226 |
|
|
| 227 |
if len(portrange) == 0: |
|
| 228 |
print "WARN: No port or portrange defined for instance %s" \ |
|
| 229 |
% instancename |
|
| 230 |
|
|
| 231 |
pluginprofile = "pluginprofile:%s" % config.get(key, "pluginprofile") |
|
| 232 |
if not config.has_section(pluginprofile): |
|
| 233 |
print "WARN: Definition for pluginprofile %s not found, skipping" \ |
|
| 234 |
% config.get(key, "pluginprofile") |
|
| 235 |
continue |
|
| 236 |
|
|
| 237 |
plugins = {}
|
|
| 238 |
tentative_pluginlist = config.get(pluginprofile, "plugins").split(",")
|
|
| 239 |
assert(len(tentative_pluginlist) > 0) |
|
| 240 |
for tentative_plugin in tentative_pluginlist: |
|
| 241 |
tentative_plugin = tentative_plugin.strip() |
|
| 242 |
if not modules.has_key(tentative_plugin): |
|
| 243 |
print "WARN: Pluginprofile %s specifies unknown plugin %s" \ |
|
| 244 |
% (pluginprofile, tentative_plugin) |
|
| 245 |
continue |
|
| 246 |
|
|
| 247 |
# support more than one instanciation of the same plugin. |
|
| 248 |
plugininstancename = tentative_plugin |
|
| 249 |
i=2 |
|
| 250 |
while (plugins.has_key(plugininstancename)): |
|
| 251 |
plugininstancename = tentative_plugin + str(i) |
|
| 252 |
i += 1 |
|
| 253 |
|
|
| 254 |
plugins[plugininstancename] = modules[tentative_plugin] |
|
| 255 |
|
|
| 256 |
for portinstance in portrange: |
|
| 257 |
instanceconfig = dict() |
|
| 258 |
|
|
| 259 |
for k,v in config.items(key): |
|
| 260 |
instanceconfig[k] = v |
|
| 261 |
|
|
| 262 |
instanceconfig["plugins"] = plugins |
|
| 263 |
|
|
| 264 |
instanceconfig["name"] = "%s-%s" % (instancename, portinstance) |
|
| 265 |
instanceconfig["expanded_port"] = portinstance |
|
| 266 |
|
|
| 267 |
instances.append(instanceconfig) |
|
| 268 |
# XXX: need to store what handlers we should have. |
|
| 269 |
|
|
| 270 |
# output sample munin config for the poller |
|
| 271 |
if "--muninconf" in sys.argv: |
|
| 272 |
for i in instances: |
|
| 273 |
print "[%s;%s]\n\taddress %s\n\tuse_node_name yes\n\tport %s\n" \ |
|
| 274 |
% ( "fromhell", i["name"], config.get("base","hostname"), i["port"])
|
|
| 275 |
|
|
| 276 |
|
|
| 277 |
if "--run" in sys.argv: |
|
| 278 |
servers = start_servers(instances) |
|
| 279 |
|
|
| 280 |
try: |
|
| 281 |
while True: |
|
| 282 |
time.sleep(0.5) |
|
| 283 |
except KeyboardInterrupt: |
|
| 284 |
print "Caught Ctrl-c, shutting down.." |
|
| 285 |
for port, server in servers.items(): |
|
| 286 |
server.shutdown() |
|
| 287 |
sys.exit(0) |
|
| 288 |
|
|
| 289 |
if __name__ == "__main__": |
|
| 290 |
main() |
|
| tools/munin-node-from-hell/notifications.conf | ||
|---|---|---|
| 1 |
# Example config file for muninnode-from-hell. |
|
| 2 |
# |
|
| 3 |
# Run an instance with plugins that always has some |
|
| 4 |
# notification level. (unknown / warning / critical) |
|
| 5 |
|
|
| 6 |
[instance:notifications] |
|
| 7 |
pluginprofile = notif |
|
| 8 |
port = 3000 |
|
| 9 |
|
|
| 10 |
# |
|
| 11 |
#[instance:baz] |
|
| 12 |
#port = 4940 |
|
| 13 |
#sleepyness = 30 |
|
| 14 |
[pluginprofile:notif] |
|
| 15 |
plugins = always_warning, always_alarm |
|
| 16 |
|
|
| 17 |
[base] |
|
| 18 |
# when building an example config with --muninconf, what hostname to output. |
|
| 19 |
hostname = localhost |
|
| tools/munin-node-from-hell/simple.conf | ||
|---|---|---|
| 1 |
# Example config file for muninnode-from-hell. |
|
| 2 |
# |
|
| 3 |
# This is the simplest possible config, just run an ordinary munin-node |
|
| 4 |
# with a trivial amount of plugins on a single port. |
|
| 5 |
# |
|
| 6 |
|
|
| 7 |
[instance:simple] |
|
| 8 |
pluginprofile = base |
|
| 9 |
port = 4000 |
|
| 10 |
|
|
| 11 |
[pluginprofile:base] |
|
| 12 |
plugins = load, locks |
|
| 13 |
|
|
| 14 |
[base] |
|
| 15 |
# when building an example config with --muninconf, what hostname to output. |
|
| 16 |
hostname = localhost |
|
| tools/munin-node-from-hell/tarpit.conf | ||
|---|---|---|
| 1 |
# Config file for muninnode-from-hell. |
|
| 2 |
# |
|
| 3 |
|
|
| 4 |
[instance:tarpit] |
|
| 5 |
pluginprofile = tarpit |
|
| 6 |
port = 3000 |
|
| 7 |
|
|
| 8 |
[pluginprofile:tarpit] |
|
| 9 |
plugins = tarpit, load, locks |
|
| 10 |
|
|
| 11 |
[pluginprofile:base] |
|
| 12 |
plugins = load, locks, locks, load, load, locks, locks, load, load, load |
|
| 13 |
|
|
| 14 |
[base] |
|
| 15 |
# when building an example config with --muninconf, what hostname to output. |
|
| 16 |
hostname = localhost |
|
Formats disponibles : Unified diff