├── 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 --------------------------------------------------------------------------------