]> arthur.barton.de Git - netdata.git/blob - python.d/mysql.chart.py
5ea3dd2922ff3c124175fd0661e6dcf5834c2ff4
[netdata.git] / python.d / mysql.chart.py
1 # -*- coding: utf-8 -*-
2 # Description: MySQL netdata python.d plugin
3 # Author: Pawel Krupa (paulfantom)
4
5 import os
6 import sys
7
8 NAME = os.path.basename(__file__).replace(".chart.py", "")
9
10 # import 3rd party library to handle MySQL communication
11 try:
12     import MySQLdb
13
14     # https://github.com/PyMySQL/mysqlclient-python
15     sys.stderr.write(NAME + ": using MySQLdb\n")
16 except ImportError:
17     try:
18         import pymysql as MySQLdb
19
20         # https://github.com/PyMySQL/PyMySQL
21         sys.stderr.write(NAME + ": using pymysql\n")
22     except ImportError:
23         sys.stderr.write(NAME + ": You need to install MySQLdb or PyMySQL module to use mysql.chart.py plugin\n")
24         raise ImportError
25
26 from base import BaseService
27
28 # default module values (can be overridden per job in `config`)
29 update_every = 3
30 priority = 90000
31 retries = 7
32
33 # default configuration (overridden by python.d.plugin)
34 config = {
35     'local': {
36         'user': 'root',
37         'password': '',
38         'socket': '/var/run/mysqld/mysqld.sock',
39         'update_every': update_every,
40         'retries': retries,
41         'priority': priority
42     }
43 }
44
45 # query executed on MySQL server
46 QUERY = "SHOW GLOBAL STATUS"
47
48 # charts order (can be overridden if you want less charts, or different order)
49 ORDER = ['net',
50          'queries',
51          'handlers',
52          'table_locks',
53          'join_issues',
54          'sort_issues',
55          'tmp',
56          'connections',
57          'binlog_cache',
58          'threads',
59          'thread_cache_misses',
60          'innodb_io',
61          'innodb_io_ops',
62          'innodb_io_pending_ops',
63          'innodb_log',
64          'innodb_os_log',
65          'innodb_os_log_io',
66          'innodb_cur_row_lock',
67          'innodb_rows',
68          'innodb_buffer_pool_pages',
69          'innodb_buffer_pool_bytes',
70          'innodb_buffer_pool_read_ahead',
71          'innodb_buffer_pool_reqs',
72          'innodb_buffer_pool_ops',
73          'qcache_ops',
74          'qcache',
75          'qcache_freemem',
76          'qcache_memblocks',
77          'key_blocks',
78          'key_requests',
79          'key_disk_ops',
80          'files',
81          'files_rate',
82          'binlog_stmt_cache',
83          'connection_errors']
84
85 # charts definitions in format:
86 # CHARTS = {
87 #    'chart_name_in_netdata': (
88 #        "parameters defining chart (passed to CHART statement)",
89 #        [ # dimensions (lines) definitions
90 #            ("dimension_name", "dimension parameters (passed to DIMENSION statement)")
91 #        ])
92 #    }
93
94 CHARTS = {
95     'net': (
96         "'' 'mysql Bandwidth' 'kilobits/s' bandwidth mysql.net area",
97         [
98             ("Bytes_received", "in incremental 8 1024"),
99             ("Bytes_sent", "out incremental -8 1024")
100         ]),
101     'queries': (
102         "'' 'mysql Queries' 'queries/s' queries mysql.queries line",
103         [
104             ("Queries", "queries incremental 1 1"),
105             ("Questions", "questions incremental 1 1"),
106             ("Slow_queries", "slow_queries incremental -1 1")
107         ]),
108     'handlers': (
109         "'' 'mysql Handlers' 'handlers/s' handlers mysql.handlers line",
110         [
111             ("Handler_commit", "commit incremental 1 1"),
112             ("Handler_delete", "delete incremental 1 1"),
113             ("Handler_prepare", "prepare incremental 1 1"),
114             ("Handler_read_first", "read_first incremental 1 1"),
115             ("Handler_read_key", "read_key incremental 1 1"),
116             ("Handler_read_next", "read_next incremental 1 1"),
117             ("Handler_read_prev", "read_prev incremental 1 1"),
118             ("Handler_read_rnd", "read_rnd incremental 1 1"),
119             ("Handler_read_rnd_next", "read_rnd_next incremental 1 1"),
120             ("Handler_rollback", "rollback incremental 1 1"),
121             ("Handler_savepoint", "savepoint incremental 1 1"),
122             ("Handler_savepoint_rollback", "savepoint_rollback incremental 1 1"),
123             ("Handler_update", "update incremental 1 1"),
124             ("Handler_write", "write incremental 1 1")
125         ]),
126     'table_locks': (
127         "'' 'mysql Tables Locks' 'locks/s' locks mysql.table_locks line",
128         [
129             ("Table_locks_immediate", "immediate incremental 1 1"),
130             ("Table_locks_waited", "waited incremental -1 1")
131         ]),
132     'join_issues': (
133         "'' 'mysql Select Join Issues' 'joins/s' issues mysql.join_issues line",
134         [
135             ("Select_full_join", "full_join incremental 1 1"),
136             ("Select_full_range_join", "full_range_join incremental 1 1"),
137             ("Select_range", "range incremental 1 1"),
138             ("Select_range_check", "range_check incremental 1 1"),
139             ("Select_scan", "scan incremental 1 1"),
140         ]),
141     'sort_issues': (
142         "'' 'mysql Sort Issues' 'issues/s' issues mysql.sort.issues line",
143         [
144             ("Sort_merge_passes", "merge_passes incremental 1 1"),
145             ("Sort_range", "range incremental 1 1"),
146             ("Sort_scan", "scan incremental 1 1"),
147         ]),
148     'tmp': (
149         "'' 'mysql Tmp Operations' 'counter' temporaries mysql.tmp line",
150         [
151             ("Created_tmp_disk_tables", "disk_tables incremental 1 1"),
152             ("Created_tmp_files", "files incremental 1 1"),
153             ("Created_tmp_tables", "tables incremental 1 1"),
154         ]),
155     'connections': (
156         "'' 'mysql Connections' 'connections/s' connections mysql.connections line",
157         [
158             ("Connections", "all incremental 1 1"),
159             ("Aborted_connects", "aborted incremental 1 1"),
160         ]),
161     'binlog_cache': (
162         "'' 'mysql Binlog Cache' 'transactions/s' binlog mysql.binlog_cache line",
163         [
164             ("Binlog_cache_disk_use", "disk incremental 1 1"),
165             ("Binlog_cache_use", "all incremental 1 1"),
166         ]),
167     'threads': (
168         "'' 'mysql Threads' 'threads' threads mysql.threads line",
169         [
170             ("Threads_connected", "connected absolute 1 1"),
171             ("Threads_created", "created incremental 1 1"),
172             ("Threads_cached", "cached absolute -1 1"),
173             ("Threads_running", "running absolute 1 1"),
174         ]),
175     'thread_cache_misses': (
176         "'' 'mysql Threads Cache Misses' 'misses' threads mysql.thread_cache_misses area",
177         [
178             ("Thread_cache_misses", "misses misses absolute 1 100"),
179         ]),
180     'innodb_io': (
181         "'' 'mysql InnoDB I/O Bandwidth' 'kilobytes/s' innodb mysql.innodb_io area",
182         [
183             ("Innodb_data_read", "read incremental 1 1024"),
184             ("Innodb_data_written", "write incremental -1 1024"),
185         ]),
186     'innodb_io_ops': (
187         "'' 'mysql InnoDB I/O Operations' 'operations/s' innodb mysql.innodb_io_ops line",
188         [
189             ("Innodb_data_reads", "reads incremental 1 1"),
190             ("Innodb_data_writes", "writes incremental -1 1"),
191             ("Innodb_data_fsyncs", "fsyncs incremental 1 1"),
192         ]),
193     'innodb_io_pending_ops': (
194         "'' 'mysql InnoDB Pending I/O Operations' 'operations' innodb mysql.innodb_io_pending_ops line",
195         [
196             ("Innodb_data_pending_reads", "reads absolute 1 1"),
197             ("Innodb_data_pending_writes", "writes absolute -1 1"),
198             ("Innodb_data_pending_fsyncs", "fsyncs absolute 1 1"),
199         ]),
200     'innodb_log': (
201         "'' 'mysql InnoDB Log Operations' 'operations/s' innodb mysql.innodb_log line",
202         [
203             ("Innodb_log_waits", "waits incremental 1 1"),
204             ("Innodb_log_write_requests", "write_requests incremental -1 1"),
205             ("Innodb_log_writes", "incremental -1 1"),
206         ]),
207     'innodb_os_log': (
208         "'' 'mysql InnoDB OS Log Operations' 'operations' innodb mysql.innodb_os_log line",
209         [
210             ("Innodb_os_log_fsyncs", "fsyncs incremental 1 1"),
211             ("Innodb_os_log_pending_fsyncs", "pending_fsyncs absolute 1 1"),
212             ("Innodb_os_log_pending_writes", "pending_writes absolute -1 1"),
213         ]),
214     'innodb_os_log_io': (
215         "'' 'mysql InnoDB OS Log Bandwidth' 'kilobytes/s' innodb mysql.innodb_os_log_io area",
216         [
217             ("Innodb_os_log_written", "write incremental -1 1024"),
218         ]),
219     'innodb_cur_row_lock': (
220         "'' 'mysql InnoDB Current Row Locks' 'operations' innodb mysql.innodb_cur_row_lock area",
221         [
222             ("Innodb_row_lock_current_waits", "current_waits absolute 1 1"),
223         ]),
224     'innodb_rows': (
225         "'' 'mysql InnoDB Row Operations' 'operations/s' innodb mysql.innodb_rows area",
226         [
227             ("Innodb_rows_inserted", "read incremental 1 1"),
228             ("Innodb_rows_read", "deleted incremental -1 1"),
229             ("Innodb_rows_updated", "inserted incremental 1 1"),
230             ("Innodb_rows_deleted", "updated incremental -1 1"),
231         ]),
232     'innodb_buffer_pool_pages': (
233         "'' 'mysql InnoDB Buffer Pool Pages' 'pages' innodb mysql.innodb_buffer_pool_pages line",
234         [
235             ("Innodb_buffer_pool_pages_data", "data absolute 1 1"),
236             ("Innodb_buffer_pool_pages_dirty", "dirty absolute -1 1"),
237             ("Innodb_buffer_pool_pages_free", "free absolute 1 1"),
238             ("Innodb_buffer_pool_pages_flushed", "flushed incremental -1 1"),
239             ("Innodb_buffer_pool_pages_misc", "misc absolute -1 1"),
240             ("Innodb_buffer_pool_pages_total", "total absolute 1 1"),
241         ]),
242     'innodb_buffer_pool_bytes': (
243         "'' 'mysql InnoDB Buffer Pool Bytes' 'MB' innodb mysql.innodb_buffer_pool_bytes area",
244         [
245             ("Innodb_buffer_pool_bytes_data", "data absolute 1"),
246             ("Innodb_buffer_pool_bytes_dirty", "dirty absolute -1"),
247         ]),
248     'innodb_buffer_pool_read_ahead': (
249         "'' 'mysql InnoDB Buffer Pool Read Ahead' 'operations/s' innodb mysql.innodb_buffer_pool_read_ahead area",
250         [
251             ("Innodb_buffer_pool_read_ahead", "all incremental 1 1"),
252             ("Innodb_buffer_pool_read_ahead_evicted", "evicted incremental -1 1"),
253             ("Innodb_buffer_pool_read_ahead_rnd", "random incremental 1 1"),
254         ]),
255     'innodb_buffer_pool_reqs': (
256         "'' 'mysql InnoDB Buffer Pool Requests' 'requests/s' innodb mysql.innodb_buffer_pool_reqs area",
257         [
258             ("Innodb_buffer_pool_read_requests", "reads incremental 1 1"),
259             ("Innodb_buffer_pool_write_requests", "writes incremental -1 1"),
260         ]),
261     'innodb_buffer_pool_ops': (
262         "'' 'mysql InnoDB Buffer Pool Operations' 'operations/s' innodb mysql.innodb_buffer_pool_ops area",
263         [
264             ("Innodb_buffer_pool_reads", "'disk reads' incremental 1 1"),
265             ("Innodb_buffer_pool_wait_free", "'wait free' incremental -1 1"),
266         ]),
267     'qcache_ops': (
268         "'' 'mysql QCache Operations' 'queries/s' qcache mysql.qcache_ops line",
269         [
270             ("Qcache_hits", "hits incremental 1 1"),
271             ("Qcache_lowmem_prunes", "'lowmem prunes' incremental -1 1"),
272             ("Qcache_inserts", "inserts incremental 1 1"),
273             ("Qcache_not_cached", "'not cached' incremental -1 1"),
274         ]),
275     'qcache': (
276         "'' 'mysql QCache Queries in Cache' 'queries' qcache mysql.qcache line",
277         [
278             ("Qcache_queries_in_cache", "queries absolute 1 1"),
279         ]),
280     'qcache_freemem': (
281         "'' 'mysql QCache Free Memory' 'MB' qcache mysql.qcache_freemem area",
282         [
283             ("Qcache_free_memory", "free absolute 1"),
284         ]),
285     'qcache_memblocks': (
286         "'' 'mysql QCache Memory Blocks' 'blocks' qcache mysql.qcache_memblocks line",
287         [
288             ("Qcache_free_blocks", "free absolute 1"),
289             ("Qcache_total_blocks", "total absolute 1 1"),
290         ]),
291     'key_blocks': (
292         "'' 'mysql MyISAM Key Cache Blocks' 'blocks' myisam mysql.key_blocks line",
293         [
294             ("Key_blocks_unused", "unused absolute 1 1"),
295             ("Key_blocks_used", "used absolute -1 1"),
296             ("Key_blocks_not_flushed", "'not flushed' absolute 1 1"),
297         ]),
298     'key_requests': (
299         "'' 'mysql MyISAM Key Cache Requests' 'requests/s' myisam mysql.key_requests area",
300         [
301             ("Key_read_requests", "reads incremental 1 1"),
302             ("Key_write_requests", "writes incremental -1 1"),
303         ]),
304     'key_disk_ops': (
305         "'' 'mysql MyISAM Key Cache Disk Operations' 'operations/s' myisam mysql.key_disk_ops area",
306         [
307             ("Key_reads", "reads incremental 1 1"),
308             ("Key_writes", "writes incremental -1 1"),
309         ]),
310     'files': (
311         "'' 'mysql Open Files' 'files' files mysql.files line",
312         [
313             ("Open_files", "files absolute 1 1"),
314         ]),
315     'files_rate': (
316         "'' 'mysql Opened Files Rate' 'files/s' files mysql.files_rate line",
317         [
318             ("Opened_files", "files incremental 1 1"),
319         ]),
320     'binlog_stmt_cache': (
321         "'' 'mysql Binlog Statement Cache' 'statements/s' binlog mysql.binlog_stmt_cache line",
322         [
323             ("Binlog_stmt_cache_disk_use", "disk incremental 1 1"),
324             ("Binlog_stmt_cache_use", "all incremental 1 1"),
325         ]),
326     'connection_errors': (
327         "'' 'mysql Connection Errors' 'connections/s' connections mysql.connection_errors line",
328         [
329             ("Connection_errors_accept", "accept incremental 1 1"),
330             ("Connection_errors_internal", "internal incremental 1 1"),
331             ("Connection_errors_max_connections", "max incremental 1 1"),
332             ("Connection_errors_peer_address", "peer_addr incremental 1 1"),
333             ("Connection_errors_select", "select incremental 1 1"),
334             ("Connection_errors_tcpwrap", "tcpwrap incremental 1 1")
335         ])
336 }
337
338
339 class Service(BaseService):
340     def __init__(self, configuration=None, name=None):
341         super(self.__class__, self).__init__(configuration=configuration, name=name)
342         self.configuration = self._parse_config(configuration)
343         self.connection = None
344         self.defs = {}
345
346     def _parse_config(self, configuration):
347         """
348         Parse configuration to collect data from MySQL server
349         :param configuration: dict
350         :return: dict
351         """
352         if self.name is None:
353             self.name = 'local'
354         if 'user' not in configuration:
355             configuration['user'] = 'root'
356         if 'password' not in configuration:
357             configuration['password'] = ''
358         if 'my.cnf' in configuration:
359             configuration['socket'] = ''
360             configuration['host'] = ''
361             configuration['port'] = 0
362         elif 'socket' in configuration:
363             configuration['my.cnf'] = ''
364             configuration['host'] = ''
365             configuration['port'] = 0
366         elif 'host' in configuration:
367             configuration['my.cnf'] = ''
368             configuration['socket'] = ''
369             if 'port' in configuration:
370                 configuration['port'] = int(configuration['port'])
371             else:
372                 configuration['port'] = 3306
373
374         return configuration
375
376     def _connect(self):
377         """
378         Try to connect to MySQL server
379         """
380         try:
381             self.connection = MySQLdb.connect(user=self.configuration['user'],
382                                               passwd=self.configuration['password'],
383                                               read_default_file=self.configuration['my.cnf'],
384                                               unix_socket=self.configuration['socket'],
385                                               host=self.configuration['host'],
386                                               port=self.configuration['port'],
387                                               connect_timeout=self.update_every)
388         except Exception as e:
389             self.error(NAME + " has problem connecting to server:", e)
390             raise RuntimeError
391
392     def _get_data(self):
393         """
394         Get raw data from MySQL server
395         :return: dict
396         """
397         if self.connection is None:
398             try:
399                 self._connect()
400             except RuntimeError:
401                 return None
402         try:
403             cursor = self.connection.cursor()
404             cursor.execute(QUERY)
405             raw_data = cursor.fetchall()
406         except Exception as e:
407             self.error(NAME + ": cannot execute query.", e)
408             self.connection.close()
409             self.connection = None
410             return None
411
412         return dict(raw_data)
413
414     def check(self):
415         """
416         Check if service is able to connect to server
417         :return: boolean
418         """
419         try:
420             self.connection = self._connect()
421             return True
422         except RuntimeError:
423             self.connection = None
424             return False
425
426     def create(self):
427         """
428         Create graphs
429         :return: boolean
430         """
431         for name in ORDER:
432             self.defs[name] = []
433             for line in CHARTS[name][1]:
434                 self.defs[name].append(line[0])
435
436         idx = 0
437         data = self._get_data()
438         if data is None:
439             return False
440         for name in ORDER:
441             header = "CHART mysql_" + \
442                      str(self.name) + "." + \
443                      name + " " + \
444                      CHARTS[name][0] + " " + \
445                      str(self.priority + idx) + " " + \
446                      str(self.update_every)
447             content = ""
448             # check if server has this data point
449             for line in CHARTS[name][1]:
450                 if line[0] in data:
451                     content += "DIMENSION " + line[0] + " " + line[1] + "\n"
452             if len(content) > 0:
453                 print(header)
454                 print(content)
455                 idx += 1
456
457         if idx == 0:
458             return False
459         return True
460
461     def update(self, interval):
462         """
463         Update data on graphs
464         :param interval: int
465         :return: boolean
466         """
467         data = self._get_data()
468         if data is None:
469             return False
470         try:
471             data['Thread cache misses'] = int(int(data['Threads_created']) * 10000 / int(data['Connections']))
472         except Exception:
473             pass
474         for chart, dimensions in self.defs.items():
475             header = "BEGIN mysql_" + str(self.name) + "." + chart + " " + str(interval) + '\n'
476             lines = ""
477             for d in dimensions:
478                 try:
479                     lines += "SET " + d + " = " + data[d] + '\n'
480                 except KeyError:
481                     pass
482             if len(lines) > 0:
483                 print(header + lines + "END")
484
485         return True