memcached.chart.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # -*- coding: utf-8 -*-
  2. # Description: memcached netdata python.d module
  3. # Author: Pawel Krupa (paulfantom)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. from bases.FrameworkServices.SocketService import SocketService
  6. ORDER = [
  7. 'cache',
  8. 'net',
  9. 'connections',
  10. 'items',
  11. 'evicted_reclaimed',
  12. 'get',
  13. 'get_rate',
  14. 'set_rate',
  15. 'cas',
  16. 'delete',
  17. 'increment',
  18. 'decrement',
  19. 'touch',
  20. 'touch_rate',
  21. ]
  22. CHARTS = {
  23. 'cache': {
  24. 'options': [None, 'Cache Size', 'MiB', 'cache', 'memcached.cache', 'stacked'],
  25. 'lines': [
  26. ['avail', 'available', 'absolute', 1, 1 << 20],
  27. ['used', 'used', 'absolute', 1, 1 << 20]
  28. ]
  29. },
  30. 'net': {
  31. 'options': [None, 'Network', 'kilobits/s', 'network', 'memcached.net', 'area'],
  32. 'lines': [
  33. ['bytes_read', 'in', 'incremental', 8, 1000],
  34. ['bytes_written', 'out', 'incremental', -8, 1000],
  35. ]
  36. },
  37. 'connections': {
  38. 'options': [None, 'Connections', 'connections/s', 'connections', 'memcached.connections', 'line'],
  39. 'lines': [
  40. ['curr_connections', 'current', 'incremental'],
  41. ['rejected_connections', 'rejected', 'incremental'],
  42. ['total_connections', 'total', 'incremental']
  43. ]
  44. },
  45. 'items': {
  46. 'options': [None, 'Items', 'items', 'items', 'memcached.items', 'line'],
  47. 'lines': [
  48. ['curr_items', 'current', 'absolute'],
  49. ['total_items', 'total', 'absolute']
  50. ]
  51. },
  52. 'evicted_reclaimed': {
  53. 'options': [None, 'Evicted and Reclaimed Items', 'items', 'items', 'memcached.evicted_reclaimed', 'line'],
  54. 'lines': [
  55. ['reclaimed', 'reclaimed', 'absolute'],
  56. ['evictions', 'evicted', 'absolute']
  57. ]
  58. },
  59. 'get': {
  60. 'options': [None, 'Get Requests', 'requests', 'get ops', 'memcached.get', 'stacked'],
  61. 'lines': [
  62. ['get_hits', 'hits', 'percent-of-absolute-row'],
  63. ['get_misses', 'misses', 'percent-of-absolute-row']
  64. ]
  65. },
  66. 'get_rate': {
  67. 'options': [None, 'Get Request Rate', 'requests/s', 'get ops', 'memcached.get_rate', 'line'],
  68. 'lines': [
  69. ['cmd_get', 'rate', 'incremental']
  70. ]
  71. },
  72. 'set_rate': {
  73. 'options': [None, 'Set Request Rate', 'requests/s', 'set ops', 'memcached.set_rate', 'line'],
  74. 'lines': [
  75. ['cmd_set', 'rate', 'incremental']
  76. ]
  77. },
  78. 'delete': {
  79. 'options': [None, 'Delete Requests', 'requests', 'delete ops', 'memcached.delete', 'stacked'],
  80. 'lines': [
  81. ['delete_hits', 'hits', 'percent-of-absolute-row'],
  82. ['delete_misses', 'misses', 'percent-of-absolute-row'],
  83. ]
  84. },
  85. 'cas': {
  86. 'options': [None, 'Check and Set Requests', 'requests', 'check and set ops', 'memcached.cas', 'stacked'],
  87. 'lines': [
  88. ['cas_hits', 'hits', 'percent-of-absolute-row'],
  89. ['cas_misses', 'misses', 'percent-of-absolute-row'],
  90. ['cas_badval', 'bad value', 'percent-of-absolute-row']
  91. ]
  92. },
  93. 'increment': {
  94. 'options': [None, 'Increment Requests', 'requests', 'increment ops', 'memcached.increment', 'stacked'],
  95. 'lines': [
  96. ['incr_hits', 'hits', 'percent-of-absolute-row'],
  97. ['incr_misses', 'misses', 'percent-of-absolute-row']
  98. ]
  99. },
  100. 'decrement': {
  101. 'options': [None, 'Decrement Requests', 'requests', 'decrement ops', 'memcached.decrement', 'stacked'],
  102. 'lines': [
  103. ['decr_hits', 'hits', 'percent-of-absolute-row'],
  104. ['decr_misses', 'misses', 'percent-of-absolute-row']
  105. ]
  106. },
  107. 'touch': {
  108. 'options': [None, 'Touch Requests', 'requests', 'touch ops', 'memcached.touch', 'stacked'],
  109. 'lines': [
  110. ['touch_hits', 'hits', 'percent-of-absolute-row'],
  111. ['touch_misses', 'misses', 'percent-of-absolute-row']
  112. ]
  113. },
  114. 'touch_rate': {
  115. 'options': [None, 'Touch Request Rate', 'requests/s', 'touch ops', 'memcached.touch_rate', 'line'],
  116. 'lines': [
  117. ['cmd_touch', 'rate', 'incremental']
  118. ]
  119. }
  120. }
  121. class Service(SocketService):
  122. def __init__(self, configuration=None, name=None):
  123. SocketService.__init__(self, configuration=configuration, name=name)
  124. self.order = ORDER
  125. self.definitions = CHARTS
  126. self.request = 'stats\r\n'
  127. self.host = 'localhost'
  128. self.port = 11211
  129. self._keep_alive = True
  130. self.unix_socket = None
  131. def _get_data(self):
  132. """
  133. Get data from socket
  134. :return: dict
  135. """
  136. response = self._get_raw_data()
  137. if response is None:
  138. # error has already been logged
  139. return None
  140. if response.startswith('ERROR'):
  141. self.error('received ERROR')
  142. return None
  143. try:
  144. parsed = response.split('\n')
  145. except AttributeError:
  146. self.error('response is invalid/empty')
  147. return None
  148. # split the response
  149. data = {}
  150. for line in parsed:
  151. if line.startswith('STAT'):
  152. try:
  153. t = line[5:].split(' ')
  154. data[t[0]] = t[1]
  155. except (IndexError, ValueError):
  156. self.debug('invalid line received: ' + str(line))
  157. if not data:
  158. self.error("received data doesn't have any records")
  159. return None
  160. # custom calculations
  161. try:
  162. data['avail'] = int(data['limit_maxbytes']) - int(data['bytes'])
  163. data['used'] = int(data['bytes'])
  164. except (KeyError, ValueError, TypeError):
  165. pass
  166. return data
  167. def _check_raw_data(self, data):
  168. if data.endswith('END\r\n'):
  169. self.debug('received full response from memcached')
  170. return True
  171. self.debug('waiting more data from memcached')
  172. return False
  173. def check(self):
  174. """
  175. Parse configuration, check if memcached is available
  176. :return: boolean
  177. """
  178. self._parse_config()
  179. data = self._get_data()
  180. if data is None:
  181. return False
  182. return True