Projet

Général

Profil

Révision d33f5b31

IDd33f5b319f5d0c05224ef05ca5423a1aad505beb
Parent 5cc0fa32
Enfant c1a5b601, 6df2ce23

Ajouté par Lasse Karstensen il y a presque 14 ans

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

Voir les différences:

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