]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #632 from paulfantom/master
authorCosta Tsaousis <costa@tsaousis.gr>
Sat, 2 Jul 2016 16:35:36 +0000 (19:35 +0300)
committerGitHub <noreply@github.com>
Sat, 2 Jul 2016 16:35:36 +0000 (19:35 +0300)
Wrappers around charts creation + LogService

python.d/Makefile.am
python.d/apache.chart.py
python.d/apache_cache.chart.py [new file with mode: 0644]
python.d/nginx.chart.py
python.d/phpfpm.chart.py
python.d/python_modules/base.py

index d96ead822b0ac4d40486495b3dd116355bc95af7..2d6d70c18d8d8f8c677bda3cf00ffba81592e6a9 100644 (file)
@@ -10,6 +10,7 @@ dist_python_SCRIPTS = \
        phpfpm.chart.py \
        apache.chart.py \
        nginx.chart.py \
+       apache_cache.chart.py \
        python-modules-installer.sh \
        $(NULL)
 
index f61242ecce16c47a811ff5c1b52981d995a8096f..f6262a540b633379bbf8d7bd7f2366bcc3601306 100644 (file)
@@ -22,58 +22,47 @@ ORDER = ['requests', 'connections', 'conns_async', 'net', 'workers', 'reqpersec'
 
 CHARTS = {
     'bytesperreq': {
-        'options': "'' 'apache Lifetime Avg. Response Size' 'bytes/request' statistics apache.bytesperreq area",
+        'options': [None, 'apache Lifetime Avg. Response Size', 'bytes/request', 'statistics', 'apache.bytesperreq', 'area'],
         'lines': [
-            {"name": "size_req",
-             "options": "'' absolute 1 1"}
+            ["size_req"]
         ]},
     'workers': {
-        'options': "'' 'apache Workers' 'workers' workers apache.workers stacked",
+        'options': [None, 'apache Workers', 'workers', 'workers', 'apache.workers', 'stacked'],
         'lines': [
-            {"name": "idle",
-             "options": "'' absolute 1 1"},
-            {"name": "busy",
-             "options": "'' absolute 1 1"}
+            ["idle"],
+            ["busy"]
         ]},
     'reqpersec': {
-        'options': "'' 'apache Lifetime Avg. Requests/s' 'requests/s' statistics apache.reqpersec area",
+        'options': [None, 'apache Lifetime Avg. Requests/s', 'requests/s', 'statistics', 'apache.reqpersec', 'area'],
         'lines': [
-            {"name": "requests_sec",
-             "options": "'' absolute 1 1"}
+            ["requests_sec"]
         ]},
     'bytespersec': {
-        'options': "'' 'apache Lifetime Avg. Bandwidth/s' 'kilobytes/s' statistics apache.bytesperreq area",
+        'options': [None, 'apache Lifetime Avg. Bandwidth/s', 'kilobytes/s', 'statistics', 'apache.bytesperreq', 'area'],
         'lines': [
-            {"name": "size_sec",
-             "options": "'' absolute 1 1000"}
+            ["size_sec", None, 'absolute', 1, 1000]
         ]},
     'requests': {
-        'options': "'' 'apache Requests' 'requests/s' requests apache.requests line",
+        'options': [None, 'apache Requests', 'requests/s', 'requests', 'apache.requests', 'line'],
         'lines': [
-            {"name": "requests",
-             "options": "'' incremental 1 1"}
+            ["requests", None, 'incremental']
         ]},
     'net': {
-        'options': "'' 'apache Bandwidth' 'kilobytes/s' bandwidth apache.net area",
+        'options': [None, 'apache Bandwidth', 'kilobytes/s', 'bandwidth', 'apache.net', 'area'],
         'lines': [
-            {"name": "sent",
-             "options": "'' incremental 1 1"}
+            ["sent", None, 'incremental']
         ]},
     'connections': {
-        'options': "'' 'apache Connections' 'connections' connections apache.connections line",
+        'options': [None, 'apache Connections', 'connections', 'connections', 'apache.connections', 'line'],
         'lines': [
-            {"name": "connections",
-             "options": "'' absolute 1 1"}
+            ["connections"]
         ]},
     'conns_async': {
-        'options': "'' 'apache Async Connections' 'connections' connections apache.conns_async stacked",
+        'options': [None, 'apache Async Connections', 'connections', 'connections', 'apache.conns_async', 'stacked'],
         'lines': [
-            {"name": "keepalive",
-             "options": "'' absolute 1 1"},
-            {"name": "closing",
-             "options": "'' absolute 1 1"},
-            {"name": "writing",
-             "options": "'' absolute 1 1"}
+            ["keepalive"],
+            ["closing"],
+            ["writing"]
         ]}
 }
 
@@ -84,7 +73,7 @@ class Service(UrlService):
         if len(self.url) == 0:
             self.url = "http://localhost/server-status?auto"
         self.order = ORDER
-        self.charts = CHARTS
+        self.definitions = CHARTS
         self.assignment = {"BytesPerReq": 'size_req',
                            "IdleWorkers": 'idle',
                            "BusyWorkers": 'busy',
diff --git a/python.d/apache_cache.chart.py b/python.d/apache_cache.chart.py
new file mode 100644 (file)
index 0000000..115414a
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Description: apache cache netdata python.d plugin
+# Author: Pawel Krupa (paulfantom)
+
+from base import LogService
+
+priority = 60000
+retries = 5
+update_every = 3
+
+ORDER = ['cache']
+CHARTS = {
+    'cache': {
+        'options': [None, 'apache cached responses', 'percent cached', 'cached', 'apache_cache.cache', 'stacked'],
+        'lines': [
+            ["hit", 'cache', "percentage-of-absolute-row"],
+            ["miss", None, "percentage-of-absolute-row"]
+        ]}
+}
+
+
+class Service(LogService):
+    def __init__(self, configuration=None, name=None):
+        LogService.__init__(self, configuration=configuration, name=name)
+        if len(self.log_path) == 0:
+            self.log_path = "/var/log/apache2/cache.log"
+        self.order = ORDER
+        self.definitions = CHARTS
+
+    def _formatted_data(self):
+        """
+        Parse new log lines
+        :return: dict
+        """
+        try:
+            raw = self._get_data()
+            if raw is None:
+                return None
+        except (ValueError, AttributeError):
+            return None
+
+        hit = 0
+        miss = 0
+        for line in raw:
+            if "cache hit" in line:
+                hit += 1
+            elif "cache miss" in line:
+                miss += 1
+
+        total = hit + miss
+        if total == 0:
+            return None
+
+        return {'hit': int(hit/float(total) * 100),
+                'miss': int(miss/float(total) * 100)}
index 89752f565d6b508bb004fe0437fd36b055c6c30b..94d78d5ade6579ee0924166cdb845dd581e9b364 100644 (file)
@@ -22,34 +22,27 @@ ORDER = ['connections', 'requests', 'connection_status', 'connect_rate']
 
 CHARTS = {
     'connections': {
-        'options': "'' 'nginx Active Connections' 'connections' nginx nginx.connections line",
+        'options': [None, 'nginx Active Connections', 'connections', 'nginx', 'nginx.connections', 'line'],
         'lines': [
-            {"name": "active",
-             "options": "'' absolute 1 1"}
+            ["active"]
         ]},
     'requests': {
-        'options': "'' 'nginx Requests' 'requests/s' nginx nginx.requests line",
+        'options': [None, 'nginx Requests', 'requests/s', 'nginx', 'nginx.requests', 'line'],
         'lines': [
-            {"name": "requests",
-             "options": "'' incremental 1 1"}
+            ["requests", None, 'incremental']
         ]},
     'connection_status': {
-        'options': "'' 'nginx Active Connections by Status' 'connections' nginx nginx.connection.status line",
+        'options': [None, 'nginx Active Connections by Status', 'connections', 'nginx', 'nginx.connection.status', 'line'],
         'lines': [
-            {"name": "reading",
-             "options": "'' absolute 1 1"},
-            {"name": "writing",
-             "options": "'' absolute 1 1"},
-            {"name": "waiting",
-             "options": "idle absolute 1 1"}
+            ["reading"],
+            ["writing"],
+            ["waiting", "idle"]
         ]},
     'connect_rate': {
-        'options': "'' 'nginx Connections Rate' 'connections/s' nginx nginx.performance line",
+        'options': [None, 'nginx Connections Rate', 'connections/s', 'nginx', 'nginx.performance', 'line'],
         'lines': [
-            {"name": "accepts",
-             "options": "accepted incremental 1 1"},
-            {"name": "handled",
-             "options": "'' incremental 1 1"}
+            ["accepts", "accepted", "incremental"],
+            ["handled", None, "incremental"]
         ]}
 }
 
@@ -60,7 +53,7 @@ class Service(UrlService):
         if len(self.url) == 0:
             self.url = "http://localhost/stub_status"
         self.order = ORDER
-        self.charts = CHARTS
+        self.definitions = CHARTS
 
     def _formatted_data(self):
         """
index 8fadf2beefa2ce8b5ed8b8abce19bef4b587f3de..00f6336175a0fff2e53fbef77bec1a2a558f1102 100755 (executable)
@@ -22,29 +22,22 @@ ORDER = ['connections', 'requests', 'performance']
 
 CHARTS = {
     'connections': {
-        'options': "'' 'PHP-FPM Active Connections' 'connections' phpfpm phpfpm.connections line",
+        'options': [None, '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"}
+            ["active"],
+            ["maxActive", 'max active'],
+            ["idle"]
         ]},
     'requests': {
-        'options': "'' 'PHP-FPM Requests' 'requests/s' phpfpm phpfpm.requests line",
+        'options': [None, 'PHP-FPM Requests', 'requests/s', 'phpfpm', 'phpfpm.requests', 'line'],
         'lines': [
-            {"name": "requests",
-             "options": "'' incremental 1 1"}
+            ["requests", None, "incremental"]
         ]},
     'performance': {
-
-        'options': "'' 'PHP-FPM Performance' 'status' phpfpm phpfpm.performance line",
+        'options': [None, '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"}
+            ["reached", 'max children reached'],
+            ["slow", 'slow requests']
         ]}
 }
 
@@ -55,7 +48,7 @@ class Service(UrlService):
         if len(self.url) == 0:
             self.url = "http://localhost/status"
         self.order = ORDER
-        self.charts = CHARTS
+        self.definitions = CHARTS
         self.assignment = {"active processes": 'active',
                            "max active processes": 'maxActive',
                            "idle processes": 'idle',
index 1183c42886ed1a85cceeaf99decaa4cc106831e4..7b54e6a4e46624c011df86707d674a0f789cd847 100644 (file)
@@ -4,11 +4,13 @@
 
 import time
 import sys
+import os
 try:
     from urllib.request import urlopen
 except ImportError:
     from urllib2 import urlopen
 
+# from subprocess import STDOUT, PIPE, Popen
 import threading
 import msg
 
@@ -25,7 +27,7 @@ class BaseService(threading.Thread):
         :param name: str
         """
         threading.Thread.__init__(self)
-        self.data_stream = ""
+        self._data_stream = ""
         self.daemon = True
         self.retries = 0
         self.retries_left = 0
@@ -34,8 +36,8 @@ class BaseService(threading.Thread):
         self.name = name
         self.override_name = None
         self.chart_name = ""
-        self.dimensions = []
-        self.charts = []
+        self._dimensions = []
+        self._charts = []
         if configuration is None:
             self.error("BaseService: no configuration parameters supplied. Cannot create Service.")
             raise RuntimeError
@@ -142,15 +144,18 @@ class BaseService(threading.Thread):
         Converts *params to string and joins them with one space between every one.
         :param params: str/int/float
         """
-        self.data_stream += instruction
+        self._data_stream += instruction
         for p in params:
-            p = str(p)
+            if p is None:
+                p = ""
+            else:
+                p = str(p)
             if len(p) == 0:
                 p = "''"
             if ' ' in p:
                 p = "'" + p + "'"
-            self.data_stream += " " + p
-        self.data_stream += "\n"
+            self._data_stream += " " + p
+        self._data_stream += "\n"
 
     def chart(self, type_id, name="", title="", units="", family="",
               category="", charttype="line", priority="", update_every=""):
@@ -166,8 +171,7 @@ class BaseService(threading.Thread):
         :param priority: int/str
         :param update_every: int/str
         """
-        self.charts.append(type_id)
-
+        self._charts.append(type_id)
         self._line("CHART", type_id, name, title, units, family, category, charttype, priority, update_every)
 
     def dimension(self, id, name=None, algorithm="absolute", multiplier=1, divisor=1, hidden=False):
@@ -196,7 +200,7 @@ class BaseService(threading.Thread):
         if algorithm not in ("absolute", "incremental", "percentage-of-absolute-row", "percentage-of-incremental-row"):
             algorithm = "absolute"
 
-        self.dimensions.append(id)
+        self._dimensions.append(id)
         if hidden:
             self._line("DIMENSION", id, name, algorithm, multiplier, divisor, "hidden")
         else:
@@ -209,7 +213,7 @@ class BaseService(threading.Thread):
         :param microseconds: int
         :return: boolean
         """
-        if type_id not in self.charts:
+        if type_id not in self._charts:
             self.error("wrong chart type_id:", type_id)
             return False
         try:
@@ -228,7 +232,7 @@ class BaseService(threading.Thread):
         :param value: int/float
         :return: boolean
         """
-        if id not in self.dimensions:
+        if id not in self._dimensions:
             self.error("wrong dimension id:", id)
             return False
         try:
@@ -246,8 +250,8 @@ class BaseService(threading.Thread):
         """
         Upload new data to netdata
         """
-        print(self.data_stream)
-        self.data_stream = ""
+        print(self._data_stream)
+        self._data_stream = ""
 
     def error(self, *params):
         """
@@ -293,27 +297,88 @@ class BaseService(threading.Thread):
         return False
 
 
-class UrlService(BaseService):
+class SimpleService(BaseService):
     def __init__(self, configuration=None, name=None):
-        self.charts = {}
-        # charts definitions in format:
-        # charts = {
-        #    'chart_name_in_netdata': {
-        #        'options': "parameters defining chart (passed to CHART statement)",
-        #        'lines': [
-        #           { 'name': 'dimension_name',
-        #             'options': 'dimension parameters (passed to DIMENSION statement)"
-        #           }
-        #        ]}
-        #    }
         self.order = []
         self.definitions = {}
+        BaseService.__init__(self, configuration=configuration, name=name)
+
+    def _get_data(self):
+        """
+        Get raw data from http request
+        :return: str
+        """
+        return ""
+
+    def _formatted_data(self):
+        """
+        Format data received from http request
+        :return: dict
+        """
+        return {}
+
+    def check(self):
+        """
+        :return:
+        """
+        return True
+
+    def create(self):
+        """
+        Create charts
+        :return: boolean
+        """
+        data = self._formatted_data()
+        if data is None:
+            return False
+
+        idx = 0
+        for name in self.order:
+            options = self.definitions[name]['options'] + [self.priority + idx, self.update_every]
+            self.chart(self.__module__ + "_" + self.name + "." + name, *options)
+            # check if server has this datapoint
+            for line in self.definitions[name]['lines']:
+                if line[0] in data:
+                    self.dimension(*line)
+            idx += 1
+
+        self.commit()
+        return True
+
+    def update(self, interval):
+        """
+        Update charts
+        :param interval: int
+        :return: boolean
+        """
+        data = self._formatted_data()
+        if data is None:
+            return False
+
+        updated = False
+        for chart in self.order:
+            if self.begin(self.__module__ + "_" + str(self.name) + "." + chart, interval):
+                updated = True
+                for dim in self.definitions[chart]['lines']:
+                    try:
+                        self.set(dim[0], data[dim[0]])
+                    except KeyError:
+                        pass
+                self.end()
+
+        self.commit()
+
+        return updated
+
+
+class UrlService(SimpleService):
+    def __init__(self, configuration=None, name=None):
         # definitions are created dynamically in create() method based on 'charts' dictionary. format:
         # definitions = {
         #     'chart_name_in_netdata' : [ charts['chart_name_in_netdata']['lines']['name'] ]
         # }
         self.url = ""
-        BaseService.__init__(self, configuration=configuration, name=name)
+        SimpleService.__init__(self, configuration=configuration, name=name)
 
     def _get_data(self):
         """
@@ -333,13 +398,6 @@ class UrlService(BaseService):
                 pass
         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
@@ -359,68 +417,57 @@ class UrlService(BaseService):
         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
-        data_stream = ""
-        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 += "\nDIMENSION " + line['name'] + " " + line['options']
+class LogService(SimpleService):
+    def __init__(self, configuration=None, name=None):
+        # definitions are created dynamically in create() method based on 'charts' dictionary. format:
+        # definitions = {
+        #     'chart_name_in_netdata' : [ charts['chart_name_in_netdata']['lines']['name'] ]
+        # }
+        self.log_path = ""
+        self._last_position = 0
+        # self._log_reader = None
+        SimpleService.__init__(self, configuration=configuration, name=name)
+        # FIXME Remove preventing of frequent log parsing
+        if self.timetable['freq'] < 3:
+            self.timetable['freq'] = 3
+        self.retries = 100000  # basically always retry
 
-            if len(content) > 0:
-                data_stream += header + content + "\n"
-                idx += 1
+    def _get_data(self):
+        lines = []
+        try:
+            if os.path.getsize(self.log_path) < self._last_position:
+                self._last_position = 0
+            with open(self.log_path, "r") as fp:
+                fp.seek(self._last_position)
+                for i, line in enumerate(fp):
+                    lines.append(line)
+                self._last_position = fp.tell()
+        except Exception as e:
+            msg.error(self.__module__, str(e))
 
-        print(data_stream)
+        if len(lines) != 0:
+            return lines
+        return None
 
-        if idx == 0:
-            return False
-        return True
+    def check(self):
+        if self.name is not None or self.name != str(None):
+            self.name = ""
+        else:
+            self.name = str(self.name)
+        try:
+            self.log_path = str(self.configuration['path'])
+        except (KeyError, TypeError):
+            self.error("No path to log specified. Using: '" + self.log_path + "'")
 
-    def update(self, interval):
-        """
-        Update charts
-        :param interval: int
-        :return: boolean
-        """
-        data = self._formatted_data()
-        if data is None:
+        if os.access(self.log_path, os.R_OK):
+            return True
+        else:
+            self.error("Cannot access file: '" + self.log_path + "'")
             return False
 
-        data_stream = ""
-        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:
-                data_stream += header + c + "\nEND\n"
-        print(data_stream)
+    def create(self):
+        status = SimpleService.create(self)
+        self._last_position = 0
+        return status
 
-        return True