├── README.md
├── bin
└── moncli
├── init
├── lib
├── moncli
│ ├── __init__.py
│ ├── engine.py
│ ├── event.py
│ ├── help.py
│ └── tools.py
└── repository
│ ├── cpu
│ └── d8d1ed5d339390b9d9ddbb9958c11c2d
│ ├── disks
│ └── b567265aceb147762571995e009a25b0
│ ├── hadoop
│ └── c64c24e98176491023753d40e8e76e83
│ ├── load
│ └── 6eee2010a568109e06782b5fd8fead28
│ ├── memory
│ └── f4f2b50762bf835ce86b16a1576a4e70
│ ├── ping
│ └── c2376e4d6c4935750cc586bdd0931bdc
│ ├── processes
│ └── 7384027d47b04ca2af22f00d8f920e81
│ └── uptime
│ └── 216f25704df3c2d409d5f200c7302fb3
└── var
└── .gitignore
/README.md:
--------------------------------------------------------------------------------
1 |
2 | _____ _____ _ _
3 | | |___ ___| | |_|
4 | | | | | . | | --| | |
5 | |_|_|_|___|_|_|_____|_|_|
6 |
7 |
8 |
9 | Moncli is a generic MONitoring CLIent which executes and processes requests on an external system in order to interact with
10 | the host's local information sources which are normally not available over the network.
11 | Once Moncli has executed and evaluated the request it submits the check results back into the message broker infrastructure,
12 | where the results are ready to be by another process.
13 |
14 | * Powerful and flexible communication:
15 | All communication going from and to MonCli goes over the RabbitMQ message broker. This allows us to leverage all qualities
16 | RabbitMQ has to offer into your monitoring setup.
17 |
18 | * Easy but powerful configuration:
19 | Moncli receives all assignments and commands through the message broker infrastructure. All events MonCli should perform
20 | are defined in JSON format. This allows you to control all Moncli instances efficiently from a centralized location with just
21 | submitting a JSON document.
22 |
23 | * Have more checks on your monitoring server:
24 | Moncli has a built in scheduler. This scheduler receives a one time configuration of what to execute and evaluate. The
25 | scheduler repeats that check at the cycle you defined in this configuration. From that moment on, the results just flow
26 | into your monitoring system as passive checks without any effort required from your Monitoring server's scheduler. That
27 | significantly offloads the load on your monitoring server. Changing the properties of such a scheduled check is as
28 | simple as just submitting a new configuration to Moncli. The status of the scheduler along with the configurations is
29 | also written to disk on regular intervals so Moncli just continues working after a restart.
30 |
31 | * Simplify plugin/check development:
32 | Moncli uses as a data source plugins which only have to produce key/value pairs. Creating a script which only produces
33 | that kind of information is pretty easy and makes development of plugins accessible to non programmers.
34 |
35 | * Improve plugin quality:
36 | Moncli has all evaluation logic and other goodies built in so you don't have to worry about that when creating plugins.
37 | This standardizes the results more and improves the level of plugin quality.
38 |
39 | * Deliver more helpful information:
40 | Moncli plugins can optionally produce verbose information at your choice which rolls up into your Nagios interface.
41 | This is helpful for engineers who are debugging a certain problem.
42 |
43 | * Built-in plugin update system:
44 | Moncli has a built-in update system which allows you to transparently update the plugins from a centralized repository.
45 |
46 | * Resilience:
47 | When your monitoring infrastructure is not available or reachable all check results are queued in the broker environment,
48 | waiting to be processed. When a server is not online, all submitted commands will wait in the queue until MonCli picks
49 | them up.
50 |
51 | * Security:
52 | Only plugins with the correct hash can be executed which prevents the execution of changed or non conform plugins.
53 |
54 | * Compatibility:
55 | Moncli works together with monitoring frameworks which are based upon or derived from Nagios Core. It allows you
56 | to work with Nagios as you are used to it.
57 |
58 | * Ease of distribution:
59 | Moncli is written in Python. Once you have all its dependencies installed, you can "freeze" Moncli into a stand
60 | alone executable, which facilitates distribution to your nodes.
61 |
62 |
63 | For installation instruction please visit:
64 |
65 | http://www.smetj.net/wiki/Moncli_documentation
66 |
--------------------------------------------------------------------------------
/bin/moncli:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # moncli
5 | #
6 | # Copyright 2012 Jelle Smet development@smetj.net
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 |
25 | from optparse import OptionParser
26 | from threading import enumerate
27 | import os
28 | import sys
29 | import daemon
30 | import time
31 | import logging
32 | import traceback
33 |
34 |
35 | class Server():
36 | '''Starts the whole program and blocks from exiting'''
37 | def __init__(self, config=None):
38 | self.config = config
39 | self.thread_block = True
40 |
41 | def initLogging(self,debug,loglevel):
42 | if debug == True:
43 | tools.logger(loglevel=loglevel)
44 | else:
45 | tools.logger(syslog=True,loglevel=loglevel)
46 |
47 | self.logging = logging.getLogger(__name__)
48 |
49 | def checkRunning(self):
50 | try:
51 | if os.path.exists(self.config['pid']):
52 | pidfile = open(self.config['pid'], 'r')
53 | pid = pidfile.readline()
54 | pidfile.close()
55 | try:
56 | os.kill(int(pid), 0)
57 | except OSError:
58 | try:
59 | os.remove(self.config['pid'])
60 | except Exception as err:
61 | sys.stderr.write('I could not delete pid %s. Reason: %s\n' % (self.config['pid'], err))
62 | sys.exit(1)
63 | else:
64 | sys.stderr.write('There is already a version of Moncli running with PID %s\n' % (pid))
65 | sys.exit(1)
66 | except Exception as err:
67 | sys.stderr.write('There was a problem handling the PID file. Reason: %s\n' % (str(err)))
68 | sys.exit(1)
69 |
70 | def block(self):
71 | return self.thread_block
72 |
73 | def __shutdown(self):
74 | self.logging.info('Sending shutdown to all running threads. Waiting for all threads to end.')
75 | self.scheduler.shutdown()
76 | self.thread_block = False
77 | self.scheduler.shutdown()
78 | for thread in enumerate():
79 | if thread.isAlive():
80 | try:
81 | thread._Thread__stop()
82 | except:
83 | pass
84 | os.remove(self.config['pid'])
85 | sys.exit()
86 |
87 | def stop(self):
88 | sys.stdout.write('Stopping all queues in a polite way. Sending a Sigint (2) again will make me exit (or moncli stop).\n')
89 |
90 | try:
91 | pidfile = open(self.config['pid'], 'r')
92 | os.kill(int(pidfile.readline()), 2)
93 | pidfile.close()
94 | except Exception as err:
95 | sys.stdout.write('I could not stop Moncli. Reason: %s\n' % (err))
96 |
97 | def start(self,debug=False):
98 | #Enable profiling.
99 | #from moncli.tools import Profile
100 | #prof = Profile()
101 |
102 | self.initLogging(debug=debug,loglevel=logging.INFO)
103 |
104 | sys.path.append(self.config['lib'])
105 |
106 | #Create pid
107 | pidfile = open(self.config['pid'], 'w')
108 | pidfile.write(str(os.getpid()))
109 | pidfile.close()
110 | broker = None
111 | scheduler = None
112 | try:
113 | #Initialize Broker
114 | self.broker = engine.Broker(host=self.config['broker_host'],
115 | vhost=self.config['broker_vhost'],
116 | username=self.config['broker_username'],
117 | password=self.config['broker_password'],
118 | block=self.block)
119 |
120 | #Setup scheduler
121 | self.scheduler = engine.JobScheduler(cache_file=self.config['cache'],
122 | local_repo=self.config['local_repo'],
123 | remote_repo=self.config['remote_repo'])
124 |
125 | #Connect one with the other
126 | self.scheduler.submitBroker = self.broker.outgoing_queue
127 | self.broker.scheduler_callback = self.scheduler.do
128 |
129 | #Load the scheduler cache if available.
130 | self.scheduler.load()
131 |
132 | #Start broker Connection
133 | self.broker.start()
134 |
135 | #Block
136 | while self.block() == True:
137 | time.sleep(0.1)
138 |
139 | except KeyboardInterrupt:
140 | #prof.write()
141 | self.__shutdown()
142 |
143 | except Exception as err:
144 | self.logging.warning ( 'An error occurred which should not happen. Please file a bugreport. Reason: %s' % err )
145 | time.sleep(1)
146 |
147 | if __name__ == '__main__':
148 | try:
149 | #Parse command line options
150 | parser = OptionParser(add_help_option=False)
151 | parser.add_option("--host", dest="broker_host", default="127.0.0.1", type="string", help="IPaddress or hostname of the broker to connect to. Default is localhost.")
152 | parser.add_option("--vhost", dest="broker_vhost", default="/", type="string", help="The broker virtual host. Defaults to /.")
153 | parser.add_option("--username", dest="broker_username", default="guest", type="string", help="The username used to authenticate against the broker. Defaults to guest.")
154 | parser.add_option("--password", dest="broker_password", default="guest", type="string", help="The password used to authenticate against the broker. Defaults to guest.")
155 | parser.add_option("--local_repo", dest="local_repo", default=os.getcwd() + '/', type="string", help="Location of the local plugin repository.")
156 | parser.add_option("--remote_repo", dest="remote_repo", default=None, type="string", help="Location of the remote plugin repository.")
157 | parser.add_option("--cache", dest="cache", default=os.getcwd() + '/moncli.cache', type="string", help="Scheduler configuration cache.")
158 | parser.add_option("--pid", dest="pid", default=os.getcwd() + "/moncli.pid", type="string", help="The location of the pid file.")
159 | parser.add_option("--lib", dest='lib', default='/opt/moncli/lib', type='string', help="The library path to include to the search.")
160 | parser.add_option("--rand_window", dest='rand_window', default='60', type='string', help="The value in seconds which is added to the first schedule of a job in order to spread jobs.")
161 |
162 | cli_options, cli_actions = parser.parse_args()
163 | config = vars(cli_options)
164 |
165 | ##Extend path environment
166 | sys.path.append(cli_options.lib)
167 | from moncli import engine
168 | from moncli import tools
169 | from moncli.help import help
170 |
171 | server = Server(config=config)
172 |
173 | #Execute command
174 | if len(cli_actions) != 1:
175 | help()
176 | sys.exit
177 | elif cli_actions[0] == 'start':
178 | print ("Starting Moncli in background.")
179 | server.log_screen = False
180 | server.checkRunning()
181 | with daemon.DaemonContext():
182 | server.start()
183 | elif cli_actions[0] == 'debug':
184 | print ("Starting Moncli in foreground.")
185 | server.checkRunning()
186 | server.start(debug=True)
187 | elif cli_actions[0] == 'stop':
188 | print ("Stopping Moncli gracefully. Tail log for progress.")
189 | server.stop()
190 | elif cli_actions[0] == 'kill':
191 | print ("Killing Moncli forcefully.")
192 | server.kill()
193 | elif cli_actions[0] == 'dump':
194 | pass
195 | else:
196 | help()
197 | print ('Unknown option %s \n' % (cli_actions[0]))
198 | sys.exit()
199 | except Exception as err:
200 | sys.stderr.write('A fatal error has occurred.\n')
201 | sys.stderr.write('Please file a bug report to https://github.com/smetj/Moncli/issues including:\n')
202 | sys.stderr.write('\t - Moncli version.\n')
203 | sys.stderr.write('\t - Startup parameters.\n')
204 | sys.stderr.write('\t - A copy of your moncli.cache file.\n')
205 | sys.stderr.write('\t - Your OS and version.\n')
206 | sys.stderr.write('\t - Your Python version.\n')
207 | sys.stderr.write('\t - The steps to take to reproduce this error.\n')
208 | sys.stderr.write('\t - This piece of information: '+ str(type(err))+" "+str(err) + "\n" )
209 | sys.stderr.write(str(traceback.print_exc()))
210 | sys.exit(1)
211 |
--------------------------------------------------------------------------------
/init:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # moncli Start/Stop Moncli.
4 | #
5 | # chkconfig: - 62 38
6 | # description: Moncli is a metric transport and evaluation daemon.
7 | #
8 | # processname: moncli
9 | #
10 | # By: Jelle Smet
11 | #
12 | #
13 | #
14 |
15 | # Source function library
16 | #. /etc/init.d/functions
17 |
18 | # Get network config
19 | #. /etc/sysconfig/network
20 |
21 | PYTHON_EXEC='/usr/bin/python'
22 | MONCLI_EXEC='/opt/moncli/bin/moncli'
23 | RETVAL=0
24 |
25 | start() {
26 | echo -n $"Starting Moncli: "
27 | # Start me up!
28 | $PYTHON_EXEC $MONCLI_EXEC start --host besrvup-sss01 --local_repo /opt/moncli/lib/repository --cache /opt/moncli/var/moncli.cache --pid /opt/moncli/var/moncli.pid
29 | RETVAL=$?
30 | echo
31 | return $RETVAL
32 | }
33 |
34 | stop() {
35 | echo -n $"Stopping Moncli: "
36 | $PYTHON_EXEC $MONCLI_EXEC stop --host besrvup-sss01 --local_repo /opt/moncli/lib/repository --cache /opt/moncli/var/moncli.cache --pid /opt/moncli/var/moncli.pid
37 | return $RETVAL
38 | }
39 |
40 | restart() {
41 | stop
42 | start
43 | }
44 |
45 | reload() {
46 | stop
47 | start
48 | }
49 |
50 | case "$1" in
51 | start)
52 | start
53 | ;;
54 | stop)
55 | stop
56 | ;;
57 | status)
58 | status instiki
59 | ;;
60 | restart)
61 | restart
62 | ;;
63 | reload)
64 | reload
65 | ;;
66 | *)
67 | echo $"Usage: $0 {start|stop|status|restart|reload}"
68 | exit 1
69 | esac
70 |
71 | exit $?
72 |
--------------------------------------------------------------------------------
/lib/moncli/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # __init__.py
5 | #
6 | # Copyright 2011 Jelle Smet
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 |
--------------------------------------------------------------------------------
/lib/moncli/engine.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # engine.py
5 | #
6 | # Copyright 2012 Jelle Smet development@smetj.net
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 | from urllib import urlretrieve
25 | from apscheduler import scheduler
26 | from amqplib import client_0_8 as amqp
27 | from threading import Lock
28 | import Queue
29 | from random import randint
30 | from datetime import datetime, timedelta
31 | from subprocess import Popen, STDOUT, PIPE
32 | from tools import PluginManager
33 | from moncli.event import Request
34 | from signal import SIGTERM
35 | from socket import getfqdn
36 | from re import findall
37 | import threading
38 | import pickle
39 | import json
40 | import os
41 | import time
42 | import logging
43 | import sys
44 |
45 |
46 |
47 | class Broker(threading.Thread):
48 | '''Creates an object doing all broker I/O. It's meant to be resillient to disconnects and broker unavailability.
49 | Data going to the broker goes into Broker.outgoing_queue. Data coming from the broker is submitted to the scheduler_callback method'''
50 |
51 | def __init__(self,host,vhost,username,password,block=None):
52 | threading.Thread.__init__(self)
53 | self.logging = logging.getLogger(__name__)
54 | self.host=host
55 | self.vhost=vhost
56 | self.username=username
57 | self.password=password
58 | self.outgoing_queue=Queue.Queue(0)
59 | self.scheduler_callback=None
60 | self.block=block
61 | self.connected=False
62 | self.daemon=True
63 |
64 | def __setup(self):
65 | self.conn = amqp.Connection(host="%s:5672"%(self.host), userid=self.username,password=self.password, virtual_host=self.vhost, insist=False)
66 | self.incoming = self.conn.channel()
67 | self.outgoing = self.conn.channel()
68 | self.logging.info('Connected to broker')
69 |
70 | def __createQE(self):
71 | '''Creates all required queues and exchanges.'''
72 |
73 | #create exchange and queue
74 | #Outgoing
75 | self.outgoing.exchange_declare(exchange='moncli_reports', type="fanout", durable=True,auto_delete=False)
76 | self.outgoing.queue_declare(queue='moncli_reports', durable=True,exclusive=False, auto_delete=False)
77 | self.outgoing.queue_bind(queue='moncli_reports', exchange='moncli_reports')
78 |
79 | #incoming
80 | self.incoming.exchange_declare(exchange='moncli_requests', type="direct", durable=True,auto_delete=False)
81 | self.incoming.exchange_declare(exchange='moncli_requests_broadcast', type="fanout", durable=True,auto_delete=False)
82 | self.incoming.queue_declare(queue=getfqdn(), durable=True,exclusive=False, auto_delete=False)
83 | self.incoming.queue_bind(queue=getfqdn(), exchange='moncli_requests')
84 | self.incoming.queue_bind(queue=getfqdn(), exchange='moncli_requests_broadcast')
85 |
86 | def submitBroker(self):
87 | while self.block() == True:
88 | while self.connected == True:
89 | while self.outgoing_queue.qsize() > 0:
90 | try:
91 | self.logging.info('Submitting data to broker')
92 | self.produce(json.dumps(self.outgoing_queue.get()))
93 | except:
94 | break
95 | time.sleep(0.1)
96 | time.sleep(1)
97 |
98 | def run(self):
99 | night=0.5
100 | self.startProduceThread()
101 | while self.block() == True:
102 | while self.connected==False:
103 | try:
104 | if night < 512:
105 | night *=2
106 | self.__setup()
107 | self.__createQE()
108 | self.connected=True
109 | night=0.5
110 | except Exception as err:
111 | self.connected=False
112 | self.logging.warning('Connection to broker lost. Reason: %s. Try again in %s seconds.' % (err,night) )
113 | time.sleep(night)
114 |
115 | #Register our callback
116 | try:
117 | self.incoming.basic_consume(queue=getfqdn(), callback=self.consume, consumer_tag='request')
118 | except:
119 | self.connected = False
120 |
121 | while self.block() == True and self.connected == True:
122 | try:
123 | self.incoming.wait()
124 | except Exception as err:
125 | self.incoming.basic_cancel("request")
126 | self.logging.warning('Connection to broker lost. Reason: %s' % err )
127 | self.connected == False
128 | self.incoming.close()
129 | self.conn.close()
130 | break
131 |
132 | self.produce.join()
133 |
134 | def startProduceThread(self):
135 | self.produce_thread = threading.Thread(target=self.submitBroker)
136 | self.produce_thread.start()
137 |
138 | def consume(self,doc):
139 | try:
140 | data = json.loads(doc.body)
141 | Request.validate(data=data)
142 | except Exception as err:
143 | self.logging.warn('Problem processing incoming data. Reason: %s' % (err))
144 | else:
145 | self.scheduler_callback(json.loads(doc.body))
146 | self.incoming.basic_ack(doc.delivery_tag)
147 |
148 | def produce(self,data):
149 | if self.connected == True:
150 | msg = amqp.Message(str(data))
151 | msg.properties["delivery_mode"] = 2
152 | self.outgoing.basic_publish(msg,exchange='moncli_reports')
153 | else:
154 | raise Exception('Not Connected to broker')
155 |
156 |
157 | class ReportRequestExecutor():
158 | '''Don't share this class over multiple threads/processes.'''
159 |
160 | def __init__(self, local_repo, remote_repo, submitBroker):
161 | self.logging = logging.getLogger(__name__)
162 | self.pluginManager = PluginManager(local_repository=local_repo,
163 | remote_repository=remote_repo)
164 | self.executePlugin = ExecutePlugin()
165 | self.submitBroker = submitBroker
166 | self.cache = {}
167 |
168 | def do(self,doc):
169 | try:
170 | self.logging.info('Executing a request with destination %s:%s' % (doc['destination']['name'], doc['destination']['subject']))
171 | request = Request(doc=doc)
172 | command = self.pluginManager.getExecutable(command=request.plugin['name'], hash=request.plugin['hash'])
173 | output = self.executePlugin.do(request.plugin['name'], command, request.plugin['parameters'], request.plugin['timeout'])
174 | (raw, verbose, metrics) = self.processOutput(request.plugin['name'],output)
175 | request.insertPluginOutput(raw, verbose, metrics)
176 | self.submitBroker.put(request.answer)
177 | except Exception as err:
178 | self.logging.warning('There is a problem executing %s:%s. Reason: %s' % (doc['destination']['name'], doc['destination']['subject'], err))
179 |
180 | def processOutput(self,name,data):
181 | output = []
182 | verbose = []
183 | dictionary = {}
184 | while len(data) != 0:
185 | line = data.pop(0)
186 | if str(line) == '~==.==~\n':
187 | for i,v in enumerate(data):
188 | data[i] = v.rstrip('\n')
189 | verbose = data
190 | break
191 | else:
192 | output.append(line)
193 | try:
194 | key_value = line.split(":")
195 | dictionary[self.__cleanKey(key_value[0])] = key_value[1].rstrip('\n')
196 | except Exception as err:
197 | self.logging.warn('Possible dirty key value list produced by plugin. Reason: %s' % (err))
198 | #Add epoch time
199 | dictionary["epoch"] = round(time.time())
200 | #Extend the metrics with the previous ones.
201 | metrics = self.__cache(name, dictionary)
202 | return (output, verbose, metrics)
203 |
204 | def __cache(self, plugin, dictionary):
205 | merged_dictionary={}
206 | cached_dictionary = self.cache.get(plugin, dictionary)
207 | for value in cached_dictionary:
208 | merged_dictionary['pre_' + value] = cached_dictionary[value]
209 | merged_dictionary.update(dictionary)
210 | self.cache[plugin] = dictionary
211 | return merged_dictionary
212 |
213 | def __cleanKey(self,key):
214 | '''Keys can only contains numbers, letters, dots and underscores. All the rest is filtered out.'''
215 | return ''.join(findall('\w|\d|\.|_',key))
216 |
217 |
218 | class JobScheduler():
219 |
220 | def __init__(self, cache_file, local_repo, remote_repo):
221 | self.logging = logging.getLogger(__name__)
222 | self.sched = scheduler.Scheduler()
223 | self.submitBroker = None
224 | self.request = {}
225 | self.cache_file = cache_file
226 | self.local_repo = local_repo
227 | self.remote_repo = remote_repo
228 | self.do_lock = Lock()
229 | self.sched.start()
230 |
231 | def do(self, doc):
232 | name = self.__name(doc)
233 | if self.request.has_key(name):
234 | self.__unschedule(name=name, object=self.request[name]['scheduler'])
235 | if doc['request']['cycle'] == 0:
236 | self.logging.debug('Executed imediately job %s' % (name))
237 | #ToDo (smetj): take broker communication out of ReportRequestExecutor. It doesn't belong there.
238 | job = ReportRequestExecutor(local_repo=self.local_repo, remote_repo=self.remote_repo)
239 | job.do(doc=doc)
240 | else:
241 | self.__schedule(doc=doc)
242 | self.__save()
243 |
244 | def __unschedule(self, name, object):
245 | self.logging.debug('Unscheduled job %s' % (name))
246 | self.sched.unschedule_job(object)
247 | del self.request[name]
248 |
249 | def __register(self, doc):
250 | name = self.__name(doc)
251 | self.logging.debug('Registered job %s' % (name))
252 | self.request[name] = {'function': None, 'scheduler': None, 'document': None}
253 | self.request[name]['document'] = doc
254 | self.request[name]['function'] = ReportRequestExecutor(local_repo=self.local_repo,
255 | remote_repo=self.remote_repo,
256 | submitBroker=self.submitBroker)
257 |
258 | def __schedule(self, doc):
259 | name = self.__name(doc)
260 | self.logging.debug('Scheduled job %s' % (name))
261 | random_wait = randint(1, int(60))
262 | self.__register(doc)
263 | self.request[name]['scheduler'] = self.sched.add_interval_job(self.request[name]['function'].do,
264 | seconds=int(doc['request']['cycle']),
265 | name=name,
266 | coalesce=True,
267 | start_date=datetime.now() + timedelta(0, random_wait),
268 | kwargs={'doc': doc})
269 |
270 | def __name(self, doc):
271 | return '%s:%s' % (doc['destination']['name'], doc['destination']['subject'])
272 |
273 | def __save(self):
274 | try:
275 | output = open(self.cache_file, 'wb')
276 | cache = []
277 | for doc in self.request:
278 | cache.append(self.request[doc]['document'])
279 | pickle.dump(cache, output)
280 | output.close()
281 | self.logging.info('Job scheduler: Moncli cache file saved.')
282 | except Exception as err:
283 | self.logging.warn('Job scheduler: Moncli cache file could not be saved. Reason: %s.' % (err))
284 |
285 | def load(self):
286 | try:
287 | input = open(self.cache_file, 'r')
288 | jobs = pickle.load(input)
289 | input.close()
290 | for job in jobs:
291 | self.__schedule(doc=job)
292 | self.logging.info('Job scheduler: Loaded cache file.')
293 | except Exception as err:
294 | self.logging.info('Job scheduler: I could not open cache file: Reason: %s.' % (err))
295 |
296 | def shutdown(self):
297 | self.sched.shutdown()
298 |
299 |
300 | class ExecutePlugin():
301 |
302 | def __init__(self):
303 | self.logging = logging.getLogger(__name__)
304 | self.process = None
305 |
306 | def do(self, name, command=None, parameters=[], timeout=30):
307 | self.process = None
308 | command = ("%s %s" % (command, ' '.join(parameters)))
309 | self.output=None
310 | def target():
311 | self.process = Popen(command, shell=True, bufsize=0, stdout=PIPE, stderr=STDOUT, close_fds=True, preexec_fn=os.setsid)
312 | self.output = self.process.stdout.readlines()
313 | self.process.stdout.close()
314 | self.process.wait()
315 | thread = threading.Thread(target=target)
316 | thread.start()
317 | thread.join(timeout)
318 |
319 | if thread.is_alive():
320 | self.logging.debug ( 'Plugin %s is running too long, will terminate it.' % (name) )
321 | os.killpg(self.process.pid,SIGTERM)
322 | self.logging.debug ('Waiting for thread %s to exit.' % (thread.getName()))
323 | thread.join()
324 | self.logging.debug ('Thread %s exit.' % (thread.getName()))
325 | raise Exception( 'Plugin %s running too long. Terminated.' % (name) )
326 | else:
327 | return self.output
328 |
329 |
330 |
--------------------------------------------------------------------------------
/lib/moncli/event.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # event.py
5 | #
6 | # Copyright 2012 Jelle Smet development@smetj.net
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 |
25 | from jsonschema import Validator
26 | from uuid import uuid4
27 | from time import strftime, localtime
28 | from tools import Calculator
29 | from socket import getfqdn
30 | import logging
31 |
32 | class Request():
33 |
34 | def __init__(self,doc):
35 | self.logging = logging.getLogger(__name__)
36 | self.calc = Calculator()
37 | self.__load(doc)
38 | self.answer = self.__initReport(doc)
39 |
40 | def __load(self,doc):
41 | self.__dict__.update(doc)
42 |
43 | @staticmethod
44 |
45 | def validate(data):
46 | checker = Validator()
47 | document_schema = {
48 | "type" : "object",
49 | "additionalProperties": False,
50 | "required" : True,
51 | "properties" : {
52 | "destination" : {
53 | "type" : "object",
54 | "required" : True,
55 | "additionalProperties": False,
56 | "properties" : {
57 | "name" : {"type" : "string", "required" : True },
58 | "subject" : {"type" : "string", "required" : True }
59 | }
60 | },
61 | "report" : {
62 | "type" : "object",
63 | "required" : True,
64 | "additionalProperties": False,
65 | "properties" : {
66 | "message" : {"type" : "string", "required" : True },
67 | }
68 | },
69 | "request" : {
70 | "type" : "object",
71 | "required" : True,
72 | "additionalProperties": False,
73 | "properties" : {
74 | "uuid" : {"type" : "string", "required" : True },
75 | "source" : {"type" : "string", "required" : True },
76 | "time" : {"type" : "string", "required" : True },
77 | "day_of_year" : {"type" : "number", "required" : True },
78 | "day_of_week" : {"type" : "number", "required" : True },
79 | "week_of_year" : {"type" : "number", "required" : True },
80 | "month" : {"type" : "number", "required" : True },
81 | "year" : {"type" : "number", "required" : True },
82 | "day" : {"type" : "number", "required" : True },
83 | "cycle" : {"type" : "number", "required" : True },
84 | }
85 | },
86 | "plugin" : {
87 | "type" : "object",
88 | "required" : True,
89 | "additionalProperties": False,
90 | "properties" : {
91 | "name" : {"type" : "string", "required" : True },
92 | "hash" : {"type" : "string", "required" : True },
93 | "timeout" : {"type" : "number", "required" : True },
94 | "parameters" : {"type" : "array", "required" : True }
95 | }
96 | },
97 | "evaluators" : {
98 | "type" : "object",
99 | "required" : True
100 | },
101 | "tags" : {
102 | "type" : "array",
103 | "required" : True
104 | }
105 | }
106 | }
107 | evaluator_schema = {
108 | "type" : "object",
109 | "additionalProperties": False,
110 | "properties" : {
111 | "evaluator" : {"type" : "string", "required" : True },
112 | "metric" : {"type" : "string", "required" : True },
113 | "thresholds" : {"type" : "object", "required" : True }
114 | }
115 | }
116 | threshold_schema = {
117 | "type" : "string"
118 | }
119 | checker.validate(data,document_schema)
120 | for evaluator in data['evaluators']:
121 | checker.validate(data['evaluators'][evaluator],evaluator_schema)
122 | for threshold in data['evaluators'][evaluator]['thresholds']:
123 | checker.validate(data['evaluators'][evaluator]['thresholds'][threshold],threshold_schema)
124 |
125 | def generateReport(self):
126 | pass
127 |
128 | def __initReport(self,doc):
129 | here_now = localtime()
130 | iso8601 = strftime("%Y-%m-%dT%H:%M:%S",here_now)
131 | iso8601 += strftime("%z")
132 | return {
133 | "destination":{
134 | "name":doc['destination']['name'],
135 | "subject":doc['destination']['subject'],
136 | },
137 | "request":doc['request'],
138 | "report":{
139 | "uuid":str(uuid4()),
140 | "source":getfqdn(),
141 | "message":None,
142 | "time":iso8601,
143 | "timezone":strftime("%z"),
144 | "day_of_year":int(strftime("%j",here_now)),
145 | "day_of_week":int(strftime("%w",here_now)),
146 | "week_of_year":int(strftime("%W",here_now)),
147 | "month":int(strftime("%m",here_now)),
148 | "year":int(strftime("%Y",here_now)),
149 | "day":int(strftime("%d",here_now))
150 | },
151 | "plugin":{
152 | "name":doc['plugin']['name'],
153 | "verbose":None,
154 | "metrics":None,
155 | "raw":None
156 | },
157 | "evaluators":{
158 | },
159 | "tags":doc['tags']
160 | }
161 |
162 | def __calculate(self):
163 | for evaluator in self.evaluators:
164 | (value,status) = self.calc.do( output=self.answer['plugin']['raw'],
165 | dictionary=self.answer['plugin']['metrics'],
166 | evaluator=self.evaluators[evaluator]['evaluator'],
167 | thresholds=self.evaluators[evaluator]['thresholds'])
168 |
169 | self.answer["evaluators"].update({ evaluator : { "status" : status, "metric" : self.evaluators[evaluator]['metric'], "value" : value } })
170 |
171 | self.answer['report']['message']=self.buildMessage(evaluators=self.answer['evaluators'],message=self.report['message'])
172 |
173 | def buildMessage(self,evaluators,message):
174 | for evaluator in evaluators:
175 | message=message.replace('#'+str(evaluator),'(%s) %s'%(evaluators[evaluator]['status'],evaluators[evaluator]['value']))
176 | return message
177 |
178 | def insertPluginOutput(self, raw, verbose, metrics):
179 | self.answer['plugin']['raw'] = raw
180 | self.answer['plugin']['verbose'] = verbose
181 | self.answer['plugin']['metrics'] = metrics
182 | self.__calculate()
183 | self.answer['plugin']['metrics'] = self.removePreData(self.answer['plugin']['metrics'])
184 |
185 | def removePreData(self,metrics):
186 | '''Removes all the pre_ metrics. There's no use to send that back as one could have stored the previous report.'''
187 | for key in metrics.keys():
188 | if key.startswith('pre_'):
189 | metrics.pop(key)
190 | return metrics
191 |
192 |
193 |
--------------------------------------------------------------------------------
/lib/moncli/help.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # help.py
5 | #
6 | # Copyright 2011 Jelle Smet
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 |
25 | __version__='0.2.5'
26 |
27 | def help():
28 | '''Produces command line help message.'''
29 | print ('Moncli %s by Jelle Smet ' %(__version__))
30 | print
31 | print ('''Usage: moncli command [--broker address] [--local_repo directory] [--remote_repo url] [--cache filename] [--pid filename] [--log filename]
32 |
33 | Valid commands:
34 |
35 | start Starts the Moncli daemon in the background.
36 |
37 | stop Gracefully stops the Moncli daemon running in the background.
38 |
39 | kill Kills the Moncli daemon running with the pid defined in your config file.
40 |
41 | debug Starts the Moncli daemon in the foreground while showing real time log and debug messages.
42 | The process can be stopped with ctrl+c which will ends Moncli gracefully.
43 | A second ctrl+c will kill Moncli.
44 |
45 | Parameters:
46 | --host The ipaddress of the message broker Moncli should listen to.
47 | --vhost The broker virtual host. Defaults to "/".
48 | --username The username used to authenticate against the broker. Defaults to "guest".
49 | --password The password used to authenticate against the broker. Defaults to "guest".
50 | --local_repo The location of the local plugin repository.
51 | --remote_repo The location of the remote plugin repository.
52 | --cache The location where the configuration cache is written and read from on startup.
53 | --pid The location of the pid file.
54 | --lib The library path to include to the search path.
55 | --rand_window The value in seconds which is added to the first schedule of a job in order to spread jobs.
56 | Default value is 60 seconds.
57 |
58 | Moncli is distributed under the Terms of the GNU General Public License Version 3. (http://www.gnu.org/licenses/gpl-3.0.html)
59 |
60 | For more information please visit http://www.smetj.net/moncli/
61 | ''')
62 |
--------------------------------------------------------------------------------
/lib/moncli/tools.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # tools.py
5 | #
6 | # Copyright 2012 Jelle Smet development@smetj.net
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 | from __future__ import division
25 | from platform import system,machine
26 | from hashlib import md5
27 | from urllib2 import urlopen
28 | from logging.handlers import SysLogHandler
29 | import logging
30 | import os
31 | import re
32 | #import yappi
33 |
34 | class Calculator():
35 |
36 | def __init__(self):
37 | self.logging = logging.getLogger(__name__)
38 | self.whitelist=[ '+','-','/','*','^','(',')','.',' ' ]
39 |
40 | def do(self,output,dictionary,evaluator,thresholds):
41 | if evaluator[:3] == 're:':
42 | value = self.__ShellExecuteegex(output=output,regex=evaluator[3:])
43 | elif evaluator[:3] == 'fm:':
44 | value = self.__executeFormula(dictionary=dictionary,formula=evaluator[3:])
45 | else:
46 | raise RuntimeError("The evaluator does not start with a known type: %s" %(evaluator))
47 | status = self.__evaluateThresholds(thresholds=thresholds,value=value)
48 | return (value,status)
49 |
50 | def __executeFormula(self,dictionary,formula):
51 | for key, val in dictionary.items():
52 | formula = re.sub('(\+|-|\/|\*|\^|\(|\)|\s|^)'+key+'(?=\+|-|\/|\*|\^|\(|\)|\s|\n|$)','\\1 '+str(val),formula)
53 | to_evaluate=re.findall('\D',formula)
54 | to_evaluate=list(set(to_evaluate))
55 | for element in to_evaluate:
56 | if not element in self.whitelist:
57 | raise RuntimeError("Error in the evaluator formula: %s" %(formula))
58 | try:
59 | result= round(eval(str(formula)),2)
60 | except:
61 | result= None
62 | return result
63 |
64 | def __ShellExecuteegex(self,output,regex):
65 | matches=0
66 | try:
67 | for line in output:
68 | if re.search(regex,line):
69 | matches+=1
70 | evaluator=matches
71 | return matches
72 | except:
73 | raise RuntimeError("Error in the eveluator regex: %s" %(evaluator))
74 |
75 | def __evaluateThresholds(self,thresholds,value):
76 | ''' Nagios threshold definitions
77 | 1) 10 < 0 or > 10, (outside the range of {0 .. 10})
78 | 2) 10: < 10, (outside {10 .. ~})
79 | 3) ~:10 > 10, (outside the range of {-~ .. 10})
80 | 4) 10:20 < 10 or > 20, (outside the range of {10 .. 20})
81 | 5) @10:20 >= 10 and <= 20, (inside the range of {10 .. 20})
82 | '''
83 | evaluator_1=re.compile('(^\d*$)')
84 | evaluator_2=re.compile('(^\d*?):$')
85 | evaluator_3=re.compile('^~:(\d*)')
86 | evaluator_4=re.compile('(\d*):(\d*)')
87 | evaluator_5=re.compile('^@(\d*):(\d*)')
88 | for threshold in thresholds:
89 | if evaluator_1.match(thresholds[threshold]):
90 | number=evaluator_1.match(thresholds[threshold])
91 | if int(value) < 0 or int(value) > int(number.group(1)):
92 | return threshold
93 | elif evaluator_2.match(thresholds[threshold]):
94 | number=evaluator_2.match(thresholds[threshold])
95 | if int(value) < int(number.group(1)):
96 | return threshold
97 | elif evaluator_3.match(thresholds[threshold]):
98 | number=evaluator_3.match(thresholds[threshold])
99 | if int(value) < int(number.group(1)):
100 | return threshold
101 | elif evaluator_4.match(thresholds[threshold]):
102 | number=evaluator_4.match(thresholds[threshold])
103 | if int(value) < int(number.group(1)) or int(value) > int(number.group(2)):
104 | return threshold
105 | elif evaluator_5.match(thresholds[threshold]):
106 | number=evaluator_5.match(thresholds[threshold])
107 | if int(value) >= int(number.group(1)) and int(value) <= int(number.group(2)):
108 | return threshold
109 | else:
110 | raise RuntimeError('Invalid Threshold :'+str(threshold))
111 | return "OK"
112 |
113 |
114 | class StatusCalculator():
115 | '''Contains a number of methods facilitating different kind of status calculations.'''
116 |
117 | def __init__(self,weight_map='default',template=None):
118 | self.logging = logging.getLogger(__name__)
119 | if weight_map == 'nagios:service':
120 | self.template=self.__setNagiosService()
121 | elif weight_map == 'nagios:host':
122 | self.template=self.__setNagiosHost()
123 | else:
124 | self.template=self.__setDefault()
125 | self.states=[]
126 |
127 | def result(self):
128 | results={}
129 | for state in self.states:
130 | if self.__templateContainsName(name=state,template=self.template):
131 | if not results.has_key(state):
132 | results[state]=0
133 | results[state]+=1
134 | for key in sorted(self.template.iterkeys(),reverse=True):
135 | if results.has_key(self.template[key]['name']) and results[self.template[key]['name']] >= self.template[key]['weight'] :
136 | return self.template[key]['name']
137 | return self.template[sorted(self.template.iterkeys(),reverse=True)[0]]['name']
138 |
139 | def __setDefault(self):
140 | return { 0: { 'name': 'OK', 'weight': 1},
141 | 1: { 'name': 'warning', 'weight': 1},
142 | 2: { 'name': 'critical', 'weight': 1},
143 | 3: { 'name': 'unknown', 'weight': 1} }
144 |
145 | def __setNagiosService(self):
146 | return { 0: { 'name': 'OK', 'weight': 1},
147 | 1: { 'name': 'warning', 'weight': 1},
148 | 2: { 'name': 'critical', 'weight': 1},
149 | 3: { 'name': 'unknown', 'weight': 1} }
150 |
151 | def __setNagiosHost(self):
152 | return { 0: { 'name': 'OK', 'weight': 1},
153 | 1: { 'name': 'updown', 'weight': 1},
154 | 2: { 'name': 'down', 'weight': 1},
155 | 3: { 'name': 'down', 'weight': 1} }
156 |
157 | def __templateContainsName(self,name,template):
158 | for element in template:
159 | if template[element]['name'] == name:
160 | return True
161 | return False
162 |
163 |
164 | class PluginManager():
165 | '''Provides the name of the plugin to execute, verifies its hash and downloads a new plugin version if required.'''
166 |
167 | def __init__(self,local_repository,remote_repository):
168 | self.logging = logging.getLogger(__name__)
169 | self.local_repository=local_repository
170 | self.remote_repository=remote_repository
171 | if self.local_repository[-1] != '/':
172 | self.local_repository += '/'
173 | if self.remote_repository != None and self.remote_repository[-1] != '/':
174 | self.remote_repository += '/'
175 | self.logging.debug('PluginManager Initiated')
176 |
177 | def getExecutable(self,command,hash=None):
178 | if not os.path.exists(self.local_repository+command):
179 | self.__createCommand(dir=self.local_repository+command)
180 | if not os.path.isfile(self.local_repository+command+'/'+hash) and self.remote_repository != '':
181 | self.__downloadVersion(self.remote_repository,self.local_repository,command,hash)
182 | if self.__checkHash(fullpath=self.local_repository+'/'+command+'/'+hash,file=hash) == True:
183 | return self.local_repository+'/'+command+'/'+hash
184 |
185 | def __checkHash(self,fullpath,file):
186 | plugin = open(fullpath,'r')
187 | plugin_hash = md5()
188 | plugin_hash.update((''.join(plugin.readlines())))
189 | plugin.close()
190 | if file != plugin_hash.hexdigest():
191 | raise Exception ( 'Plugin filename does not match its hash value.' )
192 | self.logging.warning ( 'Plugin filename %s does not match its hash value %s.'%(file,plugin_hash.hexdigest() ) )
193 | return True
194 |
195 | def __createCommand(self,dir):
196 | os.mkdir(dir)
197 |
198 | def __downloadVersion(self,remote_repository,local_repository,command,hash):
199 | full_url = "%s%s(%s)/%s/%s"%(remote_repository,system(),machine(),command,hash)
200 | self.logging.info ('Downloading update %s.'%(full_url))
201 | try:
202 | response = urlopen(full_url)
203 | except Exception as err:
204 | self.logging.critical ( 'Error downloading update. Reason: %s'%(str(err) + " - "+full_url) )
205 | raise Exception (str(err) + " - "+full_url )
206 |
207 | output = open ( local_repository+'/'+command+'/'+hash, 'w' )
208 | output.write(response.read())
209 | response.close()
210 | output.close()
211 | #Make executable
212 | os.chmod(local_repository+'/'+command+'/'+hash,0750)
213 |
214 |
215 | class Profile():
216 | '''Used for profiling purposes'''
217 |
218 | def __init__(self):
219 | yappi.start()
220 | self.yappi_results = open ( '/opt/moncli/var/profile.yappi','w' )
221 |
222 | def write(self):
223 | for line in yappi.get_stats( yappi.SORTTYPE_TTOTAL,
224 | yappi.SORTORDER_ASCENDING,
225 | yappi.SHOW_ALL):
226 | self.yappi_results.write(line+"\n")
227 | print line
228 |
229 |
230 | def logger(syslog=False,loglevel=logging.INFO):
231 | format=('%(asctime)s %(levelname)s %(name)s %(message)s')
232 | if syslog == False:
233 | logging.basicConfig(level=loglevel, format=format)
234 | else:
235 | logger = logging.getLogger()
236 | logger.setLevel(loglevel)
237 | syslog = SysLogHandler(address='/dev/log')
238 | formatter = logging.Formatter(format)
239 | syslog.setFormatter(formatter)
240 | logger.addHandler(syslog)
241 |
--------------------------------------------------------------------------------
/lib/repository/cpu/d8d1ed5d339390b9d9ddbb9958c11c2d:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 |
5 | my $type='now';
6 | my $title='Values at the moment of plugin execution.';
7 | my $command;
8 |
9 | if ( defined $ARGV[0] && $ARGV[0] eq '-now' ){
10 | $command = 'mpstat -P ALL 1 1|';
11 | }
12 | else{
13 | $type = 'avg';
14 | $title='Average values since uptime.';
15 |
16 | $command = 'mpstat -P ALL|';
17 | }
18 |
19 | open FH,$command;
20 | my @output=;
21 | close FH;
22 |
23 | $output[2] =~ s/%//gm;
24 | my @header = split(/\s+/,$output[2]);
25 |
26 | foreach (reverse(@output)){
27 | if ( $_ =~ /^Average:.*/ || $_ =~ /^\d\d:\d\d:\d\d.*/ ){
28 | $_ =~ s/AM|PM//;
29 | my @line = split(/\s+/,$_);
30 | if ( $line[1] =~ /all|\d+/){
31 | for my $index (2 .. $#line){
32 | printf ( "%s.%s:%s\n", $line[1], $header[$index+1],$line[$index] );
33 | }
34 | }
35 | }
36 | else{
37 | last;
38 | }
39 | }
40 |
41 | print "~==.==~\n";
42 | print $title,"\n\n";
43 | print @output,"\n";
44 | print "Source: $0\n";
45 |
--------------------------------------------------------------------------------
/lib/repository/disks/b567265aceb147762571995e009a25b0:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | IFS=$'\n'
4 |
5 | #Produce disk sizes
6 | for line in $(df -T -x tmpfs -x devtmpfs -P);do
7 | if [[ $line != *Mounted* ]];then
8 | name=$(echo $line|tr -s ' '|cut -d ' ' -f 7)
9 | used=$(echo $line|tr -s ' '|tr -d '%'|cut -d ' ' -f 6)
10 | echo -e "disk_$name:$used"
11 | fi
12 | done
13 |
14 | #produce inode size
15 | for line in $(df -T -x tmpfs -x devtmpfs -P -i);do
16 | if [[ $line != *Mounted* ]];then
17 | name=$(echo $line|tr -s ' '|cut -d ' ' -f 7)
18 | used=$(echo $line|tr -s ' '|tr -d '%'|cut -d ' ' -f 6)
19 | echo -e "inode_$name:$used"
20 | fi
21 | done
22 |
23 | #optional output
24 | echo -e "~==.==~\n"
25 | echo -e "Disk Size:\n"
26 | df -h -T -x tmpfs -x devtmpfs
27 |
28 | echo -e "\nDisk Inodes:\n"
29 | df -h -T -x tmpfs -x devtmpfs -i
30 | echo -e "\nSource: $0\n";
31 |
--------------------------------------------------------------------------------
/lib/repository/hadoop/c64c24e98176491023753d40e8e76e83:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | # -*- coding: utf-8 -*-
3 | #
4 | # hadoop
5 | #
6 | # Copyright 2012 Jelle Smet development@smetj.net
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 3 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 | # MA 02110-1301, USA.
22 | #
23 | #
24 |
25 | use LWP::Simple;
26 | my $content;
27 | my $url;
28 | my @output=();
29 |
30 | my %ports = ( "nn" => "50070",
31 | "sn" => "50090",
32 | "jt" => "50030",
33 | "hm" => "60010",
34 | "dn" => "50075",
35 | "rs" => "60030",
36 | "tt" => "50060" );
37 |
38 | my $host = $ARGV[0] || 'localhost';
39 |
40 | for my $port (keys %ports){
41 | @output = ( @output, get_content("$host:$ports{$port}",$port));
42 | }
43 |
44 | for my $line (@output){
45 | print $line,"\n";
46 | }
47 |
48 | sub get_content(){
49 | my $url = shift;
50 | my $type = shift;
51 | my @output;
52 | unless (defined (@content = split ( /\n/, get "http://$url/metrics"))) {
53 | return ();
54 | }
55 | my $prefix="unknown";
56 | foreach (@content){
57 | $prefix="jvm" if $_ eq "jvm";
58 | $prefix="rpc" if $_ eq "rpc";
59 | $prefix="dfs" if $_ eq "dfs";
60 | if ( $_ =~ /(\w+)=(\d+)/ ){
61 | push ( @output, "$type.$prefix.$1:$2" );
62 | }
63 | }
64 | return @output;
65 | }
66 |
67 | print "Source: $0\n";
68 |
--------------------------------------------------------------------------------
/lib/repository/load/6eee2010a568109e06782b5fd8fead28:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | load=$(cat /proc/loadavg)
4 | echo "1_min:$(echo $load|cut -d " " -f 1)"
5 | echo "5_min:$(echo $load|cut -d " " -f 2)"
6 | echo "15_min:$(echo $load|cut -d " " -f 3)"
7 | echo "~==.==~"
8 | top -b -n 1
9 | echo "Source: $0"
10 |
--------------------------------------------------------------------------------
/lib/repository/memory/f4f2b50762bf835ce86b16a1576a4e70:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 |
5 | open MEMINFO,'/proc/meminfo';
6 | while (){
7 | if ( $_ =~ /(^.*?):\s*(\d*)/ ){
8 | printf ( "%s:%s\n",lc($1),$2 );
9 | }
10 | }
11 | close MEMINFO;
12 | print "~==.==~\n";
13 | print "Values in MB:\n";
14 | open FREE,'/usr/bin/free -t -m||/bin/free -t m|';
15 | while (){
16 | print $_;
17 | }
18 | close FREE;
19 | print "\nSource: $0\n";
20 |
--------------------------------------------------------------------------------
/lib/repository/ping/c2376e4d6c4935750cc586bdd0931bdc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | use strict;
3 | use warnings;
4 | my @results;
5 |
6 | my $ip=shift;
7 | my $count=shift;
8 | my $timeout=shift;
9 |
10 | if ( (not defined $ip) || (not defined $count) || (not defined $timeout) ){
11 | print "Missing ip, count or timeout.\nExample:\n\tping.pl 10.9.8.1 4 100\n";
12 | exit 0
13 | }
14 |
15 | my $command = sprintf ( "ping -c %s -W %s %s 2> /dev/null|", $count,$timeout,$ip);
16 |
17 | open PING,"$command";
18 | @results = ;
19 | close PING;
20 |
21 | #set default values
22 | my %results= (
23 | 'loss' => 'nan',
24 | 'time' => 'nan',
25 | 'min' => 'nan',
26 | 'avg' => 'nan',
27 | 'max' => 'nan',
28 | 'mdev' => 'nan');
29 | for (my $x=0;$x < $count; $x++) {
30 | $results{'ping_'.$x}='nan'
31 | };
32 |
33 | foreach (@results){
34 | if ($_ =~ /.*?icmp_req=(\d*).*?time=(.*?)\ .*/){
35 | $results{'ping_'.$1}=$2;
36 | }
37 |
38 | elsif ($_ =~ /.*?(\d*)?%\ packet\ loss,\ time\ (.*)?ms/){
39 | $results{'loss'}=$1;
40 | $results{'time'}=$2;
41 | }
42 | elsif ($_ =~ /.*?\ =\ (.*)?\/(.*)?\/(.*)?\/(.*)?\ .*/){
43 | $results{'min'}=$1;
44 | $results{'avg'}=$2;
45 | $results{'max'}=$3;
46 | $results{'mdev'}=$4;
47 | }
48 | }
49 | for my $key ( sort keys %results){
50 | printf "%s:%s\n",$key,$results{$key};
51 | }
52 | print "~==.==~\n";
53 | print join ("",@results);
54 | print "\nMD5: $0\n";
55 |
--------------------------------------------------------------------------------
/lib/repository/processes/7384027d47b04ca2af22f00d8f920e81:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | string="$(ps faux)"
4 |
5 | echo -e "$string"
6 | echo '~==.==~'
7 | echo -e "$string"
8 | echo -e "\n$0\n";
9 |
10 |
--------------------------------------------------------------------------------
/lib/repository/uptime/216f25704df3c2d409d5f200c7302fb3:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "seconds:$(cat /proc/uptime |cut -f '1' -d ' ')"
4 | echo "~==.==~"
5 | uptime
6 | echo -e "\nSource: $0\n"
7 |
--------------------------------------------------------------------------------
/var/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smetj/moncli/6a4281e24624044f11c04a6e344d0ab38639eaee/var/.gitignore
--------------------------------------------------------------------------------