proxysql.chart.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. # -*- coding: utf-8 -*-
  2. # Description: Proxysql netdata python.d module
  3. # Author: Ali Borhani (alibo)
  4. # SPDX-License-Identifier: GPL-3.0+
  5. from bases.FrameworkServices.MySQLService import MySQLService
  6. def query(table, *params):
  7. return 'SELECT {params} FROM {table}'.format(table=table, params=', '.join(params))
  8. # https://github.com/sysown/proxysql/blob/master/doc/admin_tables.md#stats_mysql_global
  9. QUERY_GLOBAL = query(
  10. "stats_mysql_global",
  11. "Variable_Name",
  12. "Variable_Value"
  13. )
  14. # https://github.com/sysown/proxysql/blob/master/doc/admin_tables.md#stats_mysql_connection_pool
  15. QUERY_CONNECTION_POOL = query(
  16. "stats_mysql_connection_pool",
  17. "hostgroup",
  18. "srv_host",
  19. "srv_port",
  20. "status",
  21. "ConnUsed",
  22. "ConnFree",
  23. "ConnOK",
  24. "ConnERR",
  25. "Queries",
  26. "Bytes_data_sent",
  27. "Bytes_data_recv",
  28. "Latency_us"
  29. )
  30. # https://github.com/sysown/proxysql/blob/master/doc/admin_tables.md#stats_mysql_commands_counters
  31. QUERY_COMMANDS = query(
  32. "stats_mysql_commands_counters",
  33. "Command",
  34. "Total_Time_us",
  35. "Total_cnt",
  36. "cnt_100us",
  37. "cnt_500us",
  38. "cnt_1ms",
  39. "cnt_5ms",
  40. "cnt_10ms",
  41. "cnt_50ms",
  42. "cnt_100ms",
  43. "cnt_500ms",
  44. "cnt_1s",
  45. "cnt_5s",
  46. "cnt_10s",
  47. "cnt_INFs"
  48. )
  49. GLOBAL_STATS = [
  50. 'client_connections_aborted',
  51. 'client_connections_connected',
  52. 'client_connections_created',
  53. 'client_connections_non_idle',
  54. 'proxysql_uptime',
  55. 'questions',
  56. 'slow_queries'
  57. ]
  58. CONNECTION_POOL_STATS = [
  59. 'status',
  60. 'connused',
  61. 'connfree',
  62. 'connok',
  63. 'connerr',
  64. 'queries',
  65. 'bytes_data_sent',
  66. 'bytes_data_recv',
  67. 'latency_us'
  68. ]
  69. ORDER = [
  70. 'connections',
  71. 'active_transactions',
  72. 'questions',
  73. 'pool_overall_net',
  74. 'commands_count',
  75. 'commands_duration',
  76. 'pool_status',
  77. 'pool_net',
  78. 'pool_queries',
  79. 'pool_latency',
  80. 'pool_connection_used',
  81. 'pool_connection_free',
  82. 'pool_connection_ok',
  83. 'pool_connection_error'
  84. ]
  85. HISTOGRAM_ORDER = [
  86. '100us',
  87. '500us',
  88. '1ms',
  89. '5ms',
  90. '10ms',
  91. '50ms',
  92. '100ms',
  93. '500ms',
  94. '1s',
  95. '5s',
  96. '10s',
  97. 'inf'
  98. ]
  99. STATUS = {
  100. "ONLINE": 1,
  101. "SHUNNED": 2,
  102. "OFFLINE_SOFT": 3,
  103. "OFFLINE_HARD": 4
  104. }
  105. CHARTS = {
  106. 'pool_status': {
  107. 'options': [None, 'ProxySQL Backend Status', 'status', 'status', 'proxysql.pool_status', 'line'],
  108. 'lines': []
  109. },
  110. 'pool_net': {
  111. 'options': [None, 'ProxySQL Backend Bandwidth', 'kilobits/s', 'bandwidth', 'proxysql.pool_net', 'area'],
  112. 'lines': []
  113. },
  114. 'pool_overall_net': {
  115. 'options': [None, 'ProxySQL Backend Overall Bandwidth', 'kilobits/s', 'overall_bandwidth',
  116. 'proxysql.pool_overall_net', 'area'],
  117. 'lines': [
  118. ['bytes_data_recv', 'in', 'incremental', 8, 1000],
  119. ['bytes_data_sent', 'out', 'incremental', -8, 1000]
  120. ]
  121. },
  122. 'questions': {
  123. 'options': [None, 'ProxySQL Frontend Questions', 'questions/s', 'questions', 'proxysql.questions', 'line'],
  124. 'lines': [
  125. ['questions', 'questions', 'incremental'],
  126. ['slow_queries', 'slow_queries', 'incremental']
  127. ]
  128. },
  129. 'pool_queries': {
  130. 'options': [None, 'ProxySQL Backend Queries', 'queries/s', 'queries', 'proxysql.queries', 'line'],
  131. 'lines': []
  132. },
  133. 'active_transactions': {
  134. 'options': [None, 'ProxySQL Frontend Active Transactions', 'transactions/s', 'active_transactions',
  135. 'proxysql.active_transactions', 'line'],
  136. 'lines': [
  137. ['active_transactions', 'active_transactions', 'absolute']
  138. ]
  139. },
  140. 'pool_latency': {
  141. 'options': [None, 'ProxySQL Backend Latency', 'milliseconds', 'latency', 'proxysql.latency', 'line'],
  142. 'lines': []
  143. },
  144. 'connections': {
  145. 'options': [None, 'ProxySQL Frontend Connections', 'connections/s', 'connections', 'proxysql.connections',
  146. 'line'],
  147. 'lines': [
  148. ['client_connections_connected', 'connected', 'absolute'],
  149. ['client_connections_created', 'created', 'incremental'],
  150. ['client_connections_aborted', 'aborted', 'incremental'],
  151. ['client_connections_non_idle', 'non_idle', 'absolute']
  152. ]
  153. },
  154. 'pool_connection_used': {
  155. 'options': [None, 'ProxySQL Used Connections', 'connections', 'pool_connections',
  156. 'proxysql.pool_used_connections', 'line'],
  157. 'lines': []
  158. },
  159. 'pool_connection_free': {
  160. 'options': [None, 'ProxySQL Free Connections', 'connections', 'pool_connections',
  161. 'proxysql.pool_free_connections', 'line'],
  162. 'lines': []
  163. },
  164. 'pool_connection_ok': {
  165. 'options': [None, 'ProxySQL Established Connections', 'connections', 'pool_connections',
  166. 'proxysql.pool_ok_connections', 'line'],
  167. 'lines': []
  168. },
  169. 'pool_connection_error': {
  170. 'options': [None, 'ProxySQL Error Connections', 'connections', 'pool_connections',
  171. 'proxysql.pool_error_connections', 'line'],
  172. 'lines': []
  173. },
  174. 'commands_count': {
  175. 'options': [None, 'ProxySQL Commands', 'commands', 'commands', 'proxysql.commands_count', 'line'],
  176. 'lines': []
  177. },
  178. 'commands_duration': {
  179. 'options': [None, 'ProxySQL Commands Duration', 'milliseconds', 'commands', 'proxysql.commands_duration',
  180. 'line'],
  181. 'lines': []
  182. }
  183. }
  184. class Service(MySQLService):
  185. def __init__(self, configuration=None, name=None):
  186. MySQLService.__init__(self, configuration=configuration, name=name)
  187. self.order = ORDER
  188. self.definitions = CHARTS
  189. self.queries = dict(
  190. global_status=QUERY_GLOBAL,
  191. connection_pool_status=QUERY_CONNECTION_POOL,
  192. commands_status=QUERY_COMMANDS
  193. )
  194. def _get_data(self):
  195. raw_data = self._get_raw_data(description=True)
  196. if not raw_data:
  197. return None
  198. to_netdata = dict()
  199. if 'global_status' in raw_data:
  200. global_status = dict(raw_data['global_status'][0])
  201. for key in global_status:
  202. if key.lower() in GLOBAL_STATS:
  203. to_netdata[key.lower()] = global_status[key]
  204. if 'connection_pool_status' in raw_data:
  205. to_netdata['bytes_data_recv'] = 0
  206. to_netdata['bytes_data_sent'] = 0
  207. for record in raw_data['connection_pool_status'][0]:
  208. backend = self.generate_backend(record)
  209. name = self.generate_backend_name(backend)
  210. for key in backend:
  211. if key in CONNECTION_POOL_STATS:
  212. if key == 'status':
  213. backend[key] = self.convert_status(backend[key])
  214. if len(self.charts) > 0:
  215. if (name + '_status') not in self.charts['pool_status']:
  216. self.add_backend_dimensions(name)
  217. to_netdata["{0}_{1}".format(name, key)] = backend[key]
  218. if key == 'bytes_data_recv':
  219. to_netdata['bytes_data_recv'] += int(backend[key])
  220. if key == 'bytes_data_sent':
  221. to_netdata['bytes_data_sent'] += int(backend[key])
  222. if 'commands_status' in raw_data:
  223. for record in raw_data['commands_status'][0]:
  224. cmd = self.generate_command_stats(record)
  225. name = cmd['name']
  226. if len(self.charts) > 0:
  227. if (name + '_count') not in self.charts['commands_count']:
  228. self.add_command_dimensions(name)
  229. self.add_histogram_chart(cmd)
  230. to_netdata[name + '_count'] = cmd['count']
  231. to_netdata[name + '_duration'] = cmd['duration']
  232. for histogram in cmd['histogram']:
  233. dimId = 'commands_histogram_{0}_{1}'.format(name, histogram)
  234. to_netdata[dimId] = cmd['histogram'][histogram]
  235. return to_netdata or None
  236. def add_backend_dimensions(self, name):
  237. self.charts['pool_status'].add_dimension([name + '_status', name, 'absolute'])
  238. self.charts['pool_net'].add_dimension([name + '_bytes_data_recv', 'from_' + name, 'incremental', 8, 1024])
  239. self.charts['pool_net'].add_dimension([name + '_bytes_data_sent', 'to_' + name, 'incremental', -8, 1024])
  240. self.charts['pool_queries'].add_dimension([name + '_queries', name, 'incremental'])
  241. self.charts['pool_latency'].add_dimension([name + '_latency_us', name, 'absolute', 1, 1000])
  242. self.charts['pool_connection_used'].add_dimension([name + '_connused', name, 'absolute'])
  243. self.charts['pool_connection_free'].add_dimension([name + '_connfree', name, 'absolute'])
  244. self.charts['pool_connection_ok'].add_dimension([name + '_connok', name, 'incremental'])
  245. self.charts['pool_connection_error'].add_dimension([name + '_connerr', name, 'incremental'])
  246. def add_command_dimensions(self, cmd):
  247. self.charts['commands_count'].add_dimension([cmd + '_count', cmd, 'incremental'])
  248. self.charts['commands_duration'].add_dimension([cmd + '_duration', cmd, 'incremental', 1, 1000])
  249. def add_histogram_chart(self, cmd):
  250. chart = self.charts.add_chart(self.histogram_chart(cmd))
  251. for histogram in HISTOGRAM_ORDER:
  252. dimId = 'commands_histogram_{0}_{1}'.format(cmd['name'], histogram)
  253. chart.add_dimension([dimId, histogram, 'incremental'])
  254. @staticmethod
  255. def histogram_chart(cmd):
  256. return [
  257. 'commands_histogram_' + cmd['name'],
  258. None,
  259. 'ProxySQL {0} Command Histogram'.format(cmd['name'].title()),
  260. 'commands',
  261. 'commands_histogram',
  262. 'proxysql.commands_histogram_' + cmd['name'],
  263. 'stacked'
  264. ]
  265. @staticmethod
  266. def generate_backend(data):
  267. return {
  268. 'hostgroup': data[0],
  269. 'srv_host': data[1],
  270. 'srv_port': data[2],
  271. 'status': data[3],
  272. 'connused': data[4],
  273. 'connfree': data[5],
  274. 'connok': data[6],
  275. 'connerr': data[7],
  276. 'queries': data[8],
  277. 'bytes_data_sent': data[9],
  278. 'bytes_data_recv': data[10],
  279. 'latency_us': data[11]
  280. }
  281. @staticmethod
  282. def generate_command_stats(data):
  283. return {
  284. 'name': data[0].lower(),
  285. 'duration': data[1],
  286. 'count': data[2],
  287. 'histogram': {
  288. '100us': data[3],
  289. '500us': data[4],
  290. '1ms': data[5],
  291. '5ms': data[6],
  292. '10ms': data[7],
  293. '50ms': data[8],
  294. '100ms': data[9],
  295. '500ms': data[10],
  296. '1s': data[11],
  297. '5s': data[12],
  298. '10s': data[13],
  299. 'inf': data[14]
  300. }
  301. }
  302. @staticmethod
  303. def generate_backend_name(backend):
  304. hostgroup = backend['hostgroup'].replace(' ', '_').lower()
  305. host = backend['srv_host'].replace('.', '_')
  306. return "{0}_{1}_{2}".format(hostgroup, host, backend['srv_port'])
  307. @staticmethod
  308. def convert_status(status):
  309. if status in STATUS:
  310. return STATUS[status]
  311. return -1