]> arthur.barton.de Git - netdata.git/commitdiff
New class UrlService + phpfpm python module + minor changes in python.d.conf
authorpaulfantom <paulfantom@gmail.com>
Wed, 22 Jun 2016 11:12:21 +0000 (13:12 +0200)
committerpaulfantom <paulfantom@gmail.com>
Wed, 22 Jun 2016 11:12:21 +0000 (13:12 +0200)
conf.d/python.d.conf
conf.d/python.d/phpfpm.conf [new file with mode: 0644]
python.d/phpfpm.chart.py [new file with mode: 0755]
python.d/python_modules/base.py

index e8f6e8923dba67b4c3bd48d06f076f45dbc23120..b303cde4a88dc0bd80a879549174a85dfcf91c04 100644 (file)
@@ -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 (file)
index 0000000..798d6a5
--- /dev/null
@@ -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 (executable)
index 0000000..7b34542
--- /dev/null
@@ -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
index a60afb26f2550e5d71d72931935ef8b18ec1589c..2d275beca6523ed4b8def05faf821051430c5233 100644 (file)
@@ -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