varnish.chart.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. # -*- coding: utf-8 -*-
  2. # Description: varnish netdata python.d module
  3. # Author: ilyam8
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. import re
  6. from bases.FrameworkServices.ExecutableService import ExecutableService
  7. from bases.collection import find_binary
  8. ORDER = [
  9. 'session_connections',
  10. 'client_requests',
  11. 'all_time_hit_rate',
  12. 'current_poll_hit_rate',
  13. 'cached_objects_expired',
  14. 'cached_objects_nuked',
  15. 'threads_total',
  16. 'threads_statistics',
  17. 'threads_queue_len',
  18. 'backend_connections',
  19. 'backend_requests',
  20. 'esi_statistics',
  21. 'memory_usage',
  22. 'uptime'
  23. ]
  24. CHARTS = {
  25. 'session_connections': {
  26. 'options': [None, 'Connections Statistics', 'connections/s',
  27. 'client metrics', 'varnish.session_connection', 'line'],
  28. 'lines': [
  29. ['sess_conn', 'accepted', 'incremental'],
  30. ['sess_dropped', 'dropped', 'incremental']
  31. ]
  32. },
  33. 'client_requests': {
  34. 'options': [None, 'Client Requests', 'requests/s',
  35. 'client metrics', 'varnish.client_requests', 'line'],
  36. 'lines': [
  37. ['client_req', 'received', 'incremental']
  38. ]
  39. },
  40. 'all_time_hit_rate': {
  41. 'options': [None, 'All History Hit Rate Ratio', 'percentage', 'cache performance',
  42. 'varnish.all_time_hit_rate', 'stacked'],
  43. 'lines': [
  44. ['cache_hit', 'hit', 'percentage-of-absolute-row'],
  45. ['cache_miss', 'miss', 'percentage-of-absolute-row'],
  46. ['cache_hitpass', 'hitpass', 'percentage-of-absolute-row']]
  47. },
  48. 'current_poll_hit_rate': {
  49. 'options': [None, 'Current Poll Hit Rate Ratio', 'percentage', 'cache performance',
  50. 'varnish.current_poll_hit_rate', 'stacked'],
  51. 'lines': [
  52. ['cache_hit', 'hit', 'percentage-of-incremental-row'],
  53. ['cache_miss', 'miss', 'percentage-of-incremental-row'],
  54. ['cache_hitpass', 'hitpass', 'percentage-of-incremental-row']
  55. ]
  56. },
  57. 'cached_objects_expired': {
  58. 'options': [None, 'Expired Objects', 'expired/s', 'cache performance',
  59. 'varnish.cached_objects_expired', 'line'],
  60. 'lines': [
  61. ['n_expired', 'objects', 'incremental']
  62. ]
  63. },
  64. 'cached_objects_nuked': {
  65. 'options': [None, 'Least Recently Used Nuked Objects', 'nuked/s', 'cache performance',
  66. 'varnish.cached_objects_nuked', 'line'],
  67. 'lines': [
  68. ['n_lru_nuked', 'objects', 'incremental']
  69. ]
  70. },
  71. 'threads_total': {
  72. 'options': [None, 'Number Of Threads In All Pools', 'number', 'thread related metrics',
  73. 'varnish.threads_total', 'line'],
  74. 'lines': [
  75. ['threads', None, 'absolute']
  76. ]
  77. },
  78. 'threads_statistics': {
  79. 'options': [None, 'Threads Statistics', 'threads/s', 'thread related metrics',
  80. 'varnish.threads_statistics', 'line'],
  81. 'lines': [
  82. ['threads_created', 'created', 'incremental'],
  83. ['threads_failed', 'failed', 'incremental'],
  84. ['threads_limited', 'limited', 'incremental']
  85. ]
  86. },
  87. 'threads_queue_len': {
  88. 'options': [None, 'Current Queue Length', 'requests', 'thread related metrics',
  89. 'varnish.threads_queue_len', 'line'],
  90. 'lines': [
  91. ['thread_queue_len', 'in queue']
  92. ]
  93. },
  94. 'backend_connections': {
  95. 'options': [None, 'Backend Connections Statistics', 'connections/s', 'backend metrics',
  96. 'varnish.backend_connections', 'line'],
  97. 'lines': [
  98. ['backend_conn', 'successful', 'incremental'],
  99. ['backend_unhealthy', 'unhealthy', 'incremental'],
  100. ['backend_reuse', 'reused', 'incremental'],
  101. ['backend_toolate', 'closed', 'incremental'],
  102. ['backend_recycle', 'resycled', 'incremental'],
  103. ['backend_fail', 'failed', 'incremental']
  104. ]
  105. },
  106. 'backend_requests': {
  107. 'options': [None, 'Requests To The Backend', 'requests/s', 'backend metrics',
  108. 'varnish.backend_requests', 'line'],
  109. 'lines': [
  110. ['backend_req', 'sent', 'incremental']
  111. ]
  112. },
  113. 'esi_statistics': {
  114. 'options': [None, 'ESI Statistics', 'problems/s', 'esi related metrics', 'varnish.esi_statistics', 'line'],
  115. 'lines': [
  116. ['esi_errors', 'errors', 'incremental'],
  117. ['esi_warnings', 'warnings', 'incremental']
  118. ]
  119. },
  120. 'memory_usage': {
  121. 'options': [None, 'Memory Usage', 'MiB', 'memory usage', 'varnish.memory_usage', 'stacked'],
  122. 'lines': [
  123. ['memory_free', 'free', 'absolute', 1, 1 << 20],
  124. ['memory_allocated', 'allocated', 'absolute', 1, 1 << 20]]
  125. },
  126. 'uptime': {
  127. 'lines': [
  128. ['uptime', None, 'absolute']
  129. ],
  130. 'options': [None, 'Uptime', 'seconds', 'uptime', 'varnish.uptime', 'line']
  131. }
  132. }
  133. VARNISHSTAT = 'varnishstat'
  134. re_version = re.compile(r'varnish-(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)')
  135. class VarnishVersion:
  136. def __init__(self, major, minor, patch):
  137. self.major = major
  138. self.minor = minor
  139. self.patch = patch
  140. def __str__(self):
  141. return '{0}.{1}.{2}'.format(self.major, self.minor, self.patch)
  142. class Parser:
  143. _backend_new = re.compile(r'VBE.([\d\w_.]+)\(.*?\).(beresp[\w_]+)\s+(\d+)')
  144. _backend_old = re.compile(r'VBE\.[\d\w-]+\.([\w\d_]+).(beresp[\w_]+)\s+(\d+)')
  145. _default = re.compile(r'([A-Z]+\.)?([\d\w_.]+)\s+(\d+)')
  146. def __init__(self):
  147. self.re_default = None
  148. self.re_backend = None
  149. def init(self, data):
  150. data = ''.join(data)
  151. parsed_main = Parser._default.findall(data)
  152. if parsed_main:
  153. self.re_default = Parser._default
  154. parsed_backend = Parser._backend_new.findall(data)
  155. if parsed_backend:
  156. self.re_backend = Parser._backend_new
  157. else:
  158. parsed_backend = Parser._backend_old.findall(data)
  159. if parsed_backend:
  160. self.re_backend = Parser._backend_old
  161. def server_stats(self, data):
  162. return self.re_default.findall(''.join(data))
  163. def backend_stats(self, data):
  164. return self.re_backend.findall(''.join(data))
  165. class Service(ExecutableService):
  166. def __init__(self, configuration=None, name=None):
  167. ExecutableService.__init__(self, configuration=configuration, name=name)
  168. self.order = ORDER
  169. self.definitions = CHARTS
  170. self.instance_name = configuration.get('instance_name')
  171. self.parser = Parser()
  172. self.command = None
  173. def create_command(self):
  174. varnishstat = find_binary(VARNISHSTAT)
  175. if not varnishstat:
  176. self.error("can't locate '{0}' binary or binary is not executable by user netdata".format(VARNISHSTAT))
  177. return False
  178. command = [varnishstat, '-V']
  179. reply = self._get_raw_data(stderr=True, command=command)
  180. if not reply:
  181. self.error(
  182. "no output from '{0}'. Is varnish running? Not enough privileges?".format(' '.join(self.command)))
  183. return False
  184. ver = parse_varnish_version(reply)
  185. if not ver:
  186. self.error("failed to parse reply from '{0}', used regex :'{1}', reply : {2}".format(
  187. ' '.join(command),
  188. re_version.pattern,
  189. reply,
  190. ))
  191. return False
  192. if self.instance_name:
  193. self.command = [varnishstat, '-1', '-n', self.instance_name]
  194. else:
  195. self.command = [varnishstat, '-1']
  196. if ver.major > 4:
  197. self.command.extend(['-t', '1'])
  198. self.info("varnish version: {0}, will use command: '{1}'".format(ver, ' '.join(self.command)))
  199. return True
  200. def check(self):
  201. if not self.create_command():
  202. return False
  203. # STDOUT is not empty
  204. reply = self._get_raw_data()
  205. if not reply:
  206. self.error("no output from '{0}'. Is it running? Not enough privileges?".format(' '.join(self.command)))
  207. return False
  208. self.parser.init(reply)
  209. # Output is parsable
  210. if not self.parser.re_default:
  211. self.error('cant parse the output...')
  212. return False
  213. if self.parser.re_backend:
  214. backends = [b[0] for b in self.parser.backend_stats(reply)[::2]]
  215. self.create_backends_charts(backends)
  216. return True
  217. def get_data(self):
  218. """
  219. Format data received from shell command
  220. :return: dict
  221. """
  222. raw = self._get_raw_data()
  223. if not raw:
  224. return None
  225. data = dict()
  226. server_stats = self.parser.server_stats(raw)
  227. if not server_stats:
  228. return None
  229. if self.parser.re_backend:
  230. backend_stats = self.parser.backend_stats(raw)
  231. data.update(dict(('_'.join([name, param]), value) for name, param, value in backend_stats))
  232. data.update(dict((param, value) for _, param, value in server_stats))
  233. # varnish 5 uses default.g_bytes and default.g_space
  234. data['memory_allocated'] = data.get('s0.g_bytes') or data.get('default.g_bytes')
  235. data['memory_free'] = data.get('s0.g_space') or data.get('default.g_space')
  236. return data
  237. def create_backends_charts(self, backends):
  238. for backend in backends:
  239. chart_name = ''.join([backend, '_response_statistics'])
  240. title = 'Backend "{0}"'.format(backend.capitalize())
  241. hdr_bytes = ''.join([backend, '_beresp_hdrbytes'])
  242. body_bytes = ''.join([backend, '_beresp_bodybytes'])
  243. chart = {
  244. chart_name:
  245. {
  246. 'options': [None, title, 'kilobits/s', 'backend response statistics',
  247. 'varnish.backend', 'area'],
  248. 'lines': [
  249. [hdr_bytes, 'header', 'incremental', 8, 1000],
  250. [body_bytes, 'body', 'incremental', -8, 1000]
  251. ]
  252. }
  253. }
  254. self.order.insert(0, chart_name)
  255. self.definitions.update(chart)
  256. def parse_varnish_version(lines):
  257. m = re_version.search(lines[0])
  258. if not m:
  259. return None
  260. m = m.groupdict()
  261. return VarnishVersion(
  262. int(m['major']),
  263. int(m['minor']),
  264. int(m['patch']),
  265. )