Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / tools / munin-node-from-hell / muninnode-from-hell @ 0442899e

Historique | Voir | Annoter | Télécharger (11,6 ko)

1
#!/usr/bin/python
2
# .- coding: utf-8 -.
3
#
4
# Artificial munin node that behaves in all the ways you would like
5
# ordinary nodes _not_ to behave.
6
#
7
# Intended use is for designing and debugging munin-server poller to handle 
8
# such problems.
9
#
10
# See the file MIT-LICENSE for licensing information.
11
#
12
# Copyright (C) 2011 Karstensen IT
13
# Written by Lasse Karstensen <lasse.karstensen@gmail.com>, Dec 2011.
14

    
15
import os, sys, time, random
16
import socket
17
import threading
18
import SocketServer
19
import ConfigParser
20

    
21
VERSION = "muninnode-from-hell v0.1"
22
modules = {}
23

    
24
class MuninPlugin:
25
    def sleep_fetch(self, conf):
26
        period = None
27
        if conf.get("mode") == "sleepy" and conf.get("sleepyness"):
28
            period = float(conf.get("sleepyness"))
29
        if conf.get("mode") == "exp" and conf.get("lambd"):
30
            period = random.expovariate(1 / float(conf.get("lambd")))
31

    
32
        if period:
33
            #print "will sleep %.3f seconds" % period
34
            time.sleep(period)
35
            
36
    def sleep_config(self, conf):
37
        return self.sleep_fetch(conf)
38

    
39

    
40
class load(MuninPlugin):
41
    def fetch(self, conf):
42
        self.sleep_fetch(conf)
43
        load = open("/proc/loadavg", "r").read()
44
        load, rest = load.split(" ", 1)
45
        load = float(load)
46
        return "load.value %.2f" % load
47

    
48
    def config(self, conf):
49
        self.sleep_config(conf)
50
        return """graph_title Load average
51
graph_args --base 1000 -l 0
52
graph_vlabel load
53
graph_scale no
54
graph_category system
55
load.label load
56
graph_info The load average of the machine describes how many processes are in the run-queue (scheduled to run "immediately").
57
load.info 5 minute load average """
58
modules["load"] = load()
59

    
60
class locks(MuninPlugin):
61
    def fetch(self, conf):
62
        self.sleep_fetch(conf)
63
        fp = open("/proc/locks", "r")
64
        fdata = fp.readlines()
65
        return "locks.value %i" % len(fdata)
66

    
67
    def config(self, conf):
68
        self.sleep_config(conf)
69
        return """graph_title Filesystem locks
70
graph_vlabel number of locks
71
graph_scale no
72
graph_info This graph shows file system lock info
73
graph_category system
74
locks.label number of locks
75
locks.info Number of active locks"""
76
modules["locks"] = locks()
77

    
78
class tarpit(MuninPlugin):
79
    "Nasty plugin that never responds"
80
    def fetch(self, conf):
81
        time.sleep(1000)
82

    
83
    def config(self, conf):
84
        time.sleep(1000)
85
modules["tarpit"] = tarpit()
86

    
87
class always_warning(MuninPlugin):
88
    conftext = """graph_title Always in LEVEL
89
graph_vlabel Level
90
graph_scale no
91
graph_info A simple graph that is always in LEVEL
92
graph_category always_LEVEL
93
generic.label Level
94
generic.info Level usually above warning level
95
generic.warning 5
96
generic.critical 10"""
97

    
98
    def fetch(self, conf):
99
        return "generic.value 10"
100

    
101
    def config(self, conf):
102
        return self.conftext.replace("LEVEL","warning")
103
modules["always_warning"] = always_warning()
104

    
105
class always_critical(always_warning):
106
    def fetch(self, conf):
107
        return "generic.value 20"
108

    
109
    def config(self, conf):
110
        return self.conftext.replace("LEVEL","critical")
111
modules["always_critical"] = always_critical()
112

    
113
class graph_area(MuninPlugin):
114
    "A plugin that uses STACK and AREA. From proc_pri. Use: testing the grapher"
115
    def fetch(self, conf):
116
        return """high.value 3
117
low.value 2
118
locked.value 1"""
119

    
120
    def config(self, conf):
121
        return """graph_title AREA and STACK
122
graph_order low high locked
123
graph_category graphtest
124
graph_info This graph shows nuber of processes at each priority
125
graph_args --base 1000 -l 0
126
graph_vlabel Number of processes
127
high.label high priority
128
high.draw STACK
129
high.info The number of high-priority processes (tasks)
130
low.label low priority
131
low.draw AREA
132
low.info The number of low-priority processes (tasks)
133
locked.label locked in memory
134
locked.draw STACK
135
locked.info The number of processes that have pages locked into memory (for real-time and custom IO)
136
"""
137
modules["graph_area"] = graph_area()
138

    
139
class utf8_graphcat(MuninPlugin):
140
    "A plugin with a graph category which has UTF-8 in it"
141
    def fetch(self, conf):
142
        load = open("/proc/loadavg", "r").read()
143
        load, rest = load.split(" ", 1)
144
        load = float(load)
145
        return "apples.value %.2f" % load
146

    
147
    def config(self, conf):
148
        return """graph_title Example UTF-8 graph
149
graph_vlabel apples
150
graph_category foo™
151
apples.label apples
152
graph_info Apples eaten
153
apples.info Apples eaten"""
154
modules["utf8_graphcat"] = utf8_graphcat()
155

    
156
class utf8_graphname(MuninPlugin):
157
    "A plugin with a UTF-8 name"
158
    def fetch(self, conf):
159
        load = open("/proc/loadavg", "r").read()
160
        load, rest = load.split(" ", 1)
161
        load = float(load)
162
        return "apples.value %.2f" % load
163

    
164
    def config(self, conf):
165
        return """graph_title Example UTF-8 graph
166
graph_vlabel apples
167
graph_category system
168
apples.label apples
169
graph_info Apples eaten
170
apples.info Apples eaten"""
171
modules["utf8_™graphname"] = utf8_graphname()
172

    
173

    
174
class ArgumentTCPserver(SocketServer.ThreadingTCPServer):
175
    def __init__(self, server_address, RequestHandlerClass, args):
176
        SocketServer.ThreadingTCPServer.__init__(self,server_address, RequestHandlerClass)
177
        self.args = args
178

    
179

    
180
class MuninHandler(SocketServer.StreamRequestHandler):
181
    """
182
    Munin server implementation.
183

    
184
    This is based on munin_node.py by Chris Holcombe / http://sourceforge.net/projects/pythonmuninnode/
185

    
186
    Possible commands:
187
    list, nodes, config, fetch, version or quit
188
    """
189

    
190
    def handle(self):
191
        if self.server.args.get("verbose"): print "%s: Connection from %s:%s. server args is %s" \
192
            % (self.server.args["name"], self.client_address[0], self.client_address[1], self.server.args)
193
        # slow path
194
        hostname = self.server.args["name"]
195
        full_hostname = hostname
196

    
197
        moduleprofile = self.server.args["pluginprofile"]
198
        modulenames = set(moduleprofile)
199

    
200
        self.wfile.write("# munin node at %s\n" % hostname)
201

    
202
        while True:
203
            line = self.rfile.readline().strip()
204
            try:
205
                cmd, args = line.split(" ", 1)
206
            except ValueError:
207
                cmd = line
208
                args = ""
209

    
210
            if not cmd or cmd == "quit":
211
                break
212

    
213
            if cmd == "list":
214
                # List all plugins that are available
215
                self.wfile.write(" ".join(self.server.args["plugins"].keys()) + "\n")
216
            elif cmd == "nodes":
217
                # We just support this host
218
                self.wfile.write("%s\n.\n" % full_hostname)
219
            elif cmd == "config":
220
                # display the config information of the plugin
221
                if not self.server.args["plugins"].has_key(args):
222
                    self.wfile.write("# Unknown service\n.\n" )
223
                else:
224
                    config = self.server.args["plugins"][args].config(self.server.args)
225
                    if config is None:
226
                        self.wfile.write("# Unknown service\n.\n")
227
                    else:
228
                        self.wfile.write(config + "\n.\n")
229
            elif cmd == "fetch":
230
                # display the data information as returned by the plugin
231
                if not self.server.args["plugins"].has_key(args):
232
                    self.wfile.write("# Unknown service\n.\n")
233
                else:
234
                    data = self.server.args["plugins"][args].fetch(self.server.args)
235
                    if data is None:
236
                        self.wfile.write("# Unknown service\n.\n")
237
                    else:
238
                        self.wfile.write(data + "\n.\n")
239
            elif cmd == "version":
240
                # display the server version
241
                self.wfile.write("munin node on %s version: %s\n" %
242
                                 (full_hostname, VERSION))
243
            else:
244
                self.wfile.write("# Unknown command. Try list, nodes, " \
245
                                 "config, fetch, version or quit\n")
246

    
247

    
248
def start_servers(instances):
249
    # TODO: Listen to IPv6
250
    HOST = "0.0.0.0"
251
    servers = {}
252
    for iconf in instances:
253
            print "Setting up instance %s at port %s" \
254
                % (iconf["name"], iconf["expanded_port"])
255

    
256
            server = ArgumentTCPserver((HOST, iconf["expanded_port"]), MuninHandler, iconf)
257
            server_thread = threading.Thread(target=server.serve_forever)
258
            server_thread.daemon = True
259
            server_thread.start()
260

    
261
            servers[iconf["name"]] = server
262
    return servers
263

    
264

    
265

    
266
def usage():
267
    print "Usage: %s [--run] [--verbose] [--muninconf] <configfile>" % sys.argv[0]
268

    
269
def main():
270
    if len(sys.argv) <= 2:
271
        usage()
272
        sys.exit(1)
273

    
274
    verbose = False
275
    if "--verbose" in sys.argv:
276
        verbose = True
277

    
278
    config = ConfigParser.RawConfigParser()
279
    config.read(sys.argv[-1])
280

    
281
    instancekeys = [ key for key in config.sections() if key.startswith("instance:") ]
282
    servers = {}
283

    
284
    instances = []
285

    
286
    for key in instancekeys:
287
        instancename = key.split(":", 2)[1]
288
        portrange = []
289
        if config.has_option(key, "port"):
290
            portrange = [ config.getint(key, "port") ]
291
        if config.has_option(key, "portrange"):
292
            rangestr = config.get(key, "portrange")
293
            ranges = rangestr.split("-")
294
            range_expanded = range(int(ranges[0]), int(ranges[1])+1, 1)
295
            portrange += range_expanded
296

    
297
        if len(portrange) == 0:
298
            print "WARN: No port or portrange defined for instance %s" \
299
                % instancename
300

    
301
        pluginprofile = "pluginprofile:%s" % config.get(key, "pluginprofile")
302
        if not config.has_section(pluginprofile):
303
            print "WARN: Definition for pluginprofile %s not found, skipping" \
304
                % config.get(key, "pluginprofile")
305
            continue
306

    
307
        plugins = {}
308
        tentative_pluginlist = config.get(pluginprofile, "plugins").split(",")
309
        assert(len(tentative_pluginlist) > 0)
310
        for tentative_plugin in tentative_pluginlist:
311
            tentative_plugin = tentative_plugin.strip()
312
            if not modules.has_key(tentative_plugin):
313
                print "WARN: Pluginprofile %s specifies unknown plugin %s" \
314
                    % (pluginprofile, tentative_plugin)
315
                continue
316

    
317
            # support more than one instanciation of the same plugin.
318
            plugininstancename = tentative_plugin
319
            i=2
320
            while (plugins.has_key(plugininstancename)):
321
                plugininstancename = tentative_plugin + str(i)
322
                i += 1
323

    
324
            plugins[plugininstancename] = modules[tentative_plugin]
325

    
326
        for portinstance in portrange:
327
            instanceconfig = dict()
328

    
329
            for k,v in config.items(key):
330
                instanceconfig[k] = v
331

    
332
            instanceconfig["plugins"] = plugins
333
            if "--verbose" in sys.argv:
334
                instanceconfig["verbose"] = True
335

    
336
            instanceconfig["name"] = "%s-%s" % (instancename, portinstance)
337
            instanceconfig["expanded_port"] = portinstance
338

    
339
            instances.append(instanceconfig)
340
            # XXX: need to store what handlers we should have.
341

    
342
    # output sample munin config for the poller
343
    if "--muninconf" in sys.argv:
344
        for i in instances:
345
            print "[%s;%s]\n\taddress %s\n\tuse_node_name yes\n\tport %s\n" \
346
                % ( "fromhell", i["name"], config.get("base","hostname"), i["port"])
347

    
348

    
349
    if "--run" in sys.argv:
350
        if verbose: print "Starting up.."
351
        servers = start_servers(instances)
352

    
353
        try:
354
            while True:
355
                time.sleep(0.5)
356
        except KeyboardInterrupt:
357
            print "Caught Ctrl-c, shutting down.."
358
            for port, server in servers.items():
359
                server.shutdown()
360
                sys.exit(0)
361

    
362
if __name__ == "__main__":
363
    main()