phpfpm.chart.py \
apache.chart.py \
nginx.chart.py \
+ apache_cache.chart.py \
python-modules-installer.sh \
$(NULL)
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"]
]}
}
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',
--- /dev/null
+# -*- 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)}
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"]
]}
}
if len(self.url) == 0:
self.url = "http://localhost/stub_status"
self.order = ORDER
- self.charts = CHARTS
+ self.definitions = CHARTS
def _formatted_data(self):
"""
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']
]}
}
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',
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
:param name: str
"""
threading.Thread.__init__(self)
- self.data_stream = ""
+ self._data_stream = ""
self.daemon = True
self.retries = 0
self.retries_left = 0
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
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=""):
: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):
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:
: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:
: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:
"""
Upload new data to netdata
"""
- print(self.data_stream)
- self.data_stream = ""
+ print(self._data_stream)
+ self._data_stream = ""
def error(self, *params):
"""
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):
"""
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
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