Browse Source

Add go_expvar module for the python.d.plugin

kralewitz 7 years ago
parent
commit
06b713400b

+ 1 - 0
conf.d/Makefile.am

@@ -35,6 +35,7 @@ dist_pythonconfig_DATA = \
     python.d/exim.conf \
     python.d/fail2ban.conf \
     python.d/freeradius.conf \
+    python.d/go_expvar.conf \
     python.d/haproxy.conf \
     python.d/hddtemp.conf \
     python.d/ipfs.conf \

+ 1 - 1
conf.d/python.d.conf

@@ -45,7 +45,7 @@ example: no
 
 # gunicorn_log has been replaced by web_log
 gunicorn_log: no
-
+go_expvar: no
 # haproxy: yes
 # hddtemp: yes
 # ipfs: yes

+ 106 - 0
conf.d/python.d/go_expvar.conf

@@ -0,0 +1,106 @@
+# netdata python.d.plugin configuration for go_expvar
+#
+# 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)
+#
+# 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: my_name    # the JOB's name as it will appear at the
+#                      # dashboard. If name: is not supplied the
+#                      # job_name: will be used (use _ for spaces)
+#                      # 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
+#
+# Additionally to the above, this plugin also supports the following:
+#
+#     url: 'http://127.0.0.1/debug/vars'       # the URL of the expvar endpoint
+#     ss_cert:                                 # ignore HTTPS self-signed certificate
+#     proxy:                                   # use HTTP proxy
+#
+# As the plugin cannot possibly know the port your application listens on, there is no default value. Please include
+# the whole path of the endpoint, as the expvar handler can be installed in a non-standard location.
+#
+# if the URL is password protected, the following are supported:
+#
+#     user: 'username'
+#     pass: 'password'
+#
+#     collect_memstats: true        # enables charts for Go runtime's memory statistics
+#     extra_charts: {}              # defines extra data/charts to monitor, please see the example below
+#
+# If collect_memstats is disabled and no extra charts are defined, this module will disable itself, as it has no data to
+# collect.
+#
+# Please visit the module wiki page for more information on how to use the extra_charts variable:
+#
+# https://github.com/firehol/netdata/wiki/Monitoring-Go-Applications#monitoring-custom-vars-with-go_expvar
+#
+# Configuration example
+# ---------------------
+
+#app1:
+#  name : 'app1'
+#  url  : 'http://127.0.0.1:8080/debug/vars'
+#  collect_memstats: true
+#  extra_charts:
+#    - id: "runtime_goroutines"
+#      options:
+#        name: num_goroutines
+#        title: "runtime: number of goroutines"
+#        units: goroutines
+#        family: runtime
+#        context: expvar.runtime.goroutines
+#        chart_type: line
+#      lines:
+#        - {expvar_key: 'runtime.goroutines', expvar_type: int, id: runtime_goroutines}
+#    - id: "foo_counters"
+#      options:
+#        name: counters
+#        title: "some random counters"
+#        units: awesomeness
+#        family: counters
+#        context: expvar.foo.counters
+#        chart_type: line
+#      lines:
+#        - {expvar_key: 'counters.cnt1', expvar_type: int, id: counters_cnt1}
+#        - {expvar_key: 'counters.cnt2', expvar_type: float, id: counters_cnt2}
+

File diff suppressed because it is too large
+ 0 - 0
diagrams/netdata-overview.xml


+ 1 - 0
python.d/Makefile.am

@@ -25,6 +25,7 @@ dist_python_DATA = \
     exim.chart.py \
     fail2ban.chart.py \
     freeradius.chart.py \
+    go_expvar.chart.py \
     haproxy.chart.py \
     hddtemp.chart.py \
     ipfs.chart.py \

+ 38 - 0
python.d/README.md

@@ -483,6 +483,44 @@ and restart/reload your FREERADIUS server.
 
 ---
 
+# go_expvar
+
+---
+
+The `go_expvar` module can monitor any Go application that exposes its metrics with the use of `expvar` package from the Go standard library.
+
+`go_expvar` produces charts for Go runtime memory statistics and optionally any number of custom charts. Please see the [wiki page](https://github.com/firehol/netdata/wiki/Monitoring-Go-Applications) for more info.
+
+For the memory statistics, it produces the following charts:
+
+1. **Heap allocations** in kB
+ * alloc: size of objects allocated on the heap
+ * inuse: size of allocated heap spans 
+ 
+2. **Stack allocations** in kB
+ * inuse: size of allocated stack spans
+ 
+3. **MSpan allocations** in kB
+ * inuse: size of allocated mspan structures
+ 
+4. **MCache allocations** in kB
+ * inuse: size of allocated mcache structures
+ 
+5. **Virtual memory** in kB
+ * sys: size of reserved virtual address space
+ 
+6. **Live objects**
+ * live: number of live objects in memory
+ 
+7. **GC pauses average** in ns
+ * avg: average duration of all GC stop-the-world pauses
+ 
+### configuration
+ 
+Please see the [wiki page](https://github.com/firehol/netdata/wiki/Monitoring-Go-Applications#using-netdata-go_expvar-module) for detailed info about module configuration.
+ 
+---
+
 # haproxy
 
 Module monitors frontend and backend metrics such as bytes in, bytes out, sessions current, sessions in queue current.

+ 228 - 0
python.d/go_expvar.chart.py

@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Description: go_expvar netdata python.d module
+# Author: Jan Kral (kralewitz)
+
+from __future__ import division
+from base import UrlService
+import json
+
+# default module values (can be overridden per job in `config`)
+# update_every = 2
+priority = 60000
+retries = 60
+
+
+MEMSTATS_CHARTS = {
+    'memstats_heap': {
+        'options': ['heap', 'memory: size of heap memory structures', 'kB', 'memstats', 'expvar.memstats.heap', 'line'],
+        'lines': [
+            ['memstats_heap_alloc', 'alloc', 'absolute', 1, 1024],
+            ['memstats_heap_inuse', 'inuse', 'absolute', 1, 1024]
+        ]},
+    'memstats_stack': {
+        'options': ['stack', 'memory: size of stack memory structures', 'kB', 'memstats', 'expvar.memstats.stack', 'line'],
+        'lines': [
+            ['memstats_stack_inuse', 'inuse', 'absolute', 1, 1024]
+        ]},
+    'memstats_mspan': {
+        'options': ['mspan', 'memory: size of mspan memory structures', 'kB', 'memstats', 'expvar.memstats.mspan', 'line'],
+        'lines': [
+            ['memstats_mspan_inuse', 'inuse', 'absolute', 1, 1024]
+        ]},
+    'memstats_mcache': {
+        'options': ['mcache', 'memory: size of mcache memory structures', 'kB', 'memstats', 'expvar.memstats.mcache', 'line'],
+        'lines': [
+            ['memstats_mcache_inuse', 'inuse', 'absolute', 1, 1024]
+        ]},
+    'memstats_live_objects': {
+        'options': ['live_objects', 'memory: number of live objects', 'objects', 'memstats', 'expvar.memstats.live_objects', 'line'],
+        'lines': [
+            ['memstats_live_objects', 'live']
+        ]},
+    'memstats_sys': {
+        'options': ['sys', 'memory: size of reserved virtual address space', 'kB', 'memstats', 'expvar.memstats.sys', 'line'],
+        'lines': [
+            ['memstats_sys', 'sys', 'absolute', 1, 1024]
+        ]},
+    'memstats_gc_pauses': {
+        'options': ['gc_pauses', 'memory: average duration of GC pauses', 'ns', 'memstats', 'expvar.memstats.gc_pauses', 'line'],
+        'lines': [
+            ['memstats_gc_pauses', 'avg']
+        ]},
+}
+
+MEMSTATS_ORDER = ['memstats_heap', 'memstats_stack', 'memstats_mspan', 'memstats_mcache', 'memstats_sys', 'memstats_live_objects', 'memstats_gc_pauses']
+
+
+def flatten(d, top='', sep='.'):
+    items = []
+    for key, val in d.items():
+        nkey = top + sep + key if top else key
+        if isinstance(val, dict):
+            items.extend(flatten(val, nkey, sep=sep).items())
+        else:
+            items.append((nkey, val))
+    return dict(items)
+
+
+class Service(UrlService):
+    def __init__(self, configuration=None, name=None):
+        UrlService.__init__(self, configuration=configuration, name=name)
+
+        # if memstats collection is enabled, add the charts and their order
+        if self.configuration.get('collect_memstats'):
+            self.definitions = MEMSTATS_CHARTS
+            self.order = MEMSTATS_ORDER
+        else:
+            self.definitions = dict()
+            self.order = list()
+
+        # if extra charts are defined, parse their config
+        extra_charts = self.configuration.get('extra_charts')
+        if extra_charts:
+            self._parse_extra_charts_config(extra_charts)
+
+    def check(self):
+        """
+        Check if the module can collect data:
+        1) At least one JOB configuration has to be specified
+        2) The JOB configuration needs to define the URL and either collect_memstats must be enabled or at least one
+           extra_chart must be defined.
+
+        The configuration and URL check is provided by the UrlService class.
+        """
+
+        if not (self.configuration.get('extra_charts') or self.configuration.get('collect_memstats')):
+            self.error('Memstats collection is disabled and no extra_charts are defined, disabling module.')
+            return False
+
+        return UrlService.check(self)
+
+    def _parse_extra_charts_config(self, extra_charts_config):
+
+        # a place to store the expvar keys and their types
+        self.expvars = dict()
+
+        for chart in extra_charts_config:
+
+            chart_dict = dict()
+            chart_id = chart.get('id')
+            chart_lines = chart.get('lines')
+            chart_opts = chart.get('options', dict())
+
+            if not all([chart_id, chart_lines]):
+                self.info('Chart {0} has no ID or no lines defined, skipping'.format(chart))
+                continue
+
+            chart_dict['options'] = [
+                chart_opts.get('name', ''),
+                chart_opts.get('title', ''),
+                chart_opts.get('units', ''),
+                chart_opts.get('family', ''),
+                chart_opts.get('context', ''),
+                chart_opts.get('chart_type', 'line')
+            ]
+            chart_dict['lines'] = list()
+
+            # add the lines to the chart
+            for line in chart_lines:
+
+                ev_key = line.get('expvar_key')
+                ev_type = line.get('expvar_type')
+                line_id = line.get('id')
+
+                if not all([ev_key, ev_type, line_id]):
+                    self.info('Line missing expvar_key, expvar_type, or line_id, skipping: {0}'.format(line))
+                    continue
+
+                if ev_type not in ['int', 'float']:
+                    self.info('Unsupported expvar_type "{0}". Must be "int" or "float"'.format(ev_type))
+                    continue
+
+                if ev_key in self.expvars:
+                    self.info('Duplicate expvar key {0}: skipping line.'.format(ev_key))
+                    continue
+
+                self.expvars[ev_key] = (ev_type, line_id)
+
+                chart_dict['lines'].append(
+                    [
+                        line.get('id', ''),
+                        line.get('name', ''),
+                        line.get('algorithm', ''),
+                        line.get('multiplier', 1),
+                        line.get('divisor', 100 if ev_type == 'float' else 1),
+                        line.get('hidden', False)
+                    ]
+                )
+
+            self.order.append(chart_id)
+            self.definitions[chart_id] = chart_dict
+
+    def _get_data(self):
+        """
+        Format data received from http request
+        :return: dict
+        """
+
+        raw_data = self._get_raw_data()
+        if not raw_data:
+            return None
+
+        data = json.loads(raw_data)
+
+        expvars = dict()
+        if self.configuration.get('collect_memstats'):
+            expvars.update(self._parse_memstats(data))
+
+        if self.configuration.get('extra_charts'):
+            # the memstats part of the data has been already parsed, so we remove it before flattening and checking
+            #   the rest of the data, thus avoiding needless iterating over the multiply nested memstats dict.
+            del (data['memstats'])
+            flattened = flatten(data)
+            for k, v in flattened.items():
+                ev = self.expvars.get(k)
+                if not ev:
+                    # expvar is not defined in config, skip it
+                    continue
+                try:
+                    key_type, line_id = ev
+                    if key_type == 'int':
+                        expvars[line_id] = int(v)
+                    elif key_type == 'float':
+                        # if the value type is float, multiply it by 1000 and set line divisor to 1000
+                        expvars[line_id] = float(v) * 100
+                except ValueError:
+                    self.info('Failed to parse value for key {0} as {1}, ignoring key.'.format(k, key_type))
+                    del self.expvars[k]
+
+        return expvars
+
+    @staticmethod
+    def _parse_memstats(data):
+
+        memstats = data['memstats']
+
+        # calculate the number of live objects in memory
+        live_objs = int(memstats['Mallocs']) - int(memstats['Frees'])
+
+        # calculate GC pause times average
+        # the Go runtime keeps the last 256 GC pause durations in a circular buffer,
+        #  so we need to filter out the 0 values before the buffer is filled
+        gc_pauses = memstats['PauseNs']
+        try:
+            gc_pause_avg = sum(gc_pauses) / len([x for x in gc_pauses if x > 0])
+        # no GC cycles have occured yet
+        except ZeroDivisionError:
+            gc_pause_avg = 0
+
+        return {
+            'memstats_heap_alloc': memstats['HeapAlloc'],
+            'memstats_heap_inuse': memstats['HeapInuse'],
+            'memstats_stack_inuse': memstats['StackInuse'],
+            'memstats_mspan_inuse': memstats['MSpanInuse'],
+            'memstats_mcache_inuse': memstats['MCacheInuse'],
+            'memstats_sys': memstats['Sys'],
+            'memstats_live_objects': live_objs,
+            'memstats_gc_pauses': gc_pause_avg,
+        }

+ 11 - 0
web/dashboard_info.js

@@ -277,6 +277,12 @@ netdataDashboard.menu = {
         title: 'SNMP',
         icon: '<i class="fa fa-random" aria-hidden="true"></i>',
         info: undefined
+    },
+
+    'go_expvar': {
+        title: 'Go - expvars',
+        icon: '<i class="fa fa-eye" aria-hidden="true"></i>',
+        info: 'Statistics about running Go applications exposed by the <a href="https://golang.org/pkg/expvar/" target="_blank">expvar package</a>.'
     }
 };
 
@@ -389,6 +395,11 @@ netdataDashboard.submenu = {
             else
                 return 'Statistics for per CPUs core SoftIRQs related to network receive work. Total for all CPU cores can be found at <a href="#menu_system_submenu_softnet_stat">System / softnet statistics</a>.';
         }
+    },
+
+    'go_expvar.memstats': {
+        title: 'Memory statistics',
+        info: 'Go runtime memory statistics. See <a href="https://golang.org/pkg/runtime/#MemStats" target="_blank">runtime.MemStats</a> documentation for more info about each chart and the values.'
     }
 };
 

+ 8 - 0
web/index.html

@@ -1226,6 +1226,14 @@
                         chart.menu_pattern = 'cgroup';
                     break;
 
+                case 'go':
+                    chart.menu = chart.type;
+                    if(parts.length > 2 && parts[1] === 'expvar')
+                        chart.menu_pattern = tmp + '_' + parts[1];
+                    else if(parts.length > 1)
+                        chart.menu_pattern = tmp;
+                    break;
+
                 case 'isc':
                     chart.menu = chart.type;
                     if(parts.length > 2 && parts[1] === 'dhcpd')

Some files were not shown because too many files changed in this diff