Viewing file: go_expvar.chart.py (8.54 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- coding: utf-8 -*- # Description: go_expvar netdata python.d module # Author: Jan Kral (kralewitz) # SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import division
import json from collections import namedtuple
from bases.FrameworkServices.UrlService import UrlService
MEMSTATS_ORDER = [ 'memstats_heap', 'memstats_stack', 'memstats_mspan', 'memstats_mcache', 'memstats_sys', 'memstats_live_objects', 'memstats_gc_pauses', ]
MEMSTATS_CHARTS = { 'memstats_heap': { 'options': ['heap', 'memory: size of heap memory structures', 'KiB', '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', 'KiB', 'memstats', 'expvar.memstats.stack', 'line'], 'lines': [ ['memstats_stack_inuse', 'inuse', 'absolute', 1, 1024] ] }, 'memstats_mspan': { 'options': ['mspan', 'memory: size of mspan memory structures', 'KiB', 'memstats', 'expvar.memstats.mspan', 'line'], 'lines': [ ['memstats_mspan_inuse', 'inuse', 'absolute', 1, 1024] ] }, 'memstats_mcache': { 'options': ['mcache', 'memory: size of mcache memory structures', 'KiB', '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', 'KiB', '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'] ] } }
EXPVAR = namedtuple( "EXPVAR", [ "key", "type", "id", ] )
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 = dict(MEMSTATS_CHARTS) self.order = list(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 = list()
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
# self.expvars[ev_key] = (ev_type, line_id) self.expvars.append(EXPVAR(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 ev in self.expvars: v = flattened.get(ev.key)
if v is None: continue
try: if ev.type == 'int': expvars[ev.id] = int(v) elif ev.type == 'float': expvars[ev.id] = float(v) * 100 except ValueError: self.info('Failed to parse value for key {0} as {1}, ignoring key.'.format(ev.key, ev.type)) return None
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, }
|