--- /dev/null
+# -*- 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
# -*- 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):
@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()
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):
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