go_expvar.chart.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # -*- coding: utf-8 -*-
  2. # Description: go_expvar netdata python.d module
  3. # Author: Jan Kral (kralewitz)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. from __future__ import division
  6. import json
  7. from collections import namedtuple
  8. from bases.FrameworkServices.UrlService import UrlService
  9. MEMSTATS_ORDER = [
  10. 'memstats_heap',
  11. 'memstats_stack',
  12. 'memstats_mspan',
  13. 'memstats_mcache',
  14. 'memstats_sys',
  15. 'memstats_live_objects',
  16. 'memstats_gc_pauses',
  17. ]
  18. MEMSTATS_CHARTS = {
  19. 'memstats_heap': {
  20. 'options': ['heap', 'memory: size of heap memory structures', 'KiB', 'memstats',
  21. 'expvar.memstats.heap', 'line'],
  22. 'lines': [
  23. ['memstats_heap_alloc', 'alloc', 'absolute', 1, 1024],
  24. ['memstats_heap_inuse', 'inuse', 'absolute', 1, 1024]
  25. ]
  26. },
  27. 'memstats_stack': {
  28. 'options': ['stack', 'memory: size of stack memory structures', 'KiB', 'memstats',
  29. 'expvar.memstats.stack', 'line'],
  30. 'lines': [
  31. ['memstats_stack_inuse', 'inuse', 'absolute', 1, 1024]
  32. ]
  33. },
  34. 'memstats_mspan': {
  35. 'options': ['mspan', 'memory: size of mspan memory structures', 'KiB', 'memstats',
  36. 'expvar.memstats.mspan', 'line'],
  37. 'lines': [
  38. ['memstats_mspan_inuse', 'inuse', 'absolute', 1, 1024]
  39. ]
  40. },
  41. 'memstats_mcache': {
  42. 'options': ['mcache', 'memory: size of mcache memory structures', 'KiB', 'memstats',
  43. 'expvar.memstats.mcache', 'line'],
  44. 'lines': [
  45. ['memstats_mcache_inuse', 'inuse', 'absolute', 1, 1024]
  46. ]
  47. },
  48. 'memstats_live_objects': {
  49. 'options': ['live_objects', 'memory: number of live objects', 'objects', 'memstats',
  50. 'expvar.memstats.live_objects', 'line'],
  51. 'lines': [
  52. ['memstats_live_objects', 'live']
  53. ]
  54. },
  55. 'memstats_sys': {
  56. 'options': ['sys', 'memory: size of reserved virtual address space', 'KiB', 'memstats',
  57. 'expvar.memstats.sys', 'line'],
  58. 'lines': [
  59. ['memstats_sys', 'sys', 'absolute', 1, 1024]
  60. ]
  61. },
  62. 'memstats_gc_pauses': {
  63. 'options': ['gc_pauses', 'memory: average duration of GC pauses', 'ns', 'memstats',
  64. 'expvar.memstats.gc_pauses', 'line'],
  65. 'lines': [
  66. ['memstats_gc_pauses', 'avg']
  67. ]
  68. }
  69. }
  70. EXPVAR = namedtuple(
  71. "EXPVAR",
  72. [
  73. "key",
  74. "type",
  75. "id",
  76. ]
  77. )
  78. def flatten(d, top='', sep='.'):
  79. items = []
  80. for key, val in d.items():
  81. nkey = top + sep + key if top else key
  82. if isinstance(val, dict):
  83. items.extend(flatten(val, nkey, sep=sep).items())
  84. else:
  85. items.append((nkey, val))
  86. return dict(items)
  87. class Service(UrlService):
  88. def __init__(self, configuration=None, name=None):
  89. UrlService.__init__(self, configuration=configuration, name=name)
  90. # if memstats collection is enabled, add the charts and their order
  91. if self.configuration.get('collect_memstats'):
  92. self.definitions = dict(MEMSTATS_CHARTS)
  93. self.order = list(MEMSTATS_ORDER)
  94. else:
  95. self.definitions = dict()
  96. self.order = list()
  97. # if extra charts are defined, parse their config
  98. extra_charts = self.configuration.get('extra_charts')
  99. if extra_charts:
  100. self._parse_extra_charts_config(extra_charts)
  101. def check(self):
  102. """
  103. Check if the module can collect data:
  104. 1) At least one JOB configuration has to be specified
  105. 2) The JOB configuration needs to define the URL and either collect_memstats must be enabled or at least one
  106. extra_chart must be defined.
  107. The configuration and URL check is provided by the UrlService class.
  108. """
  109. if not (self.configuration.get('extra_charts') or self.configuration.get('collect_memstats')):
  110. self.error('Memstats collection is disabled and no extra_charts are defined, disabling module.')
  111. return False
  112. return UrlService.check(self)
  113. def _parse_extra_charts_config(self, extra_charts_config):
  114. # a place to store the expvar keys and their types
  115. self.expvars = list()
  116. for chart in extra_charts_config:
  117. chart_dict = dict()
  118. chart_id = chart.get('id')
  119. chart_lines = chart.get('lines')
  120. chart_opts = chart.get('options', dict())
  121. if not all([chart_id, chart_lines]):
  122. self.info('Chart {0} has no ID or no lines defined, skipping'.format(chart))
  123. continue
  124. chart_dict['options'] = [
  125. chart_opts.get('name', ''),
  126. chart_opts.get('title', ''),
  127. chart_opts.get('units', ''),
  128. chart_opts.get('family', ''),
  129. chart_opts.get('context', ''),
  130. chart_opts.get('chart_type', 'line')
  131. ]
  132. chart_dict['lines'] = list()
  133. # add the lines to the chart
  134. for line in chart_lines:
  135. ev_key = line.get('expvar_key')
  136. ev_type = line.get('expvar_type')
  137. line_id = line.get('id')
  138. if not all([ev_key, ev_type, line_id]):
  139. self.info('Line missing expvar_key, expvar_type, or line_id, skipping: {0}'.format(line))
  140. continue
  141. if ev_type not in ['int', 'float']:
  142. self.info('Unsupported expvar_type "{0}". Must be "int" or "float"'.format(ev_type))
  143. continue
  144. # self.expvars[ev_key] = (ev_type, line_id)
  145. self.expvars.append(EXPVAR(ev_key, ev_type, line_id))
  146. chart_dict['lines'].append(
  147. [
  148. line.get('id', ''),
  149. line.get('name', ''),
  150. line.get('algorithm', ''),
  151. line.get('multiplier', 1),
  152. line.get('divisor', 100 if ev_type == 'float' else 1),
  153. line.get('hidden', False)
  154. ]
  155. )
  156. self.order.append(chart_id)
  157. self.definitions[chart_id] = chart_dict
  158. def _get_data(self):
  159. """
  160. Format data received from http request
  161. :return: dict
  162. """
  163. raw_data = self._get_raw_data()
  164. if not raw_data:
  165. return None
  166. data = json.loads(raw_data)
  167. expvars = dict()
  168. if self.configuration.get('collect_memstats'):
  169. expvars.update(self._parse_memstats(data))
  170. if self.configuration.get('extra_charts'):
  171. # the memstats part of the data has been already parsed, so we remove it before flattening and checking
  172. # the rest of the data, thus avoiding needless iterating over the multiply nested memstats dict.
  173. del (data['memstats'])
  174. flattened = flatten(data)
  175. for ev in self.expvars:
  176. v = flattened.get(ev.key)
  177. if v is None:
  178. continue
  179. try:
  180. if ev.type == 'int':
  181. expvars[ev.id] = int(v)
  182. elif ev.type == 'float':
  183. expvars[ev.id] = float(v) * 100
  184. except ValueError:
  185. self.info('Failed to parse value for key {0} as {1}, ignoring key.'.format(ev.key, ev.type))
  186. return None
  187. return expvars
  188. @staticmethod
  189. def _parse_memstats(data):
  190. memstats = data['memstats']
  191. # calculate the number of live objects in memory
  192. live_objs = int(memstats['Mallocs']) - int(memstats['Frees'])
  193. # calculate GC pause times average
  194. # the Go runtime keeps the last 256 GC pause durations in a circular buffer,
  195. # so we need to filter out the 0 values before the buffer is filled
  196. gc_pauses = memstats['PauseNs']
  197. try:
  198. gc_pause_avg = sum(gc_pauses) / len([x for x in gc_pauses if x > 0])
  199. # no GC cycles have occurred yet
  200. except ZeroDivisionError:
  201. gc_pause_avg = 0
  202. return {
  203. 'memstats_heap_alloc': memstats['HeapAlloc'],
  204. 'memstats_heap_inuse': memstats['HeapInuse'],
  205. 'memstats_stack_inuse': memstats['StackInuse'],
  206. 'memstats_mspan_inuse': memstats['MSpanInuse'],
  207. 'memstats_mcache_inuse': memstats['MCacheInuse'],
  208. 'memstats_sys': memstats['Sys'],
  209. 'memstats_live_objects': live_objs,
  210. 'memstats_gc_pauses': gc_pause_avg,
  211. }