diff options
author | Costa Tsaousis <costa@tsaousis.gr> | 2017-05-17 02:53:59 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-17 02:53:59 +0300 |
commit | 102b6f388893e0c01c43b7102f3807331c538e53 (patch) | |
tree | d4113d492c3a0cfb4200833ed6563afd0f174546 | |
parent | 5fb2a5d7250ecb4dedf0d63ad5a75ecbb406b16c (diff) | |
parent | f176175952dbe9c896a62eddb1a0d6dc7b044afd (diff) |
Merge pull request #2182 from l2isbad/rabbitmq_plugin
Rabbitmq plugin
-rw-r--r-- | conf.d/Makefile.am | 1 | ||||
-rw-r--r-- | conf.d/apps_groups.conf | 2 | ||||
-rw-r--r-- | conf.d/python.d.conf | 2 | ||||
-rw-r--r-- | conf.d/python.d/rabbitmq.conf | 75 | ||||
-rw-r--r-- | python.d/Makefile.am | 1 | ||||
-rw-r--r-- | python.d/README.md | 54 | ||||
-rw-r--r-- | python.d/rabbitmq.chart.py | 187 | ||||
-rw-r--r-- | python.d/redis.chart.py | 3 |
8 files changed, 322 insertions, 3 deletions
diff --git a/conf.d/Makefile.am b/conf.d/Makefile.am index 028392e3db..008e7a65ad 100644 --- a/conf.d/Makefile.am +++ b/conf.d/Makefile.am @@ -49,6 +49,7 @@ dist_pythonconfig_DATA = \ python.d/phpfpm.conf \ python.d/postfix.conf \ python.d/postgres.conf \ + python.d/rabbitmq.conf \ python.d/redis.conf \ python.d/retroshare.conf \ python.d/samba.conf \ diff --git a/conf.d/apps_groups.conf b/conf.d/apps_groups.conf index 070065b91c..318a743326 100644 --- a/conf.d/apps_groups.conf +++ b/conf.d/apps_groups.conf @@ -224,7 +224,7 @@ media: mpd minidlnad mt-daapd avahi* Plex* X: X Xorg xinit lightdm xdm pulseaudio gkrellm xfwm4 xfdesktop xfce* Thunar X: xfsettingsd xfconfd gnome-* gdm gconf* dconf* xfconf* *gvfs gvfs* kdm slim -X: evolution-* firefox chromium opera epiphany WebKit* +X: evolution-* firefox chromium opera vivaldi-bin epiphany WebKit* # ----------------------------------------------------------------------------- # Kernel / System diff --git a/conf.d/python.d.conf b/conf.d/python.d.conf index 22a18efac3..ef01e723fa 100644 --- a/conf.d/python.d.conf +++ b/conf.d/python.d.conf @@ -62,9 +62,11 @@ nginx_log: no # phpfpm: yes # postfix: yes # postgres: yes +# rabbitmq: yes # redis: yes # retroshare: yes # sensors: yes +# samba: yes # smartd_log: yes # squid: yes # tomcat: yes diff --git a/conf.d/python.d/rabbitmq.conf b/conf.d/python.d/rabbitmq.conf new file mode 100644 index 0000000000..eccf65df92 --- /dev/null +++ b/conf.d/python.d/rabbitmq.conf @@ -0,0 +1,75 @@ +# netdata python.d.plugin configuration for rabbitmq +# +# This file is in YaML format. Generally the format is: +# +# name: value +# +# There are 2 sections: +# - global variables +# - one or more JOBS +# +# JOBS allow you to collect values from multiple sources. +# Each source will have its own set of charts. +# +# JOB parameters have to be indented (using spaces only, example below). + +# ---------------------------------------------------------------------- +# Global Variables +# These variables set the defaults for all JOBs, however each JOB +# may define its own, overriding the defaults. + +# update_every sets the default data collection frequency. +# If unset, the python.d.plugin default is used. +# update_every: 1 + +# priority controls the order of charts at the netdata dashboard. +# Lower numbers move the charts towards the top of the page. +# If unset, the default for python.d.plugin is used. +# priority: 60000 + +# retries sets the number of retries to be made in case of failures. +# If unset, the default for python.d.plugin is used. +# Attempts to restore the service are made once every update_every +# and only if the module has collected values in the past. +# retries: 5 + +# ---------------------------------------------------------------------- +# JOBS (data collection sources) +# +# The default JOBS share the same *name*. JOBS with the same name +# are mutually exclusive. Only one of them will be allowed running at +# any time. This allows autodetection to try several alternatives and +# pick the one that works. +# +# Any number of jobs is supported. +# +# All python.d.plugin JOBS (for all its modules) support a set of +# predefined parameters. These are: +# +# job_name: +# name: myname # the JOB's name as it will appear at the +# # dashboard (by default is the job_name) +# # JOBs sharing a name are mutually exclusive +# update_every: 1 # the JOB's data collection frequency +# priority: 60000 # the JOB's order on the dashboard +# retries: 5 # the JOB's number of restoration attempts +# +# Additionally to the above, rabbitmq plugin also supports the following: +# +# host: 'ipaddress' # Server ip address or hostname. Default: 127.0.0.1 +# port: 'port' # Rabbitmq port. Default: 15672 +# scheme: 'scheme' # http or https. Default: http +# +# if the URL is password protected, the following are supported: +# +# user: 'username' +# pass: 'password' +# +# ---------------------------------------------------------------------- +# AUTO-DETECTION JOBS +# only one of them will run (they have the same name) +# +local: + host: '127.0.0.1' + user: 'guest' + pass: 'guest' diff --git a/python.d/Makefile.am b/python.d/Makefile.am index e10bb7d8a3..a279a10adc 100644 --- a/python.d/Makefile.am +++ b/python.d/Makefile.am @@ -39,6 +39,7 @@ dist_python_DATA = \ phpfpm.chart.py \ postfix.chart.py \ postgres.chart.py \ + rabbitmq.chart.py \ redis.chart.py \ retroshare.chart.py \ samba.chart.py \ diff --git a/python.d/README.md b/python.d/README.md index 7daec41a56..d4486f68dc 100644 --- a/python.d/README.md +++ b/python.d/README.md @@ -1208,6 +1208,60 @@ When no configuration file is found, module tries to connect to TCP/IP socket: ` --- +# rabbitmq + +Module monitor rabbitmq performance and health metrics. + +Following charts are drawn: + +1. **Queued Messages** + * ready + * unacknowledged + +2. **Message Rates** + * ack + * redelivered + * deliver + * publish + +3. **Global Counts** + * channels + * consumers + * connections + * queues + * exchanges + +4. **File Descriptors** + * used descriptors + +5. **Socket Descriptors** + * used descriptors + +6. **Erlang processes** + * used processes + +7. **Memory** + * free memory in megabytes + +8. **Disk Space** + * free disk space in gigabytes + +### configuration + +```yaml +socket: + name : 'local' + host : '127.0.0.1' + port : 15672 + user : 'guest' + pass : 'guest' + +``` + +When no configuration file is found, module tries to connect to: `localhost:15672`. + +--- + # redis Get INFO data from redis instance. diff --git a/python.d/rabbitmq.chart.py b/python.d/rabbitmq.chart.py new file mode 100644 index 0000000000..f996c2bebf --- /dev/null +++ b/python.d/rabbitmq.chart.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# Description: rabbitmq netdata python.d module +# Author: l2isbad + +from base import UrlService +from socket import gethostbyname, gaierror +try: + from queue import Queue +except ImportError: + from Queue import Queue +from threading import Thread +from collections import namedtuple +from json import loads + +# default module values (can be overridden per job in `config`) +update_every = 1 +priority = 60000 +retries = 60 + +METHODS = namedtuple('METHODS', ['get_data_function', 'url', 'stats']) + +NODE_STATS = [('fd_used', None), + ('mem_used', None), + ('sockets_used', None), + ('proc_used', None), + ('disk_free', None) + ] +OVERVIEW_STATS = [('object_totals.channels', None), + ('object_totals.consumers', None), + ('object_totals.connections', None), + ('object_totals.queues', None), + ('object_totals.exchanges', None), + ('queue_totals.messages_ready', None), + ('queue_totals.messages_unacknowledged', None), + ('message_stats.ack', None), + ('message_stats.redeliver', None), + ('message_stats.deliver', None), + ('message_stats.publish', None) + ] +ORDER = ['queued_messages', 'message_rates', 'global_counts', + 'file_descriptors', 'socket_descriptors', 'erlang_processes', 'memory', 'disk_space'] + +CHARTS = { + 'file_descriptors': { + 'options': [None, 'File Descriptors', 'descriptors', 'overview', + 'rabbitmq.file_descriptors', 'line'], + 'lines': [ + ['fd_used', 'used', 'absolute'] + ]}, + 'memory': { + 'options': [None, 'Memory', 'MB', 'overview', + 'rabbitmq.memory', 'line'], + 'lines': [ + ['mem_used', 'used', 'absolute', 1, 1024 << 10] + ]}, + 'disk_space': { + 'options': [None, 'Disk Space', 'GB', 'overview', + 'rabbitmq.disk_space', 'line'], + 'lines': [ + ['disk_free', 'free', 'absolute', 1, 1024 ** 3] + ]}, + 'socket_descriptors': { + 'options': [None, 'Socket Descriptors', 'descriptors', 'overview', + 'rabbitmq.sockets', 'line'], + 'lines': [ + ['sockets_used', 'used', 'absolute'] + ]}, + 'erlang_processes': { + 'options': [None, 'Erlang Processes', 'processes', 'overview', + 'rabbitmq.processes', 'line'], + 'lines': [ + ['proc_used', 'used', 'absolute'] + ]}, + 'global_counts': { + 'options': [None, 'Global Counts', 'counts', 'overview', + 'rabbitmq.global_counts', 'line'], + 'lines': [ + ['channels', None, 'absolute'], + ['consumers', None, 'absolute'], + ['connections', None, 'absolute'], + ['queues', None, 'absolute'], + ['exchanges', None, 'absolute'] + ]}, + 'queued_messages': { + 'options': [None, 'Queued Messages', 'messages', 'overview', + 'rabbitmq.queued_messages', 'stacked'], + 'lines': [ + ['messages_ready', 'ready', 'incremental'], + ['messages_unacknowledged', 'unacknowledged', 'incremental'] + ]}, + 'message_rates': { + 'options': [None, 'Message Rates', 'messages/s', 'overview', + 'rabbitmq.message_rates', 'stacked'], + 'lines': [ + ['ack', None, 'incremental'], + ['redeliver', None, 'incremental'], + ['deliver', None, 'incremental'], + ['publish', None, 'incremental'] + ]} +} + + +class Service(UrlService): + def __init__(self, configuration=None, name=None): + UrlService.__init__(self, configuration=configuration, name=name) + self.order = ORDER + self.definitions = CHARTS + self.host = self.configuration.get('host', '127.0.0.1') + self.port = self.configuration.get('port', 15672) + self.scheme = self.configuration.get('scheme', 'http') + + def check(self): + # We can't start if <host> AND <port> not specified + if not (self.host and self.port): + self.error('Host is not defined in the module configuration file') + return False + + # Hostname -> ip address + try: + self.host = gethostbyname(self.host) + except gaierror as error: + self.error(str(error)) + return False + + # Add handlers (auth, self signed cert accept) + url = '%s://%s:%s/api' % (self.scheme, self.host, self.port) + self.opener = self._build_opener(url=url) + if not self.opener: + return False + # Add methods + api_node = url + '/nodes' + api_overview = url + '/overview' + self.methods = [METHODS(get_data_function=self._get_overview_stats, url=api_node, stats=NODE_STATS), + METHODS(get_data_function=self._get_overview_stats, url=api_overview, stats=OVERVIEW_STATS)] + + result = self._get_data() + if not result: + self.error('_get_data() returned no data') + return False + self._data_from_check = result + return True + + def _get_data(self): + threads = list() + queue = Queue() + result = dict() + + for method in self.methods: + th = Thread(target=method.get_data_function, args=(queue, method.url, method.stats)) + th.start() + threads.append(th) + + for thread in threads: + thread.join() + result.update(queue.get()) + + return result or None + + def _get_overview_stats(self, queue, url, stats): + """ + Format data received from http request + :return: dict + """ + + raw_data = self._get_raw_data(url) + + if not raw_data: + return queue.put(dict()) + data = loads(raw_data) + data = data[0] if isinstance(data, list) else data + + to_netdata = fetch_data_(raw_data=data, metrics_list=stats) + return queue.put(to_netdata) + + +def fetch_data_(raw_data, metrics_list): + to_netdata = dict() + for metric, new_name in metrics_list: + value = raw_data + for key in metric.split('.'): + try: + value = value[key] + except (KeyError, TypeError): + break + if not isinstance(value, dict): + to_netdata[new_name or key] = value + return to_netdata diff --git a/python.d/redis.chart.py b/python.d/redis.chart.py index 8a5bab39e4..4bc1d41f9d 100644 --- a/python.d/redis.chart.py +++ b/python.d/redis.chart.py @@ -100,13 +100,12 @@ class Service(SocketService): :return: dict """ if self.passwd: - info_request = self.request self.request = "AUTH " + self.passwd + "\r\n" raw = self._get_raw_data().strip() if raw != "+OK": self.error("invalid password") return None - self.request = info_request + self.request = "INFO\r\n" response = self._get_raw_data() if response is None: # error has already been logged |