]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #1568 from l2isbad/varnish_plugin
authorCosta Tsaousis <costa@tsaousis.gr>
Wed, 18 Jan 2017 07:41:43 +0000 (09:41 +0200)
committerGitHub <noreply@github.com>
Wed, 18 Jan 2017 07:41:43 +0000 (09:41 +0200)
varnish plugin (varnishstat)

conf.d/Makefile.am
conf.d/health.d/varnish.conf [new file with mode: 0644]
conf.d/python.d/varnish.conf [new file with mode: 0644]
netdata-installer.sh
python.d/Makefile.am
python.d/varnish.chart.py [new file with mode: 0644]

index 7ab660f0d96dfbeec8e1b6dbc9f504a7cf24a3d9..27777144a3117f85a422ed347b4e964a4d4d029e 100644 (file)
@@ -50,6 +50,7 @@ dist_pythonconfig_DATA = \
     python.d/sensors.conf \
     python.d/squid.conf \
     python.d/tomcat.conf \
+    python.d/varnish.conf \
     $(NULL)
 
 healthconfigdir=$(configdir)/health.d
@@ -80,6 +81,7 @@ dist_healthconfig_DATA = \
     health.d/swap.conf \
     health.d/tcp_resets.conf \
     health.d/udp_errors.conf \
+    health.d/varnish.conf \
     $(NULL)
 
 chartsconfigdir=$(configdir)/charts.d
diff --git a/conf.d/health.d/varnish.conf b/conf.d/health.d/varnish.conf
new file mode 100644 (file)
index 0000000..cca7446
--- /dev/null
@@ -0,0 +1,9 @@
+   alarm: varnish_last_collected
+      on: varnish.uptime
+    calc: $now - $last_collected_t
+   units: seconds ago
+   every: 10s
+    warn: $this > (($status >= $WARNING)  ? ($update_every) : ( 5 * $update_every))
+    crit: $this > (($status == $CRITICAL) ? ($update_every) : (60 * $update_every))
+    info: number of seconds since the last successful data collection
+      to: sysadmin
diff --git a/conf.d/python.d/varnish.conf b/conf.d/python.d/varnish.conf
new file mode 100644 (file)
index 0000000..56dc633
--- /dev/null
@@ -0,0 +1,65 @@
+# netdata python.d.plugin configuration for varnish
+#
+# This file is in YaML format. Generally the format is:
+#
+# name: value
+#
+# There are 2 sections:
+#  - global variables
+#  - one or more JOBS
+#
+# JOBS allow you to collect values from multiple sources.
+# Each source will have its own set of charts.
+#
+# JOB parameters have to be indented (using spaces only, example below).
+
+# ----------------------------------------------------------------------
+# Global Variables
+# These variables set the defaults for all JOBs, however each JOB
+# may define its own, overriding the defaults.
+
+# update_every sets the default data collection frequency.
+# If unset, the python.d.plugin default is used.
+# update_every: 1
+
+# priority controls the order of charts at the netdata dashboard.
+# Lower numbers move the charts towards the top of the page.
+# If unset, the default for python.d.plugin is used.
+# priority: 60000
+
+# retries sets the number of retries to be made in case of failures.
+# If unset, the default for python.d.plugin is used.
+# Attempts to restore the service are made once every update_every
+# and only if the module has collected values in the past.
+# retries: 5
+
+# ----------------------------------------------------------------------
+# JOBS (data collection sources)
+#
+# The default JOBS share the same *name*. JOBS with the same name
+# are mutually exclusive. Only one of them will be allowed running at
+# any time. This allows autodetection to try several alternatives and
+# pick the one that works.
+#
+# Any number of jobs is supported.
+#
+# All python.d.plugin JOBS (for all its modules) support a set of
+# predefined parameters. These are:
+#
+# job_name:
+#     name: myname     # the JOB's name as it will appear at the
+#                      # dashboard (by default is the job_name)
+#                      # JOBs sharing a name are mutually exclusive
+#     update_every: 1  # the JOB's data collection frequency
+#     priority: 60000  # the JOB's order on the dashboard
+#     retries: 5       # the JOB's number of restoration attempts
+#
+#
+#
+# The only you need is to add netdata to 'varnish' group
+#
+# Check it from cmd
+# id netdata
+# 
+# uid=999(netdata) gid=999(netdata) группы=999(netdata),118(varnish)
+#
index 95732e9a35af980acc23726d1a68c06a82ee299c..ff8def57820208ceb7ae2feaed718f6c41051811 100755 (executable)
@@ -692,12 +692,14 @@ run find ./system/ -type f -a \! -name \*.in -a \! -name Makefile\* -a \! -name
 
 NETDATA_ADDED_TO_DOCKER=0
 NETDATA_ADDED_TO_NGINX=0
+NETDATA_ADDED_TO_VARNISH=0
 if [ ${UID} -eq 0 ]
     then
     portable_add_group netdata
     portable_add_user netdata
     portable_add_user_to_group docker netdata && NETDATA_ADDED_TO_DOCKER=1
     portable_add_user_to_group nginx  netdata && NETDATA_ADDED_TO_NGINX=1
+    portable_add_user_to_group varnish  netdata && NETDATA_ADDED_TO_VARNISH=1
 
     if [ -d /etc/logrotate.d -a ! -f /etc/logrotate.d/netdata ]
         then
@@ -1343,6 +1345,15 @@ if [ $? -eq 0 -a "${NETDATA_ADDED_TO_NGINX}" = "1" ]
     echo "   gpasswd -d netdata nginx"
 fi
 
+getent group varnish > /dev/null
+if [ $? -eq 0 -a "${NETDATA_ADDED_TO_VARNISH}" = "1" ]
+    then
+    echo
+    echo "You may also want to remove the netdata user from the varnish group"
+    echo "by running:"
+    echo "   gpasswd -d netdata varnish"
+fi
+
 UNINSTALL
 chmod 750 netdata-uninstaller.sh
 
index c8a33479bd52f1e3a0f4f1c69cae0ded1302d8d2..436bceeae776c7ce62f0c8480caf4a2c2b80b816 100644 (file)
@@ -36,6 +36,7 @@ dist_python_SCRIPTS = \
     sensors.chart.py \
     squid.chart.py \
     tomcat.chart.py \
+    varnish.chart.py \
     python-modules-installer.sh \
     $(NULL)
 
diff --git a/python.d/varnish.chart.py b/python.d/varnish.chart.py
new file mode 100644 (file)
index 0000000..9d2c780
--- /dev/null
@@ -0,0 +1,234 @@
+# -*- coding: utf-8 -*-
+# Description:  varnish netdata python.d module
+# Author: l2isbad
+
+from base import SimpleService
+from re import compile
+from os import access as is_executable, X_OK
+from subprocess import Popen, PIPE
+
+
+# default module values (can be overridden per job in `config`)
+# update_every = 2
+priority = 60000
+retries = 60
+
+ORDER = ['session', 'hit_rate', 'chit_rate', 'expunge', 'threads', 'backend_health', 'memory_usage', 'bad', 'uptime']
+
+CHARTS = {'backend_health': 
+             {'lines': [['backend_conn', 'conn', 'incremental', 1, 1],
+                       ['backend_unhealthy', 'unhealthy', 'incremental', 1, 1],
+                       ['backend_busy', 'busy', 'incremental', 1, 1],
+                       ['backend_fail', 'fail', 'incremental', 1, 1],
+                       ['backend_reuse', 'reuse', 'incremental', 1, 1],
+                       ['backend_recycle', 'resycle', 'incremental', 1, 1],
+                       ['backend_toolate', 'toolate', 'incremental', 1, 1],
+                       ['backend_retry', 'retry', 'incremental', 1, 1],
+                       ['backend_req', 'req', 'incremental', 1, 1]],
+              'options': [None, 'Backend health', 'connections', 'Backend health', 'varnish.backend_traf', 'line']},
+          'bad': 
+             {'lines': [['sess_drop_b', None, 'incremental', 1, 1],
+                       ['backend_unhealthy_b', None, 'incremental', 1, 1],
+                       ['fetch_failed', None, 'incremental', 1, 1],
+                       ['backend_busy_b', None, 'incremental', 1, 1],
+                       ['threads_failed_b', None, 'incremental', 1, 1],
+                       ['threads_limited_b', None, 'incremental', 1, 1],
+                       ['threads_destroyed_b', None, 'incremental', 1, 1],
+                       ['thread_queue_len_b', 'queue_len', 'absolute', 1, 1],
+                       ['losthdr_b', None, 'incremental', 1, 1],
+                       ['esi_errors_b', None, 'incremental', 1, 1],
+                       ['esi_warnings_b', None, 'incremental', 1, 1],
+                       ['sess_fail_b', None, 'incremental', 1, 1],
+                       ['sess_pipe_overflow_b', None, 'incremental', 1, 1]],
+              'options': [None, 'Misbehavior', 'problems', 'Problems summary', 'varnish.bad', 'line']},
+          'expunge':
+             {'lines': [['n_expired', 'expired', 'incremental', 1, 1],
+                       ['n_lru_nuked', 'lru_nuked', 'incremental', 1, 1]],
+              'options': [None, 'Object expunging', 'objects', 'Cache performance', 'varnish.expunge', 'line']},
+          'hit_rate': 
+             {'lines': [['cache_hit_perc', 'hit', 'absolute', 1, 100],
+                       ['cache_miss_perc', 'miss', 'absolute', 1, 100],
+                       ['cache_hitpass_perc', 'hitpass', 'absolute', 1, 100]],
+              'options': [None, 'All history hit rate ratio','percent', 'Cache performance', 'varnish.hit_rate', 'stacked']},
+          'chit_rate': 
+             {'lines': [['cache_hit_cperc', 'hit', 'absolute', 1, 100],
+                       ['cache_miss_cperc', 'miss', 'absolute', 1, 100],
+                       ['cache_hitpass_cperc', 'hitpass', 'absolute', 1, 100]],
+              'options': [None, 'Current poll hit rate ratio','percent', 'Cache performance', 'varnish.chit_rate', 'stacked']},
+          'memory_usage': 
+             {'lines': [['s0.g_space', 'available', 'absolute', 1, 1048576],
+                       ['s0.g_bytes', 'allocated', 'absolute', -1, 1048576]],
+              'options': [None, 'Memory usage', 'megabytes', 'Memory usage', 'varnish.memory_usage', 'stacked']},
+          'session': 
+             {'lines': [['sess_conn', 'conn', 'incremental', 1, 1],
+                       ['client_req', 'requests', 'incremental', 1, 1],
+                       ['sess_dropped', 'dropped', 'incremental', 1, 1]],
+              'options': [None, 'Sessions', 'units', 'Client metrics', 'varnish.session', 'line']},
+          'threads': 
+             {'lines': [['threads', None, 'absolute', 1, 1],
+                       ['threads_created', 'created', 'incremental', 1, 1],
+                       ['threads_failed', 'failed', 'incremental', 1, 1],
+                       ['threads_limited', 'limited', 'incremental', 1, 1],
+                       ['thread_queue_len', 'queue_len', 'incremental', 1, 1],
+                       ['sess_queued', 'sess_queued', 'incremental', 1, 1]],
+              'options': [None, 'Thread status', 'threads', 'Thread-related metrics', 'varnish.threads', 'line']},
+          'uptime': 
+             {'lines': [['uptime', None, 'absolute', 1, 1]],
+              'options': [None, 'Varnish uptime', 'seconds', 'Uptime', 'varnish.uptime', 'line']}
+}
+
+DIRECTORIES = ['/bin/', '/usr/bin/', '/sbin/', '/usr/sbin/']
+
+
+class Service(SimpleService):
+    def __init__(self, configuration=None, name=None):
+        SimpleService.__init__(self, configuration=configuration, name=name)
+        try:
+            self.varnish = [''.join([directory, 'varnishstat']) for directory in DIRECTORIES
+                         if is_executable(''.join([directory, 'varnishstat']), X_OK)][0]
+        except IndexError:
+            self.varnish = False
+        self.rgx_all = compile(r'([A-Z]+\.)([\d\w_.]+)\s+(\d+)')
+        # Could be
+        # VBE.boot.super_backend.pipe_hdrbyte (new)
+        # or
+        # VBE.default2(127.0.0.2,,81).bereq_bodybytes (old)
+        # Regex result: [('super_backend', 'beresp_hdrbytes', '0'), ('super_backend', 'beresp_bodybytes', '0')]
+        self.rgx_bck = (compile(r'VBE.([\d\w_.]+)\(.*?\).(beresp[\w_]+)\s+(\d+)'),
+                        compile(r'VBE\.[\d\w-]+\.([\w\d_]+).(beresp[\w_]+)\s+(\d+)'))
+        self.cache_prev = list()
+
+    def check(self):
+        # Cant start without 'varnishstat' command
+        if not self.varnish:
+            self.error('\'varnishstat\' command was not found in %s or not executable by netdata' % DIRECTORIES)
+            return False
+
+        # If command is present and we can execute it we need to make sure..
+        # 1. STDOUT is not empty
+        reply = self._get_raw_data()
+        if not reply:
+            self.error('No output from \'varnishstat\' (not enough privileges?)')
+            return False
+
+        # 2. Output is parsable (list is not empty after regex findall)
+        is_parsable = self.rgx_all.findall(reply)
+        if not is_parsable:
+            self.error('Cant parse output (only varnish version 4+ supported)')
+            return False
+
+        # We need to find the right regex for backend parse
+        self.backend_list = self.rgx_bck[0].findall(reply)[::2]
+        if self.backend_list:
+            self.rgx_bck = self.rgx_bck[0]
+        else:
+            self.backend_list = self.rgx_bck[1].findall(reply)[::2]
+            self.rgx_bck = self.rgx_back[1]
+
+        # We are about to start!
+        self.create_charts()
+
+        self.info('Plugin was started successfully')
+        return True
+     
+    def _get_raw_data(self):
+        try:
+            reply = Popen([self.varnish, '-1'], stdout=PIPE, stderr=PIPE, shell=False)
+        except OSError:
+            return None
+
+        raw_data = reply.communicate()[0]
+
+        if not raw_data:
+            return None
+
+        return raw_data
+
+    def _get_data(self):
+        """
+        Format data received from shell command
+        :return: dict
+        """
+        raw_data = self._get_raw_data()
+        data_all = self.rgx_all.findall(raw_data)
+        data_backend = self.rgx_bck.findall(raw_data)
+
+        if not data_all:
+            return None
+
+        # 1. ALL data from 'varnishstat -1'. t - type(MAIN, MEMPOOL etc)
+        to_netdata = {k: int(v) for t, k, v in data_all}
+        
+        # 2. ADD backend statistics
+        to_netdata.update({'_'.join([n, k]): int(v) for n, k, v in data_backend})
+
+        # 3. ADD additional keys to dict
+        # 3.1 Cache hit/miss/hitpass OVERALL in percent
+        cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
+                             to_netdata.get('cache_hitpass', 0)])
+        to_netdata['cache_hit_perc'] = find_percent(to_netdata.get('cache_hit', 0), cache_summary, 10000)
+        to_netdata['cache_miss_perc'] = find_percent(to_netdata.get('cache_miss', 0), cache_summary, 10000)
+        to_netdata['cache_hitpass_perc'] = find_percent(to_netdata.get('cache_hitpass', 0), cache_summary, 10000)
+
+        # 3.2 Cache hit/miss/hitpass CURRENT in percent
+        if self.cache_prev:
+            cache_summary = sum([to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0),
+                                 to_netdata.get('cache_hitpass', 0)]) - sum(self.cache_prev)
+            to_netdata['cache_hit_cperc'] = find_percent(to_netdata.get('cache_hit', 0) - self.cache_prev[0], cache_summary, 10000)
+            to_netdata['cache_miss_cperc'] = find_percent(to_netdata.get('cache_miss', 0) - self.cache_prev[1], cache_summary, 10000)
+            to_netdata['cache_hitpass_cperc'] = find_percent(to_netdata.get('cache_hitpass', 0) - self.cache_prev[2], cache_summary, 10000)
+        else:
+            to_netdata['cache_hit_cperc'] = 0
+            to_netdata['cache_miss_cperc'] = 0
+            to_netdata['cache_hitpass_cperc'] = 0
+
+        self.cache_prev = [to_netdata.get('cache_hit', 0), to_netdata.get('cache_miss', 0), to_netdata.get('cache_hitpass', 0)]
+
+        # 3.3 Problems summary chart
+        for elem in ['backend_busy', 'backend_unhealthy', 'esi_errors', 'esi_warnings', 'losthdr', 'sess_drop',
+                     'sess_fail', 'sess_pipe_overflow', 'threads_destroyed', 'threads_failed', 'threads_limited', 'thread_queue_len']:
+            to_netdata[''.join([elem, '_b'])] = to_netdata.get(elem, 0)
+
+        # Ready steady go!
+        return to_netdata
+
+    def create_charts(self):
+        # If 'all_charts' is true...ALL charts are displayed. If no only default + 'extra_charts'
+        #if self.configuration.get('all_charts'):
+        #    self.order = EXTRA_ORDER
+        #else:
+        #    try:
+        #        extra_charts = list(filter(lambda chart: chart in EXTRA_ORDER, self.extra_charts.split()))
+        #    except (AttributeError, NameError, ValueError):
+        #        self.error('Extra charts disabled.')
+        #        extra_charts = []
+    
+        self.order = ORDER[:]
+        #self.order.extend(extra_charts)
+
+        # Create static charts
+        self.definitions = {chart: values for chart, values in CHARTS.items() if chart in self.order}
+        # Create dynamic backend charts
+        if self.backend_list:
+            for backend in self.backend_list:
+                self.order.insert(0, ''.join([backend[0], '_resp_stats']))
+                self.definitions.update({''.join([backend[0], '_resp_stats']): {
+                    'options': [None,
+                                '%s response statistics' % backend[0].capitalize(),
+                                "kilobit/s",
+                                'Backend response',
+                                'varnish.backend',
+                                'area'],
+                    'lines': [[''.join([backend[0], '_beresp_hdrbytes']),
+                               'header', 'incremental', 8, 1000],
+                              [''.join([backend[0], '_beresp_bodybytes']),
+                               'body', 'incremental', -8, 1000]]}})
+
+
+def find_percent(value1, value2, multiply):
+    # If value2 is 0 return 0
+    if not value2:
+        return 0
+    else:
+        return round(float(value1) / float(value2) * multiply)