From 77563deeed3129bbe1fd5a9996633b874bbffeff Mon Sep 17 00:00:00 2001 From: paulfantom Date: Wed, 22 Jun 2016 13:12:21 +0200 Subject: [PATCH] New class UrlService + phpfpm python module + minor changes in python.d.conf --- conf.d/python.d.conf | 5 +- conf.d/python.d/phpfpm.conf | 4 + python.d/phpfpm.chart.py | 77 ++++++++++++++++++ python.d/python_modules/base.py | 139 ++++++++++++++++++++++++++++++-- 4 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 conf.d/python.d/phpfpm.conf create mode 100755 python.d/phpfpm.chart.py diff --git a/conf.d/python.d.conf b/conf.d/python.d.conf index e8f6e892..b303cde4 100644 --- a/conf.d/python.d.conf +++ b/conf.d/python.d.conf @@ -5,5 +5,6 @@ enabled: no # By default python.d.plugin enables all modules stored in python.d # Modules can be disabled with setting "module_name = no" -example: no -mysql: no +example: yes +mysql: yes +phpfpm: yes diff --git a/conf.d/python.d/phpfpm.conf b/conf.d/python.d/phpfpm.conf new file mode 100644 index 00000000..798d6a52 --- /dev/null +++ b/conf.d/python.d/phpfpm.conf @@ -0,0 +1,4 @@ +update_every: 3 + +local: + url: "http://localhost/status" \ No newline at end of file diff --git a/python.d/phpfpm.chart.py b/python.d/phpfpm.chart.py new file mode 100755 index 00000000..7b34542f --- /dev/null +++ b/python.d/phpfpm.chart.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Description: PHP-FPM netdata python.d plugin +# Author: Pawel Krupa (paulfantom) + +from base import UrlService + +# default module values (can be overridden per job in `config`) +update_every = 2 +priority = 60000 +retries = 5 + +# default job configuration (overridden by python.d.plugin) +config = {'local': { + 'update_every': update_every, + 'retries': retries, + 'priority': priority, + 'url': 'http://localhost/status' +}} + +# charts order (can be overridden if you want less charts, or different order) +ORDER = ['connections', 'requests', 'performance'] + +CHARTS = { + 'connections': { + 'options': "'' 'PHP-FPM Active Connections' 'connections' phpfpm phpfpm.connections line", + 'lines': [ + {"name": "active", + "options": "'' absolute 1 1"}, + {"name": "maxActive", + "options": "'max active' absolute 1 1"}, + {"name": "idle", + "options": "'' absolute 1 1"} + ]}, + 'requests': { + 'options': "'' 'PHP-FPM Requests' 'requests/s' phpfpm phpfpm.requests line", + 'lines': [ + {"name": "requests", + "options": "'' incremental 1 1"} + ]}, + 'performance': { + + 'options': "'' 'PHP-FPM Performance' 'status' phpfpm phpfpm.performance line", + 'lines': [ + {"name": "reached", + "options": "'max children reached' absolute 1 1"}, + {"name": "slow", + "options": "'slow requests' absolute 1 1"} + ]} +} + + +class Service(UrlService): + url = "http://localhost/status" + order = ORDER + charts = CHARTS + assignment = {"active processes": 'active', + "max active processes": 'maxActive', + "idle processes": 'idle', + "accepted conn": 'requests', + "max children reached": 'reached', + "slow requests": 'slow'} + + def _formatted_data(self): + """ + Format data received from http request + :return: dict + """ + raw = self._get_data().split('\n') + data = {} + for row in raw: + tmp = row.split(":") + if str(tmp[0]) in self.assignment: + try: + data[self.assignment[tmp[0]]] = int(tmp[1]) + except (IndexError, ValueError) as a: + pass + return data diff --git a/python.d/python_modules/base.py b/python.d/python_modules/base.py index a60afb26..2d275bec 100644 --- a/python.d/python_modules/base.py +++ b/python.d/python_modules/base.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- -# Description: base for netdata python.d plugins +# Description: prototypes for netdata python.d modules # Author: Pawel Krupa (paulfantom) from time import time import sys +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen class BaseService(object): @@ -62,7 +66,7 @@ class BaseService(object): @staticmethod def error(msg, exception=""): if exception != "": - exception = " " + str(exception).replace("\n"," ") + exception = " " + str(exception).replace("\n", " ") sys.stderr.write(str(msg)+exception+"\n") sys.stderr.flush() @@ -71,7 +75,7 @@ class BaseService(object): check() prototype :return: boolean """ - self.error("Service " + str(self.__name__) + "doesn't implement check() function") + self.error("Service " + str(self.__module__) + "doesn't implement check() function") return False def create(self): @@ -79,13 +83,136 @@ class BaseService(object): create() prototype :return: boolean """ - self.error("Service " + str(self.__name__) + "doesn't implement create() function?") + self.error("Service " + str(self.__module__) + "doesn't implement create() function?") return False - def update(self): + def update(self, interval): """ update() prototype + :param interval: int :return: boolean """ - self.error("Service " + str(self.__name__) + "doesn't implement update() function") + self.error("Service " + str(self.__module__) + "doesn't implement update() function") return False + + +class UrlService(BaseService): + charts = {} + # charts definitions in format: + # charts = { + # 'chart_name_in_netdata': ( + # "parameters defining chart (passed to CHART statement)", + # [ # dimensions (lines) definitions + # ("dimension_name", "dimension parameters (passed to DIMENSION statement)") + # ]) + # } + order = [] + definitions = {} + # definitions are created dynamically in create() method based on 'charts' dictionary. format: + # definitions = { + # 'chart_name_in_netdata' : (charts['chart_name_in_netdata'][1], charts['chart_name_in_netdata'][0]) + # } + url = "" + + def _get_data(self): + """ + Get raw data from http request + :return: str + """ + try: + f = urlopen(self.url) + raw = f.read().decode('utf-8') + f.close() + except Exception as e: + self.error(self.__module__, str(e)) + return None + return raw + + def _formatted_data(self): + """ + Format data received from http request + :return: dict + """ + return {} + + def check(self): + """ + Format configuration data and try to connect to server + :return: boolean + """ + if self.name is None: + self.name = 'local' + else: + self.name = str(self.name) + try: + self.url = str(self.configuration['url']) + except (KeyError, TypeError): + pass + + if self._formatted_data() is not None: + return True + else: + return False + + def create(self): + """ + Create charts + :return: boolean + """ + for name in self.order: + if name not in self.charts: + continue + self.definitions[name] = [] + for line in self.charts[name]['lines']: + self.definitions[name].append(line['name']) + + idx = 0 + data = self._formatted_data() + if data is None: + return False + for name in self.order: + header = "CHART " + \ + self.__module__ + "_" + \ + self.name + "." + \ + name + " " + \ + self.charts[name]['options'] + " " + \ + str(self.priority + idx) + " " + \ + str(self.update_every) + content = "" + # check if server has this datapoint + for line in self.charts[name]['lines']: + if line['name'] in data: + content += "DIMENSION " + line['name'] + " " + line['options'] + "\n" + + if len(content) > 0: + print(header) + print(content) + idx += 1 + + if idx == 0: + return False + return True + + def update(self, interval): + """ + Update charts + :param interval: int + :return: boolean + """ + data = self._formatted_data() + if data is None: + return False + + for chart, dimensions in self.definitions.items(): + header = "BEGIN " + self.__module__ + "_" + str(self.name) + "." + chart + " " + str(interval) + c = "" + for dim in dimensions: + try: + c += "\nSET " + dim + " = " + str(data[dim]) + except KeyError: + pass + if len(c) != 0: + print(header + c) + print("END") + + return True -- 2.39.2