varnish.chart.py 8.6 KB

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